본문 바로가기

Pwnable/개념

[Pwnable] Dreamhack STAGE 6

https://dreamhack.io/lecture/roadmaps/2

 

System Hacking

시스템 해킹을 공부하기 위한 로드맵입니다.

dreamhack.io


[ Pwnable stage 6 _ Stack Canary ]

 

* 스택 카나리(Stack Canary)

 : 스택 버퍼 오버플로우로부터 반환 주소 보호

 -> 함수의 프롤로그에서 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고, 함수의 에필로그에서 해당 값의 변조를 확인하는 보호 기법

 -> 카나리 값의 변조가 확인되면 프로세스는 강제 종료됨

 

 

* 카나리 정적 분석

Canary.c파일을 -fno-stack-protector 옵션을 추가해서 카나리 없이 컴파일하면 segmentation fault가 발생한다.

No_canary.asm 파일은 아래와 같다고 한다.

2  push rbp

3  mov rbp,rsp

4  sub rsp,0x10

5  lea rax,[rbp-0x8]

6  mov edx,0x20

7  mov rsi,rax

8  mov edi,0x0

9  call read@plt

10 mov eax,0x0

11  leave

12  ret

 

이제 같은 canary.c 파일을 옵션 없이 다시 컴파일해서 입력을 주면 stack smashing detected와 aborted라는 에러가 발생하는데, 이는 스택 버퍼 오버플로우가 탐지되어 프로세스가 강제 종료되었음을 의미한다.

Canary.asm 파일은 아래와 같다.’’

 

2  push rbp

3  mov rbp,rsp

4  sub rsp,0x10

5  mov rax,QWORD PTR fs:0x28

6  mov QWORD PTR [rbp-0x8],rax

7  xor eax,eax

8  lea rax,[rbp-0x10]

mov edx,0x20

10  mov rsi,rax

11  mov edi,0x0

12  call read@plt

13  mov eax,0x0

14  mov rcx,QWORD PTR [rbp-0x8]

15  xor rcx,QWORD PTR fs:0x28

16  je 0x6f0 <main+70>

17  call __stack_chk_fail@plt

18  leave

19  ret

 

 

* 카나리 동적 분석

 -> fs : 세그먼트 레지스터의 일종으로, 리눅스는 프로세스가 시작될 때 fs:0x28에 랜덤 값을 저장함.

Canary를 분석한 결과 rax에 아래와 같은 값이 저장되어 있고, 이 값은 main+17(위 코드에서 6번째 줄)에서 rbp-0x8에 저장됨

이후 main+50(위 코드에서 14번째 줄)에서 rcx에 이 값을 저장하고, 15번째 줄에서 xor을 통해 두 값이 동일한지 확인하고 있다.

두 값이 동일하면 연산 결과가 0이 되어 je의 조건을 만족하고, main 함수는 정상적으로 반환됨.

두 값이 동일하지 않으면 __stack_chk_fail이 호출되면서 프로그램 강제 종료

 

 

* 카나리 생성 과정

 => 카나리 값은 프로세스가 시작될 때, TLS에 전역 변수로 저장되고, 각 함수마다 프롤로그와 에필로그에서 이 값을 참조함.

 -> TLS의 주소 파악

- fs는 TLS를 가리키므로 fs의 값을 알면 TLS의 주소를 알 수 있음

- 그러나 리눅스에서 fs의 값은 특정 시스템 콜을 사용해야만 조회하거나 설정할 수 있음

- gdb에서 다른 레지스터의 값을 출력하듯 “info register fs”나 “print $fs”와 같은 방식으로는 값을 알 수 없음

=> fs의 값을 설정할 때 호출되는 arch_prctl(int code, unsigned long addr) 시스템 콜에 중단점을 설정하여 fs가 어떤 값으로 설정되는지 조사

    -> 이 시스템 콜을 arch_prctl(ARCH_SET_FS, addr)의 형태로 호출하면 fs의 값은 addr로 설정됨

    -> gdb의 catch 명령어: 특정 이벤트가 발생했을 때, 프로세스를 중지시키는 명령어

*** rdi와 rsi를 확인하는 건 그냥 정해져 있는 건가,,?

 

 

* 카나리 우회

1. 무차별 대입

 - x64 아키텍처에서는 8바이트의 카나리 생성 / x86 아키텍처에서는 4바이트의 카나리 생성

 - 각각의 카나리에는 null 바이트가 포함되어 있으므로, 실제로는 7바이트와 3바이트의 랜덤한 값

 - 무차별 대입으로 카나리 값을 알아내려면 각각 최대 256^7번, 256^3번의 연산 필요

 - 실제 서버를 대상으로 많은 무차별 대입을 시도하는 것은 불가능

2. TLS 접근

 - 카나리는 TLS에 전역변수로 저장되며, 매 함수마다 이를 참조해서 사용

 - TLS의 주소는 매 실행마다 바뀌지만 만약 실행 중에 TLS의 주소를 알 수 있고, 임의 주소에 대한 읽고 또는 쓰기가 가능하다면 TLS에 설정된 카나리 값을 읽거나, 이를 임의의 값으로 조작할 수 있음

 - 그 뒤, 스택 버퍼 오버플로우를 수행할 때 알아낸 카나리 값 또는 조작한 카나리 값으로 스택 카나리를 덮으면 함수의 에필로그에 있는 검사 우회 가능

3. 스택 카나리 릭

 - 함수의 프롤로그에서 스택에 카나리 값을 저장하므로, 이를 읽어낼 수 있으면 카나리를 우회할 수 있음

 - 가장 현실적인 카나리 우회 기법

'Pwnable > 개념' 카테고리의 다른 글

[Pwnable] Dreamhack STAGE 8  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 8  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 7  (0) 2022.08.26
[Pwnable] Dreamhack STAGE 5  (0) 2022.08.25
[Pwnable] Dreamhack STAGE 1 ~ 3  (0) 2022.08.15