본문 바로가기

개념정리/System Hacking

6. pwntools

 

저번 시간에는 gdb 디버거에 대하여 공부하였다.

이번 시간은 pwntools에 대하여 공부할 것이다.

 


1. pwntools

프로그램에 대하여 익스플로잇을 수행할 때 효율적이고 편하게 도움주는 파이썬 모듈이다.  파이썬으로 여러 개의 익스프로잇 스크립트를 작성하다보면 자주 사용되는 함수들이 있다. 예를 들어 패킹 함수, 언패킹 함수들은 익스프로잇 과정에서 항상 필요하다. 이런 함수들을 반복적으로 구현하는 것은 비효율적이기 때문에 시스템 해커들이 편리성을 위해 제작하였다. 

 

2. pwntools 설치

pwntools는 깃허브에 오픈 소스로 공개되어 있다.

1) 밑에 명령어를 이용하여 리눅스에 pwntools를 설치한다.

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

2) pwntools를 임포트했을 때 아래와 같이 에러가 발생하지 않으면 제대로 설치한 것이다.

 

3. pwntools API 사용법

1) process, remote

  • process : 익스플로잇을 로컬 바이너리를 대상으로 할 때 사용 (테스트하고 디버깅 할 때)
  • remote : 익스플로잇을 원격 서버를 대상으로 할 때 사용 (대상 서버를 실제로 공격할 때)
from pwn import *

p = process('./test') 
p = remote('host',port)

 

2) send

데이터를 프로세스에 전송하기 위해 사용한다.

  • send('A') : 'A'를 입력
  • sendline('A') : 'A' + '\n'을 입력
  • sendafter('input : ', 'A') : 프로세스에서 'input : '을 출력하면 'A'를 입력
  • sendlineafter('input : ', 'A') : 프로세스에서 'input : '을 출력하면 'A' + '\n'을 입력
from pwn import *

p = process('./test')

p.send('A')
p.sendline('A')
p.sendafter('input : ','A')
p.sendlineafter('input : ','A')

 

3) recv

프로세스에서 데이터를 받기 위해 사용한다.

  • recv(100) : 프로세스가 출력하는 데이터를 최대 100바이트까지 받아서 저장한다.
  • recvline() : 프로세스가 출력하는 데이터를 개행문자('\n')를 만날 때까지 받아서 저장한다.
  • recvn(8) : 프로세스가 출력하는 데이터를 8바이트만 받아서 저장한다.
  • recvuntil('example') : 프로세스가 출력하는  데이터를 'example'가 출력될 때까지 받아서 저장한다.
  • recvall() : 프로세스가 출력하는 데이터를 프로세스가 종료될 때까지 받아서 저장한다.
from pwn import *

p = process('./test')

# 프로세스에서 출력하는 데이터를 함수에 맞게 받아서 payload에 저장한다.
payload = p.recv(100)
payload = p.recvline()
payload = p.recvn(8)
payload = p.recvuntil('example')
payload = p.recvall()

# 많이 사용하는 recv 형식
p.recvuntil('example')
payload = p.recvline()[:-1]

 

4) packing, unpacking

익스플로잇을 작성하다 보면 어떤 값을 리틀 엔디언의 바이트 배열로 변경하거나 역의 과정을 거쳐야 할 때 사용한다.

  • packing : 정수를 리틀 엔디언의 바이트 배열로 변환할 때 사용
  • unpacking : 패킹된 값을 다시 정수로 변환할 때 사용
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)))
#출력
b'DCBA'
b'HGFEDCBA'

0x44434241
0x4847464544434241

 

5) interactive

셸을 획득하거나, 익스프로잇의 특정 상황에 직접 입력을 주면서 출력을 확인하고 싶을 때 사용하는 함수이다. 호출하면 터미널로 프로세스에 데잍를 입력하고, 프로세스의 출력을 확인할 수 있다.

from pwn import *

p = process('./test')

p.interactive()

 

6) ELF

ELF 헤더에는 익스플로잇에 사용될 수 있는 각종 정보가 기록되어 있다. pwntools를 사용하면 이 정보를 참조할 수 있다.

from pwn import *

e= ELF('./test')

read_plt = e.plt['read']

 

7) context.log

익스플로잇에 버그가 발생하면 익스플로잇도 디버깅을 해야 한다. pwntools에는 디버그의 편의를 돕는 로깅 기능이 있으며, 로그 레벨은 context.log_level변수로 조절할  수 있다.

  • context.log_level = 'error' : 에러만 출력
  • context.log_level = 'debug' : 대상 프로세스와 익스플로잇 간에 오가는 모든 데이터 화면에 출력
  • context.log_level = 'info' : 비교적 중요한 정보들만 출력
from pwn import *

context.log_level = 'error'

context.log_level = 'debug'

context.log_level = 'info'

 

8) context.arch

pwntools는 셸코드를 생성하거나 코드를 디스어셈블하는 기능을 가지고 있는데 공격 대상의 아키텍처에 영향을 받는다. 그래서 아키텍처 정보를 지정할 때 사용한다.

from pwn import *

context.arch = "amd64" # x86-64 아키텍처
context.arch = "i386"  # x86 아키텍처
context.arch = "arm"   # arm 아키텍처

# 일반적인 사용법
context(arch = "amd64", ox = "linux")

 

9) shellcraft

공격에 필요한 셸 코드를 쉽게 꺼내 쓸 수 있게 해준다. 매우 편리하지만 정적으로 생성된 셸 코드는 셸 코드가 실행될 때의 메모리 상태를 반영하지 못한다. 또한 코드 길이 구성 가능 문자의 제한을 반영하기 어렵다. 그렇기 때문에 직접 셸 코드를 작성하는 것이 좋다.

from pwn import *

context(arch = "amd64", ox = "linux") 

# 셸을 실행하는 셸 코드
payload = shellcraft.sh()

 

10) asm

pwntools는 어셈블 기능을 제공한다. 이 기능은 대상 아키텍처가 중요하기 때문에 아키텍처를 미리 지정해야 한다.

from pwn import *

context(arch = "amd64", os = "linux")

# 셸을 실행하는 셸 코드
payload = shellcraft.sh()
# 셸 코드를 기계어로 어셈블
payload = asm(code)

 


 

이번 시간은 pwntools에 대하여 공부하였다.

다음 시간은 Shellcode에 대하여 공부할 것이다.