こんにちは、ky0ta168 です。
難易度Easyですが、TryHackMe | Reversing ELF を自力で全て解くことができたので、記念にWriteupを残したいと思います。こちらのRoomはCTF形式となっています。
※ コマンド、ツール等の説明はしません。
※ フラグは x で伏せています。
Crackme1
Download Task Filesをクリックすると crackme1 をダウンロードできます。(以降の問題も同じようにファイルをダウンロードできるので省略します。)
Let's start with a basic warmup, can you run the binary? と書かれているので実行してみます。
$ ./crackme1flag{xxxxxxxxxxxxxxxxxxxx}フラグ取得です。
Crackme2
とりあえず実行してみます。
$ ./crackme2Usage: ./crackme2 passwordコマンドライン引数が必要のようです。
aaaa を入力してみます。
$ ./crackme2 aaaaAccess denied.が、もちろん Access denied. です。
次は strings で調査します。
$ strings crackme2... (省略)Usage: %s passwordsuper_secret_passwordAccess denied.Access granted....コマンドライン引数を求めるメッセージや、パスワード失敗のメッセージがある中、super_secret_password という気になる文字列があります。
super_secret_password を入力してみます。
$ ./crackme2 super_secret_passwordAccess granted.flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}フラグ取得です。
Crackme3
とりあえず実行してみます。
$ ./crackme3Usage: ./crackme3 PASSWORDコマンドライン引数が必要のようです。
aaaa を入力してみます。
$ ./crackme3 aaaCome on, even my aunt Mildred got this one!strings で調査します。
$ strings crackme3...(省略)Usage: %s PASSWORDmalloc failedZjByX3kwdXJfNWVjMG5kX2xlNTVvbl91bmJhc2U2NF80bGxfN2gzXzdoMW5nNQ==Correct password!Come on, even my aunt Mildred got this one!ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/...ZjByX3kwdXJfNWVjMG5kX2xlNTVvbl91bmJhc2U2NF80bGxfN2gzXzdoMW5nNQ== が Base64 エンコードされていそうなので、デコードしてみます。
(Base64 については base64ってなんぞ??理解のために実装してみた が面白くておすすめです。)
$ echo "ZjByX3kwdXJfNWVjMG5kX2xlNTVvbl91bmJhc2U2NF80bGxfN2gzXzdoMW5nNQ==" | base64 -dxxx_xxxx_xxxxxx_xxxxxx_xxxxxxxx_xxx_xxx_xxxxxx$ ./crackme3 xxx_xxxx_xxxxxx_xxxxxx_xxxxxxxx_xxx_xxx_xxxxxxCorrect password!flag{} のような形式ではありませんが、フラグ取得です。
Crackme4
とりあえず実行してみます。
$ ./crackme4Usage : ./crackme4 passwordThis time the string is hidden and we used strcmpコマンドライン引数が必要のようです。
aaaa を入力してみます。
$ ./crackme4 aaaapassword "aaaa" not OKstrings では注目するような文字列はなかったため、 Ghidra で調査します。
main()
main() は以下のようになっています。

コマンドライン引数がある場合 compare_pwd() にコマンドライン引数を渡しています。
compare_pwd()
compare_pwd() は以下のようになっています。

get_pwd() があり、&local_28 が渡されています。&local_28 のバイナリをASCII文字列に変換しても印字不可のため、get_pwd() のなかで変換されることが予想されます。
その後、strcmp() にて、&local_28 と コマンドライン引数 を比較して、一致している場合、password OK が表示されます。
get_pwd()
get_pwd() の中を見てみます。

&local_28 のバイナリを1文字ずつみて、'\0' になるまでループします。
ループの中では&local_28 のデータを1文字ずつを 0x24 で xor しています。
その後 compare_pwd() で見たようにコマンドライン引数と比較されるため、&local_28 のデータを1文字ずつを 0x24 で xor された文字列がパスワードとなります。
Solver
$ file crackme4crackme4: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=862ee37793af334043b423ba50ec91cfa132260a, not stripped64bit なので struct.pack の際に "<Q" を指定する。
Python で Solver を書きました。
import struct
# 0x7B175614497B5D49 などをリトルエンディアンに変換するkey = struct.pack("<Q", 0x7B175614497B5D49)key += struct.pack("<Q", 0x547B175651474157)# 余計な 0 が含まれてしまうので "<H" を指定するkey += struct.pack("<H", 0x4053)
# パスワードpwd = []
# key を1文字ずつ 0x24 で xor をするfor k in key: pwd.append(chr(k ^ 0x24))
print("".join(pwd))$ python3 solver.pyxx_xxxx_xxxxxx_xxxflag{} のような形式ではありませんが、フラグ取得です。
Crackme5
とりあえず実行してみます。
$ ./crackme5Enter your input:aaaaAlways dig deeperstrings では注目するような文字列はなかったため、 objdump で調査します。
main を見てみたところ、文字列になりそうな部分がありました。
$ objdump -d -M intel crackme5 | grep -A65 '<main>:'...(省略) 400791: c6 45 d0 4f mov BYTE PTR [rbp-0x30],0x4f 400795: c6 45 d1 66 mov BYTE PTR [rbp-0x2f],0x66 400799: c6 45 d2 64 mov BYTE PTR [rbp-0x2e],0x64 40079d: c6 45 d3 6c mov BYTE PTR [rbp-0x2d],0x6c 4007a1: c6 45 d4 44 mov BYTE PTR [rbp-0x2c],0x44 4007a5: c6 45 d5 53 mov BYTE PTR [rbp-0x2b],0x53 4007a9: c6 45 d6 41 mov BYTE PTR [rbp-0x2a],0x41 4007ad: c6 45 d7 7c mov BYTE PTR [rbp-0x29],0x7c 4007b1: c6 45 d8 33 mov BYTE PTR [rbp-0x28],0x33 4007b5: c6 45 d9 74 mov BYTE PTR [rbp-0x27],0x74 4007b9: c6 45 da 58 mov BYTE PTR [rbp-0x26],0x58 4007bd: c6 45 db 62 mov BYTE PTR [rbp-0x25],0x62 4007c1: c6 45 dc 33 mov BYTE PTR [rbp-0x24],0x33 4007c5: c6 45 dd 32 mov BYTE PTR [rbp-0x23],0x32 4007c9: c6 45 de 7e mov BYTE PTR [rbp-0x22],0x7e 4007cd: c6 45 df 58 mov BYTE PTR [rbp-0x21],0x58 4007d1: c6 45 e0 33 mov BYTE PTR [rbp-0x20],0x33 4007d5: c6 45 e1 74 mov BYTE PTR [rbp-0x1f],0x74 4007d9: c6 45 e2 58 mov BYTE PTR [rbp-0x1e],0x58 4007dd: c6 45 e3 40 mov BYTE PTR [rbp-0x1d],0x40 4007e1: c6 45 e4 73 mov BYTE PTR [rbp-0x1c],0x73 4007e5: c6 45 e5 58 mov BYTE PTR [rbp-0x1b],0x58 4007e9: c6 45 e6 60 mov BYTE PTR [rbp-0x1a],0x60 4007ed: c6 45 e7 34 mov BYTE PTR [rbp-0x19],0x34 4007f1: c6 45 e8 74 mov BYTE PTR [rbp-0x18],0x74 4007f5: c6 45 e9 58 mov BYTE PTR [rbp-0x17],0x58 4007f9: c6 45 ea 74 mov BYTE PTR [rbp-0x16],0x74 4007fd: c6 45 eb 7a mov BYTE PTR [rbp-0x15],0x7a...この [rbp-0x30] 以降に設定されているバイナリをPythonでASCII文字列に変換してみます。
# [rbp-0x30] に以降に設定されているバイナリkey = [0x4F, 0x66, 0x64, 0x6C, 0x44, 0x53, 0x41, 0x7C, 0x33, 0x74, 0x58, 0x62, 0x33, 0x32, 0x7E, 0x58, 0x33, 0x74, 0x58, 0x40, 0x73, 0x58, 0x60, 0x34, 0x74, 0x58, 0x74, 0x7A]
# ASCII文字列に変換for k in key: print(chr(k), end="")全て印字可能な文字列でした。
$ python3 solver.pyxxxxxxxxxxxxxxxxxxxxxxxxxxxxcrackme5 に入力してみます。
$ ./crackme5Enter your input:xxxxxxxxxxxxxxxxxxxxxxxxxxxxGood gameGood game が出力されました。フラグだったようです。
フラグ取得です。
Crackme6
とりあえず実行してみます。
$ ./crackme6Usage : ./crackme6 passwordGood luck, read the sourceコマンドライン引数が必要のようです。
$ ./crackme6 aaaapassword "aaaa" not OKstrings では注目するような文字列はなかったため、 Ghidra で調査します。
main -> compare_pwd -> my_secure_test のような流れになっています。
my_secure_test
my_secure_test は以下のようになっています。

コマンドライン引数を1文字ずつ、パスワードと検証しています。
単純に比較している1文字ずつをプログラムに入力してみます。
$ ./crackme6 xxxx_xxxpassword OKpassword OK となりました。
フラグ取得です。
Crackme7
とりあえず実行してみます。
$ ./crackme7Menu:
[1] Say hello[2] Add numbers[3] Quit
[>]入力した数字によって処理が変わるプログラムのようです。
nm でシンボル名を調査します。
$ nm crackme7...(省略)080486a6 t giveFlag080484bb T main...giveFlag が気になります。
objdump で main から giveFlag が呼ばれている箇所を調査します
$ objdump -d -M intel crackme7 | grep -15 '<giveFlag>' 8048648: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc] 804864b: 83 f8 03 cmp eax,0x3 804864e: 75 12 jne 8048662 <main+0x1a7> 8048650: 83 ec 0c sub esp,0xc 8048653: 68 b3 88 04 08 push 0x80488b3 8048658: e8 13 fd ff ff call 8048370 <puts@plt> 804865d: 83 c4 10 add esp,0x10 8048660: eb 35 jmp 8048697 <main+0x1dc> 8048662: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc] 8048665: 3d 69 7a 00 00 cmp eax,0x7a69 804866a: 75 17 jne 8048683 <main+0x1c8> 804866c: 83 ec 0c sub esp,0xc 804866f: 68 bc 88 04 08 push 0x80488bc 8048674: e8 f7 fc ff ff call 8048370 <puts@plt> 8048679: 83 c4 10 add esp,0x10 804867c: e8 25 00 00 00 call 80486a6 <giveFlag>...(省略)cmp eax,0x3 と cmp eax,0x7a69 に注目です。 eax と 0x3 を比較しているので、eax には入力した数字が設定されていることが予想されます。
そのため、cmp eax,0x7a69 とあるので、0x7a69 (31337) を入力することで、giveFlag が実行されそうです。
$ ./crackme7Menu:
[1] Say hello[2] Add numbers[3] Quit
[>] 31337Wow such h4x0r!flag{xxxx_xxxxxxxxx_xxxx_xxx_xxx}フラグ取得です。
Crackme8
とりあえず実行してみます。
$ ./crackme8Usage: ./crackme8 passwordコマンドライン引数が必要のようです。
$ ./crackme8 aaaaAccess denied.nm でシンボル名を調査します。
$ nm crackme8...(省略)08048524 t giveFlag0804849b T main...giveFlag が気になります。
objdump で main から giveFlag が呼ばれている箇所を調査します
$ objdump -d -M intel crackme8 | grep -15 '<giveFlag>' 80484db: 50 push eax 80484dc: e8 9f fe ff ff call 8048380 <atoi@plt> 80484e1: 83 c4 10 add esp,0x10 80484e4: 3d 0d f0 fe ca cmp eax,0xcafef00d 80484e9: 74 17 je 8048502 <main+0x67> 80484eb: 83 ec 0c sub esp,0xc 80484ee: 68 74 86 04 08 push 0x8048674 80484f3: e8 58 fe ff ff call 8048350 <puts@plt> 80484f8: 83 c4 10 add esp,0x10 80484fb: b8 01 00 00 00 mov eax,0x1 8048500: eb 1a jmp 804851c <main+0x81> 8048502: 83 ec 0c sub esp,0xc 8048505: 68 83 86 04 08 push 0x8048683 804850a: e8 41 fe ff ff call 8048350 <puts@plt> 804850f: 83 c4 10 add esp,0x10 8048512: e8 0d 00 00 00 call 8048524 <giveFlag>...(省略)push eax call 8048380 <atoi@plt> cmp eax,0xcafef00d が注目ポイントです。コマンドライン引数が atoi に渡され、それを 0xcafef00d と比較しています。
0xcafef00d を10進数に変換して、入力してみます。
$ python3>>> int(0xcafef00d)3405705229$ ./crackme8 3405705229Access denied.間違っているようです。符号付の可能性があるので、下記のように変換して入力してみます。
$ python3>>> ~(0xcafef00d ^ 0xffffffff)-889262067$ ./crackme8 -889262067Access granted.flag{xx_xxxxx_xxxx_xxxx_xxxx_xxxx_xxxx_xxxxxx_xxxx_xxxxxxx}フラグ取得しました。
感想
Reversing の初心者におすすめな問題でした。
Ghidra は便利ですね。