process
익스플로잇을 로컬 바이너리를 대상으로 할 때
보통 테스트를 위해
remote
원격 서버를 대상으로 할 때
대상 서버를 실제로 공격하기 위해
from pwn import * p = process('./test') #로컬 바이너리 'test'를 대상으로 익스플로잇 수행 p = remote('example.com',31337) #'example.com'의 31337 포트에서 실행 중인 프로세스를 대상으로 익스플로잇 수행
데이터를 프로세스에 전송하기 위해
from pwn import * p = process('./test') p.send('A') # ./test에 'A'를 입력 p.sendline('A') # ./test에 'A'+'\n'을 입력 p.sendafter('hello','A') # ./test가 'hello'를 출력하면, 'A'를 입력 p.sendlineafter('hello','A') # ./test가 'hello'를 출력하면, 'A' + '\n'을 입력
프로세스에서 데이터를 받기 위해
from pwn import * p = process('./test') data = p.recv(1024) #p가 출력하는 데이터를 최대 1024바이트까지 받아서 data에 저장 data = p.recvline() #p가 출력하는 데이터를 개행문자를 만날 때까지 받아서 data에 저장 data = p.recvn(5) #p가 출력하는 데이터를 5바이트만 받아서 data에 저장 data = p.recvuntil('hello') #p가 출력하는 데이터를 'hello'가 출력될 때까지 받아서 data에 저장 data = p.recvall() #p가 출력하는 데이터를 프로세스가 종료될 받아서 data에 저장
-> 주의: r
ecv()와 recvn()의 차이
어떤 값을 리틀 엔디언의 바이트 배열로 변경하거나, 또는 역의 과정
from pwn import * s32 = 0x41424344 s64 = 0x4142434445464748 print(p32(s32)) print(p64(s64)) s32 = "ABCD" s64 = "ABCDEFGH" print(hex(u32(s32))) print(hex(u64(s64)))
$ python3 pup.py b'DCBA' b'HGFEDCBA' 0x44434241 0x4847464544434241
셸을 획득했거나, 익스플로잇의 특정 상황에 직접 입력을 주면서 출력을 확인하고 싶을 때 사용하는 함수
호출하고 나면 터미널로 프로세스에 데이터를 입력하고, 프로세스의 출력을 확인할 수 있다.
from pwn import * p = process('./test') p.interactive()
ELF 헤더에는 익스플로잇에 사용될 수 있는 각종 정보가 기록되어있다. pwntools를 사용하면 이 정보들을 쉽게 참조할 수 있다.
from pwn import * e= ELF('./test') puts_plt = e.plt['puts'] # ./test에서 puts()의 PLT주소를 찾아서 puts_plt에 저장 read_got = e.got['read'] # ./test에서 read()의 GOT주소를 찾아서 read_got에 저장
익스플로잇 디버깅 시
디버그의 편의를 돕는 로깅 기능이 있다.
로그 레벨은 context.log_level변수로 조절가능
from pwn import * context.log_level = 'error' # 에러만 출력 context.log_level = 'debug' # 대상 프로세스와 익스플로잇간에 오가는 모든 데이터를 화면에 출력 context.log_level = 'info' # 비교적 중요한 정보들만 출력
셸코드 생성, 어셈블, 디스어셈블 등등의 기능은 공격 대상의 아키텍처에 영향을 받기 때문에 아키텍쳐 정보를 프로그래머가 지정할 수 있게 한다.
from pwn import * context.arch = "amd64" # x86-64 아키텍처 context.arch = "i386" # x86 아키텍처 context.arch = "arm" # arm 아키텍처
자주 사용되는 셸 코드들이 저장되어 있음.
하지만 정적으로 생성된 셸 코드는 셸 코드가 실행될 대의 메모리 상태를 반영하지 못한다. 또한, 제약 조건을 반영하기 어렵기 때문에, 제약 조건이 존재하는 상황에서는 직접 셸 코드를 작성하는 것이 좋다.
#Name: shellcraft.py from pwn import * context.arch = 'amd64' # 대상 아키텍처 x86-64 code = shellcraft.sh() # 셸을 실행하는 셸 코드 print(code
$ python3 shellcraft.py /* execve(path='/bin///sh', argv=['sh'], envp=0) */ /* push b'/bin///sh\x00' */ push 0x68 mov rax, 0x732f2f2f6e69622f ... syscall
대상 아키텍처가 중요하므로, 아키텍처를 미리 지정해야 함.
#Name: asm.py from pwn import * context.arch = 'amd64' # 익스플로잇 대상 아키텍처 'x86-64' code = shellcraft.sh() # 셸을 실행하는 셸 코드 code = asm(code) # 셸 코드를 기계어로 어셈블 print(code)
$ python3 asm.py b'jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05'