[Writeup] TryHackMe - Reversing ELF

こんにちは、ky0he1 です。

難易度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? と書かれているので実行してみます。

$ ./crackme1
flag{xxxxxxxxxxxxxxxxxxxx}

フラグ取得です。

Crackme2

とりあえず実行してみます。

 $ ./crackme2
Usage: ./crackme2 password

コマンドライン引数が必要のようです。

aaaa を入力してみます。

$ ./crackme2 aaaa
Access denied.

が、もちろん Access denied. です。

次は strings で調査します。

$ strings crackme2
... (省略)
Usage: %s password
super_secret_password
Access denied.
Access granted.
...

コマンドライン引数を求めるメッセージや、パスワード失敗のメッセージがある中、super_secret_password という気になる文字列があります。

super_secret_password を入力してみます。

$  ./crackme2 super_secret_password
Access granted.
flag{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}

フラグ取得です。

Crackme3

とりあえず実行してみます。

$ ./crackme3
Usage: ./crackme3 PASSWORD

コマンドライン引数が必要のようです。

aaaa を入力してみます。

$ ./crackme3 aaa
Come on, even my aunt Mildred got this one!

strings で調査します。

$ strings crackme3
...(省略)
Usage: %s PASSWORD
malloc failed
ZjByX3kwdXJfNWVjMG5kX2xlNTVvbl91bmJhc2U2NF80bGxfN2gzXzdoMW5nNQ==
Correct password!
Come on, even my aunt Mildred got this one!
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
...

ZjByX3kwdXJfNWVjMG5kX2xlNTVvbl91bmJhc2U2NF80bGxfN2gzXzdoMW5nNQ==Base64 エンコードされていそうなので、デコードしてみます。

(Base64 については base64ってなんぞ??理解のために実装してみた - Qiita が面白くておすすめです。)

$ echo "ZjByX3kwdXJfNWVjMG5kX2xlNTVvbl91bmJhc2U2NF80bGxfN2gzXzdoMW5nNQ==" | base64 -d
xxx_xxxx_xxxxxx_xxxxxx_xxxxxxxx_xxx_xxx_xxxxxx
$ ./crackme3 xxx_xxxx_xxxxxx_xxxxxx_xxxxxxxx_xxx_xxx_xxxxxx
Correct password!

flag{} のような形式ではありませんが、フラグ取得です。

Crackme4

とりあえず実行してみます。

$ ./crackme4
Usage : ./crackme4 password
This time the string is hidden and we used strcmp

コマンドライン引数が必要のようです。

aaaa を入力してみます。

$  ./crackme4 aaaa
password "aaaa" not OK

strings では注目するような文字列はなかったため、 Ghidra で調査します。

main()

main() は以下のようになっています。

crackme4_ghidra_main

コマンドライン引数がある場合 compare_pwd()コマンドライン引数を渡しています。

compare_pwd()

compare_pwd() は以下のようになっています。

crackme4_ghidra_compare_pwd

get_pwd() があり、&local_28 が渡されています。&local_28 のバイナリをASCII文字列に変換しても印字不可のため、get_pwd() のなかで変換されることが予想されます。

その後、strcmp() にて、&local_28コマンドライン引数 を比較して、一致している場合、password OK が表示されます。

get_pwd()

get_pwd() の中を見てみます。

crackme4_ghidra_get_pwd

&local_28 のバイナリを1文字ずつみて、'\0' になるまでループします。 ループの中では&local_28 のデータを1文字ずつを 0x24xor しています。

その後 compare_pwd() で見たようにコマンドライン引数と比較されるため、&local_28 のデータを1文字ずつを 0x24xor された文字列がパスワードとなります。

Solver

$ file crackme4
crackme4: 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 stripped

64bit なので 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.py
xx_xxxx_xxxxxx_xxx

flag{} のような形式ではありませんが、フラグ取得です。

Crackme5

とりあえず実行してみます。

$  ./crackme5
Enter your input:
aaaa
Always dig deeper

strings では注目するような文字列はなかったため、 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.py
xxxxxxxxxxxxxxxxxxxxxxxxxxxx

crackme5 に入力してみます。

$ ./crackme5
Enter your input:
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
Good game

Good game が出力されました。フラグだったようです。

フラグ取得です。

Crackme6

とりあえず実行してみます。

$ ./crackme6
Usage : ./crackme6 password
Good luck, read the source

コマンドライン引数が必要のようです。

$ ./crackme6 aaaa
password "aaaa" not OK

strings では注目するような文字列はなかったため、 Ghidra で調査します。

main -> compare_pwd -> my_secure_test のような流れになっています。

my_secure_test

my_secure_test は以下のようになっています。

crackme6_ghidra_my_secure_test

コマンドライン引数を1文字ずつ、パスワードと検証しています。

単純に比較している1文字ずつをプログラムに入力してみます。

$ ./crackme6 xxxx_xxx
password OK

password OK となりました。

フラグ取得です。

Crackme7

とりあえず実行してみます。

$ ./crackme7
Menu:

[1] Say hello
[2] Add numbers
[3] Quit

[>]

入力した数字によって処理が変わるプログラムのようです。

nm でシンボル名を調査します。

$ nm crackme7
...(省略)
080486a6 t giveFlag
080484bb T main
...

giveFlag が気になります。

objdumpmain から 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,0x3cmp eax,0x7a69 に注目です。 eax0x3 を比較しているので、eax には入力した数字が設定されていることが予想されます。

そのため、cmp eax,0x7a69 とあるので、0x7a69 (31337) を入力することで、giveFlag が実行されそうです。

$ ./crackme7
Menu:

[1] Say hello
[2] Add numbers
[3] Quit

[>] 31337
Wow such h4x0r!
flag{xxxx_xxxxxxxxx_xxxx_xxx_xxx}

フラグ取得です。

Crackme8

とりあえず実行してみます。

$ ./crackme8
Usage: ./crackme8 password

コマンドライン引数が必要のようです。

$ ./crackme8 aaaa
Access denied.

nm でシンボル名を調査します。

$ nm crackme8
...(省略)
08048524 t giveFlag
0804849b T main
...

giveFlag が気になります。

objdumpmain から 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 3405705229
Access denied.

間違っているようです。符号付の可能性があるので、下記のように変換して入力してみます。

$ python3
>>> ~(0xcafef00d ^ 0xffffffff)
-889262067
$  ./crackme8 -889262067
Access granted.
flag{xx_xxxxx_xxxx_xxxx_xxxx_xxxx_xxxx_xxxxxx_xxxx_xxxxxxx}

フラグ取得しました。

感想

Reversing の初心者におすすめな問題でした。

Ghidra が便利ですね。crackme8 のデコンパイル結果を見たら、-0x35010ff3 となっており使っていればすぐに解けていました。

なぜ、マイナスとなるのかがわからないので勉強が必要ですね。