Pwntools 소개
Pwntools 는 리눅스 환경에서 익스플로잇을 짜는 것을 쉽게 할 수 있게 해주는 파이썬 라이브러리다.
공식 Github 주소 : https://github.com/Gallopsled/pwntools
공식 문서 주소 : http://docs.pwntools.com/en/stable/
Pwntools 설치
우분투 환경에서 다음 명령어를 입력하면 Pwntools를 쉽게 설치할 수 있다.
sudo apt update
sudo apt install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
sudo python3 -m pip install --upgrade pip
sudo python3 -m pip install --upgrade pwntools
Pwntools 는 파이썬 2.7 환경만 지원하므로 주의하자.
2020년부로 파이선 2.7 지원이 종료됨에 따라 이제 파이썬 3 를 권장한다.
기본중의 기본
Pwntools를 사용하려면 파이썬 파일의 처음에 다음과 같이 pwn 모듈을 import 시켜야 한다.
from pwn import *
다음과 같이 process 클래스의 인스턴스를 만드는 것으로 elf 파일을 실행시킬 수 있다.
r = process("파일이름")
위의 줄을 실행하면 해당 프로그램을 실행시키고, 입력을 대기하게 된다.
이후 실행된 프로그램에는 r 변수를 통해 접근할 수 있다.
process() 로 실행시킨 프로그램은 입출력을 화면에 직접 할 수 없다. 대신에, r 변수를 통해 함수를 호출시키면서 파이썬 코드를 통해 프로그램의 stdin에 입력을 넣어주고, stdout에서 출력을 받아올 수 있다.
# stdin에 "Hello, World" 문자열을 넣음
r.send("Hello, World")
# stdin에 "Hello, pwntools\n" 문자열을 넣음
r.sendline("Hello, pwntools")
# stdout에서 4바이트의 문자열을 읽어와 반환
r.recv(4)
# stdout에서 한 줄의 문자열을 읽어와 반환
r.recvline()
# stdout에서 "ttt"라는 문자열까지를 반환
r.recvuntil('ttt')
# 유저가 직접 화면에 입출력 할 수 있게 한다.
r.interactive()
r.interactive() 함수가 특이한데, 프로그램을 평소대로 실행했던 때 처럼 입출력 할 수 있게 유저에게 입출력을 다시 돌려준다. 프로그램에 직접 입출력 조작이 필요할 때, 이 함수를 호출하면 된다.
예제를 통해 자세히 알아보자.
#include <stdio.h>
#include <stdlib.h>
int main() {
int i, a, b, ans;
srand(time(0));
for (i = 0; i < 100; i++) {
a = rand() % 20000;
b = rand() % 20000;
printf("#%d: %d + %d = ?\n", i, a, b);
printf("> ");
scanf("%d", &ans);
if (a + b == ans) {
printf("Correct!\n");
}
else {
printf("Wrong!\n");
return 0;
}
}
printf("Congrats! flag{welcome_to_pwntools}");
}
쉬운 ctf문제로 나오는 문제 유형 중 하나이다. 100개의 덧셈 문제를 모두 풀면 플래그를 내놓는다.
손으로 하기엔 엄청난 노가다이지만, pwntools 을 활용해 문제를 읽어들이고 답을 입력시키면 빠르게 해결 가능하다.
from pwn import *
# ./test 를 실행시킨다.
r = process("./test")
# 총 100번 반복한다.
for i in range(100):
# 몇번째 문제인지 표시
print("Solving " + str(i))
# 문제의 숫자 앞까지 읽어들이기
r.recvuntil(":")
# + 앞까지 읽어서 첫번째 항 숫자 읽어들이기
rawa = r.recvuntil("+")[:-1]
a = int(rawa)
# = 앞까지 읽어서 두번째 항 숫자 읽어들이기
rawb = r.recvuntil("=")[:-1]
b = int(rawb)
# 답을 구함
ans = a + b
# 답을 프로그램에 입력시킴
r.sendline(str(ans))
# Correct! 문자열 읽어줌
r.recvuntil("Correct!\n")
# 다 끝나면 interactive() 로 제어를 넘겨받음
r.interactive()
[:-1] 은 파이썬의 문법을 활용한 것인데, "ABCDE"[:-1] 의 결과는 "ABCD" 가 된다.
이에 대한 자세한 내용은 "파이썬 문자열 슬라이싱" 을 검색하면 된다.
위의 c 코드를 gcc test.c -o test 로 컴파일하고 위의 파이썬 코드를 실행시켜 보자.


플래그가 나온 것을 확인할 수 있다.
그런데 실제 문제는 로컬의 바이너리로 주지 않고, 원격으로 nc로 접속해야 할 때가 많다.
이에 따라 pwntools 에서도 원격으로 접속할 수 있는 기능을 제공한다.
r = remote("주소", 포트)
process 대신에 remote 클래스를 사용하면 원격에 접속할 수 있다.
remote 를 사용해도 process 로 실행했을 때와 완전히 동일하게 send 와 recv 계열 함수를 사용할 수 있다.
nc 123.456.789.123 12345
로 접속하는 것은 다음과 동일하다.
r = remote("123.456.789.123", 12345)