본문 바로가기

Etc/Pwntools reference

0. Getting Started


유용한 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