[모각코] 2025 동계 3회차

안우진·2025년 1월 15일
0

모각코

목록 보기
9/19

[Stack Canary]

Stack Canary 기법은 할당된 영역의 return address와 rbp 다음으로 Canary인 임의의 값을 프롤로그에서 넣고,
에필로그에서 값을 검사하여 변경되었다면 프로그램을 종료시킨다.
SBO로 return address를 조작하기 위해서 Canary를 덮어야하기 때문에 이와같은 활용이 가능하다.
즉, Canary를 모른다면 return address를 조작할 수 없는 것이다.


최근 gcc는 기본적으로 Stack Canary가 적용되어 있다.
이 때, SBO로 return address를 조작하면, 아래와 같은 메세지가 출력되며 프로그램이 종료된다.

*** stack smashing detected ***: <unknown> terminated
Aborted

gdb로 executable file을 디버깅하다보면 call <__stack_chk_fail@plt> 을 볼 수 있는데, 이것이 Stack Canary의 일부였다는 것을 알았다.
-fno-stack-protector 옵션을 주면 Stack Canary를 적용하지 않고 컴파일을 할 수 있다.

아래는 C코드와 gcc test.c -o test 로 컴파일한 실행파일을 디버깅한 결과이다.

#include <stdio.h>

int main() {
	int a;
    scanf("%d", &a);
    printf("%d", a);
}
pwndbg> disas main
Dump of assembler code for function main:
   0x0000000000001189 <+0>:     endbr64
   0x000000000000118d <+4>:     push   rbp
   0x000000000000118e <+5>:     mov    rbp,rsp
   0x0000000000001191 <+8>:     sub    rsp,0x10
   0x0000000000001195 <+12>:    mov    rax,QWORD PTR fs:0x28
   0x000000000000119e <+21>:    mov    QWORD PTR [rbp-0x8],rax
   0x00000000000011a2 <+25>:    xor    eax,eax
   0x00000000000011a4 <+27>:    lea    rax,[rbp-0xc]
   0x00000000000011a8 <+31>:    mov    rsi,rax
   0x00000000000011ab <+34>:    lea    rax,[rip+0xe52]        # 0x2004
   0x00000000000011b2 <+41>:    mov    rdi,rax
   0x00000000000011b5 <+44>:    mov    eax,0x0
   0x00000000000011ba <+49>:    call   0x1090 <__isoc99_scanf@plt>
   0x00000000000011bf <+54>:    mov    eax,DWORD PTR [rbp-0xc]
   0x00000000000011c2 <+57>:    mov    esi,eax
   0x00000000000011c4 <+59>:    lea    rax,[rip+0xe39]        # 0x2004
   0x00000000000011cb <+66>:    mov    rdi,rax
   0x00000000000011ce <+69>:    mov    eax,0x0
   0x00000000000011d3 <+74>:    call   0x1080 <printf@plt>
   0x00000000000011d8 <+79>:    mov    eax,0x0
   0x00000000000011dd <+84>:    mov    rdx,QWORD PTR [rbp-0x8]
   0x00000000000011e1 <+88>:    sub    rdx,QWORD PTR fs:0x28
   0x00000000000011ea <+97>:    je     0x11f1 <main+104>
   0x00000000000011ec <+99>:    call   0x1070 <__stack_chk_fail@plt>
   0x00000000000011f1 <+104>:   leave
   0x00000000000011f2 <+105>:   ret
End of assembler dump.

주목할 것은 fs:0x28fs 또한 레지스터이다.
리눅스는 프로세스가 시작될 때 fs:0x28에 랜덤값을 저장한다.
이를 프롤로그에서 rbp-0x8에 저장하고, 에필로그에서 검사하여 rbp-0x8 = fs:0x28이 아니면 프로그램을 종료시키는 것이다.

fs의 값은 TLS를 가리키는 레지스터로, 일반적으로 gdb의 i r이나 p로는 확인할 수 없다.
gdb의 catch로 특정 이벤트 발생 시 프로세스를 중지시키는 명령어로 syscall 등에 걸어 fs의 값을 추적할 수 있다고 한다.

SBO를 완벽하게 막아줄 것 같은 Stack Canary도 여러 취약점이 있다.
TLS에 접근한다거나 Canary 값을 읽는 방법이 그 중 하나이다.

0개의 댓글