미역줄기의 이모저모
[ Dreamhack ] Stack Canary 본문
스택 카나리는 함수의 프롤로그에서 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고, 함수의 에필로그에서 해당 값의 변조를 확인하는 보호 기법이다. 스택버퍼 오버플로우 공격을 한층 더 어렵게 만든다.
스택 버퍼 오버플로우로 반환 주소를 덮으려면 반드시 카나리를 먼저 덮어야 하므로 카나리 값을 모르는 공격자는 반환 주소를 덮을 때 카나리 값을 변조하게 된다. 이 경우, 에필로그에서 변조가 확인되어 공격자는 실행 흐름을 획득하지 못한다.
그림은 저렇게 그렸지만!!! 사실은
sfp 앞에 있고 !! 정확히 buf 보다는 데이터 인자 라고 하는게 맞겠다!!!!
카나리의 작동 원리
// Name: canary.c
#include <unistd.h>
int main() {
char buf[8];
read(0, buf, 32);
return 0;
}
이 코드에는 스택 버퍼 오버플로우 취약점이 존재한다. read부분을 보면 알 수 있다.
카나리 없이 컴파일 하려면 컴파일 옵션으로 -fno-stack-protector 를 추가한다. 카나리 없이 컴파일 하고 길이가 긴 입력을 주면, 반환 주소가 덮여서 segmentation fault가 발생한다.
카나리를 적용하여 다시 컴파일하고, 긴 입력을 주면 stack smashing detected와 aborted라는 에러가 발생한다. 스택 버퍼 오버플로우가 탐지되어 프로세스가 강제 종료되었음을 의미한다.
카라니를 비활성화 했을 때와 비교하면, main 함수의 프롤로그와 에필로그에 각각 다음의 코드가 추가되었다.
0x00000000000006b2 <+8>: mov rax,QWORD PTR fs:0x28
0x00000000000006bb <+17>: mov QWORD PTR [rbp-0x8],rax
0x00000000000006bf <+21>: xor eax,eax
0x00000000000006dc <+50>: mov rcx,QWORD PTR [rbp-0x8]
0x00000000000006e0 <+54>: xor rcx,QWORD PTR fs:0x28
0x00000000000006e9 <+63>: je 0x6f0 <main+70>
0x00000000000006eb <+65>: call 0x570 <__stack_chk_fail@plt>
추가된 프롤로그의 코드에 중단점을 설정하고 바이너리를 실행시켜보자.
main+12는 fs:0x28의 데이터를 읽어서 rax에 저장한다. fs는 세그먼트 레지스터의 일종으로, 리눅스는 프로세스가 시작될 때 fs:0x28에 랜덤 값을 저장한다. 따라서 main+8의 결과로 rax에는 리눅스가 생성한 랜덤 값이 저장된다.
ni를 입력해서 코드를 한 줄 실행하면 rax에 첫 바이트가 널 바이트인 8바이트 데이터가 저장되어있다.
생성한 랜덤값은 main+21에서 rbp-0x8에 저장된다.
main+54는 rbp-8에 저장한 카나리를 rdx로 옮긴다. 그 뒤, fs:0x28에 저장된 카나리와 rdx를 비교한다. 다르다면 je 명령어를 통과하지 않고 __stack_chk_fail이 호출되면서 프로그램이 강제 종료된다. 같아면 main함수는 정상적으로 반환된다.
main+54에 중단점을 걸고 16개의 H를 입력해서 카나리를 변조하고 실행 흐름이 어떻게 되는지 보자.
이제 코드를 한 줄 더실행시켜서 rdx를 확인해보자.
rbp-0x8에 저장된 카나리 값이 버퍼오버플로우로 인해 484848~~이 된 것을 확인할 수 있다. main+58의 연산결과가 0이 아니므로 main+69의 __stack_chk_fail을 실행하게 된다..
이 함수가 실행되면 위의 메시지가 출력되어 프로세스가 강제 종료된다.
카나리 생성 과정
카나리 값은 프로세스가 시작될 때 TLS에 전역 변수로 저장되고, 각 함수마다 프롤로그와 에필로그에서 이 값을 참조한다.
TLS의 주소 파악
fs는 TLS를 가리키므로 fs의 값을 알면 TLS의 주소를 알 수 있다. fs의 값을 설정할 때 호출되는 arch_prctl(int code, unsigned long addr) 시스템 콜에 중단점을 설정하여 fs가 어떤 값으로 설정되는지 조사하면 된다. 이 시스템 콜을 arch_prctl(ARCH_SET_FS, addr)의 형태로 호출하면 fs의 값은 addr로 설정된다.
gdb에는 특정 이벤트가 발생했을 때, 프로세스를 중단시키는 catch 명령어가 있다. catch로 arch_prctl에 catchpoint를 설정하고 실습에 사용했던 canary를 실행한다.
$ gdb -q ./canary
pwndbg> catch syscall arch_prctl
Catchpoint 1 (syscall 'arch_prctl' [158])
pwndbg> run
catchpoint에 도달할 때까지 continue 명령어를 실행한다.
pwndbg> c
...
pwndbg> c
Continuing.
Catchpoint 1 (call to syscall arch_prctl), init_tls (naudit=naudit@entry=0) at ./elf/rtld.c:818
818 ./elf/rtld.c: No such file or directory.
...
─────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*RAX 0xffffffffffffffda
*RBX 0x7fffffffe090 ◂— 0x1
*RCX 0x7ffff7fe3e1f (init_tls+239) ◂— test eax, eax
*RDX 0xffff80000827feb0
*RDI 0x1002
*RSI 0x7ffff7d7f740 ◂— 0x7ffff7d7f740
pwndbg> info register $rdi
rdi 0x1002 4098
pwndbg> info register $rsi
rsi 0x7ffff7d7f740 140737351513920
pwndbg> x/gx 0x7ffff7d7f740 + 0x28
0x7ffff7d7f768: 0x0000000000000000
pwndbg>
catchpoint에 도달했을 때, rdi의 값이 0x1002인데 이 값은 ARCH_SET_FS의 상숫값이다. rsi의 값이 0x7ffff7d7f740 이므로, 이 프로세스는 TLS를 0x7ffff7d7f740에 저장할 것이며, fs는 이를 가리키게 될 것이다.
카나리가 저장될 fs+0x28 즉 0x7ffff7d7f740을 보면 아직 어떤 값도 설정되어 있지 않다.
카나리 값 설정
TLS의 주소를 알았으니 gdb의 watch 명령어로 TLS+0x28에 값을 쓸 때 프로세스를 중단시킨다. watch는 특정 주소에 저장된 값이 변경되면 프로세스를 중단시키는 명령어이다.
pwndbg> watch *(0x7ffff7d7f740+0x28)
Hardware watchpoint 4: *(0x7ffff7d7f740+0x28)
watchpoint를 설정하고 프로세스를 계속 진행시키면 security_init 함수에서 프로세스가 멈춘다.
pwndbg> continue
Continuing.
Hardware watchpoint 4: *(0x7ffff7d7f740+0x28)
Old value = 0
New value = 2005351680
security_init () at rtld.c:870
870 in rtld.c
여기서 TLS+0x28의 값을 조회하면 0x8ab7f53277873d00이 카나리로 설정된 것을 확인할 수 있다.
pwndbg> x/gx 0x7ffff7d7f740+0x28
0x7ffff7d7f768: 0x8ab7f53277873d00
카나리 우회
1. 무차별 대입
x64 아키텍처에서는 8바이트의 카나리가 생성된다. x86 아키텍처에서는 4바이트의 카나리가 생성된다. 각각의 카나리에는 NULL 바이트가 포함되어 있으므로 , 실제로는 7바이트와 3바이트의 랜덤한 값이 포함된다.
2. TLS 접근
카나리는 TLS에 전역변수로 저장되며, 매 함수마다 이를 참조해서 사용한다. TLS의 주소를 알 수 있고, 임의 주소에 대한 읽기 또는 쓰기가 가능하다면, TLS에 설정된 카나리 값을 읽거나, 이를 임의의 값으로 조작할 수 있다.
'Hacking > DreamHack 시스템 해킹 강의' 카테고리의 다른 글
[ Dreamhack ] link (0) | 2024.02.09 |
---|---|
[ Dreamhack ] NX & ASLR (0) | 2024.02.08 |
[ Dreamhack ] Return Address Overwrite (0) | 2024.02.06 |
[ Dreamhack ] Tool: gdb (1) | 2024.02.05 |
[ Dreamhack ] 어셈블리어와 x86-64(2) (1) | 2024.02.05 |