취약점이 존재하는 예제 프로그램을 공격하고, 쉘을 획득해보는 실습
사용할 예제 코드 :
// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie
#include <stdio.h>
#include <unistd.h>
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
}
void get_shell() {
char *cmd = "/bin/sh";
char *args[] = {cmd, NULL};
execve(cmd, args, NULL);
}
int main() {
char buf[0x28];
init();
printf("Input: ");
scanf("%s", buf);
return 0;
}
취약점 분석
scanf("%s", buf);
scanf는 입력의 길이를 제한하지않고 공백 문자인 띄어쓰기, 탭, 개행 문자 등이 들어올 때까지 계속 입력을 받는다.
이런 특징으로 인하여 버퍼의 크기보다 큰 데이터를 입력할 경우 오버플로우가 발생함
= 정확히 n개의 문자만 입력받는 "%[n]s"의 형태로 사용해야 한다
scanf처럼 버퍼를 다루면서 길이를 입력하지 않는 함수들은 strcpy, strcat, sprintf 등이 있다.
코드를 작성할 때 버퍼의 크기를 같이 입력하는 strncpy, strncat, snprintf, fgets, memcpy등을 함께 사용하는 것이 바람직하다
위의 코드에서는 크기가 0x28인 버퍼에 scanf로 입력을 받으므로 버퍼 오버플로우를 일으켜 main함수의 반환 주소를 덮을 수 있을 것이다.
Segmentation fault라는 에러가 출력되었음에도 불구하고 코어 파일을 생성하지 않아서
ulimit -c unlimited 커맨드를 사용해주었다
(코어 파일은 실행 프로그램이 있는 위치에 자동으로 생성됨!)
gdb에는 코어 파일을 분석하는 기능이 있으므로, 이를 이용하여 입력이 스택에 어떻게 저장되었는지 살펴본다.
$ gdb -c core
해당 명령어로 코어 파일을 열면 프로그램이 종료된 원인과,
어떤 주소의 명령어를 실행하다가 문제가 발생했는지 보여준다.
프로그램이 main함수에서 반환하려고 하는데, 스택 최상단에 저장된 값이 입력값의 일부인 0x4141...(AA...)임을 알 수 있음
이는 실행 가능한 메모리의 주소가 아니므로 segmentation fault가 발생한 것이고,
이 값이 원하는 코드 주소가 되도록 적절한 입력을 주면 main함수에서 반환될 때 원하는 코드가 실행되도록 조작할 수 있을 것이다.
익스플로잇
스택 버퍼에 오버플로우를 발생시켜서 반환 주소를 덮으려면, 해당 버퍼가 스택 프레임의 어디에 위치하는지 알아야 함
main의 어셈블리 코드를 살펴봐야 하는데, scanf에 인자를 전달하는 부분을 주목해서 봐야함
이를 의사 코드로 표현하면 다음과 같다
scanf("%s", (rbp-0x30));
= 오버플로우를 발생시킬 버퍼는 rbp-0x30에 위치한다
rbp에는 스택 프레임 포인터(SFP)가 저장되고, rbp+0x8에는 반환 주소가 저장된다.
입력할 버퍼와 반환 주소 사이에 0x38만큼의 거리가 있으므로, 그만큼을 dummy data로 채우고 실행하고자 하는 코드의 주소를 입력하면 실행 흐름을 조작할 수 있을 것이다.
예제에서는 셸을 실행해주는 get_shell() 함수가 있으므로
이 함수의 주소로 main함수의 반환 주소를 덮어서 쉘을 획득할 수 있을 것이다
get_shell()의 주소를 찾기 위해 gdb를 사용함
get_shell()의 주소가 0x401199임을 확인했음
이제 익스플로잇에 사용할 페이로드를 구성해야 한다.
페이로드 : 공격을 위해 프로그램에 전달되는 데이터를 의미
앞서 파악한 정보를 바탕으로 다음과 같은 페이로드를 구성할 수 있다.
"A"를 0x30만큼의 공간에 채우고, "B"를 0x8공간만큼 채운 뒤, 나머지 공간에 get_shell()의 주소를 채워넣는다
구성한 페이로드는 적절한 Endian을 적용해서 프로그램에 전달해야 한다.
엔디언 : 메모리에서 데이터가 정렬되는 방식
리틀 엔디언(MSB가 가장 높은 주소에 저장) / 빅 엔디언(MSB가 가장 낮은 주소에 저장) 방식이 사용됨
이 커리큘럼에서는 리틀 엔디언을 사용하는 인텔x86-64 아키텍처를 대상으로 하므로
get_shell()의 주소인 0x401199는 \x99\x11\x40\x00\x00\x00\x00\x00으로 전달되어야 한다
엔디언을 적용하여 페이로드를 작성한 후에는 다음의 커맨드로 rao에 전달한다.
원래라면 id를 입력했을때 아래 화면처럼 쉘이 출력되어야 하는데 출력되지가 않는다.. 이 부분은 나중에 한번 다시 봐야겠음
'INTERLUDE ✦ > 2022 SYSTEM STUDY' 카테고리의 다른 글
[ DreamHack ] basic_exploitation_001 (0) | 2022.11.17 |
---|---|
[ DreamHack ] Return Address Overwrite-2 (0) | 2022.11.15 |
[ DreamHack ] Stack Buffer Overflow (0) | 2022.11.08 |
[ DreamHack ] ShellCode (0) | 2022.11.03 |
[ DreamHack ] Shell_basic 풀이 (0) | 2022.11.01 |