[Pwnable] Mitigation 정리 및 우회법

Kioreo·2023년 2월 24일
0

Pwnable

목록 보기
1/1

Mitigation


Canary

스택 버퍼 오버플로우로부터 반환 주소를 보호하는 스택 카나리(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 / DEP

NX( No-eXecute )는 실행에 사용되는 메모리 영역과 쓰기에 사용되는 메모리 영역을 분리하는 보호 기법입니다. stack, heap과 같이 buffer overflow 공격에 이용되는 메모리 공간에 있는 코드를 실행하지 못하게 만듭니다. DEP( Data Execution Prevention )는 NX와 같은 개념으로 윈도우에서 사용하는 보호 기법입니다. 코드 영역에 쓰기 권한이 있으면 공격자는 코드를 수정하여 원하는 코드가 실행되게 할 수 있고, 반대로 스택이나 데이터 영역에 실행 권한이 있으면 Return to Shellcode와 같은 공격을 시도할 수 있습니다.

우회법

  • RTL( Return to Libc )
    공유 라이브러리 함수의 주소를 가져와 ret overwrite하여 이를 호출하는 공격 기법입니다.
    바이너리에 호출하려는 함수가 없더라도 메모리에 적재된 공유 라이브러리 함수를 가져와 사용할 수 있습니다.

ASLR

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/

  • NOP Sled
    셸 코드를 사용할 때 많이 사용하는 기법으로 아무 명령도 실행하지 않는 명령어인 NOP(0x90)을 활용하여 랜덤한 주소로 점프하였을 때 NOP를 만난다면, 셸 코드를 실행할 수 있습니다.

RELRO

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에는 쓰기 권한이 부여되지 않습니다.

우회법

  • Partial RELRO
    - GOT ovewrite
    .got.plt영역에 대한 쓰기 권한이 존재하므로 GOT overwrite이 가능하다.

  • Full RELRO
    - Hook ovewrite
    .got 영역에도 쓰기 권한이 제거되어 덮어쓸 수 있는 다른 포인터를 찾다 라이브러리에 위치한 hook을 찾아냈습니다. 라이브러리 함수의 대표적인 hook이 malloc hook, free hook입니다. 이 포인터는 동적 메모리 할당과 해제 과정에서 발생하는 버그를 디버깅하기 쉽게 하려고 만들어졌지만 악용되어 Full RELRO를 우회할 수 있습니다.

PIE

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는 첫 번째 argmain의 func ptr을 넘기면서 __libc_start_main을 호출하므로,
__libc_start_main에 bp를 걸고 확인하면 main의 주소를 얻을 수 있습니다.

우회법

  • 코드 베이스 구하기
    ASLR환경에서 PIE가 적용된 바이너리는 실행될 때마다 다른 주소에 적재됩니다.
    코드 영역의 가젯을 사용하거나, 데이터 영역에 접근하려면 바이너리가 적재된 주소를 알아야 합니다. 이 주소를 PIE 베이스, 또는 코드 베이스라고 부릅니다. 코드 베이스를 구하려면 라이브러리의 베이스 주소를 구할 때처럼 코드 영역의 임의 주소를 읽고, 그 주소에서 오프셋을 빼야 합니다.
  • Partial Overwrite
    코드 베이스를 구하기 어렵다면 반환 주소의 일부 바이트만 덮는 공격을 고려해볼 수도 있습니다. 이러한 공격 기법을 Partial Overwrite라고 부릅니다. 일반적으로 함수의 반환 주소는 호출 함수(Caller)의 내부를 가리킵니다. 특정 함수의 호출 관계는 정적 분석 또는 동적 분석으로 쉽게 확인할 수 있으므로, 공격자는 반환 주소를 예측할 수 있습니다.
    ASLR의 특성상, 코드 영역의 주소도 하위 12비트 값은 항상 같습니다. 따라서 사용하려는 코드 가젯의 주소가 반환 주소와 하위 한 바이트만 다르다면, 이 값만 덮어서 원하는 코드를 실행시킬 수 있습니다. 그러나 만약 두 바이트 이상이 다른 주소로 실행 흐름을 옮기고자 한다면, ASLR로 뒤섞이는 주소를 맞춰야 하므로 무차별 대입( Brute Force )이 필요하며, 공격이 확률에 따라 성공하게 됩니다.

참고
[Dreamhack] System Hacking Stage 6, 7, 8

profile
Layer7 23rd

0개의 댓글