how2heap分析-_2.23
how2heap

用how2heap辅助一下

按说明书操作就对了

源码加载进去

directory /home/shark/Desktop/ctf/claude_code/how2heap/glibc_2.23/malloc
start
add-symbol-file /home/shark/Desktop/ctf/claude_code/how2heap/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/.debug/lib/x86_64-linux-gnu/libc-2.23.so 0x7ffff7800000(gibc开始地址)
不用了,有更好的方法


太超模了
fastbin_dup
fastbin中简单的double free

先看看正常运行后的结果

可以看到第二轮malloc的时候返回了两次同样的addr
用pwndbg调试看看

先free了一个a,这是bins链表和heap空间的布局

free b后用头插法进入fastbins bins
接下来看看double free

可以看出a的next指针指向了b

看看

仅检查 fastbin 头部是否 == 被 free 的指针
也就是说只要隔一个chunk就可以实现double free

old装的chunk b
然后malloc回来

为什么malloc后fastbin的链表没变化?
不对,有变化
从aba变成bab了



循环了,怪不得
fastbin的检查还是太weak了

fastbin_dup_into_stack
任意地址分配
先run一下

用fastbin_dup伪造后还得伪造size

看看源码

stack_var在stack上

伪造size
一个正常的 Chunk 结构是:prev_size (8 bytes) -> size (8 bytes) -> user_data


这时候的d指向a,我们要return stack_var+8就得在&stack_var伪造size通过检查

改完后


malloc出来了
house_of_spirit
fake chunk这一块
在可控空间内伪造chunk结构**(比如栈、BSS段)**,然后调用free,malloc后返回,实现任意地址写
pwn.college里写过好多了
这回先看看源码

初始化栈空间
fake_chunk注意16字节对齐

next size的value只要>16, <128kb左右(系统分配的堆内存总大小)就可以了,不要忘了top chunk也是chunk


ni步进



_int_free检查

成了

返回satck_addr

unsorted_bin_attack
将一个libc的地址写入到一个任意的内存地址中
glibc2.23中的_int_malloc在遍历unsorted bin并取出chunk时缺少链表完整性检查

看看unsorted_chunks()宏,

简单粗暴

可以修改unsorted bin chunk中的bk,取出时bck->fd = libc_addr
先看看源码

19:此时,这个 Chunk 会被 glibc 放入 Unsorted Bin 的双向链表中。
在这个状态下,p 的前向指针 (fd) 和后向指针 (bk) 都会指向 libc 内部的 main_arena 结构体中的某个地址。p[1] 其实就是访问这个 Chunk 的 bk 指针
25:模拟UAF,把chunk的bk指针改成&stack_var - 2,即目标地址-0x10
free后


改bk成功


unsafe_unlink
利用free时chunk向后合并,触发unlink
fastbin的free不会改变相邻chunk的PREV_INUSE,也不会触发合并,所以要malloc大一点
利用条件:
1.有一个已知地址的指针(p)指向堆块
2.off-by-one,要清空prev_inuse位
3.malloc要大于fastbin的范围, 这样free的时候才会触发合并调用unlink宏
我们先看看unlink的校验

也就是说当调用free(p)时,unlink会检查:我的前一个节点的下一个节点必须是我,我的后一个节点的前一个节点也必须是我
我们只要在p指向的chunk附近(0x10,0x18 处)伪造fd,bk就可以绕过。
设p指向chunk0
p->fd就是&chunk0+0x18,p->bk就是&chunk0+0x10,我们的检查要求&chunk0+0x18处的指针指向的地址+0x18处的地址储存的指针指向p,&chunk0+0x10处的指针指向的地址+0x10处储存的指针指向p

通过检查后就开始双向链表摘除,
FD->bk = BK => *(&P_ptr) = &P_ptr - 0x10
BK->fd = FD => *(&P_ptr) = &P_ptr - 0x18
p被赋值了两次
将p指针的值改成自己地址-0x18的位置(p一般在bss,data段)
开始伪造chunk1 head

prev_size = 0x80

还有这写法
看看_int_free中的consolidate backward


chunk1 - 0x80 = chunk0 + 0x10 = chunk0_ptr + 0x10
正常free chunk0时prev_size应该是0x90但这里要配合fake chunk

free chunk1后经过!prev_inuse检查,调用unlink()宏
chunk0_ptr = &chunk0_ptr - 0x18

这样就可以控制p指针了,

vic_str 里面的value本来是hello,将vic_str的地址赋到chunk0[3]上,即将p指针重新赋值然后对chunk0[0]赋值 等价于*(victim_string) =?????
这就是利用unlink对任意地址的值覆盖流程
下面用pwndbg看看


chunk0 malloc了0x90

fd赋值

bk赋值

改chunk1的prev_size

清空prev_inuse

unlink之前

unlink后


ok


成功
house_of_force
通过改top chunk的size实现对任意内存的读写
这个在pwn.college里听yan教授讲过的,yan好像挺喜欢这个,原理上不难,把top chunk的size值改成-1就行了,
看看源码
目标是覆写bss段的数据

改top chunk size

当前的 malloc(evil_size) 执行完后,新的 Top Chunk 地址变成 bss_var - 0x10

这时再malloc返回的就是bss_var,然后就任意覆写了

初始化heap

改top chunk size

好


使top chunk的addr到bss_var -0x10


成了

0xffffffffffffffff - 0xffffffffffffef00 = 0x10ff,glibc算出来的top chunk剩余空间
0x555555558028: 0x676e697274732061 -> “a string”
0x555555558030: 0x6577207461687420 -> “ that we”


成了
fastbin_dup_consolidate
利用fastbin的free时的chunk合并跟UAF,拿到两个指针指向同一个内存
看看源码

先free p1(fastbin)

为了触发chunk合并,第二次得malloc超过fastbin的chunk(检查small bins失败后调用malloc_consolidate)
此时, p3 指向的addr 应该等同于p1

free p1等同于free p3,但p3指针仍然可用

再malloc跟p3一样大的chunk,就得到了指向同一chunk且可用的指针
pwndbg启动


free p1进入fastbin,不合并,不会清除Top chunk的PREV_INUSE

还有这细节

malloc 0x400,加上header是0x410,属于large chunk,在到常规bin遍历前glibc会调用malloc_consolidate()防止内存碎片化,然后p1就会被合并进top chunk

触发合并,

p3,p1指向同一chunk
free p1

chunk3被top chunk吞了

p4 = p3
成功
house_of_lore

好熟悉的手法
利用 smallbin 链表伪造实现在栈上分配 chunk


伪造chunk


先malloc 0x100

free 掉


然后malloc一个大块

0x110被归类到了smallbins


改bk指针



取出

检查

bk指针指向stack_buffer_1

malloc 出来

成功
large_bin_attack

向任意地址写入一个heap的指针,可以用它改global_max_fast,使后续的chunk malloc,free 都当fastbin处理


heap初始布局

free p1,p2,进入unsorted bin

malloc 0x90

然后free 3
改p2
(这里回顾一下large bin的排序规则,large bin中有两套链,fd/bk 正常双向链表和fd_nextsize / bk_nextsize 按size排序的辅助链用来连接
可以概括成同一个large bin里,chunk按size从大到小排列,相同size的chunk会插到该size组的第二个位置,这样可以不动 fd_nextsize / bk_nextsize链表
large bin本身是按size范围分bin的,进入同一个large bin之后,还会按精确的size值排序
先选一个large bin 按size分组,然后在同一个large bin的内部按chunk的size排序


fd_nextsize / bk_nextsize链表可以减少遍历次数

)

然后malloc 0x90,遍历unsorted bin,p3 进入large bin,因为p3 size >p2 size
largebin排序进入这个分支(else)

(fwk,bck 是链表插入位置的前后节点的临时指针,glibc会先根据size找到插入位置,然后设置bck和fwd)
victim = p3 chunk header
fwd = p2 chunk header
我们看看这两行指令
victim->bk_nextsize = fwd->bk_nextsize
victim->bk_nextseize->fd_nextsize = victim
可知
fwd = p2
p2->bk_nextsize = &stack_var2 - 0x20
victim = p3
so
victim->bk_nextsize = &stack_var2 - 0x20
(&stack_var2 - 0x20)->fd_nextsize = p3
the offset of fd_nextsize is 0x20
stack_var2 = p3 chunk header
同理

gdb启动


初始布局


free 1,2后

用malloc触发遍历

然后free 3

然后改p2

改完后再用malloc触发遍历



开始malloc 0x90


成了
overlapping_chunks
通过改unsorted bin 中的freed chunk的size,使接下来return的chunk覆盖下一个chunk,原理不难
看看源码

先malloc了三个chunk

把chunk2 free了

改了chunk2的size段

malloc一块size差不多的内存,这样p4就可以包含p3
pwndbg启动


free掉了chunk2

改size
malloc后,可以看到p4跟p3重合了
overlapping_chunks2
我看看,非相邻chunk合并?
这回是改正在使用中的chunk,然后通过free使其产生更大的free chunk
看看源码

先malloc了5个chunk,chunk5防止chunk4被top chunk合并

填入字符方便观察

free掉chunk4

如果有堆溢出的话,通过chunk1可以改chunk2的size段
把 p2 的 size 从 0x400 改成了 0x800(即 p2 和 p3 大小的总和),并保留 prev_inuse 为 1(防止触发向后合并)

然后把chunk2free掉
这里因为chunk4 已经free掉了,保留了chunk2的prev_inuse,不向前合并

进入分支

改了 p2 的 size,此时 nextchunk 跨过了 p3,直接指向了 p4!因为 p4 后面还有个 p5 挡着,所以 nextchunk 不是 Top Chunk,进入分支
4014~4017会把chunk4加上,chunk4本就是合法的,不存在问题

将大chunk malloc回来

开始调试


free掉chunk4

改chunk2的size



成功伪造大freed chunk

malloc

验证

成功
poison_null_byte
单字节溢出?
用一个NULL byte 污染chunk size
源码分析

malloc 4个chunk

malloc unlink的时候会有检查
chunksize(P) == prev_size(next_chunk(P))
提前写0x200绕过

先free,后覆盖size低字节

从之前free掉的b中切一块给b1


malloc b2,依然从之前free 掉的 chunk中切
并把其中塞满’B’


接着free 掉 b1和c
当 b1,b2,c 其中b2在使用时,free b1,c不会让它们合并成一个大chunk
但是现在的metadata错位了,从0x210变成了0x200
free c时,glibc根据c->prev_size 和前一个chunk的状态判断是否合并
这时候glibc就会忽略b2

随后malloc 0x300会return那个合并后的chunk,其中包含正在使用的b2
gdb启动



初始化

提前写0x200绕过

free b



poison null


malloc 0x100

从free掉的b中切


malloc 0x80


free b1,c
free c时,glibc根据c->prev_size 和前一个chunk的状态判断是否合并
这时候glibc就会忽略b2

feee b1

free c
变成一个0x300多的大块

然后会malloc 0x300,return刚才合并的大块

成了

house_of_einherjar
利用off-by-one null byte 清掉下一个chunk的PREV_INUSE位,让free()以为前面有一个free chunk,然后向后合并到我们伪造的fake chunk,最后malloc 返回可控的地址附近
源码分析

malloc a = 0x38

造fake chunk
为通过unlink的检查,fwd和bck都指向自己


malloc 0xf8 ,b
prve_inuse清0

free b 后跟fake chunk合并

return fake chunk附近的可控地址
gdb启动




fake chunk

malloc b

清0

写fake prev_size

把 B 的 prev_size 改成:
B - fake_chunk_addr

更新fake chunk【1】


free b, 触发合并

成功

malloc回来

ok
unsorted_bin_into_stack
篡改 unsorted bin chunk 的 bk,让下一次 malloc() 从伪造的栈上 fake chunk 返回地址
源码分析

我去,就这么点

先初始化一块地方用来造fake chunk
然后malloc 0x100,malloc p1用来防止后续与top chunk合并

free 掉victim进入unsorted bin

create a fake chunk
set size and bk pointer

victim[-1] = 32; // 改小 victim 的 size (0x20) , 16字节对齐
victim[1] = (intptr_t)stack_buffer; // 篡改 victim 的 bk 指向栈上的 Fake Chunk
当 malloc 遍历 unsorted bin 时,会顺着 bk 指针往下找。把 bk 改成 stack_buffer,就能把栈强行拉入 unsorted bin 的链表中
改成 32 后,malloc 遍历到 victim 时,发现 size (32) 不等于请求的 size (0x110),就会将 victim 从 unsorted bin 中卸下,并丢进对应的 Small Bin,然后继续顺着 bk 遍历下一个 chunk(stack_buffer)

然后malloc出来

把ret 地址改成后门函数来检验
gdb启动


那个0x400的是缓冲区,下面两个0x111的才是victim和p1

free victim



fake chunk

改victim,uaf

之前

改完

这时malloc 0x100

成了
house_of_orange
终于看到io了,top chunk还能这么用?
the core of the house of orange is to get a free unsorted chunk without free function
this operation uses an overflow in the heap to corrupt the io_list_all pointer
It requries a leak of heap and libc
源码分析

先malloc一块chunk

利用溢出把 top chunk size 改成0xc01,要页对齐,同时PREV_INUSE位要保留

malloc一个大chunk,此时top chunk size 小于0x1000
触发sysmalloc()扩heap
old top chunk 进入unsorted bin
这就在没有明面上的free()的情况下实现了unsorted bin attack

此时old top chunk进入unsorted bin

这样就可以反推libc地址

old top chunk在unsorted bin中,它的fd/bk会指向main_arena里的unsorted bin 链表头附近,而io_list_all也是libc的全局符号

然后改bk地址


将file结构写入old top chunk
old top chunk作为fake file

把old top chunk的size改坏,以便后续触发malloc_printer(),malloc_printer()会触发abort(),abort()会触发_io_flush_all_lockp(),它会遍历io_list_all,然后fake file就有用了

继续伪造fake file
fp ->_mode = 0是为了通过检查
fp->_mode <= 0 是 glibc 在刷新 FILE 流时用来判断:
这个流是不是普通的字节流,能不能按普通 _IO_FILE 的方式去 flush
_mode < 0 还没确定 / 未初始化状态
_mode = 0 普通字节流
_mode > 0 宽字符流

glibc 在刷新所有流时,会判断这个 FILE 有没有待输出的数据:
if (fp->_IO_write_ptr > fp->_IO_write_base)
所以这里故意设置成:
_IO_write_base = 2
_IO_write_ptr = 3
于是:
3 > 2
条件成立。
glibc 就会认为:
这个 FILE 的写缓冲区里还有数据没刷新
然后继续调用:
_IO_OVERFLOW(fp, EOF);
底层就是:
fp->vtable->__overflow(fp, EOF);
overflow是写缓冲区满了,或者需要把缓冲区内容刷出去时,调用的处理函数,即输出缓冲区处理函数

伪造vtable
jump_table对应io_overflow,,这里是jump到winner()函数

winner()调用的是system(ptr)这里prt指向fake file开头,也就是”/bin/sh\x00”

触发malloc_printerr()
get shell
gdb启动



改top chunk

malloc 0x1000, unsorted bin attack


ok


对的对的

改bk


ok

最后触发

ok
house_of_mind_fastbin
伪造非main arena,然后把一个fastbin chunk 的non_main_arena位打开,这样在free时glibc会通过chunk地址找fake heap_info,然后取其中的ar_ptr当arena,最后把freed chunk 指针写入fake arena 的fastbinsY,这样就能将一个chunk地址写入指定内存中
源码分析

伪造fake arena

伪造malloc_state.system_mem



把heap推到fake_heap_info附近

malloc 0x50

伪造heap_info 的ar_ptr

改size标志位

free掉

ok
house_of_roman
还有hook hijack
直接利用unsorted bin attack 中的libc地址