DEF CON CTF Qualifier 2019 write-up
5/11 0:00-5/13 0:00(UTC) に開催されたDEF CON CTF Qualifier 2019で解けた問題のwrite-upです。チーム名 whitecas でソロ参加し、310pts獲得して148位でした。
welcome_to_the_game
参加表明?的なフラグ提出。
Welcome to the 2019 DEF CON CTF quals! Let's start it out with a free flag :) If you can't submit it and get points there must be something very wrong, and we hope it's on your end :D
Flag: OOO{Game on!}
cant_even_unplug_it
You know, we had this up and everything. Prepped nice HTML5, started deploying on a military-grade-secrets.dev subdomain, got the certificate, the whole shabang. Boss-man got moody and wanted another name, we set up the new names and all. Finally he got scared and unplugged the server. Can you believe it? Unplugged. Like that can keep it secret…
なにやらmilitary-grade-secrets.dev
のサブドメインで提供していたWEBサービス?があったんだけど、ボスがびびってサーバーをネットワークから切り離したからコンテンツ見れんぞ。という設定の問題らしい。
とりあえず、subdomain のスキャナツールを探し、OWASPのツールでAmassというのを使ってみた。
結果
$ sudo docker run amass --passive -d military-grade-secrets.dev secret-storage.military-grade-secrets.dev now.under.even-more-militarygrade.pw.military-grade-secrets.dev www.military-grade-secrets.dev OWASP Amass v2.9.10 https://github.com/OWASP/Amass -------------------------------------------------------------------------------- 3 names discovered - cert: 2, scrape: 1
secret-storage.military-grade-secrets.dev
にアクセスしてみると、forget-me-not.even-more-militarygrade.pw
へ302リダイレクトされる。でもSYN/ACKが返ってこない。これがUnplugってことかな?
別の方法を考えてみてCensysでforget-me-not.even-more-militarygrade.pw
のIPv4アドレス206.189.162.22
の検索を試してみる。
Censysがアクセスしたときのコンテンツを記録してくれているみたいで、おっ!?と思い、HTTPコンテンツを覗いてみるとフラグがあった。
Flag: OOO{DAMNATIO_MEMORIAE}
know_your_mem
pwn練習問題?Shellcodeを提出して、フラグを表示させれば勝ち。
問題ファイルは、練習用ファイル(simplified.c
)と本番サーバーと同じソースファイル(know_your_mem.c
) があり、練習用ファイルはヒント表示がある。ソースとしては一緒だが、simplified.c
は、simplified_shellcode.so.c
をコンパイルしてできるsimplified_shellcode.so
のshellcode
という関数を呼び出し、know_your_mem.c
は、標準入力からfreadしたshellcodeをメモリ上に格納し、処理をしてくれる。
フラグは、ファイルからreadした文字列を0x0000100000000000UL
~0x00001ffffffff000UL
の間のランダムな場所にmmapで確保されたメモリー領域に格納されている。
コマンドプロンプトでmake check
とすれば、shellcodeのテストができる。simplified.c
はフラグが格納されているアドレスのポインタを突き止めればOK。know_your_mem.c
は、そのポインタを利用してフラグを表示すればOK。
$ make check : ./topkt.py shellcode.bin ./simplified [ ] This challenge may be slightly easier in Linux 4.17+. Here, we're running on Linux 4.18.0-18-generic (x86_64) Loading your simplified solution from ./simplified_shellcode.so [ ] Putting the flag somewhere in memory... Secret loaded (header + 107 bytes) [H] The flag is at 0x107bfb3f9000 [ ] Putting red herrings in memory... [H] Red herring at 0x1631b4f87000 [H] Red herring at 0x191914eea000 : [H] Red herring at 0x1cd4c7262000 [H] Red herring at 0x171470311000 [*] seccomp filter now active! Hi! Soon I'll be your shellcode! [*] Your shellcode returned 0x107bfb3f9000 [^] Success! Make sure you're also printing the flag, and that it's not taking too long. Next: convert your solution to raw shellcode -- you can start with C code, BTW! shellcode.c shows one way to do it. Good, the simplified version worked! Let's now try raw shellcode... ./know_your_mem < shellcode.bin.pkt | tee | fgrep --text 'OOO{theflagwillbehere}' [ ] Putting the flag somewhere in memory... [ ] Putting red herrings in memory... [*] seccomp filter now active! [*] Your shellcode returned 0x19900cd26000
フラグ文字列を指すポインタを求める処理は↓のように書いた。ポインタは、mmapの返却値としてstack上に一時保存していた値が残っているので、stack上の値を適当に検索して、保存対象のアドレス範囲を指すポインタ値っぽいかどうか雑に比較して求めた。
// This is an example of turning simple C into raw shellcode. // make shellcode.bin will compile to assembly // make shellcode.bin.pkt will prepend the length so you can // ./know_your_mem < shellcode.bin.pkt // Note: Right now the 'build' does not support .(ro)data // If you want them you'll have to adjust the Makefile. // They're not really necessary to solve this challenge though. // From https://chromium.googlesource.com/linux-syscall-support/ static int my_errno = 0; #define SYS_ERRNO my_errno #include "linux-syscall-support/linux_syscall_support.h" #define ADDR_MIN 0x0000100000000000UL #define ADDR_MASK 0x00000ffffffff000UL void* _start() { //sys_write(1, __builtin_frame_address(0), 5); // Prints something (note: best avoid literals) //sys_exit_group(2); // Exit void **pt; pt = (void**)(((unsigned long)&pt) - 0x100); while (1){ if ((unsigned long)*pt < 0x00001ffffffff000UL && (unsigned long)*pt > ADDR_MIN) { return *pt; } pt = (void**)((unsigned long)pt + 0x4); } return (void*)0x1234; }
know_your_mem (フラグの出力)
Makefile
の処理でshellcode.cに書かれた処理がコンパイルされ、know_your_memに送るshellcodeが作成される。shellcode.c -> shellcode.elf -> shellcode.bin.pkt という風にshellcodeが作成されている。(※shellcode.bin.pktの先頭2バイトはshellcodeの長さを表すunsigned short) make処理では、↑で書いたポインタを求める処理だけコンパイルし、そして、know_your_mem.c
で特定したメモリアドレスをwriteするshellcode部分は以下のように書いて、↑で作成したshellcode.bin.pktに追加して最終的なshellcodeを作成した。(system callでwriteを呼び出している)
4D 31 D2 XOR R10, R10 48 C7 C2 50000000 MOV RDX, 50 48 89 C6 MOV RSI, RAX 48 C7 C7 00000000 MOV RDI, 0 50 PUSH RAX 49 8D 42 01 LEA RAX, QWORD PTR [R10+1] 0F 05 SYSCALL 58 POP RAX
最終的にサーバーへ送信したshellcodeは以下のようになった。
00000000 59 00 48 8d 84 24 f8 fe ff ff 48 b9 ff ff ff ff |Y.H..$....H.....| 00000010 ff ef ff ff 48 be fe ef ff ff ff 0f 00 00 48 89 |....H.........H.| 00000020 44 24 f8 48 8b 54 24 f8 48 8b 02 48 8d 3c 08 48 |D$.H.T$.H..H.<.H| 00000030 39 f7 76 0b 48 83 c2 04 48 89 54 24 f8 eb e4 4d |9.v.H...H.T$...M| 00000040 31 d2 48 c7 c2 50 00 00 00 48 89 c6 48 c7 c7 00 |1.H..P...H..H...| 00000050 00 00 00 50 49 8d 42 01 0f 05 58 c3 |...PI.B...X.|
送信結果
ubuntu@ubuntu-VirtualBox:/media/sf_onedrive_ctf/DEFCON_2019/first_contact/know_your_mem$ cat shellcode1.bin.pkt | nc know_your_mem.quals2019.oooverflow.io 4669 [ ] This challenge may be slightly easier in Linux 4.17+. Here, we're running on Linux 4.15.0-1037-aws (x86_64) Send the length (uint16), then the shellcode. [ ] All right, I read 89 bytes. I will call the first byte in a bit. [ ] Putting the flag somewhere in memory... Secret loaded (header + 36 bytes) [ ] Putting red herrings in memory... [*] seccomp filter now active! OOO{so many bits, so many syscalls}
Flag: OOO{so many bits, so many syscalls}
以上でっす。
DEF CON CTF Qualifier 2017 write-up
4/29 0:00-5/1 0:00(UTC) に開催されたDEF CON CTF Qualifier 2017で解けた問題のwrite-upです。
チーム名 CTF-infinitとして参加し、43pts獲得して173位でした。
crackme1 (Baby'sFirst)
crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me:10001 [https://2017.notmalware.ru/add6a6232ea1e9d42b925a7d2a2c5781bfccd6fd/8a97fb8c264a3b34dad0a707dbfc92832067a0fa0f2b5a576c73557960b11506.tar.bz2]
x86-64のELFが提供され、リモートに繋いでみると何か答えをBase64で送れと言われる。
$ file magic_dist magic_dist: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, stripped $ nc crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me 10001 send your solution as base64, followed by a newline
とりあえずELFファイルを逆アセンブルし、解析
$ objdump -D -Mintel magic_dist > magic_dist.asm
c6cから始まる関数を見てみると955, 971など細かく関数をcallしている。
引数(rdi)に渡しているアドレスは、1バイトずつずらしている。
c6c: 55 push rbp c6d: 53 push rbx c6e: 48 89 fd mov rbp,rdi c71: 48 83 ec 08 sub rsp,0x8 c75: 48 0f be 3f movsx rdi,BYTE PTR [rdi] c79: e8 bd fc ff ff call 93b <_init+0x243> c7e: 48 0f be 7d 01 movsx rdi,BYTE PTR [rbp+0x1] c83: 48 c1 f8 03 sar rax,0x3 c87: 48 89 c3 mov rbx,rax c8a: e8 c6 fc ff ff call 955 <_init+0x25d> c8f: 48 0f be 7d 02 movsx rdi,BYTE PTR [rbp+0x2] c94: 48 01 c3 add rbx,rax c97: 48 c1 fb 03 sar rbx,0x3 c9b: e8 d1 fc ff ff call 971 <_init+0x279> ca0: 48 0f be 7d 03 movsx rdi,BYTE PTR [rbp+0x3] ca5: 48 01 c3 add rbx,rax ca8: 48 c1 fb 03 sar rbx,0x3 cac: e8 da fc ff ff call 98b <_init+0x293> cb1: 48 0f be 7d 04 movsx rdi,BYTE PTR [rbp+0x4] cb6: 48 01 c3 add rbx,rax cb9: 48 c1 fb 03 sar rbx,0x3 cbd: e8 e3 fc ff ff call 9a5 <_init+0x2ad> cc2: 48 0f be 7d 05 movsx rdi,BYTE PTR [rbp+0x5] :
呼び出されている関数の中身を見てみると、引数(rdi)と1バイト比較。
イコールならeaxに値を代入してret。notイコールのときに呼ばれる758は、<.plt.got>セクションにあるし、exit関数っぽい。とりあえず比較している1バイトを集める。
: 93b: 48 83 ff 79 cmp rdi,0x79 ; "y" 93f: 74 0e je 94f <_init+0x257> 941: 48 83 ec 08 sub rsp,0x8 945: bf 01 00 00 00 mov edi,0x1 94a: e8 09 fe ff ff call 758 <_init+0x60> 94f: b8 a7 00 00 00 mov eax,0xa7 954: c3 ret 955: 48 83 ff 65 cmp rdi,0x65 ; "e" 959: 74 0e je 969 <_init+0x271> 95b: 48 83 ec 08 sub rsp,0x8 95f: bf 02 00 00 00 mov edi,0x2 964: e8 ef fd ff ff call 758 <_init+0x60> 969: 48 c7 c0 9b ff ff ff mov rax,0xffffffffffffff9b 970: c3 ret 971: 48 83 ff 73 cmp rdi,0x73 ; "s" 975: 74 0e je 985 <_init+0x28d> 977: 48 83 ec 08 sub rsp,0x8 97b: bf 03 00 00 00 mov edi,0x3 980: e8 d3 fd ff ff call 758 <_init+0x60> 985: b8 a0 00 00 00 mov eax,0xa0 98a: c3 ret :
集めた文字を連結すると、"yes and his hands shook with ex"という文字列になった。試しにBase64でエンコードして送信してみたらフラグだった。
$ echo "yes and his hands shook with ex" | base64 | nc crackme1_f92e0ab22352440383d58be8f046bebe.quals.shallweplayaga.me 10001 send your solution as base64, followed by a newline 4a2181aaf70b04ec984c233fbe50a1fe600f90062a58d6b69ea15b85531b9652 The flag is: important videos best playlist Wigeekuk8
magic (CrackMe_2000)
cm2k-magic_b46299df0752c152a8e0c5f0a9e5b8f0.quals.shallweplayaga.me:12001 [https://2017.notmalware.ru/5e1d5bc4e73657be1b008fbb6b08a7604e81d7cb/91ae7f2ec76f00975849c44b3d8ec8ed897fab7335c156d949bd15ea156338b3.tar.bz2]
解凍するとx86-64のELFが200個ほどあった。とりあえず逆アセンブルして眺めてみたところ、crackme1と同じ形式なことがわかった。
: 93b: 48 83 ff 64 cmp rdi,0x64 ; d 93f: 74 0e je 94f <_init+0x257> 941: 48 83 ec 08 sub rsp,0x8 945: bf 01 00 00 00 mov edi,0x1 94a: e8 09 fe ff ff call 758 <_init+0x60> 94f: b8 6c 00 00 00 mov eax,0x6c 954: c3 ret 955: 48 83 ff 65 cmp rdi,0x65 ; e 959: 74 0e je 969 <_init+0x271> 95b: 48 83 ec 08 sub rsp,0x8 95f: bf 02 00 00 00 mov edi,0x2 964: e8 ef fd ff ff call 758 <_init+0x60> 969: b8 49 00 00 00 mov eax,0x49 96e: c3 ret 96f: 48 83 ff 2e cmp rdi,0x2e ; . 973: 74 0e je 983 <_init+0x28b> 975: 48 83 ec 08 sub rsp,0x8 979: bf 03 00 00 00 mov edi,0x3 97e: e8 d5 fd ff ff call 758 <_init+0x60> 983: b8 72 00 00 00 mov eax,0x72 988: c3 ret : df6: 55 push rbp df7: 53 push rbx df8: 48 89 fd mov rbp,rdi dfb: 48 83 ec 08 sub rsp,0x8 dff: 48 0f be 3f movsx rdi,BYTE PTR [rdi] e03: e8 33 fb ff ff call 93b <_init+0x243> e08: 48 0f be 7d 01 movsx rdi,BYTE PTR [rbp+0x1] e0d: 48 c1 f8 03 sar rax,0x3 ; 0x6c >> 0x3 = 0xd e11: 48 89 c3 mov rbx,rax e14: e8 3c fb ff ff call 955 <_init+0x25d> e19: 48 0f be 7d 02 movsx rdi,BYTE PTR [rbp+0x2] e1e: 48 01 c3 add rbx,rax e21: 48 c1 fb 03 sar rbx,0x3 e25: e8 45 fb ff ff call 96f <_init+0x277> e2a: 48 0f be 7d 03 movsx rdi,BYTE PTR [rbp+0x3] :
200個を手作業でやるわけにはいかないので、pythonで自動収集するプログラムを作成。
問題の文字列収集は、比較部分のニーモニック(cmp rdi,0xXX)に該当する機械語が"48 83 ff XX"だったので、逆アセンブル結果から該当する機械語を含む行だけ抽出したら、うまいぐあいに文字の比較部分だけ抽出できた。最終的な回答コードが以下になった。
#!/usr/bin/python import os import re import glob import base64 import subprocess import socket def read_data(f, delim="\n"): data = "" while not data.endswith(delim): data += f.read(1) return data def make_ans(fname, data): data = data.split("\n") cmp = "" for d in data: if "\t48 83 ff " not in d: continue a = d.split(",")[1] a = chr(int(a, 16)) print d, "; ", a cmp += a print fname print "[+]str: ", cmp print "[+]Base64: ", base64.b64encode(cmp) return base64.b64encode(cmp) if __name__ == "__main__": # run objdump files = glob.glob("./magic_dist/*") ans_list = {} # collect ans for f in files: arg = ["objdump", "-d", "-Mintel", f] asm = subprocess.Popen(arg, stdout=subprocess.PIPE) data, err = asm.communicate() q = f.split("/")[2] ans_list[q] = make_ans(f, data) # connect to server host = "cm2k-magic_b46299df0752c152a8e0c5f0a9e5b8f0.quals.shallweplayaga.me" port = 12001 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) sf = s.makefile("rw", bufsize=0) while True: data = read_data(sf) print data if "flag" in data: break elif 'send your solution as base64, followed by a newline' in data: continue data = data.split("\n")[0] ans = ans_list[data] sf.write(ans + "\n")
コードとしては、Objdumpで逆アセンブル、その結果から"\t48 83 ff " を含む行を抽出。比較されているバイトを連結し、Base64エンコードして辞書作成。サーバーへの回答時は、サーバーからの質問をキーとしてエンコードしている回答を送信しているだけ。
何度か回答に成功するとフラグがもらえた。
$ ./solve.py 93b: 48 83 ff 64 cmp rdi,0x64 ; d 955: 48 83 ff 6f cmp rdi,0x6f ; o 96f: 48 83 ff 63 cmp rdi,0x63 ; c 989: 48 83 ff 74 cmp rdi,0x74 ; t 9a3: 48 83 ff 6f cmp rdi,0x6f ; o 9bd: 48 83 ff 72 cmp rdi,0x72 ; r 9d7: 48 83 ff 20 cmp rdi,0x20 ; 9f3: 48 83 ff 63 cmp rdi,0x63 ; c a0d: 48 83 ff 61 cmp rdi,0x61 ; a a27: 48 83 ff 6d cmp rdi,0x6d ; m a41: 48 83 ff 65 cmp rdi,0x65 ; e a5b: 48 83 ff 20 cmp rdi,0x20 ; a75: 48 83 ff 69 cmp rdi,0x69 ; i a8f: 48 83 ff 6e cmp rdi,0x6e ; n aa9: 48 83 ff 2c cmp rdi,0x2c ; , ac3: 48 83 ff 20 cmp rdi,0x20 ; adf: 48 83 ff 73 cmp rdi,0x73 ; s af9: 48 83 ff 68 cmp rdi,0x68 ; h b13: 48 83 ff 61 cmp rdi,0x61 ; a b2d: 48 83 ff 6b cmp rdi,0x6b ; k b47: 48 83 ff 69 cmp rdi,0x69 ; i b61: 48 83 ff 6e cmp rdi,0x6e ; n b7b: 48 83 ff 67 cmp rdi,0x67 ; g b95: 48 83 ff 20 cmp rdi,0x20 ; bb1: 48 83 ff 68 cmp rdi,0x68 ; h bcb: 48 83 ff 69 cmp rdi,0x69 ; i be5: 48 83 ff 73 cmp rdi,0x73 ; s c01: 48 83 ff 20 cmp rdi,0x20 ; c1b: 48 83 ff 68 cmp rdi,0x68 ; h c35: 48 83 ff 65 cmp rdi,0x65 ; e c4f: 48 83 ff 61 cmp rdi,0x61 ; a c69: 48 83 ff 64 cmp rdi,0x64 ; d c83: 48 83 ff 2e cmp rdi,0x2e ; . c9f: 48 83 ff 20 cmp rdi,0x20 ; cbb: 48 83 ff 22 cmp rdi,0x22 ; " cd5: 48 83 ff 57 cmp rdi,0x57 ; W cef: 48 83 ff 65 cmp rdi,0x65 ; e d0b: 48 83 ff 6c cmp rdi,0x6c ; l ./magic_dist/e005a1973167b4cf47100c81bca178b98646a128e05a6beb804226888be3f344 [+]str: doctor came in, shaking his head. "Wel [+]Base64: ZG9jdG9yIGNhbWUgaW4sIHNoYWtpbmcgaGlzIGhlYWQuICJXZWw= : send your solution as base64, followed by a newline c0d5312126c2a52cb23f91694a22ba32284e59336fb5176ed92dc1cd23b62655 : 省略 : be422c4d780fcc9f29d01283ad76088529af9b4dc7a69c3bb9979fb51a23824e The flag is: a color map of the sun sokemsUbif
解けた問題は以上です。力不足感が半端ないので、もっと精進したい。
PlaidCTF 2017 write-up
4/22-23に開催されたPlaidCTF 2017で解けた問題のwrite-upです。
チーム名 CTFinfinitとして参加し、226pts獲得して185位でした。
- sanity check (Misc/1pts)
- zipper (Misc/50pts)
- Down the Reversing Hole (Misc/50pts)
- no_mo_flo (Reversing/125pts)
sanity check (Misc/1pts)
The flag is PCTF{poop}
テスト問題なので、フラグ投稿するだけ。
zipper (Misc/50pts)
Something doesn't seem quite right with this zip file. Can you fix it and get the flag?
とりあえずzipinfoでは "filename too long" と出力される。
ubuntu@zipper$ zipinfo zipper.zip Archive: zipper.zip Zip file size: 236 bytes, number of entries: 1 warning: filename too long--truncating. -rw-rw-r-- 3.0 unx 246 tx defX 17-Apr-18 19:15
バイナリエディタで眺めてみると、0x2329 となっていて確かにファイル名長さの値が大きすぎる。また、ファイル名が入りそうな領域がすべて 0x00 になっている。
8文字分だったので、適当にファイル名を flag.txt として値を修正。
あとは、修正したファイルを展開して中のファイルを閲覧して終わり。
Huzzah, you have captured the flag: PCTF{f0rens1cs_yay}
Down the Reversing Hole (Misc/50pts)
Don't forget. This is a MISC challenge.
MISCチャレンジということで、実行ファイルに何か隠されているだろうとバイナリを眺めてみたらMZスタブが普通の実行ファイルに比べて大きい。あからさまに何かある。
seg000:0000 public start seg000:0000 start proc near seg000:0000 B8 03 00 mov ax, seg dseg seg000:0003 8E D8 mov ds, ax seg000:0005 assume ds:dseg seg000:0005 8D 16 00 00 lea dx, unk_30 seg000:0009 B4 09 mov ah, 9 ; Write string to STDOUT seg000:000B CD 21 int 21h ; DOS - PRINT STRING seg000:000B ; DS:DX -> string terminated by "$" seg000:000D B9 00 00 mov cx, 0 seg000:0010 seg000:0010 loc_10: seg000:0010 83 F9 39 cmp cx, 39h ; '9' ; 39文字分 seg000:0013 74 17 jz short loc_ seg000:0015 8D 1E 2B 00 lea bx, unk_5B ; 0x25B~ seg000:0019 03 D9 add bx, cx seg000:001B 8A 17 mov dl, [bx] seg000:001D 8D 1E 64 00 lea bx, unk_94 ; 0x294~ seg000:0021 03 D9 add bx, cx seg000:0023 32 17 xor dl, [bx] seg000:0025 B4 02 mov ah, 2 ; Write character to STDOUT seg000:0027 CD 21 int 21h ; DOS - DISPLAY OUTPUT seg000:0027 ; DL = character to send to standard output seg000:0029 41 inc cx seg000:002A EB E4 jmp short loc_10 seg000:002C seg000:002C loc_2C: seg000:002C B4 4C mov ah, 4Ch ; Exit program seg000:002E CD 21 int 21h ; DOS - 2+ - QUIT WITH EXIT CODE (EXIT) seg000:002E start endp ; AL = exit code seg000:002E seg000:002E seg000 ends
フラグを得るプログラムは以下のようになった。
#!/usr/bin/python f = open("reversing-hole.exe", "rb") data = f.read() f.close() f = open("flag.txt", "wb") dl = data[0x25B:0x294] bx = data[0x294:0x2CD] for i in range(len(dl)): t = ord(dl[i]) ^ ord(bx[i]) f.write(chr(t)) f.write("\n") f.close()
ubuntu@misc$ ./solve.py; cat flag.txt But here's a flag PCTF{at_l3a5t_th3r3s_d00m_p0rts_0n_d0s}
no_mo_flo (Reversing/125pts)
Can you go with the flow?
とりあえず、逆アセンブル。
objdump -D -Mintel no_flo > no_flo.asm
mainのアセンブリ。
; main 40272e: 55 push rbp 40272f: 48 89 e5 mov rbp,rsp 402732: 48 81 ec b0 00 00 00 sub rsp,0xb0 402739: 48 8d 45 d0 lea rax,[rbp-0x30] 40273d: ba 20 00 00 00 mov edx,0x20 402742: 48 89 c6 mov rsi,rax 402745: bf 00 00 00 00 mov edi,0x0 40274a: e8 31 de ff ff call 400580 <read@plt> 40274f: 89 45 f8 mov DWORD PTR [rbp-0x8],eax 402752: 83 7d f8 20 cmp DWORD PTR [rbp-0x8],0x20 ; 32文字 402756: 74 14 je 40276c <strerror@plt+0x21ac> ; error; 402758: bf 58 2d 40 00 mov edi,0x402d58 40275d: e8 ee dd ff ff call 400550 <puts@plt> 402762: b8 ff ff ff ff mov eax,0xffffffff 402767: e9 9b 00 00 00 jmp 402807 <strerror@plt+0x2247> ; for 40276c: c7 45 fc 00 00 00 00 mov DWORD PTR [rbp-0x4],0x0 402773: eb 3a jmp 4027af <strerror@plt+0x21ef> 402775: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 402778: 01 c0 add eax,eax 40277a: 48 98 cdqe 40277c: 0f b6 44 05 d0 movzx eax,BYTE PTR [rbp+rax*1-0x30] 402781: 0f be d0 movsx edx,al 402784: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 402787: 48 98 cdqe 402789: 89 94 85 50 ff ff ff mov DWORD PTR [rbp+rax*4-0xb0],edx 402790: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 402793: 01 c0 add eax,eax 402795: 83 c0 01 add eax,0x1 402798: 48 98 cdqe 40279a: 0f b6 44 05 d0 movzx eax,BYTE PTR [rbp+rax*1-0x30] 40279f: 0f be d0 movsx edx,al 4027a2: 8b 45 fc mov eax,DWORD PTR [rbp-0x4] 4027a5: 48 98 cdqe 4027a7: 89 54 85 90 mov DWORD PTR [rbp+rax*4-0x70],edx 4027ab: 83 45 fc 01 add DWORD PTR [rbp-0x4],0x1 4027af: 83 7d fc 0f cmp DWORD PTR [rbp-0x4],0xf 4027b3: 7e c0 jle 402775 <strerror@plt+0x21b5> ; } 4027b5: b8 00 00 00 00 mov eax,0x0 4027ba: e8 b8 04 00 00 call 402c77 <strerror@plt+0x26b7> ; [rbp-0xb0]のチェック 4027bf: 48 8d 85 50 ff ff ff lea rax,[rbp-0xb0] 4027c6: 48 89 c7 mov rdi,rax 4027c9: e8 f8 de ff ff call 4006c6 <strerror@plt+0x106> 4027ce: 89 45 f4 mov DWORD PTR [rbp-0xc],eax ; [rbp-0x70]のチェック 4027d1: 48 8d 45 90 lea rax,[rbp-0x70] 4027d5: 48 89 c7 mov rdi,rax 4027d8: e8 3b e7 ff ff call 400f18 <strerror@plt+0x958> 4027dd: 89 45 f0 mov DWORD PTR [rbp-0x10],eax 4027e0: 83 7d f4 00 cmp DWORD PTR [rbp-0xc],0x0 4027e4: 74 12 je 4027f8 <strerror@plt+0x2238> 4027e6: 83 7d f0 00 cmp DWORD PTR [rbp-0x10],0x0 4027ea: 74 0c je 4027f8 <strerror@plt+0x2238> 4027ec: bf 65 2d 40 00 mov edi,0x402d65 ; "Good flow!!" 4027f1: e8 5a dd ff ff call 400550 <puts@plt> 4027f6: eb 0a jmp 402802 <strerror@plt+0x2242> 4027f8: bf 78 2d 40 00 mov edi,0x402d78 ; "You aint goin with the flow...." 4027fd: e8 4e dd ff ff call 400550 <puts@plt> 402802: b8 00 00 00 00 mov eax,0x0 402807: c9 leave 402808: c3 ret
main関数はなんとなくこんな感じ。
readで標準入力から読み込んだデータを2つのバッファへ交互に格納。
f_4006c6()、f_400f18()でそれぞれ入力されたデータのチェック。両関数の戻り値が0でなければ、Good flow!!が出力される流れ。なので、おそらく入力データがフラグになるだろうと予想。
int main( ) { int i; // rbp-0x4 int var_0xc, var_0x10; char buff_0x30[33], buff_0x70[16], buff_0xb0[16]; if (0x20 != read(0, buff_0x30, 0x20)) { return -1; } for (i=0; i<=0xf; i++) { buff_0xb0[i*4] = buff_0x30[2 * i]; buff_0x70[i*4] = buff_0x30[(2 * i) + 1]; } f_402c77(); var_0xc = f_4006c6(buff_0xb0); var_0x10 = f_400f18(buff_0x70); if (var_0xc == 0 || var_0x10 == 0) { puts("You aint goin with the flow...."); } else { puts("Good flow!!"); } return 0; }
f_4006c6の処理は、単純でcmpの結果 je されるルート(比較結果がイコール)になる入力を求める。
関数の返却値は、r8dレジスタに格納され、NGの文字が入力されていた場合、r8dに0が格納される。
: 4006f6: 8b 3a mov edi,DWORD PTR [rdx] ; データ 4006f8: 89 fa mov edx,edi 4006fa: 83 c2 05 add edx,0x5 ; データに5が加わる↓ 4006fd: 89 d7 mov edi,edx 4006ff: 83 ff 55 cmp edi,0x55 ; 0x50 = "P" ↑で5加わっているので、入力は減算 400702: 4c 8d 1c 25 30 07 40 lea r11,ds:0x400730 ; OKの場合の次のアドレス 400709: 00 40070a: 0f 84 00 08 00 00 je 400f10 <strerror@plt+0x950> ; OK 400710: 4c 8d 1c 25 1d 07 40 lea r11,ds:0x40071d ; ↓でjmpして戻ってくるアドレス 400717: 00 400718: e9 f3 07 00 00 jmp 400f10 <strerror@plt+0x950> ; 一旦jmpして戻ってくる 40071d: 41 b8 00 00 00 00 mov r8d,0x0 ; NG 400723: 4c 8d 1c 25 3d 07 40 lea r11,ds:0x40073d :
f_400f18の処理は、f_402c77でsigaction@pltで登録されたシグナル処理を利用してデータチェックをしている。
sigactionでは、SIGFPEのシグナル時の処理として、f_4005a0が登録されている。SIGFPEは浮動小数点例外(0除算処理)時のシグナル。
sigemptyset@plt に渡す前にsa_flagsにSA_SIGINFOを指定しているので、シグナルハンドラーが呼び出される際の引数として、最初の引き数としてシグナル番号を、二番目の引き数として siginfo_t へのポインターを、三番目の引き数として (void * にキャストした) ucontext_t へのポインターを受けとる。*2
402c77: 55 push rbp 402c78: 48 89 e5 mov rbp,rsp 402c7b: 48 81 ec a0 00 00 00 sub rsp,0xa0 402c82: c7 45 e8 04 00 00 00 mov DWORD PTR [rbp-0x18],0x4 ; SA_SIGINFO 402c89: 48 c7 85 60 ff ff ff mov QWORD PTR [rbp-0xa0],0x402b06 402c90: 06 2b 40 00 402c94: 48 8d 85 60 ff ff ff lea rax,[rbp-0xa0] 402c9b: 48 83 c0 08 add rax,0x8 402c9f: 48 89 c7 mov rdi,rax 402ca2: e8 f9 d8 ff ff call 4005a0 <sigemptyset@plt> ; 初期化 402ca7: 48 8d 85 60 ff ff ff lea rax,[rbp-0xa0] 402cae: ba 00 00 00 00 mov edx,0x0 ; struct sigaction *old 402cb3: 48 89 c6 mov rsi,rax ; struct sigaction *act 402cb6: bf 08 00 00 00 mov edi,0x8 ; SIGFPE 402cbb: e8 a0 d8 ff ff call 400560 <sigaction@plt> 402cc0: c9 leave 402cc1: c3 ret
チェック処理は、400f84にあるようにワザと0除算をして、SIGFPEに登録したシグナルハンドラを呼び出している。シグナルハンドラーの結果がOKだったら、0x603340にjmp先のアドレスが返却されてくる。
f_400f18関数の返却値は、esiレジスタに格納され、NGの文字が入力されていた場合、esiに0が格納される。
: 400f37: 8b 02 mov eax,DWORD PTR [rdx] ; データ 400f39: 89 c2 mov edx,eax 400f3b: 83 ea 03 sub edx,0x3 400f3e: 89 d0 mov eax,edx 400f40: 83 f8 40 cmp eax,0x40 ; 0x43 = "C" 400f43: 4c 8d 14 25 28 10 40 lea r10,ds:0x401028 400f4a: 00 400f4b: 49 c7 c3 05 00 00 00 mov r11,0x5 400f52: c7 04 25 28 33 60 00 mov DWORD PTR ds:0x603328,0x1 ; 比較に利用 400f59: 01 00 00 00 400f5d: 48 89 04 25 30 33 60 mov QWORD PTR ds:0x603330,rax ; "C" 400f64: 00 400f65: 48 c7 c0 00 00 00 00 mov rax,0x0 400f6c: 48 89 14 25 38 33 60 mov QWORD PTR ds:0x603338,rdx 400f73: 00 400f74: 48 8d 15 00 00 00 00 lea rdx,[rip+0x0] # 400f7b <strerror@plt+0x9bb> 400f7b: 48 89 14 25 48 33 60 mov QWORD PTR ds:0x603348,rdx 400f82: 00 400f83: 99 cdq 400f84: 48 f7 3c 25 20 33 60 idiv QWORD PTR ds:0x603320 ; 0除算でsigハンドラ呼び出し 400f8b: 00 400f8c: 48 c7 04 25 20 33 60 mov QWORD PTR ds:0x603320,0x0 400f93: 00 00 00 00 00 400f98: 48 8b 04 25 30 33 60 mov rax,QWORD PTR ds:0x603330 400f9f: 00 400fa0: 48 8b 14 25 38 33 60 mov rdx,QWORD PTR ds:0x603338 400fa7: 00 400fa8: 4c 8b 1c 25 40 33 60 mov r11,QWORD PTR ds:0x603340 ; 次のアドレス 400faf: 00 400fb0: 41 ff e3 jmp r11 ; OKなら次の検査 400fb3: be 00 00 00 00 mov esi,0x0 ; NG 400fb8: 4c 8d 14 25 28 10 40 lea r10,ds:0x401028 :
シグナルハンドラーでは、第3引数として受け取ったコンテキスト(ucontext_t へのポインター)のなかのEFLAGSを利用してデータの比較結果を確認していた。
40289a: 55 push rbp 40289b: 48 89 e5 mov rbp,rsp 40289e: 48 89 7d d8 mov QWORD PTR [rbp-0x28],rdi 4028a2: 48 89 75 d0 mov QWORD PTR [rbp-0x30],rsi 4028a6: 48 8b 45 d8 mov rax,QWORD PTR [rbp-0x28] 4028aa: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax 4028ae: 48 8b 05 93 0a 20 00 mov rax,QWORD PTR [rip+0x200a93] # 603348 <strerror@plt+0x202d88> 4028b5: 48 83 c0 38 add rax,0x38 4028b9: 48 89 45 f0 mov QWORD PTR [rbp-0x10],rax 4028bd: 48 8b 45 d0 mov rax,QWORD PTR [rbp-0x30] ; シグナルハンドラー呼び出し時のEFLAGS 4028c1: 83 e0 40 and eax,0x40 ; ZF 結果が0 4028c4: 48 85 c0 test rax,rax 4028c7: 0f 9f c0 setg al 4028ca: 0f b6 c0 movzx eax,al 4028cd: 89 45 ec mov DWORD PTR [rbp-0x14],eax 4028d0: 48 8b 45 d0 mov rax,QWORD PTR [rbp-0x30] 4028d4: 25 00 08 00 00 and eax,0x800 ; OF オーバーフロー 4028d9: 48 85 c0 test rax,rax 4028dc: 0f 9f c0 setg al 4028df: 0f b6 c0 movzx eax,al 4028e2: 89 45 e8 mov DWORD PTR [rbp-0x18],eax 4028e5: 48 8b 45 d0 mov rax,QWORD PTR [rbp-0x30] 4028e9: 25 80 00 00 00 and eax,0x80 ; SF 符号。負の場合。 4028ee: 48 85 c0 test rax,rax 4028f1: 0f 9f c0 setg al :
比較結果の確認のパターンとしては、イコールになるか X+1 > X > X-1 となるXか?の2種類だった(気がする)。例えば、入力文字Xが0x40="@" > X > 0x3e=">"を満たすかどうかをチェックしていた。
Flag: PCTF{n0_fl0?_m0_like_ah_h3ll_n0}