1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reverse -> "FlagCasino" Hack the box business ctf 2024

Last updated at Posted at 2024-05-23

はじめに

Hack the boxはオフェンシブセキュリティ技術を磨くことのできるe-learning系Webサービスである。
Hack the box社は、HTB Business CTF 2024: The Vault Of Hope というセキュリティコンテストを2024年5月18日~22日にかけて開催していた。
著者も1プレイヤーとして参加しており、その際に取り組んだ、Reverse -> FlagCasino という問題についてwrite-upを記す。

問題概要

  • ジャンル: reverse (リバースエンジニアリング)
  • 問題名: FlagCasino
  • 問題文(要約): ロボットのディーラーに勝ちましょう
  • 難易度: very easy
  • 添付ファイル: "casino"

write-up

表層解析

  1. "casino"をテキストエディタで開く
  2. ELFから始まるバイナリ、ロボットのアスキーアートのようなものが見える
  3. 早速linux上で実行するとプロンプト風のものが立ち上がる
    試しに今回のCTFのフラグ形式である"HTB{a}"といった文字列を入力する
    ┌──(kali㉿kali)-[~/Downloads/rev_flagcasino]
    └─$ ./casino
    [ ** WELCOME TO ROBO CASINO **]
         ,     ,
        (\____/)
         (_oo_)
           (O)
         __||__    \)
      []/______\[] /
      / \______/ \/
     /    /__\
    (\   /____\
    ---------------------
    [*** PLEASE PLACE YOUR BETS ***]
    > HTB{a}
    [ * CORRECT *]
    > [ * CORRECT *]
    > [ * CORRECT *]
    > [ * CORRECT *]
    > [ * INCORRECT * ]
    [ *** ACTIVATING SECURITY SYSTEM - PLEASE VACATE *** ]
    
  4. どうやら1文字ずつ取得したうえで、'a'のところで不正解と判定されている
  5. ghidra + gdb環境で動的解析を試みる

ghidra + gdbによる動的解析

  1. さっそく読み込み + デコンパイル
    image.png

  2. デコンパイルされたコードからヒントを読み取る

    • local_cはループされるたびにインクリメントされるカウンタ
      • 不正解以外でループを抜ける条件
      • すなわちフラグの最大文字数は0x1c=26とわかる
    • iVar1は標準入力から1文字を受け取り、その後加工される変数
      • srand()の引数にされているため、1文字をシードとした線形合同法の乱数を利用している
      • rand()の引数を受けているため、実質iVar1の内容をhashしたようなもの
    • iVar1はcheckの中身を加工したものと比較されている ★
      • 加工の内容は、local_cの値*4の加算
  3. ディスアセンブルされたコードからヒントを読み取る
    image.png

    • iVar1と、checkの中身を加工したものとの比較は、CMP命令で実施 ★

      • EAX側にiVar1の値、EDX側にcheckから読み取った値を加工したものを入力している
        image.png
    • checkの中身はEDXつまり32ビット(4バイト)で扱われているため、配列の中身を整理

      • バイト列を右クリック -> Data -> Choose Data Type
      • undefined1[116] -> int[29] に変更
        image.png
      • 整理したcheck配列の中身はこちら
      check
        check[0]	244B28BEh
        check[1]	0AF77805h
        check[2]	110DFC17h
        check[3]	07AFC3A1h
        check[4]	6AFEC533h
        check[5]	4ED659A2h
        check[6]	33C5D4B0h
        check[7]	286582B8h
        check[8]	43383720h
        check[9]	055A14FCh
        check[10]	19195F9Fh
        check[11]	43383720h
        check[12]	63149380h
        check[13]	615AB299h
        check[14]	6AFEC533h
        check[15]	6C6FCFB8h
        check[16]	43383720h
        check[17]	0F3DA237h
        check[18]	6AFEC533h
        check[19]	615AB299h
        check[20]	286582B8h
        check[21]	055A14FCh
        check[22]	3AE44994h
        check[23]	06D7DFE9h
        check[24]	4ED659A2h
        check[25]	0CCD4ACDh
        check[26]	57D8ED64h
        check[27]	615AB299h
        check[28]	22E9BC2Ah
      
  4. 得られたヒントから、解法を決める

    1. 簡易レインボーテーブル作戦 (今回実施したもの)
      1. iVar1に候補となる1バイトの文字データ(ASCIIコード)を当てはめた簡易レインボーテーブルを作成
      2. checkの中身と突合させる
    2. stdlib.hで実装されている乱数生成の方法を探る
    3. 標準入力を総なめしてブルートフォース

簡易レインボーテーブル作戦

  1. テーブルを吐きだすプログラムを作成

    seeds.c
     #include <stdio.h>
     #include <stdlib.h>
     #include <string.h>
     
     int main(int argc, char** argv)
     {
     	int i,length;
     	int iVar1;
     	char possibles[] = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~";
     	char local_d;
     	unsigned int local_c;
     
     	// try to hash like a casino bot
     
     	length = strlen(possibles);
     	for(i=0; i<length; i++)
     	{
     		local_d = possibles[i];
     		srand((int)local_d);
     		iVar1 = rand();
     		printf("%c\t%08x\n",possibles[i],iVar1);
     	}
     	
     	return 0;
     
     }
    
  2. 実行して簡易レインボーテーブルを準備

    table.txt
     !	276adee7
     "	15a0cf46
     #	43227a88
     $	30cd1ee4
     %	1ea510b7
     &	4c92ae68
     '	7a83d71c
     (	68e2d99a
     )	55ca9f6d
     *	0448be46
     +	3231f044
     ,	5ff762f9
     -	4df13892
     .	3c1eff45
     /	69afe3c6
     0	5788e9e7
     1	055a14fc
     2	72791e2a
     3	615ab299
     4	4ed659a2
     5	7d25629e
     6	2a2e4a08
     7	18c55c46
     8	06a8a23f
     9	74a095da
     :	621e4f69
     ;	1037a0ef
     <	7e1ed939
     =	6bed1b32
     >	598adfc2
     ?	4754662b
     @	35bbd0a8
     A	23a8da61
     B	110dfc17
     C	3e8dee8f
     D	6d54898a
     E	5af8e81d
     F	48fd3040
     G	3661a994
     H	244b28be
     I	11a67610
     J	7fcd7c76
     K	2db19fd3
     L	1c0096e1
     M	09c9bbb2
     N	372e6f70
     O	25866403
     P	53374143
     Q	415fd31c
     R	6ea28ad7
     S	5cd61ef1
     T	0af77805
     U	78960173
     V	6632344e
     W	14f987af
     X	024daf71
     Y	7093ff41
     Z	5e53760b
     [	4b7a4937
     \	7a2b633b
     ]	67d63b8e
     ^	16070ca1
     _	43383720
     a	5f5bd74e
     b	0ccd4acd
     c	3ae44994
     d	286582b8
     e	56baef0a
     f	04d48dc2
     g	32046301
     h	20083f2f
     i	4e06a707
     j	7c55cc79
     k	2a328fb5
     l	57d8ed64
     m	45730404
     n	33c5d4b0
     o	612663c5
     p	0f3da237
     q	3cc11c77
     r	6afec533
     s	19195f9f
     t	06d7dfe9
     u	747c9c5e
     v	63149380
     w	0fecfc51
     x	3e8a5639
     y	6c6fcfb8
     z	59a2e262
     {	07afc3a1
     |	75d6394e
     }	22e9bc2a
     ~	1143a539
    
  3. check配列の中身をhashed.txtに準備する

    hashed.txt
     244B28BE
     0AF77805
     110DFC17
     07AFC3A1
     6AFEC533
     4ED659A2
     33C5D4B0
     286582B8
     43383720
     055A14FC
     19195F9F
     43383720
     63149380
     615AB299
     6AFEC533
     6C6FCFB8
     43383720
     0F3DA237
     6AFEC533
     615AB299
     286582B8
     055A14FC
     3AE44994
     06D7DFE9
     4ED659A2
     0CCD4ACD
     57D8ED64
     615AB299
     22E9BC2A
    
  4. hashed.txtの中身を、table.txtのマッピングに応じて、dehashed.txtに保存する

  5. (手作業が大変なので、pythonスクリプトに任せる

    dehash.py
     def load_mapping(table_file):
         """Load the 4-byte to 1-byte character mapping from table.txt."""
         mapping = {}
         with open(table_file, 'r') as file:
             for line in file:
                 parts = line.strip().split('\t')  # タブで分割
                 if len(parts) == 2:
                     value, key = parts  # 1バイトの文字を値として、16進数のキーを保存
                     mapping[key.lower()] = value  # 小文字に変換して保存
         return mapping
     
     def dehash_file(hashed_file, dehashed_file, mapping):
         """Convert hashed_dec.txt content based on the mapping and save to dehashed.txt."""
         with open(hashed_file, 'r') as infile, open(dehashed_file, 'w') as outfile:
             for line in infile:
                 key = line.strip().lower()  # 小文字に変換して一致を確実にする
                 if key in mapping:
                     outfile.write(mapping[key])
                 else:
                     # Handle the case where the key is not found in the mapping
                     # You can choose to write a placeholder or skip
                     outfile.write('?')  # Using '?' as a placeholder for missing keys
                     # Optionally, log the missing key for debugging
                     print(f"Missing key: {key}")
     
     def main():
         table_file = 'table.txt'
         hashed_file = 'hashed.txt'
         dehashed_file = 'dehashed.txt'
     
         mapping = load_mapping(table_file)
         dehash_file(hashed_file, dehashed_file, mapping)
     
     if __name__ == '__main__':
         main()
    
  6. 得られたdehashされた文字列はこちら

    dehashed.txt
     HTB{r4nd_1s_v3ry_pr3d1ct4bl3}
    

おわりに

所属チーム内では、週次でghidraを用いた静的解析の勉強会に参加していたため、その経験が役立った。
インタフェースが似ており、またgdbの使用経験もあることから、それらを応用して無事にフラグを得ることが出来た。
また、pythonのスクリプトは生成AIを利用することで時短を測ったが、かなり精度の高いものを出力したため驚いた。
CTFでreverse問題を最後まで貫徹したのは今回が初めてであった。この成功体験を生かして、今後の勉強会やCTFに役立てたい。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?