pwntools 프로세스 통신 기능
일반적으로 프로세스 간 통신을 수행하려면 통신하려는 프로세스 사이의
1. 연결을 맺고,
2. 데이터를 주고 받으며 통신하고,
3. 연결을 종료
하는 과정을 거친다.
연결을 맺는 함수
통신을 위한 첫 번째 단계로 연결을 맺어야한다. 이 연결에도 몇 가지 종류가 존재한다.
연결을 맺는 함수가 성공적으로 실행되면 pwnlib.tubes 클래스를 반환함
process()
- 로컬에 위치한 프로그램을 실행하여 통신할 때 사용
- 인자로 실행하여 통신할 프로그램의 경로를 전달하면, 전달된 프로그램을 실행한 뒤 프로그램과 연결을 맺어준다. → process(통신할 프로그램의 경로)
from pwn import *
p = process("./example")
process에서 성공적으로 연결이 맺어지면 해당 함수는 데이터를 송수신하는데 사용될 pwnlib.tubes 클래스를 반환하고 해당 클래스는 p에 저장된다.
from pwn import *
p = process(["./example", "AAAA"], env={"LD_PRELOAD":"./libc.so.6"})
|
./example
|
실행할 파일
|
|
"AAAA"
|
명령줄 인자 (즉, argv[1] 값으로 전달됨)
|
|
env={"LD_PRELOAD": "./libc.so.6"}
|
환경 변수 설정. LD_PRELOAD는 특정 .so (공유 라이브러리)를 강제로 미리 로드하게 함
|
이 코드는 ./example 프로그램을 다음과 같은 조건으로 실행:
- 인자 AAAA를 전달해서 argv[1] = "AAAA"가 됨
- 환경 변수 LD_PRELOAD=./libc.so.6를 설정해서, 원래 시스템 라이브러리가 아닌 내가 만든 가짜 ./libc.so.6를 먼저 로드하게 만듦
remote()
- 호스트의 domain / IP 주소 / 포트번호를 인자로 받아 원격 서버에 통신할 때 사용
- process에서 성공적으로 연결이 맺어지면 pwnlib.tubes 클래스를 반환한다.
from pwn import *
r = remote("example.com", 1337)
example.com 호스트의 1337번 포트에 연결
- 기본적으로 TCP 연결을 맺지만, TCP 대신 UDP 연결을 맺고 싶다면 typ 인자에 ‘udp’를 전달
r = remote("example.com", 1337, typ='udp')

ssh()
- SSH 서버에 접속하여 통신하기 위해서 사용
- SSH = Secure Shell, 원격 호스트에 접속하기 위해 사용되는 보안 프로토콜
s = ssh("dreamhack", "127.0.0.1", port=22, password="dreamhack")
127.0.0.1 호스트의 22번 포트에 열린 ssh 서버에 dreamhack이라는 사용자 이름와 dreamhack이라는 비밀번호로 로그인을 하여 접속
데이터 송수신 함수
recv()
- 데이터를 수신하기 위해 사용
- 수신한 데이터를 bytes 클래스로 반환
from pwn import *
p = process('./example')
data = p.recv(1024)
data = p.recvline()
data = p.recvn(5)
data = p.recvuntil(b'hello')
data = p.recvall()
|
p.recv(1024)
|
p가 출력하는 데이터를 최대 1024바이트까지 받아서 저장
|
|
p.recvline()
|
p가 출력하는 데이터를 개행문자 만날 때까지 받아서 저장
|
|
p.recvn(5)
|
p가 출력하는 데이터를 5바이트만 받아서 저장
|
|
p.recvuntil(b’hello’)
|
p가 b’hello’를 출력할 때까지 데이터를 수신해 저장
|
|
p.recvall()
|
p가 출력하는 데이터를 프로세스가 종료될 때까지 받아서 저장
|
recv(n) vs. recvn(n)
recv(n) : 최대 n바이트를 받는 함수. 그만큼의 데이터를 받지 못해도 당장 받을 수 있는 만큼 수신한 뒤 함수를 종료함.
recvn(n) : 무조건 n바이트를 받는 함수. n바이트가 채워지지 않으면 채워질 때까지 수신을 계속 기다림.
send()
- 데이터를 전송하기 위해 사용
- 데이터를 bytes 클래스 인자로 받아 전송
from pwn import*
p = process('./example')
p.send(b'A')
p.sendline(b'A')
p.sendafter(b'hello', b'A')
p.sendlineafter(b'hello', b'A')
|
send(b'A')
|
# ./example에 b'A'를 입력
|
|
sendline(b'A')
|
# ./example에 b'A' + b'\n'을 입력
|
|
sendafter(b'hello', b'A')
|
# ./test가 b'hello'를 출력하면, b'A'를 입력
|
|
sendlineafter(b'hello', b'A')
|
# ./test가 b'hello'를 출력하면, b'A' + b'\n'을 입력
|
sendafter() vs. sendlineafter()
→ 실제 인자로 전달된 내용들이 나올 때까지 수신한 뒤 데이터를 전송
sendafter() , sendlineafter() 를 실행한 뒤에 데이터를 수신할 경우 sendafter() , sendlineafter()에서 수신한 데이터 이후의 데이터부터 수신
interactive()
- 터미널에서 사용자가 실시간으로 데이터를 수신하고 전송할 수 있게 하는 함수
- 함수 호출시 터미널을 통해 프로세스에 입력 값을 전달할 수 있게 되고 프로세스의 출력도 실시간으로 터미널에 표시됨.
- 터미널에서 직접 프로그램을 실행시켜 조작하는 것과 비슷한 효과
from pwn import*
p = process('./example')
p.interactive()
연결을 종료하는 함수
연결된 두 프로세스 중 어느 한 프로세스가 연결을 종료한다면 해당 연결 종료
close()
- pwnlib.tubes 클래스에 구현된 함수
from pwn import *
p = process('./example')
p.close() # 실행되는 순간에 연결이 유지되고 있는 상태여야 함
로그 출력
context.log_level을 이용새 로그 출력의 상세도를 설정할 수 있다.
from pwn import *
context.log_level = 'debug'
p = process("./example")
p.recvall()
|
crititcal
|
치명적인 오류 외에 출력하지 않음
|
|
error
|
에러 메시지만 출력
|
|
warning / warn
|
경고 메세지 출력
|
|
info
|
일반적인 상태 메시지 출력 → 기본값
|
|
debug
|
송수신 데이터, 내부 변수 등 상세히 출력
|
pwntools 편리한 기능
Packing & Unpacking
데이터를 하나의 형태로 모아 포장하거나, 다시 분해하는 것
시스템 해킹에서는 정수 값을 bytes 클래스로 변환하거나 그 반대의 행위
리틀 엔디언으로 동작한다.

|
p8(), p16(), p32(), p64()
|
숫자를 bytes 클래스로 패킹하는 함수
|
|
u8(), u16(), u32(), u64()
|
bytes 클래스를 숫자로 언패킹하는 함수
|
→ 함수명 뒤에 붙는 숫자는 bytes 클래스의 비트 수
from pwn import *
s8 = 0x41
s16 = 0x4142
s32 = 0x41424344
s64 = 0x4142434445464748
print(p8(s8))
print(p16(s16))
print(p32(s32))
print(p64(s64)) -> Packing
s8 = b"A"
s16 = b"AB"
s32 = b"ABCD"
s64 = b"ABCDEFGH"
print(hex(u8(p8)))
print(hex(u16(s16)))
print(hex(u32(s32)))
print(hex(u64(s64))) -> Unpacking
-----------------------------------------------------------
b'A'
b'BA'
b'DCBA'
b'HGFEDCBA'
0x41
0x4241
0x44434241
0x4847464544434241
GDB
attach
from pwn import *
p = process("./example")
gdb.attach(p)
sleep(1) -> gdb가 성공적으로 붙을 수 있는 시간을 벌어주기 위함
gdb.attach()에 인자로 process()로 생성한 객체를 넣으면 해당 process에 gdb가 붙어 실행이 중단되고 gdb 터미널이 열려서 디버깅을 할 수 있게된다.
remote()로 생성한 객체나 프로세스의 PID를 전달해도 되지만 프로세스가 같은 장치에서 실행되고 있어서 디버깅이 가능한 프로세스인 경우에만 허용한다.
Assemble & Disassemble
from pwn import *
context.arch = "amd64" # x86-64 아키텍처
context.arch = "i386" # x86 아키텍처
context.arch = "arm" # arm 아키텍처
context.arch로 아키텍처 설정할 수 있음.
asm()과 disasm()로 어셈블과 디스어셈블을 수행할 수 있음
from pwn import *
context.arch = "amd64" # x86-64 아키텍처로 설정
machine_code = asm('mov eax, 0') # 어셈블리 'mov eax, 0'를 기계어로 변환
print(machine_code)
assembly_code = disasm(machine_code) # 기계어를 어셈블리어로 변환
print(assembly_code)
b'\xb8\x00\x00\x00\x00'
0: b8 00 00 00 00 mov eax, 0x0
ELF (Executable and Linkable Formate)
- 리눅스의 실행파일
- ELF()의 인자에 ELF 파일의 경로를 넣으면 pwnlib.elf.elf.ELF 클래스를 반환
symbol
pwnlib.elf.elf.ELF 클래스의 symbols 멤버 변수를 심볼들의 주소들을 가지고 있는 doctdict 클래스
doctdict 클래스 : 기존의 딕셔너리처럼 사용할 수 있으면서도 속성으로 접근이 가능
from pwn import *
e = ELF("./example") # ELF 파일 열기
print(hex(e.symbols['write'])) # 'write' 심볼의 주소
print(hex(e.symbols.write)) # 'write' 심볼의 주소, 속성 접근
0x400000
0x401074
0x401074
시스템 해킹 수행시에는 대부분의 상황에서 물리주소보다는 가상 주소를 필요로 함
따라서 symbols에서 제공하는 주소는 가상 주소
실행파일에 PIE 보호 기법이 적용되어 있지 않다면 가상 주소 제공
PIE 보호 기법이 적용되어있다면 이미지 베이스로부터의 상대 가상주소를 제공
'Cryptography > Cryptography' 카테고리의 다른 글
| AES(Advanced Encryption Standard) - 2 (0) | 2025.11.08 |
|---|---|
| AES(Advanced Encryption Standard) - 1 (0) | 2025.11.05 |
| Chinese Remainder Theorem (CRT) using Python (0) | 2025.10.09 |
| [Dreamhack] Cryptography - 현대암호 (0) | 2025.10.09 |
| [Dreamhack] Cryptography - 고전암호 (0) | 2025.10.09 |