w4rmup writeup

某CTFのバイナリ問題 "w4rmup"のwriteup
※このCTFチャレンジは個人的なツテで入手したもので、作問者からCTFのイベント名を公開しないことを条件にWrite Up記載の許可をもらっています。

サマリ
提供されるバイナリは"w4rmup"という64ビットのELFファイル。ユーザーの入力した値を暗号化した後、ファイル内に暗号化された状態でハードコードされている正解flagと比較して、一致していれば"Key Accepted! Congrats!!!"と表示して終了。一致していなければ"Oooh. Are you sure you typed the key correctly??? Try harder!"と表示して終了。

解析
※逆アセンブル内の変数名や関数名は一部解析に当たり、わかりやすいようにデフォルトのものから変更しています。

"w4rmup"をIDAで開いてみると、アドレスの値が極端に小さいことに気がつく。これはバイナリがPIE形式であるため。
以下はreadelfコマンドの実行結果。


$ readelf -h w4rmup
ELF Header:
Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class:                             ELF64
Data:                              2's complement, little endian
Version:                           1 (current)
OS/ABI:                            UNIX - System V
ABI Version:                       0
Type:                              DYN (Shared object file)
Machine:                           Advanced Micro Devices X86-64
Version:                           0x1
Entry point address:               0x6e0
Start of program headers:          64 (bytes into file)
Start of section headers:          10832 (bytes into file)
Flags:                             0x0
Size of this header:               64 (bytes)
Size of program headers:           56 (bytes)
Number of program headers:         9
Size of section headers:           64 (bytes)
Number of section headers:         29
Section header string table index: 28



PIE形式ではプログラム実行時のアドレスを絶対アドレスではなく、相対アドレスで表す。なので、プログラムがどこのアドレスにロードされるかは実際にプログラムを実行してみないとわからない。もし本プログラムをGDBでデバッグする場合はまず"_start"にブレークポイントを設定して(b _start)、ロード先のアドレスを確認した後、適宜解析したい箇所にブレークポイントを設定する。

逆アセンブルを眺めてみると、まず以下のコードが目につく。


.text:0000000000000AF5 C6 45 90 31             mov     [rbp+dummy_key], 31h ; '1'
.text:0000000000000AF9 C6 45 91 7A             mov     [rbp+var_6F], 7Ah ; 'z'
.text:0000000000000AFD C6 45 92 5F             mov     [rbp+var_6E], 5Fh ; '_'
.text:0000000000000B01 C6 45 93 74             mov     [rbp+var_6D], 74h ; 't'
.text:0000000000000B05 C6 45 94 68             mov     [rbp+var_6C], 68h ; 'h'
.text:0000000000000B09 C6 45 95 31             mov     [rbp+var_6B], 31h ; '1'
.text:0000000000000B0D C6 45 96 7A             mov     [rbp+var_6A], 7Ah ; 'z'
.text:0000000000000B11 C6 45 97 5F             mov     [rbp+var_69], 5Fh ; '_'
.text:0000000000000B15 C6 45 98 74             mov     [rbp+var_68], 74h ; 't'
.text:0000000000000B19 C6 45 99 68             mov     [rbp+var_67], 68h ; 'h'
.text:0000000000000B1D C6 45 9A 33             mov     [rbp+var_66], 33h ; '3'
.text:0000000000000B21 C6 45 9B 5F             mov     [rbp+var_65], 5Fh ; '_'
.text:0000000000000B25 C6 45 9C 72             mov     [rbp+var_64], 72h ; 'r'
.text:0000000000000B29 C6 45 9D 33             mov     [rbp+var_63], 33h ; '3'
.text:0000000000000B2D C6 45 9E 34             mov     [rbp+var_62], 34h ; '4'
.text:0000000000000B31 C6 45 9F 6C             mov     [rbp+var_61], 6Ch ; 'l'
.text:0000000000000B35 C6 45 A0 5F             mov     [rbp+var_60], 5Fh ; '_'
.text:0000000000000B39 C6 45 A1 66             mov     [rbp+var_5F], 66h ; 'f'
.text:0000000000000B3D C6 45 A2 6C             mov     [rbp+var_5E], 6Ch ; 'l'
.text:0000000000000B41 C6 45 A3 34             mov     [rbp+var_5D], 34h ; '4'
.text:0000000000000B45 C6 45 A4 67             mov     [rbp+var_5C], 67h ; 'g'
.text:0000000000000B49 C6 45 A5 5F             mov     [rbp+var_5B], 5Fh ; '_'
.text:0000000000000B4D C6 45 A6 31             mov     [rbp+var_5A], 31h ; '1'
.text:0000000000000B51 C6 45 A7 7A             mov     [rbp+var_59], 7Ah ; 'z'
.text:0000000000000B55 C6 45 A8 5F             mov     [rbp+var_58], 5Fh ; '_'
.text:0000000000000B59 C6 45 A9 74             mov     [rbp+var_57], 74h ; 't'
.text:0000000000000B5D C6 45 AA 68             mov     [rbp+var_56], 68h ; 'h'
.text:0000000000000B61 C6 45 AB 31             mov     [rbp+var_55], 31h ; '1'
.text:0000000000000B65 C6 45 AC 7A             mov     [rbp+var_54], 7Ah ; 'z'
.text:0000000000000B69 C6 45 AD 5F             mov     [rbp+var_53], 5Fh ; '_'
.text:0000000000000B6D C6 45 AE 6A             mov     [rbp+var_52], 6Ah ; 'j'
.text:0000000000000B71 C6 45 AF 75             mov     [rbp+var_51], 75h ; 'u'
.text:0000000000000B75 C6 45 B0 35             mov     [rbp+var_50], 35h ; '5'
.text:0000000000000B79 C6 45 B1 74             mov     [rbp+var_4F], 74h ; 't'
.text:0000000000000B7D C6 45 B2 5F             mov     [rbp+var_4E], 5Fh ; '_'
.text:0000000000000B81 C6 45 B3 66             mov     [rbp+var_4D], 66h ; 'f'
.text:0000000000000B85 C6 45 B4 34             mov     [rbp+var_4C], 34h ; '4'
.text:0000000000000B89 C6 45 B5 6E             mov     [rbp+var_4B], 6Eh ; 'n'
.text:0000000000000B8D C6 45 B6 74             mov     [rbp+var_4A], 74h ; 't'
.text:0000000000000B91 C6 45 B7 34             mov     [rbp+var_49], 34h ; '4'
.text:0000000000000B95 C6 45 B8 73             mov     [rbp+var_48], 73h ; 's'
.text:0000000000000B99 C6 45 B9 79             mov     [rbp+var_47], 79h ; 'y'
.text:0000000000000B9D C6 45 BA 00             mov     [rbp+var_46], 0



"1z_th1z_th3_r34l_fl4g_1z_th1z_ju5t_f4nt4sy" はダミーのflagで、このダミーflagを入力すると、不正解としてプログラムは終了する。

また、実行時に以下のコードで自身がデバッガーでデバッグされているか確認し (GDBなどのデバッガーはプロセスのアタッチにptrace関数を利用する)、デバッガーの存在を確認した場合プログラムを終了する。


.text:0000000000000BBA E8 01 FB FF FF          call    _ptrace         ; anti debugger
.text:0000000000000BBF 48 85 C0                test    rax, rax
.text:0000000000000BC2 79 0B                   jns     short loc_BCF



もし、本プログラムをGDBなどのデバッガーでデバッグする場合は上記のコードをNOPするなどして、書き換える必要がある。

ユーザーが入力したflagはstrcmp関数へと渡される。このstrcmp関数はカスタムの関数で、ユーザーが入力したflagを暗号化し、同じく暗号化された正解flagと一致するか確認する。flagが一致していれば戻り値に「1」を返し、一致しない場合は戻り値に「0」を返す。

strcmp関数は、まずユーザーの入力したflagが先述したダミーflagかどうかチェックする。次に入力したflagが42バイトかどうかチェックする。両方のチェックをパスすると、ユーザーの入力したflagを暗号化して、正解flagと一致するか確認する。

以降は暗号化ルーチンの解析。


.text:00000000000008B5 C6 45 B2 69             mov     [rbp+XOR_key], 69h ; 'i'
------
.text:0000000000000924 8B 45 B4                mov     eax, [rbp+var_4C]
.text:0000000000000927 48 63 D0                movsxd  rdx, eax
.text:000000000000092A 48 8B 45 A8             mov     rax, [rbp+usr_input]
.text:000000000000092E 48 01 D0                add     rax, rdx
.text:0000000000000931 0F B6 00                movzx   eax, byte ptr [rax]
.text:0000000000000934 38 45 B2                cmp     [rbp+XOR_key], al
.text:0000000000000937 74 22                   jz      short loc_95B
------
.text:0000000000000939 8B 45 B4                mov     eax, [rbp+var_4C]
.text:000000000000093C 48 63 D0                movsxd  rdx, eax
.text:000000000000093F 48 8B 45 A8             mov     rax, [rbp+usr_input]
.text:0000000000000943 48 01 D0                add     rax, rdx
.text:0000000000000946 0F B6 00                movzx   eax, byte ptr [rax]
.text:0000000000000949 8B 55 B4                mov     edx, [rbp+var_4C]
.text:000000000000094C 48 63 CA                movsxd  rcx, edx
.text:000000000000094F 48 8B 55 A8             mov     rdx, [rbp+usr_input]
.text:0000000000000953 48 01 CA                add     rdx, rcx
.text:0000000000000956 32 45 B2                xor     al, [rbp+XOR_key]
.text:0000000000000959 88 02                   mov     [rdx], al



ユーザーの入力したflagの先頭1バイトを取り出し、XOR_keyの値と等しいか確認する。等しくない場合は入力された値1バイトとXOR_keyの値をXORする。初回のXOR_keyの値は0x69


.text:000000000000095B 8B 45 B4                mov     eax, [rbp+var_4C]
.text:000000000000095E 48 63 D0                movsxd  rdx, eax
.text:0000000000000961 48 8B 45 A8             mov     rax, [rbp+usr_input]
.text:0000000000000965 48 01 D0                add     rax, rdx
.text:0000000000000968 0F B6 00                movzx   eax, byte ptr [rax]
.text:000000000000096B 0F B6 C0                movzx   eax, al
.text:000000000000096E C1 E0 04                shl     eax, 4
.text:0000000000000971 89 C1                   mov     ecx, eax
.text:0000000000000973 8B 45 B4                mov     eax, [rbp+var_4C]
.text:0000000000000976 48 63 D0                movsxd  rdx, eax
.text:0000000000000979 48 8B 45 A8             mov     rax, [rbp+usr_input]
.text:000000000000097D 48 01 D0                add     rax, rdx
.text:0000000000000980 0F B6 00                movzx   eax, byte ptr [rax]
.text:0000000000000983 C0 E8 04                shr     al, 4
.text:0000000000000986 09 C1                   or      ecx, eax
.text:0000000000000988 8B 45 B4                mov     eax, [rbp+var_4C]
.text:000000000000098B 48 63 D0                movsxd  rdx, eax
.text:000000000000098E 48 8B 45 A8             mov     rax, [rbp+usr_input]
.text:0000000000000992 48 01 D0                add     rax, rdx
.text:0000000000000995 89 CA                   mov     edx, ecx
.text:0000000000000997 88 10                   mov     [rax], dl
.text:0000000000000999 8B 45 B4                mov     eax, [rbp+var_4C]
.text:000000000000099C 48 63 D0                movsxd  rdx, eax
.text:000000000000099F 48 8B 45 A8             mov     rax, [rbp+usr_input]
.text:00000000000009A3 48 01 D0                add     rax, rdx
.text:00000000000009A6 0F B6 00                movzx   eax, byte ptr [rax]
.text:00000000000009A9 88 45 B2                mov     [rbp+XOR_key], al
.text:00000000000009AC 8B 45 B4                mov     eax, [rbp+var_4C]
.text:00000000000009AF 48 63 D0                movsxd  rdx, eax
.text:00000000000009B2 48 8B 45 A8             mov     rax, [rbp+usr_input]
.text:00000000000009B6 48 01 D0                add     rax, rdx
.text:00000000000009B9 0F B6 10                movzx   edx, byte ptr [rax]
.text:00000000000009BC 8B 45 B4                mov     eax, [rbp+var_4C]
.text:00000000000009BF 48 98                   cdqe
.text:00000000000009C1 0F B6 44 05 C0          movzx   eax, [rbp+rax+enc_flag]
.text:00000000000009C6 38 C2                   cmp     dl, al          ; dl holds user input, al holds encrypted flag



1. XORした値を左へ4シフトさせてecxレジスタに格納
2. XORした値を右へ4シフトさせてeax (al)レジスタに格納
3. ecxの値とeaxの値をOR演算して結果をedxレジスタに格納
4. 3.のOR演算結果の下位8ビットをXOR_keyに格納し、次回のXOR演算時のキーとして使用する。
5. enc_flag (正解flag) の先頭1バイトをeaxレジスタに格納。enc_flagには以下のデータが格納されている。
0xD19BAA0D2541B3CEAB3CF0FA88BBF8496170C15F83CD9B3FE0A96FE01D3734B68DDB48E26D30440747E3

最後にedx (dl) に格納されている暗号化されたユーザー入力のflag1バイトとeax (al) に格納されている暗号化された正解flag1バイトが等しいか確認する。この処理を繰り返してユーザーの入力したflagを1バイトずつ検証する。

。。。文章にしても、いまいちスッと頭に入ってこないので上述の暗号化ルーチンを擬似コードに落とし込んでみる


original_encrypted_flag = "D1 9B AA 0D 25 41 B3 CE AB 3C F0 FA 88 BB F8 49 61 70 C1 5F 83 CD 9B 3F E0 A9 6F E0 1D 37 34 B6 8D DB 48 E2 6D 30 44 07 47 E3"

XOR_key = 0x69 //初回のXORキー
i = 0

loop:
    if (usr_input[i] != XOR_key):
        XORed_user_input = (usr_input[i] ^ XOR_key)

    left_shifted = XORed_user_input << 4
    right_shifted = XORed_user_input >> 4
    encrypted_user_typed_flag = (left_shifted | right_shifted)
    XOR_key = encrypted_user_typed_flag[-8:] //暗号化したユーザー入力の下位8ビットを次回のXORキーとして使用する

    is(original_encrypted_flag[i] == encrypted_user_typed_flag)

    i += 1


平文の正解flagを取得するには上記の暗号化ルーチンと逆の処理をする必要がある。
以下のスクリプトを書いた。


#!/usr/bin/env python

## Get XOR keys
enc_flag = "209 155 170 13 37 65 179 206 171 60 240 250 136 187 248 73 97 112 193 95 131 205 155 63 224 169 111 224 29 55 52 182 141 219 72 226 109 48 68 7 71 227" ## 0xD19BAA0D2541B3CEAB3CF0FA88BBF8496170C15F83CD9B3FE0A96FE01D3734B68DDB48E26D30440747E3
enc_flag_list = enc_flag.split(' ')
enc_flag_length = len(enc_flag_list)
XOR_key = []
al = 0
i = 0
n = 0
while (n < enc_flag_length):
    OR_ed = (al << 4 | al >> 4)
    lsb = int(bin(OR_ed).replace('0b', '')[-8:], 2)
    if (lsb == int(enc_flag_list[i])):
        #print(al)
        XOR_key.append(al)
        al = 0
        i += 1
        n += 1
    else:
        al += 1


## Perform XOR with encrypted flag
encrypted_flag = "105 209 155 170 13 37 65 179 206 171 60 240 250 136 187 248 73 97 112 193 95 131 205 155 63 224 169 111 224 29 55 52 182 141 219 72 226 109 48 68 7 71" # 105 (0x69) is hard-coded initial XOR key
encrypted_flag = encrypted_flag.split(" ")
flag_length = len(encrypted_flag)
decoded_flag = ""

for i in range(0, (flag_length)):
    decoded_flag += chr(int(encrypted_flag[i]) ^ int(XOR_key[i]))

print(decoded_flag)



$ python re200-solver-final.py
th1z_1z_th3_r34l_fl4g_th1z_a1nt_n0_f4nt4sy

flagはth1z_1z_th3_r34l_fl4g_th1z_a1nt_n0_f4nt4sy

以上。

Leave a Reply

Your email address will not be published. Required fields are marked *