Pwntools 처음 쓰는 사람들에게

두비니

·

2020. 8. 9. 01:16

후배들 주려고 작성한 문서인데 다른분들에게도 될 수 있다면 좋겠습니당
+) 2022.07.05 내용 수정 / 업데이트 하였습니다. (pause관련 추가 필요) 혹시나 잘못된 내용이 있다면 댓글 부탁드리겠습니다.

 

Introduction to pwntools

작성자. dubini0

이 문서는 pwntools 공식문서를 참고하였습니다: https://docs.pwntools.com/en/stable/index.html

 

 

pwntools란

pwntools를 처음 써본다면 여태껏 한줄 페이로드로 pwnable문제를 풀거나 소켓프로그래밍을 이용했을 것입니다. 만약 앞으로도 한줄 페이로드를 작성해서 문제들을 푼다면, 문제풀이에 분명한 한계가 발생합니다. 불편하다는 단점 또한 있구요. 물론 이를 소켓프로그래밍을 통해서 풀어도 상관은 없지만, 시스템 해킹(pwnable)에 필요한 기능들로 커스터마이징이 된 파이썬 모듈이 pwntools입니다.
가볍게 pwntools를 쓰는 이유는 소켓 함수를 쓰는 경우랑 pwntools를 쓰는 경우를 비교해서 보여드리겠습니다. 다음은 소켓 함수를 이용하는 경우입니다.

from socket import *

s = socket(AF_INET,SOCK_STREA)
s.connect('localhost',4444)

원래 파이썬에서 소켓을 사용할 때 사용하는 코드


다음은 pwntools를 이용하는 경우입니다.

from pwn import *

conn=remote('localhost',4444)

pwntools를 이용한 코드



한 줄 차이 아니냐 싶을 수도 있지만, 예시로 하나 들어놓은 것이고 이거 외에도 다른 편리한 함수들을 많이 지원하기 때문에 pwntools를 사용합니다.


설치 방법

ubuntu 터미널에서 설치를 진행하면 됩니다. 공식 Github link는 다음과 같습니다: https://github.com/Gallopsled/pwntools

 

GitHub - Gallopsled/pwntools: CTF framework and exploit development library

CTF framework and exploit development library. Contribute to Gallopsled/pwntools development by creating an account on GitHub.

github.com


Python3가 권장사항이며, Python2에서도 작동은 합니다.

sudo apt-get update
sudo 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



+) 혹시 꼭 python2로 해야겠고 python-pip이 정상적으로 설치되지 않는 경우, 다음과 같이 pip을 깔아주세요

# pip2.7 다운로드
wget https://bootstrap.pypa.io/pip/2.7/get-pip.py
# pip2.7 설치
sudo python2.7 get-pip.py
# 아래 명령을 통해서 어디에 설치되었는지 확인 할 수 있음
which pip2.7

 

++) 다음과 같은 에러가 뜬다면?

Traceback (most recent call last):
  File "/usr/lib/python3.5/runpy.py", line 184, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.5/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/home/dubini0/.local/lib/python3.5/site-packages/pip/__main__.py", line 29, in <module>
    from pip._internal.cli.main import main as _main
  File "/home/dubini0/.local/lib/python3.5/site-packages/pip/_internal/cli/main.py", line 57
    sys.stderr.write(f"ERROR: {exc}")
                                   ^
SyntaxError: invalid syntax


다음 글을 확인하자: https://devpouch.tistory.com/145

 

[python3] sys.stderr.write(f"ERROR: {exc}") 에러 해결법

pip install --upgrade pip 명령어를 쳤을때 아래와 같은 에러가 뜰 때가 있다. Collecting pip Downloading https://files.pythonhosted.org/packages/ca/31/b88ef447d595963c01060998cb329251648acf4a067721b04..

devpouch.tistory.com


 

pwntools 사용법 / 자주 쓰는 명령어

pwntools를 사용하기 위해서는 간단히 from pwn import *을 통해서 import를 하면 됩니다.

다음은 주관적으로 정리한 pwntools에서 자주 사용하는 명령어들입니다.

접속 - process / remote / ssh

process함수는 로컬 바이너리에 대해서 익스플로잇을 실험해볼 때 사용하는 함수이고, remote함수는 원격 서버를 대상으로 실제 익스플로잇을 작동시킬때 사용하는 함수, ssh는 ssh를 통해서 접속하는 함수입니다.

from pwn import *

p = process('./test')                                # process(filename)
p = remote('dokhakdubini.tistory.com', 123456)        # remote(host, port)
p = ssh("fd", "pwnable.kr", 2222)                    # ssh(user, host, port, password) 순서

p.close()            # 접속 종료

참고로 당연히 p대신에 다른 변수로 받아와도 전혀 상관이 없습니다.

페이로드 보내기 - send

send 관련 함수들입니다.

from pwn import *
p = process('./test')

p.send('A')                        # ./test에 'A'입력
p.sendline('A')                    # ./test에 'A'입력 뒤에 newline character(\n)까지 입력
p.sendafter('asdf', 'A')        # ./test가 'asdf'를 출력할 시, 'A'를 입력한다
p.sendlineafter('asdf', 'A')    # ./test가 'asdf'를 출력할 시, 'A'+'\n'를 입력한다

다만 send함수를 이용할 때는 꼭 앞에 어떤걸 processremote했는지 붙여야 하기 때문에 이 경우에는 p.를 붙여주어야 합니다.

 

데이터 받기 - recv

프로그램이 출력하는 값을 봐야할 때 recv함수를 사용합니다.

from pwn import *
p = process('./test')

data = p.recv(1024)            # p가 출력하는 데이터 중 최대 1024바이트의 데이터를 받아서 data에 저장
data = p.recvline()            # p가 출력하는 데이터 중 개행문자를 만날 때까지를 data에 저장
p.recvn(5)                    # p가 출력하는 데이터 중 정확히 5바이트 받아서 data에 저장
print p.recvuntil('asdf')    # 'asdf'라는 문자열을 p가 출력할 때까지 받아서 출력
print p.recvall()            # 연결이 끊어지거나 프로세스가 종료될 때까지 받아서 data에 저장

recv()함수는 프로그램과의 interaction을 보기 위해 사용합니다. 코드를 보면 쉽게 이해할 수 있어서 크게 설명은 적지 않겠습니다.

 

한 가지 짚고 넘어갈게 있다면 p.recv()p.recvn()의 차이점인 것 같습니다. p.recv()p.recvn()의 차이점은 여타 다른 언어들처럼 `그냥 무조건 받기` / `특정 바이트 만큼 받기`의 차이점이 있습니다. 다만 주의해야 할 점은 p.recv()는 최대 n바이트를 받는 것이기 때문에, 예시의 경우 1024바이트를 모두 다 채워 받지 못해도 에러를 발생시키지 않지만, p.recvn()의 경우 정확히 인자만큼의 데이터를 받지 못하면 계속 대기합니다.

개인적으로는 print(p.recv(1024))의 방식으로 가장 많이 쓰고있습니다.

 

라이브러리/바이너리 - ELF / run

Dynamically-Linked된 바이너리를 대상으로 문제를 풀 때 ELF를 통해 필요한 정보를 가져올 수 있습니다. 가장 대표적으로 GOT, PLT등이 있는데, pwntools의 API를 통해 이런 값들을 쉽게 구할 수 있습니다. 또한 run은 바이너리를 실행시킬 수 있습니다. 개인적으로 run은 거의 사용하지 않습니다.

 

from pwn import *

p = process('./test')             
e = ELF('./test')                        # e에 test의 ELF를 불러옴, PLT
libc = ELF('./libc.so.6')                # libc에 libc.so.6 라이브러리를 불러옴, 보통 바이너리와 같이 제공

sh = p.run('/bin/sh')            # test로 /bin/sh를 바이너리에 실행시킴
sh.sendline("whoami")            # "whoami"를 전송(권한이 있다는 전제 하에)
puts_plt = e.plt['puts']        # ELF ./test에서 puts()의 PLT주소를 찾아서 puts_plt에 넣는다.
read_got = libc.got['read']        # libc base에서 read()의 GOT주소를 찾아서 read_got에 넣는다.

 

패킹 - p32, p64 / u32, u64

대부분의 CPU는 little endian방식을 이용합니다. 익스플로잇 값을 작성하다 보면 어떤 값을 little endian방식으로 packing하거나(p32, p64) unpacking해야할 때가 있는데(u32, u64), 이를 쉽게 할 수 있도록 pwntools에서는 함수로 제공합니다.

# python3 test.py

from pwn import *

data32 = 0x41424344
data64 = 0x4142434445464748

print(p32(data32))
print(p64(data64))

data32 = "ABCD"
data64 = "ABCDEFGH"

print(hex(u32(data32)))
print(hex(u64(data64)))

위 코드를 실행시키면 결과는 다음과 같습니다.

dubini0@ubuntu:~$ vi test.py
dubini0@ubuntu:~$ python3 test.py
DCBA
HGFEDCBA
0x44434241
0x4847464544434241

 

로그 출력 - context.log_level

상황에 따라 내 공격이 어디까지 실행됐는지 확인할 필요가 있습니다. 이는 context.log_level='debug'을 통해 설정할 수 있습니다.

from pwn import *

context.log_level='debug'

...

 

 

debug - gdb.attach / pause

이건 자신이 예상한 페이로드가 터질 때 유용하게 쓰입니다. 이건 페이로드를 진행하는 도중 gdb를 실행시킬 수 있는 함수입니다. 다음과 같이 사용합니다.

import pwn from *

p = process('./test')
gdb.attach(p)				# gdb에 attach시킴

실행하면 새 창에서 해당 프로그램이 gdb로 보여집니다. 다만 이건 서버 접속으로는 불가능하고, process, 즉 파일 실행으로만 가능합니다.

 

쉘 접속 - interactive

쉘을 획득한 경우이거나, 익스플로잇 특정 경우에는 직접 입력을 주면서 디버깅이 필요한 경우가 있습니다. 이 경우 이용하는 함수가 interactive입니다. 익스플로잇 파일과 프로세스와의 연결을 stdin/stdout에서 process로 바꿔줍니다. pwntools동장기 끝난 뒤에 $가 나오는 이유도 이 함수 때문입니다.

from pwn import *

p = process('./test')
p.interactive()

 


 

추가로 다운받으면 좋은 것들


vim : https://byd0105.tistory.com/7

 

- Ubuntu에 vi(vim)에디터 설치 -

Vi 에디터를 설치하기 위해서 먼저 Ubuntu가 필요하다. http://byd0105.tistory.com/6 Ubuntu가 설치 완료되었으면 Ubuntu를 실행한다. 우분투를 설치하면 기본으로 vi 에디터가 설치가 되어있는데 기존의 vi 에

byd0105.tistory.com