某CTFのバイナリ問題 "w3lc0m3"のwriteup
※このCTFチャレンジは個人的なツテで入手したもので、作問者からCTFのイベント名を公開しないことを条件にWrite Up記載の許可をもらっています。
サマリ
提供されるバイナリは"w3lc0m3"という32ビットのELFファイル。ユーザーの入力した値を暗号化してエンコードした後、ファイル内に暗号化された状態でハードコードされている正解flagと比較して、一致していれば"Key Accepted! Congrats!!!"と表示して終了。一致していなければ"Bad Key!"と表示して終了。
解析
※逆アセンブル内の変数名や関数名は一部解析に当たり、わかりやすいようにデフォルトのものから変更しています。
"w3lc0m3"はまず、ユーザーの入力した値のサイズが29バイトかどうか確認する。
.text:08048881 B8 60 8A 04 08 mov eax, offset aEnterTheKey ; "\nEnter the key: "
.text:08048886 89 04 24 mov [esp], eax
.text:08048889 E8 82 FB FF FF call _printf
.text:0804888E B8 71 8A 04 08 mov eax, offset aS ; "%s"
.text:08048893 8D 54 24 2F lea edx, [esp+2Fh]
.text:08048897 89 54 24 04 mov [esp+4], edx
.text:0804889B 89 04 24 mov [esp], eax
.text:0804889E E8 DD FB FF FF call ___isoc99_scanf
.text:080488A3 8D 44 24 2F lea eax, [esp+2Fh]
.text:080488A7 C7 44 24 1C FF FF FF FF mov dword ptr [esp+1Ch], 0FFFFFFFFh
.text:080488AF 89 C2 mov edx, eax
.text:080488B1 B8 00 00 00 00 mov eax, 0
.text:080488B6 8B 4C 24 1C mov ecx, [esp+1Ch]
.text:080488BA 89 D7 mov edi, edx
.text:080488BC F2 AE repne scasb
.text:080488BE 89 C8 mov eax, ecx
.text:080488C0 F7 D0 not eax
.text:080488C2 83 E8 01 sub eax, 1
.text:080488C5 83 F8 1D cmp eax, 29
.text:080488C8 75 33 jnz short loc_80488FD
入力した値のサイズが29バイトだった場合、flag検証の関数(アドレス: 0x80486ED)へ処理を渡す。
flag検証の関数を眺めてみて、まず目についたのが以下のデータ。
.text:0804870D C6 45 CB 65 mov [ebp+encKey], 'e'
.text:08048711 C6 45 CC 58 mov [ebp+var_34], 'X'
.text:08048715 C6 45 CD 52 mov [ebp+var_33], 'R'
.text:08048719 C6 45 CE 71 mov [ebp+var_32], 'q'
.text:0804871D C6 45 CF 5A mov [ebp+var_31], 'Z'
.text:08048721 C6 45 D0 52 mov [ebp+var_30], 'R'
.text:08048725 C6 45 D1 49 mov [ebp+var_2F], 'I'
.text:08048729 C6 45 D2 46 mov [ebp+var_2E], 'F'
.text:0804872D C6 45 D3 47 mov [ebp+var_2D], 'G'
.text:08048731 C6 45 D4 78 mov [ebp+var_2C], 'x'
.text:08048735 C6 45 D5 34 mov [ebp+var_2B], '4'
.text:08048739 C6 45 D6 59 mov [ebp+var_2A], 'Y'
.text:0804873D C6 45 D7 65 mov [ebp+var_29], 'e'
.text:08048741 C6 45 D8 32 mov [ebp+var_28], '2'
.text:08048745 C6 45 D9 67 mov [ebp+var_27], 'g'
.text:08048749 C6 45 DA 44 mov [ebp+var_26], 'D'
.text:0804874D C6 45 DB 5A mov [ebp+var_25], 'Z'
.text:08048751 C6 45 DC 57 mov [ebp+var_24], 'W'
.text:08048755 C6 45 DD 4D mov [ebp+var_23], 'M'
.text:08048759 C6 45 DE 67 mov [ebp+var_22], 'g'
.text:0804875D C6 45 DF 5A mov [ebp+var_21], 'Z'
.text:08048761 C6 45 E0 67 mov [ebp+var_20], 'g'
.text:08048765 C6 45 E1 4E mov [ebp+var_1F], 'N'
.text:08048769 C6 45 E2 5A mov [ebp+var_1E], 'Z'
.text:0804876D C6 45 E3 44 mov [ebp+var_1D], 'D'
.text:08048771 C6 45 E4 58 mov [ebp+var_1C], 'X'
.text:08048775 C6 45 E5 35 mov [ebp+var_1B], '5'
.text:08048779 C6 45 E6 32 mov [ebp+var_1A], '2'
.text:0804877D C6 45 E7 63 mov [ebp+var_19], 'c'
.text:08048781 C6 45 E8 77 mov [ebp+var_18], 'w'
.text:08048785 C6 45 E9 77 mov [ebp+var_17], 'w'
.text:08048789 C6 45 EA 58 mov [ebp+var_16], 'X'
.text:0804878D C6 45 EB 64 mov [ebp+var_15], 'd'
.text:08048791 C6 45 EC 47 mov [ebp+var_14], 'G'
.text:08048795 C6 45 ED 70 mov [ebp+var_13], 'p'
.text:08048799 C6 45 EE 7A mov [ebp+var_12], 'z'
.text:0804879D C6 45 EF 5A mov [ebp+var_11], 'Z'
.text:080487A1 C6 45 F0 6E mov [ebp+var_10], 'n'
.text:080487A5 C6 45 F1 63 mov [ebp+var_F], 'c'
.text:080487A9 C6 45 F2 3D mov [ebp+var_E], '='
上記のデータは暗号化された正解flagでユーザーが入力した値との比較に使用される。
flag比較の前にまずユーザーの入力内容が暗号化される。
.text:080487C1 8B 45 BC mov eax, [ebp+counter]
.text:080487C4 83 C0 23 add eax, 23h ; 35
.text:080487C7 89 45 C0 mov [ebp+xor_key], eax
.text:080487CA 8B 45 BC mov eax, [ebp+counter]
.text:080487CD 03 45 B4 add eax, [ebp+user_input]
.text:080487D0 8B 55 BC mov edx, [ebp+counter]
.text:080487D3 03 55 B4 add edx, [ebp+user_input]
.text:080487D6 0F B6 0A movzx ecx, byte ptr [edx]
.text:080487D9 8B 55 C0 mov edx, [ebp+xor_key]
.text:080487DC 31 CA xor edx, ecx
.text:080487DE 88 10 mov [eax], dl
.text:080487E0 83 45 BC 01 add [ebp+counter], 1
上記はユーザーの入力内容を1バイトずつXORする処理。最初に使用されるXORキーは0x23で、キーの値は処理が進むごとに1ずつ加算される。(1文字目は0x23と、 2文字目は0x24と、3文字目は0x25とXORする..といった具合。)
ユーザー入力のXOR暗号化が完了すると、それをさらにカスタムBase64エンコードして先述した暗号化された正解flag "eXRqZRIFGx4Ye2gDZWMgZgNZDX52cwwXdGpzZnc=" と一致するか比較する。
.text:0804881D 89 04 24 mov [esp], eax ; encrypted user input
.text:08048820 E8 1F FD FF FF call key_customBase64_8048544 ;
.text:08048825 89 45 C4 mov [ebp+var_3C], eax ; custom b64 encoded user input
.text:08048828 8D 45 CB lea eax, [ebp+encKey] ; eXRqZRIFGx4Ye2gDZWMgZgNZDX52cwwXdGpzZnc=
.text:0804882B 89 44 24 04 mov [esp+4], eax
.text:0804882F 8B 45 C4 mov eax, [ebp+var_3C]
.text:08048832 89 04 24 mov [esp], eax
.text:08048835 E8 C6 FB FF FF call _strcmp
key_customBase64_8048544関数はBase64エンコードを行う関数だが、通常のBase64変換テーブルではなく、以下のカスタムの変換テーブルを使用する。
ABCDEFGHIJKLNMOPQRSTUVWXYZabcdefghijklnmopqrstuvwxyz0123456789+/
※ "N"と"M"、"n"と"m"の位置が通常の変換テーブルと逆
以上の解析から、暗号化された正解flag "eXRqZRIFGx4Ye2gDZWMgZgNZDX52cwwXdGpzZnc="をカスタムBase64デコードした後、XOR処理すれば平文の正解flagを取得できることが判明した。
カスタムBase64デコードには以下のスクリプトを使用。
#!/usr/bin/python
# This is a simple script to decode / encode custom base64
# Fill the "CUSTOM_ALPHABET" with custom base64 table
'''
# Standard table
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
'''
import string
import base64
import argparse
STANDARD_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
#ENCODE_TRANS = string.maketrans(STANDARD_ALPHABET, CUSTOM_ALPHABET)
#DECODE_TRANS = string.maketrans(CUSTOM_ALPHABET, STANDARD_ALPHABET)
parser = argparse.ArgumentParser(description="Decode or encode custom base64")
parser.add_argument("-s", "--string", action="store", help="String to encode / decode", dest="STRING")
parser.add_argument("-d", "--decode", action="store_true", help="Decode the string", dest="DECODE")
parser.add_argument("-e", "--encode", action="store_true", help="Encode the string", dest="ENCODE")
parser.add_argument("-t", "--table", action="store", help="Table for custom base64", dest="TABLE")
args = parser.parse_args()
if args.DECODE and args.TABLE and not args.ENCODE:
CUSTOM_ALPHABET = args.TABLE
DECODE_TRANS = string.maketrans(CUSTOM_ALPHABET, STANDARD_ALPHABET)
print base64.b64decode(args.STRING.translate(DECODE_TRANS))
elif args.ENCODE and args.TABLE and not args.DECODE:
CUSTOM_ALPHABET = args.TABLE
ENCODE_TRANS = string.maketrans(STANDARD_ALPHABET, CUSTOM_ALPHABET)
print base64.b64encode(args.STRING.translate(ENCODE_TRANS))
else:
parser.print_help()
デコード結果はキレイに平文にはならないので、いったんhexエンコードする。
$ python custom_base64.py -t "ABCDEFGHIJKLNMOPQRSTUVWXYZabcdefghijklnmopqrstuvwxyz0123456789+/" -d -s "eXRqZRIFGx4Ye2gDZWMgZgNZDX52cwwXdGpzZnc=" | tr -d '\r\n' | xxd -p
79746a6512051b1e187b68036563606603197e76730c17746a736667
以下のスクリプトを用いてXORを行う。
#!/usr/bin/env python
import binascii
flag = "79746a6512051b1e187b68036563606603190d7e76730c17746a736667"
flag = bytearray(binascii.unhexlify(flag))
flag_length = len(flag)
XORed_bytearray = bytearray(flag_length)
key = "23"
key = bytearray(binascii.unhexlify(key))
for i in range(0, flag_length):
XORed_bytearray[i] = flag[i] ^ key[0]
key[0] += 1
print(XORed_bytearray)
$ python flag_decryptor.py
ZPOC5-243WE-JSQT0-8HAK5-OVNXX
flagはZPOC5-243WE-JSQT0-8HAK5-OVNXX
以上。