-
-
[原创]samba cve-2007-2446 堆溢出分析
-
2017-11-10 15:07 4903
-
之前翻译的【翻译】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次,导致堆溢出。
图为执行完循环,堆内存被破坏。
[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界