앞에서는 프로그램의 취약점을 이용해 실행 흐름을 조작한 뒤 스택 영역에 저장된 셸코드를 실행하였다.
하지만 일반적인 프로그램에서의 스택 메모리는 코드를 실행하는 용도로 사용되는 것이 아니라, 일시적으로 데이터를 읽고 쓰는 데 사용되기 때문에 실행 권한이 있을 필요가 없다.
이러한 이유로 프로그램의 공격을 어렵게 하기 위해, 메모리에 쓰기 권한과 실행 권한을 동시에 부여하지 않는 No-eXecute bit(NX bit)가 등장하였다.
//gcc -o example2_x example2.c -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -m32
//gcc -o example2_nx example2.c -fno-stack-protector -mpreferred-stack-boundary=2 -m32
#include <stdio.h>
unsigned char code[] = \
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80";
int main(void){
void (*shellcode)() = (void(*)())code;
printf("Executing shellcode\n");
shellcode();
}
example2.c는 셸코드를 데이터 영역에 저장한 후 main 함수에서 이를 실행하는 간단한 예제이다. 서로 다른 컴파일 옵션을 통해 NX bit 보호 기법을 적용한 example2_nx 바이너리와 적용하지 않은 example_x 바이너리를 생성하였다.
example2_nx 바이너리와 example2_x 바이너리를 gdb로 디버깅하여 메모리 권한을 확인해 보도록 하겠다.
example2_x의 경우 스택과 데이터 영역 모두 rwx, 즉 읽기, 쓰기, 실행 권한을 갖고 있다.
-> example2_x 바이너리를 실행했을 때는 정상적으로 셸코드가 실행되어 셸이 실행된다.
그러나 example2_nx의 경우, 스택과 데이터 영역 영역 메모리 모두 rw, 즉 읽기, 쓰기 권한만을 갖고 있다.
example2_nx 바이너리를 실행했을 때는 데이터 영역에 실행 권한이 없기 때문에 Sementation Fault가 발생한다.
NX Bit를 우회하는 법
NX bit가 설정되어 있을 경우에는 쓰기 권한과 실행 권한이 동시에 있는 메모리 영역이 존재하지 않는다. 공격자의 코드를 메모리에 저장할 수 없기 때문에 실행 권한이 있는 영역에 존재하는 코드만을 사용하여야 한다.
프로그램에 스택 버퍼 오버플로우가 존재한다면, 실행 흐름을 임의의 주소로 바꾸는 것은 여전히 가능하다. NX bit가 적용되어 있는 상황에서 스택 메모리 등으로 실행 흐름을 직접 바꾸어 공격하는 것은 불가능하기 때문에, 앞서 설명한 것처럼 메모리의 실행 가능한 영역에 있는 코드들을 활용해서 익스플로잇해야 한다.
C언어에서 printf와 같은 라이브러리 함수가 사용될 때, 프로그램은 메모리에 로딩된 라이브러리 파일에서 호출된 함수의 주소를 찾아 실행한다. 그러므로 프로그램에서 호출된 함수 이외에 system과 같이 익스플로잇에 유용한 함수 코드들도 함께 로딩된다.
-> 프로그램에 스택 버퍼 오버플로우 취약점이 존재할 때, 리턴 주소를 라이브러리 함수의 주소로 바꾸면 해당하는 함수를 호출할 수 있을 것이다.
참고
dreamhack - Linux Exploitation & Mitigation Part 1
'Hacking > Pwnable' 카테고리의 다른 글
[Pwnable ] NOP Sled 실습 (0) | 2021.03.11 |
---|---|
[Pwnable] RTL 실습 (0) | 2021.03.07 |
[Pwnable ] Return Address Overwrite 실습 (0) | 2021.03.01 |
[Pwnable] ELF 동적 분석 (0) | 2021.03.01 |
[Pwnable] Dreamhack - Memory Corruption - C (I) (0) | 2021.02.28 |