(드림핵 강의를 이해하기 위해 요약해본 글)
Thread Local Storage (TLS)
TLS는 쓰레드의 저장 공간이다.
ELF 바이너리를 살펴보면 각각의 목적을 가진 섹션에서 데이터를 관리한다.
(코드를 실행하기 위한 .text, 초기화되지 않은 전역 변수를 위한 .data 등)
이와 달리 TLS 영역은 쓰레드의 전역 변수를 저장하기 위한 공간으로 로더(Loader)가 할당한다.
로더에서 TLS 영역을 할당하고 초기화하는 함수인 init_tls 함수가 있는데
해당 함수 내부 _dl_allocate_tls_storage 함수에서 TLS 영역을 할당하고
이를 tcbp에 저장한 뒤 TLS_INIT_TP 매크로의 인자로 전달한다.
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;
}
SET_FS
TLS_INIT_TP 매크로는 dl_allocate_tls_storage에서 할당한 TLS 영역을 FS로 초기화한다.
arch_prctl 시스템 콜의 첫 번째 인자로 ARCH_SET_FS, 두 번째 인자로 할당한 TLS 주소가 전달된다.
첫 번째 인자인 ARCH_SET_FS는 프로세스의 FS 세그먼트 레지스터를 초기화를 수행하는 명령어이다.
명령을 수행하면 FS 세그먼트 레지스터는 TLS 영역을 가리키게 된다.
# 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; \
})
Master Canary
스택 버퍼를 사용하는 모든 함수에서 같은 카나리 값을 사용한다.
이러한 특징 때문에 임의 함수에서 메모리 릭으로 카나리를 알아낼 수 있다면
다른 함수에서 발생하는 스택 버퍼 오버플로우에서 카나리를 덮어쓰고 실행 흐름을 조작할 수 있었다.
SSP 동작 원리를 되짚어보면
버퍼를 사용하는 함수의 프롤로그에서 FS:0x28에 위치하는 값을 가져와 RBP 바로 앞에 삽입한다.
FS 세그먼트 레지스터는 앞서 arch_prctl 시스템 콜을 통해 _dl_allocate_tls_storage에서 할당한 주소로
모든 함수가 해당 주소에서 값을 가져오기 때문에 같은 카나리 값을 사용하는 것 이다.
이렇게 TLS 주소(FS)에 0x28 바이트 만큼 떨어진 주소에 위치한 랜덤한 값을 Master Canary라고 한다.
security_init 함수는 이전에 할당한 TLS 영역에 랜덤한 카나리 값을 삽입한다.
_dl_setup_stack_chk_guard 함수는 커널에서 생성한 랜덤한 값을 가지는 포인터인 _dl_random을 인자로 카나리를 생성한다.
static void security_init (void)
{
/* Set up the stack checker's canary. */
uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
#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;
}
_dl_setup_stack_chk_guard 함수는 security_init 함수에서 처음으로 호출하는 함수이다.
공용체 변수인 ret에 커널에서 생성한 랜덤한 값을 갖는 dl_random의 데이터를 복사한다.
이후 바이너리의 바이트 오더링(Byte Ordering)에 따라 AND 연산을 수행하는데
리틀 엔디언의 경우 복사한 값의 첫 바이트를 NULL로 변환한다.
(ret.num &= ~(uintptr_t) 0xff;)
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
{
memcpy (ret.bytes, dl_random, sizeof (ret));
#if BYTE_ORDER == LITTLE_ENDIAN
ret.num &= ~(uintptr_t) 0xff;
#elif BYTE_ORDER == BIG_ENDIAN
ret.num &= ~((uintptr_t) 0xff << (8 * (sizeof (ret) - 1)));
_dl_setup_stack_chk_guard에서 카나리 값을 생성했다면
해당 값을 THREAD_SET_STACk_GUARD 매크로의 인자로 전달해 호출한다.
THREAD_SETMEM 매크로를 통해 두 번째 인자인 header.stack_guard 위치에 value를 삽입한다.
/* Set the stack guard field in TCB head. */
#define THREAD_SET_STACK_GUARD(value) \
THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)
할당된 TLS 영역은 tcbhead_t 구조체로 구성되어 있다.
stack_guard는 스택 카나리의 값을 가지는 멤버 변수이다.
THREAD_SET_STACK_GUARD는 TLS + 0x28 위치에 생성된 카나리 값을 삽입하는 매크로이다.
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;
#else
int __glibc_reserved1;
#endif
/* Reservation of some values for the TM ABI. */
void *__private_tm[4];
/* GCC split stack support. */
void *__private_ss;
} tcbhead_t;
Thread Stack
스레드 함수에서 선언된 변수는 일반적인 함수에서 사용하는 스택 영역이 아닌 TLS와 인접한 영역에 할당되는 점이 다르다.
그러나 버퍼를 할당했을 때 TLS 영역에 존재하는 마스터 카나리 값을 참조한다는 점은 동일하다.
아래 코드는 pthread_create 함수 코드이다.
THREAD_COPY_STACK_GUARD 매크로를 통해 header.stack_gaurd에 위치하는 마스터 카나리 값을 가져온다.
스레드에서 할당한 변수는 마스터 카나리가 위치하는 주소보다 낮은 주소에 있기 때문에
스택 버퍼 오버플로우가 발생한다면 마스터 카나리를 덮어쓸 수 있다.
모든 함수에서는 함수가 실행될 때마다 FS 세그먼트 레지스터를 참조해
해당 주소로부터 0x28 바이트만큼 떨어진 마스터 카나리를 가져온다.
만약, 취약점을 통해 마스터 카나리를 임의의 값으로 조작할 수 있다면
스택 카나리를 알아낼 필요 없이 익스플로잇 할 수 있다.
#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
...
}
'background > linux' 카테고리의 다른 글
[glibc-2.27] _IO_FILE & fopen (1) | 2023.01.07 |
---|---|
SigReturn-Oriented Programming(SROP) (0) | 2022.12.30 |
SECCOMP (0) | 2022.12.01 |
cyclic pattern for buffer overflow attack (0) | 2022.11.11 |
Double Free Bug & Tcache Poisoning (0) | 2022.11.03 |