[Dreamhack] Background: Master Canary

Sisyphus·2022년 7월 28일
0

Dreamhack - System Hacking

목록 보기
40/49

Thread Local Storage

Thread Local Storage

Thread Local Storage (TLS)는 스레드의 전역 변수를 저장하기 위한 공간으로, 로더(Loader)에 의해서 할당됩니다.

init_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 매크로

# 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;     \
  })

TLS_INIT_TP 매크로는 dl_allocate_tls_storage에서 할당한 TLS 영역을 FS로 초기화 합니다.

arch_prctl 시스템 콜의 ARCH_SET_FS는 프로세스의 FS 세그먼트 레지스터를 초기화하는 작업을 수행하는 명령어입니다. 따라서 FS 세그먼트 레지스터는 TLS 영역을 가리키게 됩니다.



Master Canary

Master Canary

스택 버퍼를 사용하는 모든 함수에서 같은 카나리 값을 사용합니다. 그래서 임의의 함수에서 카나리 값을 알아낼 수 있으면 다른 함수에서 발생하는 스택 버퍼 오버플로우에서 카나리를 덮어쓰고 실행 흐름을 조작할 수 있습니다.

SSP 동작 원리를 되짚어보면, 함수 프롤로그에서 FS:0x28에 위치하는 값을 가져와 RBP 바로 앞에 삽입합니다.

FS 세그먼트 레지스터는 arch_prctl 시스템 콜을 통해 _dl_allocate_tls_storage에서 할당한 주소로, 모든 함수가 해당 주소에서 값을 가져오기 때문에 같은 카나리 값을 사용합니다. 이렇게 TLS 주소에서 0x28 바이트 만큼 떨어진 주소에 위치한 랜덤한 값을 마스터 카나리(Master Canary)라고 합니다.

security_init 함수

TLS 영역에 랜덤한 카나리 값을 삽입하는 함수

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 함수는 커널에서 생성한 랜덤한 값을 가지는 포인터인 _dl_random을 인자로 카나리를 생성합니다.


카나리 값 생성

_dl_setup_stack_chk_guard 함수

카나리 값을 생성하는 함수

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)));

공용체 변수인 ret에 커널에서 생성한 랜덤 값을 갖는 dl_random의 데이터를 복사합니다.
이후 바이너리의 바이트 오더링에 따라 AND 연산을 수행하는데, 리틀 엔디언의 경우 복사한 값의 첫 바이트를 NULL로 변환합니다.


카나리 값 삽입

THREAD_SET_STACK _GUARD 매크로

TLS + 0x28 위치에 생성된 카나리 값을 삽입하는 매크로

/* Set the stack guard field in TCB head.  */
#define THREAD_SET_STACK_GUARD(value) \
  THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)

디버깅

Master Canary 예제 코드

// Name: master_canary.c
// Compile: gcc -o master_canary master_canary.c -no-pie

#include <stdio.h>
#include <unistd.h>

int main()
{
	char buf[256];
	read(0, buf, 256);
}

 kali@kali  ~  gdb-gef -q master_canary
Reading symbols from master_canary...
(No debugging symbols found in master_canary)
GEF for linux ready, type `gef' to start, `gef config' to configure
90 commands loaded and 5 functions added for GDB 10.1.90.20210103-git in 0.00ms using Python engine 3.10
gef➤  start
gef➤  x/gx $fs_base+0x28
0x7ffff7faf5a8: 0x835116e48fda6500

0개의 댓글