はじめに
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
表層解析
- "casino"をテキストエディタで開く
- ELFから始まるバイナリ、ロボットのアスキーアートのようなものが見える
- 早速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 *** ]
- どうやら1文字ずつ取得したうえで、'a'のところで不正解と判定されている
- ghidra + gdb環境で動的解析を試みる
ghidra + gdbによる動的解析
-
デコンパイルされたコードからヒントを読み取る
- local_cはループされるたびにインクリメントされるカウンタ
- 不正解以外でループを抜ける条件
- すなわちフラグの最大文字数は0x1c=26とわかる
- iVar1は標準入力から1文字を受け取り、その後加工される変数
- srand()の引数にされているため、1文字をシードとした線形合同法の乱数を利用している
- rand()の引数を受けているため、実質iVar1の内容をhashしたようなもの
- iVar1はcheckの中身を加工したものと比較されている ★
- 加工の内容は、local_cの値*4の加算
- local_cはループされるたびにインクリメントされるカウンタ
-
-
iVar1と、checkの中身を加工したものとの比較は、CMP命令で実施 ★
-
checkの中身はEDXつまり32ビット(4バイト)で扱われているため、配列の中身を整理
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
-
-
得られたヒントから、解法を決める
- 簡易レインボーテーブル作戦 (今回実施したもの)
- iVar1に候補となる1バイトの文字データ(ASCIIコード)を当てはめた簡易レインボーテーブルを作成
- checkの中身と突合させる
- stdlib.hで実装されている乱数生成の方法を探る
- 標準入力を総なめしてブルートフォース
- 簡易レインボーテーブル作戦 (今回実施したもの)
簡易レインボーテーブル作戦
-
テーブルを吐きだすプログラムを作成
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; }
-
実行して簡易レインボーテーブルを準備
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
-
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
-
hashed.txtの中身を、table.txtのマッピングに応じて、dehashed.txtに保存する
-
(手作業が大変なので、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()
-
得られたdehashされた文字列はこちら
dehashed.txt
HTB{r4nd_1s_v3ry_pr3d1ct4bl3}
おわりに
所属チーム内では、週次でghidraを用いた静的解析の勉強会に参加していたため、その経験が役立った。
インタフェースが似ており、またgdbの使用経験もあることから、それらを応用して無事にフラグを得ることが出来た。
また、pythonのスクリプトは生成AIを利用することで時短を測ったが、かなり精度の高いものを出力したため驚いた。
CTFでreverse問題を最後まで貫徹したのは今回が初めてであった。この成功体験を生かして、今後の勉強会やCTFに役立てたい。