이전 글에서는 스택 메모리에 셀코드를 삽입한 후 실행 흐름을 조작하여 쉘을 획득했다. 하지만 일반적으로 스택 메모리는 코드를 실행시키는 용도로 쓰이지 않고 데이터를 읽고 쓰는데 사용되기 때문에 실행 권한이 있을 필요가 없다. 때문에 메모리를 이용한 공격을 어렵게 하기 위해 NX bit(No-eXecute bit)가 등장했다.
NX bit가 설정되었을 경우 쓰기 권한과 실행 권한이 동시에 있는 메모리 영역이 존재하지 않는다. 따라서 전처럼 쉘코드를 스택 영역에 저장해 실행 흐름을 바꾸는 공격방법은 통하지 않는다. 다만 프로그램에 스택 오버플로우 취약점이 있다면 실행권한이 있는 메모리 영역의 코드를 활용해서 익스플로잇 할 수 있다.
C언어에서 printf와 같은 라이브러리 함수가 사용될 때 프로그램은 메모리에 로딩된 라이브러리 파일에서 함수의 주소를 찾아 실행한다. 호출된 함수 이외에 같은 라이브러리의 다른 함수들도 함께 로딩되기 때문에 익스프로잇에 유용한 system함수를 사용할 수 있게 된다.
HelloWorld.c 예제를 gdb를 사용해서 자세히 보면 libc.so.6 라이브러리의 코드영역 주소가 0xf7e03000 ~ 0xf7fb3000이고, printf 함수 이외의 다른 함수들이 메모리에 존재한다는 것을 확인할 수 있다.
- RTL(Return To Libc)
RTL은 리턴 주소를 라이브러리 내에 존재하는 함수의 주소로 바꿔 NX bit를 우외하는 공격 기법이다. libc.so.6 라이브러리에는 execve, execlp, exel, execvp, system,popen 등 프로그램을 실행할 수 있는 다양한 함수들이 존재한다. 이 중 system 함수는 인자를 하나만 받기 때문에 익스플로잇 할 때 많이 사용된다. system 함수의 인자는 쉘 명령어 문자열의 주소이기 때문에 "/bin/sh" 문자열의 주소를 system 함수의 인자로 넘겨주고 호출하면 /bin/sh 바이너리가 실행된다. exemple1_nx를 이용해서 RTL을 실습해봤다.
//gcc -o example1_nx example1.c -fno-stack-protector -mpreferred-stack-boundary=2 -m32
#include <stdio.h>
int vuln(char * src){
char buf[32] = {};
strcpy(buf, src);
return 0;
}
int main(int argc, char * argv[], char * environ[]){
if (argc < 2){
exit(-1);
}
vuln(argv[1]);
return 0;
}
vuln 함수는 strcyp를 사용중이므로 스택 버퍼오버플로우를 이용할 수 있다. buf 배열부터 SFP까지는 36바이트이고 그 다음이 RET 영역이기 때문에 "A"*36+(system 함수 주소) 코드를 전달해주면 system 함수를 실행시킬 수 있다. 이번 실습의 최종목표는 system("/bin/sh")을 호출하는 것이기 때문에 system 함수를 불러오는 것뿐만 아니라 쉘을 실행시킬 수 있는 인자를 전달해야 한다. 함수 호출 규약을 참고하면 system("/bin/sh")을 호출하는 익스플로잇 코드는 다음과 같다. "A" * 36 + (system 함수 주소) + "BBBB" + ("/bin/sh" 주소) 여기서 "BBBB"는 system 함수가 종료된 후 리턴할 주소인데 나는 system("/bin/sh")을 실행하는 것이 목표이므로 아무 값이나 넣어줬다.
이제 system 함수의 주소와 /bin/sh 문자열의 주소를 알아내야 한다. gdb로 프로그램을 실행한 후 print 명령어를 사용하면 system 함수의 주소를 찾을 수 있다. 그리고 find 명령어로 libc.so.6의 라이브러리 주소와 찾고 싶은 메모리의 값을 입력으로 주면 해당 라이브러리에 존재하는 /bin/sh 문자열의 주소를 찾을 수 있다.
이렇게 알아낸 system 함수와 "/bin/sh" 문자열의 주소로 익스플로잇 코드를 완성할 수 있다. "A"*36 + "\xa0\xdd\xe3\xf7" + "BBBB" + "\x0b\xea\xf5\xf7" 이 익스플로잇 코드를 실제 바이너리의 argv[1]에 넣어 실행하면 정상적으로 셀이 실행된다.
- RTL test
system("id")를 실행해라.
// system function address : 0xf7e3dda0
// "id" string address : 0xf7f5e988
#include <stdio.h>
int main(void){
char buf[32];
gets(buf);
return 0;
}
buf 배열과 SFP를 채우기 위한 "A"*36, system 함수를 호출하기 위한 "\xa0\xdd\xe3\xf7", system 함수의 리턴 주소 임의의 값 "XXXX", "id" 문자열의 주소 "\x88\xe9\xf5\xf7"를 합치면 system("id")을 실행할 수 있다.
- 바이너리의 NX bit 적용 여부 알아내는 법
ELF 바이너리 분석 도구인 readelf를 사용하면 NX bit 적용 여부를 쉽게 확인할 수 있다. readelf -a 'path'의 출력 결과에 대해 grep을 통해 "STACK" 문자열을 검색하면 스택 메모리의 권한을 볼 수 있다. NX bit가 적용되어 있지 않은 바이너리는 RWE(읽기, 쓰기, 실행) 권한으로, NX bit가 설정되어 있는 바이너리는 RW(읽기, 쓰기) 권한으로 설정되어 있다.
readelf를 사용하지 않고 확인하는 방법도 있다. 실행되고 있는 바이너리의 메모리 맵에 있는 권한을 확인하면 된다. NX bit가 적용되어 있지 않은 바이너리의 스택 영역 권한을 보면 rwx인 것을 볼 수 있다.
'Study > System Hacking' 카테고리의 다른 글
[LOB] goblin -> orc (0) | 2021.02.02 |
---|---|
[lazenca] Protection Tech / NX bit(MS: DEP) (0) | 2021.02.02 |
[LOB] cobolt -> goblin (0) | 2021.01.28 |
[LOB] gremlin -> cobolt (0) | 2021.01.27 |
[dreamhack] Linux Exploitation1 & Minigation Part 1 / Return Address Overwrite & NOP Sled (0) | 2021.01.27 |