미역줄기의 이모저모

[ Dreamhack ] Stack Canary 본문

Hacking/DreamHack 시스템 해킹 강의

[ Dreamhack ] Stack Canary

미역줄기줄기 2024. 2. 8. 16:48
728x90

스택 카나리는 함수의 프롤로그에서 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고, 함수의 에필로그에서 해당 값의 변조를 확인하는 보호 기법이다. 스택버퍼 오버플로우 공격을 한층 더 어렵게 만든다.

 

스택 버퍼 오버플로우로 반환 주소를 덮으려면 반드시 카나리를 먼저 덮어야 하므로 카나리 값을 모르는 공격자는 반환 주소를 덮을 때 카나리 값을 변조하게 된다. 이 경우, 에필로그에서 변조가 확인되어 공격자는 실행 흐름을 획득하지 못한다.

 

 

그림은 저렇게 그렸지만!!!  사실은

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 detectedaborted라는 에러가 발생한다. 스택 버퍼 오버플로우가 탐지되어 프로세스가 강제 종료되었음을 의미한다.

 

 

 

 

카라니를 비활성화 했을 때와 비교하면, main 함수의 프롤로그와 에필로그에 각각 다음의 코드가 추가되었다.

 

좌: no_canary 우: canary

 

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