# 2025-12-18_"twilight": 251218 **2025,12,18 0:13 以前のバージョンに対する解答です。** <https://alpacahack.com/daily/challenges/twilight> Level: Medium Type: Reverse Engineering solved day: 2025,12,18 ## solution ```sh python3 solve.py Flag: Alpaca{AlpacaHack_in_Wonderland} ``` ## How I thought. ```sh ls -la .rwxrwxr-x@ 16k s12810162 18 Dec 00:12 chall .rw-rw-r--@ 193 s12810162 18 Dec 00:12 out.txt ``` ### 1. ファイルの確認 ```sh file chall chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=cad54f8480fb7120a6bf06f1647f1801e946a02c, for GNU/Linux 3.2.0, not stripped ``` * 64-bit ELF バイナリ、not stripped(シンボル情報あり) * `out.txt` には暗号化された値が16進数で格納されている ``` 0x41, 0x6D, 0x72, 0x62, 0x67, 0x64, 0x81, 0x46, 0x74, 0x79, 0x6B, 0x68, 0x6D, 0x45, 0x6F, 0x6C, 0x7B, 0x4E, 0x7B, 0x7D, 0x73, 0x42, 0x85, 0x79, 0x7C, 0x7C, 0x8C, 0x77, 0x7D, 0x73, 0x82, 0x62, ``` ### 2. バイナリの解析 ```sh strings chall Enter the flag: %33s 0x%X, ``` * フラグを入力として受け取り、何らかの処理をして16進数で出力する * 最大33文字の入力を受け付ける ### 3. `objdump` でディスアセンブル #### main関数の主要部分 ```asm main: ; "Enter the flag: " を出力 lea rax, [rip + 0xdea] mov rdi, rax call printf ; 入力を受け取る (scanf("%33s", buffer)) lea rax, [rbp - 0x40] mov rsi, rax lea rax, [rip + 0xde0] # "%33s" mov rdi, rax call scanf ; 関数ポインタの配列を準備 lea rax, [rip - 0x80] # function 'a' mov qword ptr [rbp - 0x50], rax lea rax, [rip - 0x73] # function 'b' mov qword ptr [rbp - 0x48], rax ; ループ処理 mov dword ptr [rbp - 0x54], 0x0 # i = 0 loop_start: mov eax, dword ptr [rbp - 0x54] # eax = i cdq shr edx, 0x1f add eax, edx and eax, 0x1 # eax = i % 2 sub eax, edx cdqe mov rcx, qword ptr [rbp + 8*rax - 0x50] # rcx = (i%2==0) ? a : b mov edx, dword ptr [rbp - 0x54] # edx = i (引数2) mov eax, dword ptr [rbp - 0x54] # eax = i cdqe movzx eax, byte ptr [rbp + rax - 0x40] # eax = input[i] (引数1) mov esi, edx mov edi, eax call rcx # result = func(input[i], i) ; 結果を出力 mov esi, eax lea rax, [rip + 0xd85] # "0x%X, " mov rdi, rax call printf add dword ptr [rbp - 0x54], 0x1 # i++ ; strlen(input) と比較してループ継続 ``` #### 関数 a (0x11c9) ```asm a: endbr64 push rbp mov rbp, rsp mov dword ptr [rbp - 0x4], edi # arg1 (文字) mov dword ptr [rbp - 0x8], esi # arg2 (インデックス) mov edx, dword ptr [rbp - 0x4] mov eax, dword ptr [rbp - 0x8] add eax, edx # return arg1 + arg2 pop rbp ret ``` #### 関数 b (0x11e1) ```asm b: endbr64 push rbp mov rbp, rsp mov dword ptr [rbp - 0x4], edi # arg1 (文字) mov dword ptr [rbp - 0x8], esi # arg2 (インデックス) mov eax, dword ptr [rbp - 0x4] xor eax, dword ptr [rbp - 0x8] # return arg1 ^ arg2 pop rbp ret ``` ### 4. アルゴリズムの理解 バイナリは以下の処理を行う: * 入力文字列の各文字に対して、インデックス `i` ごとに異なる関数を適用 * **偶数インデックス** (0, 2, 4, ...): 関数 `a` → `char + index` * **奇数インデックス** (1, 3, 5, ...): 関数 `b` → `char ^ index` * 結果を16進数で出力 ### 5. 復号化ロジック 逆算するには: * **偶数インデックス**: `encrypted_value = char + index` なので `char = encrypted_value - index` * **奇数インデックス**: `encrypted_value = char ^ index` なので `char = encrypted_value ^ index` (XORは自己逆演算) ### 6. 解答スクリプト (`solve.py`) ```python #!/usr/bin/env python3 # Encrypted values from out.txt encrypted = [0x41, 0x6D, 0x72, 0x62, 0x67, 0x64, 0x81, 0x46, 0x74, 0x79, 0x6B, 0x68, 0x6D, 0x45, 0x6F, 0x6C, 0x7B, 0x4E, 0x7B, 0x7D, 0x73, 0x42, 0x85, 0x79, 0x7C, 0x7C, 0x8C, 0x77, 0x7D, 0x73, 0x82, 0x62] flag = [] for i, enc_val in enumerate(encrypted): if i % 2 == 0: # Even index: used function a (char + index) # So: enc_val = char + index # Therefore: char = enc_val - index original_char = enc_val - i else: # Odd index: used function b (char ^ index) # So: enc_val = char ^ index # Therefore: char = enc_val ^ index (XOR is its own inverse) original_char = enc_val ^ i flag.append(chr(original_char)) flag_string = ''.join(flag) print(f"Flag: {flag_string}") ``` ### 7. 実行結果 ```sh python3 solve.py Flag: Alpaca{AlpacaHack_in_Wonderland} ``` ## まとめ * バイナリは入力文字列に対して、偶数インデックスでは「文字+インデックス」、奇数インデックスでは「文字^インデックス」の演算を適用 * `objdump` でディスアセンブルし、2つの関数(加算とXOR)が交互に呼ばれることを特定 * XORは自己逆演算、加算の逆は減算という性質を利用して復号化スクリプトを作成 * `out.txt` の暗号化データを逆算することでフラグを取得