CIDY
[System_Hacking] AD: stage3_Master Canary 본문
SSP에 대해서는 진작 배웠고 그걸 이용한 다양한 문제도 풀어보았다. 근데 기초만 배운 거였다고 한다.
스택 카나리의 값은 Thread Local Storage (TLS)에 존재한다. 여기서는 TLS, 그리고 카나리가 언제 어디서 할당되는지 알아보도록 하자.
*Tread Local Storage
TLS는 스레드의 저장공간이다. text영역, data영역, bss영역 등 목적에 의해 할당된 섹션들에서 데이터를 관리함을 알 것이다. 이와는 달리 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;
}
이게 무슨 함수냐면 로더에서 TLS영역을 할당하고 초기화하는 친구다. _dl_allocate_tls_storage 함수에서 TLS영역을 할당 -> tcbp에 저장 -> 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로 왔다. arch_prctl 시스템 콜의 ARCH_SET_FS는 프로세스의 FS세그먼트 레지스터를 초기화해주는 명령어이다. -> FS세그먼트 레지스터는 TLS영역을 가리키게 된다.
*Master Canary
스택 버퍼를 사용하는 모든 함수에서는 똑같은 카나리값을 사용하므로 한번 릭하면 다른 함수에서도 이용 가능했었다. -> 어셈블리 까 보면 fs:0x28에서 가져와서 [RBP - 0x8]에 넣었었다. (64비트 기준)
여기서 fs는 앞에서 말한 그 세그먼트 레지스터인데, (arch_prctl시스템 콜로 _dl_allocate_tls_stotage에서 할당한 주소) 모든 함수가 여기서 값을 가져오기 때문에 같은 것이다. -> TLS주소 + 0x28에 위치한 그 값을 Master 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;
}
이 함수는 할당한 TLS영역에 랜덤한 카나리값을 삽입하는 함수이다.
우선 _dl_setup_stack_chk_guard함수는 커널에서 생성한 랜덤한 값을 가지는 포인터인 _dl_random을 인자로 카나리를 생성한다.
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함수이다. 공용체 변수 ret에 인자로 들어온 _dl_random을 복사한다. -> 복사한 값을 갖고 엔디언 방식에 따라 and연산을 하는데 (리틀엔디언 기준) 첫 바이트가 NULL이 된다. (~0xff 랑 and하니까 첫 바이트 죽고 나머지 다 사는거.)
다시 security_init함수로 돌아가서, _dl_setup_stack_chk_guard에서 생성된 값은 THREAD_SET_STACK_GUARD매크로에다가 인자로 던져지게 된다.
/* Set the stack guard field in TCB head. */
#define THREAD_SET_STACK_GUARD(value) \
THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)
그 매크로가 이건데 ↑ 안에서 또 매크로를 써놨다. 진짜 C언어 설계한 사람 존경한다ㅋㅋ 암튼 저 매크로는 value를 인자로 받아 그걸 두 번째 인자(header.stack_guard)에 넣어주는 친구다.
할당된 TLS영역은 tcbhead_t구조체로 구성되어 있는데, 그중에서 stack_guard는 스택 카나리값을 가지는 멤버 변수이다. -> 결론적으로 THREAD_SET_STACK_GUARD는 TLS + 0x28위치에다 value(카나리값)를 넣어주는 매크로인 것임.
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;
얘는 그 구조체. stack_guard를 볼 수 있다.
아 그리고 gdb에서 다른 레지스터 주소 까보듯이 fs도 주소 까볼 수 있다.
p/x $fs_base 하면 fs의 주소를 보여준다. 그리고 p/x $fs_base + 0x28 하면 마스터 카나리를 볼 수 있음.
'Hack > DreamHack(로드맵)' 카테고리의 다른 글
[System_Hacking] AD: stage3_Master Canary2 (0) | 2022.07.11 |
---|---|
[System_Hacking] AD: stage3_문제풀이(master canary) (0) | 2022.07.11 |
[System_Hacking] AD: stage2_문제풀이(seccomp) (0) | 2022.07.10 |
[System_Hacking] AD: stage2_ABI(+예제) (0) | 2022.07.10 |
[System_Hacking] AD: stage2_문제풀이(Bypass SECCOMP-1) (0) | 2022.07.10 |