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

# はじめに
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"をテキストエディタで開く
1. ELFから始まるバイナリ、ロボットのアスキーアートのようなものが見える
1. 早速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. どうやら1文字ずつ取得したうえで、'a'のところで不正解と判定されている
1. ghidra + gdb環境で動的解析を試みる

### ghidra + gdbによる動的解析
1. さっそく読み込み + デコンパイル
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3506223/f867b47b-a060-42cb-325b-3e079be332d0.png)
1. デコンパイルされたコードからヒントを読み取る
    - local_cはループされるたびにインクリメントされるカウンタ
        - 不正解以外でループを抜ける条件
        - すなわちフラグの最大文字数は0x1c=26とわかる
    - iVar1は標準入力から1文字を受け取り、その後加工される変数
        - srand()の引数にされているため、1文字をシードとした線形合同法の乱数を利用している
        - rand()の引数を受けているため、実質iVar1の内容をhashしたようなもの
    - iVar1はcheckの中身を加工したものと比較されている ★
        - 加工の内容は、local_cの値*4の加算
1. ディスアセンブルされたコードからヒントを読み取る
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3506223/5ff5b7ca-bc0f-60e1-c935-2c248c4b6797.png)
    - iVar1と、checkの中身を加工したものとの比較は、CMP命令で実施 ★
        - EAX側にiVar1の値、EDX側にcheckから読み取った値を加工したものを入力している
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3506223/e9c51744-fee8-9936-c82d-6f93f2b6059d.png)
    - checkの中身はEDXつまり32ビット（4バイト）で扱われているため、配列の中身を整理
        - バイト列を右クリック -> Data -> Choose Data Type
        - undefined1[116] -> int[29] に変更
        ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3506223/f2131e41-7cbd-3e09-713c-b01bd67491a7.png)
        - 整理したcheck配列の中身はこちら
        <details><summary>check</summary>
    
            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
        </details>

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

### 簡易レインボーテーブル作戦
1. テーブルを吐きだすプログラムを作成
    <details><summary>seeds.c</summary>
    
        #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;
        
        }
    </details>
1. 実行して簡易レインボーテーブルを準備
    <details><summary>table.txt</summary>
    
        !	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
    </details>
1. check配列の中身をhashed.txtに準備する
    <details><summary>hashed.txt</summary>

        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
    </details>
1. hashed.txtの中身を、table.txtのマッピングに応じて、dehashed.txtに保存する
1. （手作業が大変なので、pythonスクリプトに任せる
    <details><summary>dehash.py</summary>
    
        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()
    </details>
1. 得られたdehashされた文字列はこちら
    <details><summary>dehashed.txt</summary>
    
        HTB{r4nd_1s_v3ry_pr3d1ct4bl3}
    </details>

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