유용한 CTF pwnable 툴인 pwntool 의 레퍼런스를 번역합니다.
Getting Started
pwntools 을 활용하기 위한 몇 가지 예제를 살펴보도록 하자.
익스플로잇을 작성할 때, pwntools 은 "kitchen sink" 접근법을 따른다
>>> from pwn import *
전역 네임스페이스를 다 불러올 수 있도록 한방에 import 해 준다.
어셈블, 디스어셈블, 팩, 언팩 기타등등의 모듈들을 쓸 수 있게 된다.
Making Connections
pwn 하려면 연결을 해야한다. pwntools 에서는 멍청하도록 간단한 모듈인 pwnlib.tubes 를 만들었다.
프로세스, 소켓, 시리얼 포트 외 수많은 인터페이스를 제공하므로, 다양한 작업들에 있어 끝내주는 조력자가 될 것이다. 아래와 같이 pwnlib.tubes.remote 를 통해 원격 접속을 할 수 있다.
remote('ip', port) -> recvline / recvuntil()
>>> conn = remote('ftp.debian.org',21) >>> conn.recvline() '220 ...' >>> conn.send('USER anonymous\r\n') >>> conn.recvuntil(' ', drop=True) '331' >>> conn.recvline() 'Please specify the password.\r\n' >>> conn.close()
서버를 만들기도 간단하다
listen -> wait_for_connection() -> send / recv
>>> l = listen() >>> r = remote('localhost', l.lport) >>> c = l.wait_for_connection() >>> r.send('hello') >>> c.recv() 'hello'
주 : 이렇게도 된다
>> conn = remote('0.0.0.0', 42028) [x] Opening connection to 0.0.0.0 on port 42028 [x] Opening connection to 0.0.0.0 on port 42028: Trying 0.0.0.0 [+] Opening connection to 0.0.0.0 on port 42028: Done >>> conn.send('hi') >>> conn.send('good') >>> conn.send('cheese!') |
>>> l = listen() >>> c = l.wait_for_connection() [+] Waiting for connections on 0.0.0.0:42028: Got connection from 127.0.0.1 on port 59851 >>> c.recvuntil('cheese!') 'higoodcheese!' |
listen 인자값으로 포트를 주면 정해진 포트로 통신이 가능하다.
프로세스랑 통신하고 싶으면 이렇게. pwnlib.tubes.process 를 쓰자.
timout 으로 얼마나 기다릴지 결정할 수 있다.
>>> sh = process('/bin/sh') >>> sh.sendline('sleep 3; echo hello world;') >>> sh.recvline(timeout=1) '' >>> sh.recvline(timeout=5) 'hello world\n' >>> sh.close()
코드를 짜서 프로세스와 통신을 할 수도 있고, 아래처럼 실시간으로 할 수도 있다!
>>> sh.interactive() $ whoami user
https://pwntools.readthedocs.org/en/latest/intro.html#making-connections
로컬 pwnable 이나 setuid 류 pwnable 을 해결하기 위한 SSH 모듈도 있다.
pwnlib.tubes.ssh 를 이용하면 빠르게 프로세스를 생성하고 또 결과를 받아볼 수 있다.
아니면 process 에서 interact 를 썼듯이 사용할 수도 있고.
>>> shell = ssh('bandit0', 'bandit.labs.overthewire.org', password='bandit0') >>> shell['whoami'] 'bandit0' >>> shell.download_file('/etc/motd') >>> sh = shell.run('sh') >>> sh.sendline('sleep 3; echo hello world;') >>> sh.recvline(timeout=1) '' >>> sh.recvline(timeout=5) 'hello world\n' >>> shell.close()
Packing Integers
보통 익스플로잇을 짤 때에, integer 변환을 위해 decode hex 나 struct pack 을 쓰는 것이 일반적이다.
pwntools 에서는 pwnlib.util.packing 라이브러리를 이용해 더 쉽게 자료형 패킹을 할 수 있다.
이름하야 p32(), u32() 되시겠다. 팩은 p, 언팩은 u로 확인가능.
>>> import struct >>> p32(0xdeadbeef) == struct.pack('I', 0xdeadbeef) True >>> leet = '37130000'.decode('hex') >>> u32('abcd') == struct.unpack('I', 'abcd')[0] True
- 주
struct.pack('I', 0xdeadbeef) 는 encode('hex') 를 통해 efbeadde 로 확인된다. 대충 이렇다
>>> k = p32(0xdeadbeef)
>>> print k
絶?
>>> print u32(k)
3735928559
>>> print hex(u32(k))
0xdeadbeef
>>>
패킹/언패킹 명령어는 다양한 비트를 지원하고 있다.
>>> u8('A') == 0x41 True
Setting the Target Architecture and OS
pwn 하려는 시스템 아키텍쳐가 딱 지정될 수 있다.
>>> asm('nop') '\x90' >>> asm('nop', arch='arm') '\x00\xf0 \xe3'
하지만 context 로도 이런 세팅이 한번에 가능하다. 운영체제, word 의 사이즈, 엔디안 까지 세팅할 수 있다.
>>> context.arch = 'i386' >>> context.os = 'linux' >>> context.endian = 'little' >>> context.word_size = 32
물론 일일이 줄 필요 없이, 아래처럼 약식으로도 사용 가능하다.
>>> asm('nop') '\x90' >>> context(arch='arm', os='linux', endian='big', word_size=32) >>> asm('nop') '\xe3 \xf0\x00'
주 : 다른 아키텍처 어셈블리를 사용하고 싶으면 binutil 을 설치해야 한다.
https://pwntools.readthedocs.org/en/latest/install/binutils.html
Setting Logging Verbosity
로그를 어떻게 찍을건지 설정할 수 있다.
>>> context.log_level = 'debug'
-- 이 부분은 어떻게 찍히는지 확인되지 않음.
Assembly and Disassembly
이제는 어셈블되어 있는 쉘코드를 인터넷에서 찾으려고 헤멜 필요가 없을 것이다.
pwnlib.asm 모듈은 충분히 개쩐다.
>>> asm('mov eax, 0').encode('hex') 'b800000000'
이제 짐작할수 있을 것이다. 디스어셈은 이렇게.
>>> print disasm('6a0258cd80ebf9'.decode('hex')) 0: 6a 02 push 0x2 2: 58 pop eax 3: cd 80 int 0x80 5: eb f9 jmp 0x0
>>> print disasm('6a0258cd80ebf9'.decode('hex'), arch='arm')
0: cd58026a lfmgt f0, 2, [r8, #-424] ; 0xfffffe58
4: Address 0x0000000000000004 is out of bounds.
그리고 이제는 쉘코드를 직접 작성할 필요가 없을 것이다.
pwntools 에서는 pwnlib.shellcraft 모듈을 통해 쉘코드를 제공하며, 시간을 줄이는 데 매우 유용할 것이다.
예를 들어 하나 해보자.
setreuid(getuid(), getuid()), 파일 디스크립터 4번을 stdin / stdout 으로 dup, 그리고 쉘 떨구기까지.
>>> asm(shellcraft.setreuid() + shellcraft.dupsh(4)).encode('hex')
'6a3158cd8089c389d96a4658cd806a045b6a0359496a3f58cd8075f86a68682f2f2f73682f62696e89e331c96a0b5899cd80'
>>> k = asm(shellcraft.setreuid() + shellcraft.dupsh(4)).encode('hex')
>>> print disasm(k.decode('hex'))
0: 6a 31 push 0x31
2: 58 pop eax
3: cd 80 int 0x80
5: 89 c3 mov ebx,eax
7: 89 d9 mov ecx,ebx
9: 6a 46 push 0x46
b: 58 pop eax
c: cd 80 int 0x80
e: 6a 04 push 0x4
10: 5b pop ebx
11: 6a 03 push 0x3
13: 59 pop ecx
14: 49 dec ecx
15: 6a 3f push 0x3f
17: 58 pop eax
18: cd 80 int 0x80
1a: 75 f8 jne 0x14
1c: 6a 68 push 0x68
1e: 68 2f 2f 2f 73 push 0x732f2f2f
23: 68 2f 62 69 6e push 0x6e69622f
28: 89 e3 mov ebx,esp
2a: 31 c9 xor ecx,ecx
2c: 6a 0b push 0xb
2e: 58 pop eax
2f: 99 cdq
30: cd 80 int 0x80
>>>
Misc Tools
pwnlib.util.fiddling 모듈을 쓰고, hexdump 과도 작별하자.
https://pwntools.readthedocs.org/en/latest/util/fiddling.html#module-pwnlib.util.fiddling
base
버퍼에서 오프셋을 찾을 땐 pwnlib.cyclic 을 사용하면 된다.
>>> print cyclic(20) aaaabaaacaaadaaaeaaa >>> # Assume EIP = 0x62616166 ('faab' which is pack(0x62616166)) at crash time >>> print cyclic_find('faab') 120
ELF Manipulation
ELF 파일 조작을 위해 하드코딩 할 필요도 없다. pwnlib.elf 모듈로 그냥 바로 고쳐 보자.
>>> e = ELF('/bin/cat') >>> print hex(e.address) 0x400000 >>> print hex(e.symbols['write']) 0x401680 >>> print hex(e.got['write']) 0x60b070 >>> print hex(e.plt['write']) 0x401680
주 - checksec 을 통해 확인해야 하던 것도 바로 보여준다.
>>> e = ELF('/bin/cat')
[*] '/bin/cat'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack Canary: Canary found
NX: NX enabled
PIE: No PIE
위 got / plt 로 나타난 주소는 함수의 주소이다.
>>> print e.got
{'strncmp': 6336576, 'lseek': 6336728, 'malloc': 6336896, '__assert_fail': 6336736, 'setlocale': 6336960, 'realloc': 6336944, 'mbrtowc': 6336704, 'abort': 6336560, 'iswalnum': 6336888, 'iconv_open': 6337088, '__fprintf_chk': 6337056, '__fpending': 6336600, '__libc_start_main': 6336816, '__freading': 6336936, 'close': 6336776, '__ctype_get_mb_cur_max': 6336672, 'stpcpy': 6336656, 'bindtextdomain': 6336648, 'strlen': 6336680, 'fflush': 6336904, 'strchr': 6336712, 'iconv': 6336608, 'fdopen': 6336952, 'open': 6337008, 'posix_fadvise': 6336784, 'fputs_unlocked': 6336832, 'write': 6336624, 'dcgettext': 6336664, 'strcpy': 6336592, 'exit': 6337040, '__fxstat': 6336928, 'memcmp': 6336824, '__ctype_b_loc': 6337080, 'strrchr': 6336720, '__printf_chk': 6336968, '__strdup': 6336792, '__cxa_atexit': 6337024, 'memset': 6336744, 'fileno': 6336872, 'read': 6336808, 'fclose': 6336640, 'iswcntrl': 6336616, 'error': 6337000, 'free': 6336552, 'getenv': 6336544, 'ioctl': 6336760, 'iconv_close': 6336976, 'calloc': 6336840, '__gmon_start__': 6336856, 'nl_langinfo': 6336912, 'getopt_long': 6336696, 'memcpy': 6336864, 'strnlen': 6336768, 'fseeko': 6337016, '__uflow': 6336536, 'fwrite': 6337048, 'fscanf': 6336752, 'iswprint': 6337072, 'memmove': 6336992, '__errno_location': 6336568, 'wcwidth': 6336880, '__stack_chk_fail': 6336688, '__sprintf_chk': 6337096, 'textdomain': 6336632, 'getpagesize': 6337032, 'iswspace': 6336984, 'ungetc': 6336920, '_exit': 6336584, 'strcmp': 6336848, 'memchr': 6336800, 'mbsinit': 6337064}
>>>
이런 식으로 쓸 수 있다.
파일을 패치하고 저장하는 것도 아래와 같이 할 수 있다.
>>> e = ELF('/bin/cat') >>> e.read(e.address+1, 3) 'ELF' >>> e.asm(e.address, 'ret') >>> e.save('/tmp/quiet-cat') >>> disasm(file('/tmp/quiet-cat','rb').read(1)) ' 0: c3 ret'
asm 을 이용해서 /bin/cat 에서 특정 주소를 입력할 수 있고
save 를 사용하면 원하는 장소에 저장이 가능하다.
'Etc > Pwntools reference' 카테고리의 다른 글
1. from pwn import * (0) | 2015.05.28 |
---|