스택 버퍼 오버플로우로부터 반환 주소를 보호하는 스택 카나리(Stack Canary)는 함수의 프롤로그에서 스택 버퍼와 Return Address 사이에 랜덤 값을 삽입하고 에필로그에서 변조 유무를 판단하여 버퍼 오버플로우를 탐지하는 역할을 합니다. 만약 변조가 탐지되면 Segmentation fault가 발생합니다.
무차별 대입 (Brute Force)
x64 아키텍처에서는 8바이트의 카나리가 생성되며, x86 아키텍처에서는 4바이트의 카나리가 생성됩니다. 카니리에는 NULL byte가 포함되어 있으므로, 아키텍처에 따라 7byte, 3byte의 랜덤한 값을 삽입합니다. 따라서 x64 아키텍처에서 최대 256^7번, x86 아키텍처에서는 최대 256^3번의 연산을 해야 합니다. 하지만, 연산량이 많으므로 현실적으로 카나리를 알아내기 어렵습니다.
TLS(Thread Local Storage) 접근
카나리 값은 프로세스가 시작될 때, TLS에 전역 변수로 저장되고, 각 함수마다 프롤로그, 에필로그에서 이 값을 참조합니다. TLS 주소는 매 실행마다 바뀌지만 실행중에 TLS의 주소를 알 수 있다면 TLS에 설정된 카나리 값을 읽거나 조작할 수도 있습니다.
TLS 주소를 파악하는 방법으로는 fs의 값을 알면 가능해집니다. fs는 TLS를 가리키므로 TLS의 주소를 알 수 있습니다. 하지만 리눅스에서 fs의 값은 특정 시스템 콜을 사용해야만 조회하거나 설정할 수 있습니다. 이 때문에 자주 사용되는 우회법은 아닙니다.
Canary Leak
프로그램에 스택 카나리를 확인할 수 있는 취약점이 존재한다면 이를 통해 카나리를 릭하여 검사를 우회할 수 있습니다.
Master Canary ( if. 스레드를 사용하는 환경 )
TLS 영역은 스레드의 전역 변수를 저장하기 위한 공간으로 로더( Loader )에 의해서 할당되는데 모든 스레드가 하나의 카나리 값을 사용하기 위해서 TLS 영역에 저장됩니다. TLS 주소에 0x28 바이트 만큼 떨어진 주소(== fs+0x28)에 위치한 랜덤한 값을 마스터 카나리 ( Master Canary )라고 합니다.
핵심은 스레드를 사용하는 환경에서만 마스터 카나리를 덮어 카나리를 우회할 수 있습니다. 일반적으로 스택에 마스터 카나리가 존재하지 않기 때문에 BOF가 발생해도 익스플로잇이 불가능하지만, 스레드 스택은 TCB보다 낮은 주소에 있기에 BOF를 통해 Master Canary를 덮을 수 있게 됩니다. 함수의 에필로그에서 Master Canary를 참조하는데 이 값을 덮을 수 있으므로 카나리 값과 마스터 카나리 값이 일치하도록 조작할 수 있습니다.
NX( No-eXecute )는 실행에 사용되는 메모리 영역과 쓰기에 사용되는 메모리 영역을 분리하는 보호 기법입니다. stack, heap과 같이 buffer overflow 공격에 이용되는 메모리 공간에 있는 코드를 실행하지 못하게 만듭니다. DEP( Data Execution Prevention )는 NX와 같은 개념으로 윈도우에서 사용하는 보호 기법입니다. 코드 영역에 쓰기 권한이 있으면 공격자는 코드를 수정하여 원하는 코드가 실행되게 할 수 있고, 반대로 스택이나 데이터 영역에 실행 권한이 있으면 Return to Shellcode와 같은 공격을 시도할 수 있습니다.
ASLR( Address Space Layout Randomization )은 바이너리가 실행될 때마다 Stack, Heap, Library 등의 주소를 랜덤한 영역에 배치하여, 공격에 필요한 Target address를 예측하기 어렵게 만듭니다.
/proc/sys/kernel/randomize_va_space
0 : ASLR 해제
1 : 랜덤 스택 & 랜덤 라이브러리 설정
2 : 랜덤 스택 & 랜덤 라이브러리 설정 & 랜덤 힙 설정
무차별 대입( Brute Force )
가능은 하지만 확률이 현저히 낮은 편이고,
시도 횟수 제한이 있는 프로그램이라면 사용이 불가능합니다.
Libc Leak
출력함수 사용, FSB, stdout, 함수의 got의 1byte를 syscall 하위 1byte로 덮어 syscall로 만든 뒤 return to csu, SROP등을 활용하여 Libc leak하거나 exploit할 수 있습니다.
💡 Libc가 주어지지 않았을 경우
libc의 버전을 직접 찾아줘야 합니다.
리눅스에서 ASLR이 적용됐을 때, 파일을 페이지( page ) 단위로 임의 주소에 매핑하게 됩니다.
페이지의 크기인 3byte( 12bit ) 이하로는 주소가 변경되지 않습니다.
Libc database search : https://libc.blukat.me/
RELRO( RELocation Read-Only )는 ELF 바이너리 / 프로세스의 데이터 섹션의 보안을 강화하는 보호 기법 입니다. RELRO는 쓰기 권한이 불필요한 데이터 세그먼트에 쓰기 권한을 제거합니다.
RELRO는 적용하는 범위에 따라 두 가지로 구분됩니다. 부분적으로 적용되는 Partial RELRO와 넓은 영역에 RELRO를 적용하는 Full RELRO입니다. gdb에서 vmmap등을 활용하여 메모리 맵을 확인하고 objdump를 활용하여 바이너리의 섹션 헤더를 참조해보며 확인해봅시다.
💡 바이너리 섹션 해더 확인
$ objdump -h (binary)
1. No RELRO
No RELRO 는 ELF 기본헤더, 코드영역을 제외한 거의 모든 부분에 Read, Write 권한을 주는 것입니다.
2. Partial RELRO
.ctors( .init_array ), .dtors( .fini_array ), .jcr, .dynamic영역이 Read-Only며,
.got.plt, .data, .bss 섹션들에는 쓰기가 가능합니다.
💡 .got와 .got.plt
Partial RELRO가 적용된 바이너리는 got와 관련된 섹션이 .got, .got.plt로 두 개가 존재한다.
전역 변수 중에서 실행되는 시점에 바인딩( now binding )되는 변수는 .got에 위치합니다.
실행 중에 바인딩( lazy binding )되는 변수는 .got.plt에 위치합니다.
실행 중에 값이 써져야 하므로 이 섹션에는 쓰기 권한이 부여됩니다.
Partial RELRO가 적용된 바이너리의 경우 대부분 함수의 GOT 엔트리는 .got.plt에 저장됩니다.
3. Full RELRO
got에 쓰기 권한이 제거되었으며, .data, .bss영역을 제외한 모든 영역이 쓰기 권한이 있습니다.
Full RELRO가 적용되면 라이브러리 함수들의 주소가 바이너리의 로딩 시점에 모두 바인딩되기 때문에 GOT에는 쓰기 권한이 부여되지 않습니다.
PIE( Position-Independent Executable )는 해석하면 "위치 독립 실행"으로 무작위 주소에 매핑돼도 실행 가능한 실행 파일을 뜻합니다. PIE는 재배치가 가능하므로, ASLR이 적용된 시스템에서는 실행 파일도 무작위 주소에 적재됩니다. 결론적으로 PIE 보호 기법이 적용되면 실행할 때마다 바이너리의 주소가 랜덤화 됩니다.
💡 symbol이 없을 때나 PIE가 걸려있을 때 디버깅
Entry point + offset으로 bp 걸면 됩니다.
offset은 IDA을 활용해 알아내고
Entry point는 0x00에 bp를 걸고 실행한 다음
첫 번째 call에서 리턴되는 값( $rax )이 Entry point입니다.Entry point == start 함수의 시작 주소
start는 첫 번째 arg로 main의 func ptr을 넘기면서 __libc_start_main을 호출하므로,
__libc_start_main에 bp를 걸고 확인하면 main의 주소를 얻을 수 있습니다.
참고
[Dreamhack] System Hacking Stage 6, 7, 8