どうも、CTF初心者です。
picoCTFのReversEngineering分野の難易度mediumのunpackmeを解いたので、備忘録として残します。
必要なもの
Linux(私はkali linuxを使いました)
Linuxの最低限のコマンド操作の知識
問題を解いてみよう
問題ファイルをダウンロードしたらfileコマンドでファイルフォーマットを確認する
$ ls
unpackme-upx
$ file unpackme-upx
unpackme-upx: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, no section header
次にファイルを実行してどのような動きをするか確認する
$ chmod +x unpackme-upx
$ ./unpackme-upx
What's my favorite number? 1
Sorry, that's not it!
私のお気に入りの数字は何でしょうとのこと。
適当に1を入力してみたが正解ではなかったみたい。
次はobjdumpコマンドで逆アセンブルしてコードを確認してみる。
と実行してみたはいいものの、次のようなメッセージが出てきて逆アセンブルされない。
$ objdump -D unpackme-upx
unpackme-upx: file format elf64-x86-64
特にエラーメッセージなどが出てるわけではないので、objdumpの結果が表示されない理由がわからない。
問題のヒントが一つだけあるので確認すると、
What is UPX?
というヒントをもらった。
UPXについてgoogleで調べてみる。
wikipediaに次のように書いてあった。
UPX (ultimate packer for executables)は様々なOSのファイル形式に対応したFLOSSの実行ファイル圧縮ソフトウェアである。
なんか圧縮されているようなので解凍すればいいのかもしれない。
解凍方法を調べるとupxコマンドなるものがあり、そのコマンドで解凍できるみたい。
$ upx --help
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.4 Markus Oberhumer, Laszlo Molnar & John Reiser May 9th 2024
Usage: upx [-123456789dlthVL] [-qvfk] [-o file] file..
Commands:
-1 compress faster -9 compress better
--best compress best (can be slow for big files)
-d decompress -l list compressed file
-t test compressed file -V display version number
-h give this help -L display software license
-dコマンドで解凍できるそうなので、解凍してみる。
ちなみに -o で解凍したファイルを別ファイルとして保存できるみたいなので、
適当に「new」というファイル名で出力した。
$ upx -d unpackme-upx -o new
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.4 Markus Oberhumer, Laszlo Molnar & John Reiser May 9th 2024
File size Ratio Format Name
-------------------- ------ ----------- -----------
1006445 <- 379188 37.68% linux/amd64 new
Unpacked 1 file.
$ ls
new unpackme-upx
objdumpで逆アセンブルできるか試してみる。
objdump -D new
new: file format elf64-x86-64
Disassembly of section .note.gnu.property:
0000000000400270 <__ehdr_start+0x270>:
400270: 04 00 add $0x0,%al
400272: 00 00 add %al,(%rax)
400274: 10 00 adc %al,(%rax)
400276: 00 00 add %al,(%rax)
400278: 05 00 00 00 47 add $0x47000000,%eax
40027d: 4e 55 rex.WRX push %rbp
40027f: 00 02 add %al,(%rdx)
400281: 00 00 add %al,(%rax)
400283: c0 04 00 00 rolb $0x0,(%rax,%rax,1)
400287: 00 03 add %al,(%rbx)
400289: 00 00 add %al,(%rax)
40028b: 00 00 add %al,(%rax)
40028d: 00 00 add %al,(%rax)
...
逆アセンブルできた。
しかし表示が多くて読むのが大変だと思った。
main関数を見てみたかったので、次のコマンドを使用した。
$ objdump -d new|grep "<main>:" -A 70
オプションの意味
objdump -d・・・ファイルを逆アセンブル
grep "<main>:" -A 70・・・<main>:が表示された行から70行下を表示する。
main関数の中身が表示された。
0000000000401e43 <main>:
401e43: f3 0f 1e fa endbr64
401e47: 55 push %rbp
401e48: 48 89 e5 mov %rsp,%rbp
401e4b: 48 83 ec 50 sub $0x50,%rsp
401e4f: 89 7d bc mov %edi,-0x44(%rbp)
401e52: 48 89 75 b0 mov %rsi,-0x50(%rbp)
401e56: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
401e5d: 00 00
401e5f: 48 89 45 f8 mov %rax,-0x8(%rbp)
401e63: 31 c0 xor %eax,%eax
401e65: 48 b8 41 3a 34 40 72 movabs $0x4c75257240343a41,%rax
401e6c: 25 75 4c
401e6f: 48 ba 46 41 6d 6b 30 movabs $0x30623e306b6d4146,%rdx
401e76: 3e 62 30
401e79: 48 89 45 d0 mov %rax,-0x30(%rbp)
401e7d: 48 89 55 d8 mov %rdx,-0x28(%rbp)
401e81: 48 b8 37 66 48 30 36 movabs $0x5f60643630486637,%rax
401e88: 64 60 5f
401e8b: 48 89 45 e0 mov %rax,-0x20(%rbp)
401e8f: c7 45 e8 32 61 66 37 movl $0x37666132,-0x18(%rbp)
401e96: 66 c7 45 ec 4e 00 movw $0x4e,-0x14(%rbp)
401e9c: 48 8d 3d 61 11 0b 00 lea 0xb1161(%rip),%rdi # 4b3004 <_IO_stdin_used+0x4>
401ea3: b8 00 00 00 00 mov $0x0,%eax
401ea8: e8 f3 ec 00 00 call 410ba0 <_IO_printf>
401ead: 48 8d 45 c4 lea -0x3c(%rbp),%rax
401eb1: 48 89 c6 mov %rax,%rsi
401eb4: 48 8d 3d 65 11 0b 00 lea 0xb1165(%rip),%rdi # 4b3020 <_IO_stdin_used+0x20>
401ebb: b8 00 00 00 00 mov $0x0,%eax
401ec0: e8 6b ee 00 00 call 410d30 <__isoc99_scanf>
401ec5: 8b 45 c4 mov -0x3c(%rbp),%eax
401ec8: 3d cb 83 0b 00 cmp $0xb83cb,%eax
401ecd: 75 43 jne 401f12 <main+0xcf>
401ecf: 48 8d 45 d0 lea -0x30(%rbp),%rax
401ed3: 48 89 c6 mov %rax,%rsi
401ed6: bf 00 00 00 00 mov $0x0,%edi
401edb: e8 a5 fe ff ff call 401d85 <rotate_encrypt>
401ee0: 48 89 45 c8 mov %rax,-0x38(%rbp)
401ee4: 48 8b 15 e5 d7 0d 00 mov 0xdd7e5(%rip),%rdx # 4df6d0 <stdout>
401eeb: 48 8b 45 c8 mov -0x38(%rbp),%rax
401eef: 48 89 d6 mov %rdx,%rsi
401ef2: 48 89 c7 mov %rax,%rdi
401ef5: e8 86 ea 01 00 call 420980 <_IO_fputs>
401efa: bf 0a 00 00 00 mov $0xa,%edi
401eff: e8 1c ef 01 00 call 420e20 <putchar>
401f04: 48 8b 45 c8 mov -0x38(%rbp),%rax
401f08: 48 89 c7 mov %rax,%rdi
401f0b: e8 60 cd 02 00 call 42ec70 <__free>
401f10: eb 0c jmp 401f1e <main+0xdb>
401f12: 48 8d 3d 0a 11 0b 00 lea 0xb110a(%rip),%rdi # 4b3023 <_IO_stdin_used+0x23>
401f19: e8 22 ed 01 00 call 420c40 <_IO_puts>
401f1e: b8 00 00 00 00 mov $0x0,%eax
401f23: 48 8b 4d f8 mov -0x8(%rbp),%rcx
401f27: 64 48 33 0c 25 28 00 xor %fs:0x28,%rcx
401f2e: 00 00
401f30: 74 05 je 401f37 <main+0xf4>
401f32: e8 69 ac 05 00 call 45cba0 <__stack_chk_fail>
401f37: c9 leave
401f38: c3 ret
401f39: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
上から読んでいくと気になる一行があった。
401ec8: 3d cb 83 0b 00 cmp $0xb83cb,%eax
これはeaxレジスタと0xb83cbを比較している処理である。
ファイルを実行した際の入力に0xb83cbを入力すればフラグが出力される可能性がある。
ためしに入力してみる。
$ ./new
What's my favorite number? 0xb83cb
Sorry, that's not it!
16進数をそのまま入力してもダメだったので、10進数に直してから入力してみる。
$ python3
Python 3.13.2 (main, Feb 5 2025, 01:23:35) [GCC 14.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import decimal
>>> decimal.Decimal(0xb83cb)
Decimal('754635')
$ ./new
What's my favorite number? 754635
picoCTF{up><_m3_f7w_e510a27f}
フラグが表示された。
GDBを使用して解く
GDBでupxコマンドで解凍したファイルをデバッグする。
$ gdb new
GNU gdb (Debian 16.2-8) 16.2
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from new...
(No debugging symbols found in new)
(gdb)
まずはmain関数を逆アセンブルしてみる。
(gdb) disassemble main
Dump of assembler code for function main:
0x0000000000401e43 <+0>: endbr64
0x0000000000401e47 <+4>: push %rbp
0x0000000000401e48 <+5>: mov %rsp,%rbp
0x0000000000401e4b <+8>: sub $0x50,%rsp
0x0000000000401e4f <+12>: mov %edi,-0x44(%rbp)
0x0000000000401e52 <+15>: mov %rsi,-0x50(%rbp)
0x0000000000401e56 <+19>: mov %fs:0x28,%rax
0x0000000000401e5f <+28>: mov %rax,-0x8(%rbp)
0x0000000000401e63 <+32>: xor %eax,%eax
0x0000000000401e65 <+34>: movabs $0x4c75257240343a41,%rax
0x0000000000401e6f <+44>: movabs $0x30623e306b6d4146,%rdx
0x0000000000401e79 <+54>: mov %rax,-0x30(%rbp)
0x0000000000401e7d <+58>: mov %rdx,-0x28(%rbp)
0x0000000000401e81 <+62>: movabs $0x5f60643630486637,%rax
0x0000000000401e8b <+72>: mov %rax,-0x20(%rbp)
0x0000000000401e8f <+76>: movl $0x37666132,-0x18(%rbp)
0x0000000000401e96 <+83>: movw $0x4e,-0x14(%rbp)
0x0000000000401e9c <+89>: lea 0xb1161(%rip),%rdi # 0x4b3004
0x0000000000401ea3 <+96>: mov $0x0,%eax
0x0000000000401ea8 <+101>: call 0x410ba0 <printf>
0x0000000000401ead <+106>: lea -0x3c(%rbp),%rax
0x0000000000401eb1 <+110>: mov %rax,%rsi
0x0000000000401eb4 <+113>: lea 0xb1165(%rip),%rdi # 0x4b3020
0x0000000000401ebb <+120>: mov $0x0,%eax
0x0000000000401ec0 <+125>: call 0x410d30 <__isoc99_scanf>
0x0000000000401ec5 <+130>: mov -0x3c(%rbp),%eax
0x0000000000401ec8 <+133>: cmp $0xb83cb,%eax
0x0000000000401ecd <+138>: jne 0x401f12 <main+207>
0x0000000000401ecf <+140>: lea -0x30(%rbp),%rax
0x0000000000401ed3 <+144>: mov %rax,%rsi
0x0000000000401ed6 <+147>: mov $0x0,%edi
0x0000000000401edb <+152>: call 0x401d85 <rotate_encrypt>
0x0000000000401ee0 <+157>: mov %rax,-0x38(%rbp)
0x0000000000401ee4 <+161>: mov 0xdd7e5(%rip),%rdx # 0x4df6d0 <stdout>
0x0000000000401eeb <+168>: mov -0x38(%rbp),%rax
0x0000000000401eef <+172>: mov %rdx,%rsi
0x0000000000401ef2 <+175>: mov %rax,%rdi
0x0000000000401ef5 <+178>: call 0x420980 <fputs>
0x0000000000401efa <+183>: mov $0xa,%edi
0x0000000000401eff <+188>: call 0x420e20 <putchar>
0x0000000000401f04 <+193>: mov -0x38(%rbp),%rax
0x0000000000401f08 <+197>: mov %rax,%rdi
0x0000000000401f0b <+200>: call 0x42ec70 <free>
0x0000000000401f10 <+205>: jmp 0x401f1e <main+219>
0x0000000000401f12 <+207>: lea 0xb110a(%rip),%rdi # 0x4b3023
0x0000000000401f19 <+214>: call 0x420c40 <puts>
--Type <RET> for more, q to quit, c to continue without paging--
0x0000000000401f1e <+219>: mov $0x0,%eax
0x0000000000401f23 <+224>: mov -0x8(%rbp),%rcx
0x0000000000401f27 <+228>: xor %fs:0x28,%rcx
0x0000000000401f30 <+237>: je 0x401f37 <main+244>
0x0000000000401f32 <+239>: call 0x45cba0 <__stack_chk_fail_local>
0x0000000000401f37 <+244>: leave
0x0000000000401f38 <+245>: ret
End of assembler dump.
次のコマンドで0x0000000000401ec8 <+133>: cmp $0xb83cb,%eaxにブレークポイントを設定する。
(gdb) b * 0x0000000000401ec8
Breakpoint 1 at 0x401ec8
ブレークポイントを設定したら実行してみる。
(gdb) r
Starting program: /home/kali/CTF/new
What's my favorite number?
実行したら数字の入力を求められたので適当に数字を入力した後に、
次のコマンドでeaxに数字をセットする。
(gdb) set $eax=0xb83cb
eaxに16進数をセットして次のコマンドでプログラムを進めるとフラグが表示される。
(gdb) c
Continuing.
picoCTF{up><_m3_f7w_e510a27f}
[Inferior 1 (process 39782) exited normally]
終わりに
今回は簡単なプログラムだったので、main関数だけを読むことで済みましたが、
難しくなると、ほかの関数まで調べる必要も出てきそうです。
upxという存在を知ることができただけでもこの問題を解いた意味はあったと思います。
他の問題も解けたら記事にしようと思います。
ここまで読んでいただきありがとうございました。