Writer - hoodbilen

pwntools란?


시스템해킹(pwnable)에 필요한 기능들을 최대한 집대성해 만든 파이썬 모듈이다. 실제로는 프레임워크라고 더 자주 표현한다.

How to install?


$ apt-get update
$ apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
$ pip install --upgrade pip
$ pip install --upgrade pwntools

추가 정보는 아래 확인.

https://github.com/Gallopsled/pwntools

USE


$ vi test.py
$ python test.py

이런 식으로 작성하고, 실행시킬 수 있다.

process & remote


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

from pwn import *
p = process('./test') # local 파일 test를 대상으로 exploit 실행함.
p = remote('warroom.kr', 123456) # warroom.kr의 123456번 포트에 있는 바이너리를 대상으로 exloit 실행함.

send


pwntools에는 send와 관련된 다양한 함수가 있다.

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

p.send('A') # ./test에 'A'를 입력한다.
p.sendline('A') # ./test에 'A'+'\n'을 입력한다.
p.sendafter('hello', 'A') # ./tets가 'hello'를 출력하면, 'A'를 입력한다.
p.sendlineafter('hello', 'A') # ./test가 'hello'를 출력하면 'A'+'\n'을 입력한다.

send, sendline, sendafter, sendlineafter 을 사용할 때는 process값을 받은 p.를 꼭 붙여주어야 한다.

recv


pwntools에는 recv와 관련된 다양한 함수가 있다.

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

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

주의해서 봐야할 것은 p.recvp.recvn 의 차이점인데, p.recv()는 최대 n 바이트를 받는 것 이기 때문에, 1024바이트를 다 채워 받지 못해도 에러를 발생시키지 않지만, p.recvn()의 경우 정확히 인자만큼의 데이터를 받지 못하면 계속 대기한다.

recv,recvline,recvn,recvuntil, recvall은 불필요한 문자까지 입력받은 후 send를 할때도 사용할 수 있다. 불필요한 문자를 입력받을 때는 data = 를 제외하고 p.recvline() 처럼 단독으로 사용하면 된다.

packing & unpacking


대부분의 CPU는 little endian을 이용한다. 익스플로잇을 작성하다 보면 어떤 값을 little endian스트링으로 변경하거나, 또는 역의 과정을 거쳐야 하는 경우가 자주 있는데, 이를 쉽게 할 수 있도록 pwntools에서는 함수로 제공한다.

ex)

#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))
$python test.py
DCBA
HGFEDCBA
0x44434241
0x4847464544434241

log


from pwn import *
context.log_level = 'debug' # 서버 혹은 바이너리와 이 익스플로잇간에 오고가는 모든 데이터를 화면에 hexdump형식으로 출력해줌

debug


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

익스플로잇을 실행하는 중간에 debug를 하고 싶을때 원하는 위치에서 위 명령어를 사용하면 새 창에서 해당 프로그램이 gdb로 보여진다. 정확하게 원하는 값이 채워졌는지, 릭이 정확하게 되었는지, 스택주소, 데이터 등을 볼 때 유용하다.

interactive


쉘을 획득한 경우이거나, 익스플로잇의 특정 경우에 직접 입력을 주면서 디버깅을 해보고 싶다면 사용하게 되는 함수. 익스플로잇과 프로세스와의 연결을 stdin, stdout <=> process로 바꿔준다.

pwntools 익스플로잇이 끝났을 때 쉘을 따지 않아도 $ 가 나오는 이유가 이것 때문이다.

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

ELF


리눅스에서 작동하는 실행파일 ELF 에는 각종 정보가 기록되어 있다. 그 중 대표적인 것이 GOT, PLT 인데, pwntools를 통해 이런 값들을 쉽게 구해올 수 있다.

from pwn import *
e = ELF('./test')
puts_plt = e.plt['puts'] # ELF ./test에서 puts()의 PLT주소를 찾아서 puts_plt에 넣는다.
read_got = e.got['read'] # ELF ./test에서 read()의 GOT주소를 찾아서 read_got에 넣는다.

해당 문서는 pwntools를 사용하면서 자주 마주치는 함수들에 대해서만 간략히 소개한 문서이다.

자세한 사용법을 알고싶은 사람은 아래 공식 주소를 참고.

https://docs.pwntools.com/en/stable/index.html

'hacking > tool' 카테고리의 다른 글

mac IDA create structure  (0) 2019.08.08

아....64bit rop는 거의 처음이였는데 삽질만 100만번 한 것 같다....

우선 짱짱 아이다로 문제를 열어보면 사용할 수 있는 함수가 puts, read함수 두개이다.  (32bit rop에서 wirte함수로만 풀어봐서 puts는 처음이라 당황했다.)

 

조사해보니 64bit rop는 인자를 받는 피연산자가 굉장히 중요하다더라. 인자 받는 순서는 rdi, rsi, rdx순서이다.

그럼 우선 rop를 하기위해 gadget를 찾아보자.

이런식으로 ROPgadget 명령어를 입력하면 찾을 수 있다.

pead를 이용해서도 찾을 수 있다. gadget을 확인해보니 'pop rdx'가 없어서 libc에서 찾아보도록했다. 

rp를 이용해서 찾으면 저런식으로 libc에서 해당 gadget의 offset을 구할 수 있다. 나중에 libc base를 leak해서 더해주면 사용할 수 있다.(rp는 그냥 사이트에서 다운받으면 되지만, 난 mac이여서 chmod도 해줘야했다. 삽질..ㅎ)

 

이제 plt의 주소를 구해줘야 하는데 난 귀찮아서 ELF를 이용해 구했지만 직접 구할 수도 있다.

 

짱짱 아이다 functions window라던가

gdb-peda p 명령어 라던가.

 

이제 plt도 구했으니 offset을 구해보자.

우선 gdb peda에서 info proc map으로 libc base주소를 찾아주고,(b*main 같은거 하고 r 해주고 해야한다.)

이런 식으로 함수의 offset을 구해주면 기본적인 준비는 다 끝났다.

 

이제 pwntools이용해서 페이로드만 잘 써주면 되는데 여기서도 엄청나게 삽질을 했다.

우선 gadget rdx를 쓰려면 offset만 구한 것 임으로 libc base leak를 해줘야 한다. 그런데 입력은 read로 한번만 받으니 답이 안나왔다.

페이로드를 두번 보낼 수도 없고...........(?) 라고생각하면서 생각해보니 처음 페이로드로 leak을 하고, 그 뒤에 ret에 main주소를 넣어서 다시 돌아오면 두번 쓸 수 있을 것 같았다.

main주소 구해주고,

 

이런식으로 leak해줬다. 여기서도 보면 raw_input('AA')는 페이로드 중간에 멈추게 해서 다른 창으로 pid를 입력하면 디버거 할 수 있게 해줘 주소가 잘 들어갔는지 확인 할 수 있고, recv(1024)는 계속 인자를 받아봤는데 쓰레기값이 들어가는 것 같아 덮인게 잘못 들어갔나 싶어 넣어줬더니 되었다.(아직 잘 모른단 소리)

저런 식으로 libc 주소를 leak하고, read_addr에도 실 주소를 저장해서 다음 페이로드 때 system_offset에서만 더해서 계산하면 system주소를 read_got에 덮을 수 있다.

마지막으로 다시한번 페이로드를 보내면 해결 되었다.

아 추가로 삽질 한게 있는데 rdx gadget을 보면 pop rdx; pop rsi; ret이다. 따라서 인자 순서를 반대로 넣어줘야한다,...(이거로도 몇시간,,,)

 

최종 페이로드...ㅎ

'war game > game' 카테고리의 다른 글

[practice]-19950320  (0) 2019.07.28
[practice]-daRk_TempLer  (0) 2019.07.23
[practice]-rain_dROP  (0) 2019.07.23
[practice]-derby  (0) 2019.07.17
[practice]-catchme  (0) 2019.07.16

+ Recent posts