SSP(Stack Smashing Protector)
SSP는 BOF를 방어하기 위한 보호기법 중 하나로 함수를 실행할 때, canary라는 랜덤 값을 스택에 추가하여 BOF로 스택 데이터가 변조됐는지 확인하는 방법이다.
canary가 변조된 경우는 스택 데이터가 변조됐다고 판단하고 프로그램을 종료한다.
└─# checksec ssp_chk
[*] '/root/ssp_chk'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
SSP가 바이너리에 적용됐는지 확인하는 방법은 checksec
명령을 이용하는 방법이다.
- “Stack: Canary found”라고 나오면 SSP가 적용된 바이너리다.
○ _dl_allocate_tls_storage
void *
internal_function
_dl_allocate_tls_storage (void)
{
void *result;
size_t size = GL(dl_tls_static_size);
#if TLS_DTV_AT_TP
/* Memory layout is:
[ TLS_PRE_TCB_SIZE ] [ TLS_TCB_SIZE ] [ TLS blocks ]
^ This should be returned. */
size += (TLS_PRE_TCB_SIZE + GL(dl_tls_static_align) - 1)
& ~(GL(dl_tls_static_align) - 1);
#endif
/* Allocate a correctly aligned chunk of memory. */
result = __libc_memalign (GL(dl_tls_static_align), size);
SSP 보호 기법이 적용됐다면 함수에서 스택을 사용할 때 canary가 생성된다.
스택에서 사용하는 canary는 master canary를 참조하는데, main()
함수가 호출되기 전에 랜덤으로 생성된 canary를 스레드 별 전역 변수로 사용되는 TLS(Thread Local Storage)
에 저장한다. TLS 영역은 _dl_allocate_tls_storage
함수에서 __libc_memalign
함수를 호출하여 할당한다.
○ tcbhead_t
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;
tcbhead_t
구조체다.
stack_guard
라는 변수가 존재하는데, 해당 변수에 canary가 저장된다.
○ security_init
#define THREAD_SET_STACK_GUARD(value) \
THREAD_SETMEM (THREAD_SELF, header.stack_guard, value)
...
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
stack_guard
에 canary가 저장되는 로직은 security_init()
함수에서 수행한다.
security_init()
함수는 _dl_setup_stack_chk_guard()
함수에서 반환한 랜덤 카나리 값을 설정한다.
정의된 THREAD_SET_STACK_GUARD
매크로는 TLS 영역의 header.stack_guard
에 카나리를 삽입하는 역할을 한다.
pwndbg> disass main
Dump of assembler code for function main:
0x0804872b <+0>: push ebp
0x0804872c <+1>: mov ebp,esp
0x0804872e <+3>: push edi
0x0804872f <+4>: sub esp,0x94
0x08048735 <+10>: mov eax,DWORD PTR [ebp+0xc]
0x08048738 <+13>: mov DWORD PTR [ebp-0x98],eax
0x0804873e <+19>: mov eax,gs:0x14
0x08048744 <+25>: mov DWORD PTR [ebp-0x8],eax
header.stack_guard
의 canary는 stack 영역에서 함수 호출 시 stack에 삽입된다.
<main+19>
를 보면 gs:0x14
로부터 값을 가져와서 <main+25>
에서 [ebp - 0x8]
에 저장한다.
gs:0x14
는 header.stack_guard
을 가리키며, stack_guard
에 저장된 canary를 [ebp - 0x8]
에 저장한다는 의미다.
참고로 32bit는 gs:0x14
, 64bit는 fs:0x28
다.
0x0804887f <+340>: call 0x80484e0 <__stack_chk_fail@plt>
만약, [ebp - 0x8]
에 저장된 canary가 변조된다면, __stack_chk_fail
함수가 실행되면서 프로세스가 종료된다.
이처럼 SSP는 스택 데이터 변조를 방지하기 위한 보호기법으로 사용된다.
하지만 메모리릭 취약점으로 canary를 알아낼 수 있는 상황이거나 master canary를 조작할 수 있는 상황이라면 SSP를 우회할 수 있으므로 취약점이 발생하지 않도록 추가적인 조치가 필요하다.