Advertisement
Guest User

Untitled

a guest
Apr 26th, 2018
190
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.35 KB | None | 0 0
  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::DCERPC
  8. include Msf::Exploit::Remote::SMB::Client
  9. include Msf::Exploit::Remote::SMB::Client::Authenticated
  10. include Msf::Exploit::Remote::SMB::Client::PipeAuditor
  11.  
  12. include Msf::Auxiliary::Scanner
  13. include Msf::Auxiliary::Report
  14.  
  15. def initialize(info = {})
  16. super(update_info(info,
  17. 'Name' => 'MS17-010 SMB RCE Detection',
  18. 'Description' => %q{
  19. Uses information disclosure to determine if MS17-010 has been patched or not.
  20. Specifically, it connects to the IPC$ tree and attempts a transaction on FID 0.
  21. If the status returned is "STATUS_INSUFF_SERVER_RESOURCES", the machine does
  22. not have the MS17-010 patch.
  23. If the machine is missing the MS17-010 patch, the module will check for an
  24. existing DoublePulsar (ring 0 shellcode/malware) infection.
  25. This module does not require valid SMB credentials in default server
  26. configurations. It can log on as the user "\" and connect to IPC$.
  27. },
  28. 'Author' =>
  29. [
  30. 'Sean Dillon <sean.dillon@risksense.com>', # @zerosum0x0
  31. 'Luke Jennings' # DoublePulsar detection Python code
  32. ],
  33. 'References' =>
  34. [
  35. [ 'AKA', 'DOUBLEPULSAR' ],
  36. [ 'AKA', 'ETERNALBLUE' ],
  37. [ 'CVE', '2017-0143'],
  38. [ 'CVE', '2017-0144'],
  39. [ 'CVE', '2017-0145'],
  40. [ 'CVE', '2017-0146'],
  41. [ 'CVE', '2017-0147'],
  42. [ 'CVE', '2017-0148'],
  43. [ 'MSB', 'MS17-010'],
  44. [ 'URL', 'https://zerosum0x0.blogspot.com/2017/04/doublepulsar-initial-smb-backdoor-ring.html'],
  45. [ 'URL', 'https://github.com/countercept/doublepulsar-detection-script'],
  46. [ 'URL', 'https://technet.microsoft.com/en-us/library/security/ms17-010.aspx']
  47. ],
  48. 'License' => MSF_LICENSE
  49. ))
  50.  
  51. register_options(
  52. [
  53. OptBool.new('CHECK_DOPU', [false, 'Check for DOUBLEPULSAR on vulnerable hosts', true]),
  54. OptBool.new('CHECK_ARCH', [false, 'Check for architecture on vulnerable hosts', true]),
  55. OptBool.new('CHECK_PIPE', [false, 'Check for named pipe on vulnerable hosts', false])
  56. ])
  57. end
  58.  
  59. # algorithm to calculate the XOR Key for DoublePulsar knocks
  60. def calculate_doublepulsar_xor_key(s)
  61. x = (2 * s ^ (((s & 0xff00 | (s << 16)) << 8) | (((s >> 16) | s & 0xff0000) >> 8)))
  62. x & 0xffffffff # this line was added just to truncate to 32 bits
  63. end
  64.  
  65. # The arch is adjacent to the XOR key in the SMB signature
  66. def calculate_doublepulsar_arch(s)
  67. s == 0 ? 'x86 (32-bit)' : 'x64 (64-bit)'
  68. end
  69.  
  70. def run_host(ip)
  71. begin
  72. ipc_share = "\\\\#{ip}\\IPC$"
  73.  
  74. tree_id = do_smb_setup_tree(ipc_share)
  75. vprint_status("Connected to #{ipc_share} with TID = #{tree_id}")
  76.  
  77. status = do_smb_ms17_010_probe(tree_id)
  78. vprint_status("Received #{status} with FID = 0")
  79.  
  80. if status == "STATUS_INSUFF_SERVER_RESOURCES"
  81. os = simple.client.peer_native_os
  82.  
  83. if datastore['CHECK_ARCH']
  84. case dcerpc_getarch
  85. when ARCH_X86
  86. os << ' x86 (32-bit)'
  87. when ARCH_X64
  88. os << ' x64 (64-bit)'
  89. end
  90. end
  91.  
  92. print_good("Host is likely VULNERABLE to MS17-010! - #{os}")
  93. report_vuln(
  94. host: ip,
  95. name: self.name,
  96. refs: self.references,
  97. info: "STATUS_INSUFF_SERVER_RESOURCES for FID 0 against IPC$ - #{os}"
  98. )
  99.  
  100. # vulnerable to MS17-010, check for DoublePulsar infection
  101. if datastore['CHECK_DOPU']
  102. code, signature1, signature2 = do_smb_doublepulsar_probe(tree_id)
  103.  
  104. if code == 0x51
  105. xor_key = calculate_doublepulsar_xor_key(signature1).to_s(16).upcase
  106. arch = calculate_doublepulsar_arch(signature2)
  107. print_warning("Host is likely INFECTED with DoublePulsar! - Arch: #{arch}, XOR Key: 0x#{xor_key}")
  108. report_vuln(
  109. host: ip,
  110. name: "MS17-010 DoublePulsar Infection",
  111. refs: self.references,
  112. info: "MultiPlexID += 0x10 on Trans2 request - Arch: #{arch}, XOR Key: 0x#{xor_key}"
  113. )
  114. end
  115. end
  116.  
  117. if datastore['CHECK_PIPE']
  118. pipe_name, _ = check_named_pipes(return_first: true)
  119.  
  120. return unless pipe_name
  121.  
  122. print_good("Named pipe found: #{pipe_name}")
  123.  
  124. report_note(
  125. host: ip,
  126. port: rport,
  127. proto: 'tcp',
  128. sname: 'smb',
  129. type: 'MS17-010 Named Pipe',
  130. data: pipe_name
  131. )
  132. end
  133. elsif status == "STATUS_ACCESS_DENIED" or status == "STATUS_INVALID_HANDLE"
  134. # STATUS_ACCESS_DENIED (Windows 10) and STATUS_INVALID_HANDLE (others)
  135. print_error("Host does NOT appear vulnerable.")
  136. else
  137. print_error("Unable to properly detect if host is vulnerable.")
  138. end
  139.  
  140. rescue ::Interrupt
  141. print_status("Exiting on interrupt.")
  142. raise $!
  143. rescue ::Rex::Proto::SMB::Exceptions::LoginError
  144. print_error("An SMB Login Error occurred while connecting to the IPC$ tree.")
  145. rescue ::Exception => e
  146. vprint_error("#{e.class}: #{e.message}")
  147. ensure
  148. disconnect
  149. end
  150. end
  151.  
  152. def do_smb_setup_tree(ipc_share)
  153. connect
  154.  
  155. # logon as user \
  156. simple.login(datastore['SMBName'], datastore['SMBUser'], datastore['SMBPass'], datastore['SMBDomain'])
  157.  
  158. # connect to IPC$
  159. simple.connect(ipc_share)
  160.  
  161. # return tree
  162. return simple.shares[ipc_share]
  163. end
  164.  
  165. def do_smb_doublepulsar_probe(tree_id)
  166. # make doublepulsar knock
  167. pkt = make_smb_trans2_doublepulsar(tree_id)
  168.  
  169. sock.put(pkt)
  170. bytes = sock.get_once
  171.  
  172. # convert packet to response struct
  173. pkt = Rex::Proto::SMB::Constants::SMB_TRANS_RES_HDR_PKT.make_struct
  174. pkt.from_s(bytes[4..-1])
  175.  
  176. return pkt['SMB'].v['MultiplexID'], pkt['SMB'].v['Signature1'], pkt['SMB'].v['Signature2']
  177. end
  178.  
  179. def do_smb_ms17_010_probe(tree_id)
  180. # request transaction with fid = 0
  181. pkt = make_smb_trans_ms17_010(tree_id)
  182. sock.put(pkt)
  183. bytes = sock.get_once
  184.  
  185. # convert packet to response struct
  186. pkt = Rex::Proto::SMB::Constants::SMB_TRANS_RES_HDR_PKT.make_struct
  187. pkt.from_s(bytes[4..-1])
  188.  
  189. # convert error code to string
  190. code = pkt['SMB'].v['ErrorClass']
  191. smberr = Rex::Proto::SMB::Exceptions::ErrorCode.new
  192.  
  193. return smberr.get_error(code)
  194. end
  195.  
  196. def make_smb_trans2_doublepulsar(tree_id)
  197. # make a raw transaction packet
  198. # this one is a trans2 packet, the checker is trans
  199. pkt = Rex::Proto::SMB::Constants::SMB_TRANS2_PKT.make_struct
  200. simple.client.smb_defaults(pkt['Payload']['SMB'])
  201.  
  202. # opcode 0x0e = SESSION_SETUP
  203. setup = "\x0e\x00\x00\x00"
  204. setup_count = 1 # 1 word
  205. trans = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
  206.  
  207. # calculate offsets to the SetupData payload
  208. base_offset = pkt.to_s.length + (setup.length) - 4
  209. param_offset = base_offset + trans.length
  210. data_offset = param_offset # + 0
  211.  
  212. # packet baselines
  213. pkt['Payload']['SMB'].v['Command'] = Rex::Proto::SMB::Constants::SMB_COM_TRANSACTION2
  214. pkt['Payload']['SMB'].v['Flags1'] = 0x18
  215. pkt['Payload']['SMB'].v['MultiplexID'] = 65
  216. pkt['Payload']['SMB'].v['Flags2'] = 0xc007
  217. pkt['Payload']['SMB'].v['TreeID'] = tree_id
  218. pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count
  219. pkt['Payload'].v['Timeout'] = 0x00a4d9a6
  220. pkt['Payload'].v['ParamCountTotal'] = 12
  221. pkt['Payload'].v['ParamCount'] = 12
  222. pkt['Payload'].v['ParamCountMax'] = 1
  223. pkt['Payload'].v['DataCountMax'] = 0
  224. pkt['Payload'].v['ParamOffset'] = 66
  225. pkt['Payload'].v['DataOffset'] = 78
  226.  
  227. pkt['Payload'].v['SetupCount'] = setup_count
  228. pkt['Payload'].v['SetupData'] = setup
  229. pkt['Payload'].v['Payload'] = trans
  230.  
  231. pkt.to_s
  232. end
  233.  
  234. def make_smb_trans_ms17_010(tree_id)
  235. # make a raw transaction packet
  236. pkt = Rex::Proto::SMB::Constants::SMB_TRANS_PKT.make_struct
  237. simple.client.smb_defaults(pkt['Payload']['SMB'])
  238.  
  239. # opcode 0x23 = PeekNamedPipe, fid = 0
  240. setup = "\x23\x00\x00\x00"
  241. setup_count = 2 # 2 words
  242. trans = "\\PIPE\\\x00"
  243.  
  244. # calculate offsets to the SetupData payload
  245. base_offset = pkt.to_s.length + (setup.length) - 4
  246. param_offset = base_offset + trans.length
  247. data_offset = param_offset # + 0
  248.  
  249. # packet baselines
  250. pkt['Payload']['SMB'].v['Command'] = Rex::Proto::SMB::Constants::SMB_COM_TRANSACTION
  251. pkt['Payload']['SMB'].v['Flags1'] = 0x18
  252. pkt['Payload']['SMB'].v['Flags2'] = 0x2801 # 0xc803 would unicode
  253. pkt['Payload']['SMB'].v['TreeID'] = tree_id
  254. pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count
  255. pkt['Payload'].v['ParamCountMax'] = 0xffff
  256. pkt['Payload'].v['DataCountMax'] = 0xffff
  257. pkt['Payload'].v['ParamOffset'] = param_offset
  258. pkt['Payload'].v['DataOffset'] = data_offset
  259.  
  260. # actual magic: PeekNamedPipe FID=0, \PIPE\
  261. pkt['Payload'].v['SetupCount'] = setup_count
  262. pkt['Payload'].v['SetupData'] = setup
  263. pkt['Payload'].v['Payload'] = trans
  264.  
  265. pkt.to_s
  266. end
  267. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement