Security/System Hacking

System Hacking: Tool Installation - 2

arsenic-dev 2025. 1. 21. 15:10

DreamHack의 System Hacking 로드맵을 기반으로 정리한 글입니다.

Tool: pwntools

파이썬으로 여러 개의 익스플로잇(취약점 공격) 스크립트를 작성하다 보면, 자주 사용하게 되는 함수들이 있다. 예를 들어 정수를 리틀 엔디언의 바이트 배열로 바꾸는 패킹 함수, 또는 그 역을 수행하는 언패킹 함수 등은 익스플로잇 과정에 거의 항상 필요하다.

 

※ 리틀 엔디언: 데이터를 메모리에 저장하거나 전송할 때, 값의 하위 바이트(작은 값의 바이트)를 먼저 저장하는 방식이다.

 

이런 함수들을 반복적으로 구현하는 것은 비효율적이다. 그래서 시스템 해커들은 이들을 집대성하여 pwntools라는 파이썬 모듈을 제작하였다. pwntools 덕분에 익스플로잇 제작은 전과 비교할 수 없을 정도로 간단하고, 쉬워졌다. 이제는 익스플로잇의 대부분이 pwntools를 이용하여 제작 및 공유된다.

pwntools API 사용법

process & remote

process 함수는 익스플로잇을 로컬 바이너리를 대상으로 할 때 사용하는 함수이고, remote 함수는 원격 서버를 대상으로 할 때 사용하는 함수이다. 전자는 보통 익스플로잇을 테스트하고 디버깅하기 위해, 그리고 후자는 대상 서버를 실제로 공격하기 위해 사용한다.

from pwn import *
p = process('./test')  # 로컬 바이너리 'test'를 대상으로 익스플로잇 수행
p = remote('example.com', 31337)  # 'example.com'의 31337 포트에서 실행 중인 프로세스를 대상으로 익스플로잇 수행

process & remote

 

send

send는 데이터를 프로세스에 전송하기 위해 사용한다. pwntools에는 관련된 다양한 함수가 정의되어 있다.

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

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

▶ send

 

recv

recv는 프로세스에서 데이터를 받기 위해 사용한다. send와 마찬가지로 pwntools에는 관련된 다양한 함수가 정의되어 있다.

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(b'hello')  # p가 b'hello'를 출력할 때까지 데이터를 수신하여 data에 저장
data = p.recvall()  # p가 출력하는 데이터를 프로세스가 종료될 때까지 받아서 data에 저장

▶ recv

  • recv(n): 최대 n 바이트를 받는 것으로, 그만큼을 받지 못해도 에러 발생 X
  • recvn(n): 정확히 n 바이트의 데이터를 받지 못하면 계속 기다림

packing & unpacking

익스플로잇을 작성하다 보면 어떤 값을 리틀 엔디언의 바이트 배열로 변경하거나, 또는 역의 과정을 거쳐야 하는 경우가 자주 있다. pwntools에는 관련된 함수들이 정의되어 있다.

# Name: pup.py

from pwn import *

s32 = 0x41424344
s64 = 0x4142434445464748

print(p32(s32))
print(p64(s64))

s32 = b"ABCD"
s64 = b"ABCDEFGH"

print(hex(u32(s32)))
print(hex(u64(s64)))
$ python3 pup.py
b'DCBA'
b'HGFEDCBA'
0x44434241
0x4847464544434241

▶ packing & unpacking

 

※ p32(): 정수를 4바이트 형식의 바이트 문자열로 변환 / u64(): 8바이트 형식의 바이트 문자열을 64비트 정수(10진수)로 변환

비트 수 세는 법: 정수를 이진수로 변환하여 그 자릿수를 확인

 

interactive

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

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

interactive

 

ELF

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

 

※ ELF 파일은 크게 헤더(Header)와 섹션(Sections)으로 구성된다.

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에 저장

▶ ELF

 

※ PLT 주소: 공유 라이브러리 함수 간접 호출을 위한 중계 테이블 주소(GOT 참조) / GOT 주소: 공유 라이브러리 함수의 실제 주소가 저장된 메모리 테이블의 주소

 

context.log

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

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

▶ log

 

※ 로깅: 정보를 제공하는 일련의 기록인 로그(log)를 생성하도록 시스템을 작성하는 활동이다.

 

context.arch

pwntools는 셸코드를 생성하거나, 코드를 어셈블(어셈블리 언어 -> 기계어), 디스어셈블(기계어 -> 어셈블리 언어 )하는 기능 등을 가지고 있는데, 이들은 공격 대상의 아키텍처에 영향을 받는다. 그래서 pwntools는 아키텍처 정보를 프로그래머가 지정할 수 있게 하며, 이 값에 따라 몇몇 함수들의 동작이 달라진다.

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

▶ context

 

shellcraft

pwntools에는 자주 사용되는 셸 코드들이 저장되어 있어서, 공격에 필요한 셸 코드를 쉽게 꺼내 쓸 수 있게 해준다. 매우 편리한 기능이지만 정적으로 생성된 셸 코드는 셸 코드가 실행될 때의 메모리 상태를 반영하지 못한다. 또한, 프로그램에 따라 입력할 수 있는 셸 코드의 길이나, 구성 가능한 문자의 종류에 제한이 있을 수 있는데, 이런 조건들도 반영하기 어렵다. 따라서 제약 조건이 존재하는 상황에서는 직접 셸 코드를 작성하는 것이 좋다.

 

x86-64를 대상으로 생성할 수 있는 여러 종류의 셸 코드

# 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

▶ shellcraft

 

asm

pwntools는 어셈블 기능을 제공한다. 이 기능도 대상 아키텍처가 중요하므로, 아키텍처를 미리 지정해야 한다.

# 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'

▶ asm

 

※ 익스플로잇을 작성하다가 어떤 기능이 반복적으로 사용된다고 생각되면 pwntools에 이미 구현된 함수가 있는지 찾아보는 것도 좋다.

'Security > System Hacking' 카테고리의 다른 글

System Hacking: Shellcode - 1  (1) 2025.01.23
System Hacking: Tool Installation - 1  (0) 2025.01.20
System Hacking: Background - Computer Science  (2) 2025.01.19