zoukankan      html  css  js  c++  java
  • IDAPython教程(二)

    继续我们的主题—使用IDAPython 让逆向工程师的生活变得更美好。  

    这一部分,我们将着手处理一个非常常见的问题:shellcode和恶意软件使用hash算法混淆加载的函数和链接库,这项技术被广泛使用。使用IDAPython,我们能够很容易的解决这个很有挑战性的问题。

    背景

    逆向人员经常在shellcode里遇到名字被混淆的函数。 总体来说这个过程比较简单:代码运行之后会在初始化阶段加载kernel32.dll链接库,然后继续用这个加载的映像来标识和存储LoadLibraryA 的函数,而这个函数又会去加载附加的链接库和函数 。

    这项特殊的技术使用hash算法来标识一个函数,该hash算法一般使用CRC32, 其他变种中ROR13也经常被使用。

    在逆向恶意软件的过程中,我们找到下面这段代码:

    在上面的例子中, 我们可以通过找到包含0xEDB88320来快速标识出使用CRC32算法的地方:

    现在算法和函数已经被标识出来了,我们可以使用交叉引用来看看这个函数被调用了多少次。

    在这个特殊的样本中,这个函数被调用了190次。而我们不想手动的去解码这些hash值,那么像上一部分一样,我们可以使用IDAPython来简化我们的工作。

    编写IDAPython脚本

    第一步其实没有使用到IDAPython, 但是用到了Python。 为了标识出hash值与函数的匹配关系,我们需要生成一张微软的windows操作系统最常使用函数的hash值表。为了达成这个目的,我们可以抓取windows操作系统常用的链接库列表,然后遍历里面的函数。

    def get_functions(dll_path):
      pe = pefile.PE(dll_path)
      if ((not hasattr(pe, 'DIRECTORY_ENTRY_EXPORT')) or (pe.DIRECTORY_ENTRY_EXPORT is None)):
        print "[*] No exports for %s" % dll_path
        return []
      else:
        expname = []
        for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:
          if exp.name:
            expname.append(exp.name)
        return expname

    我们可以用下面代码来计算这些函数的CRC32的值。

    def calc_crc32(string): 
      return int(binascii.crc32(string) & 0xFFFFFFFF)

    最后,我们将结果写道JSON格式的文件中,命名为output.json。这些JSON数据包含了一个很大的字典,格式如下:

    HASH => NAME

    完成的脚本代码下载链接

    https://github.com/pan-unit42/public_tools/blob/master/ida_scripts/gen_function_json.py

    生成了这个文件之后,我们回到IDA中,剩下的脚本将会在这里完成。 首先我们的脚本会从之前生成的output.json文件中读取JSON数据。 不幸的是,JSON对象不支持整数型的键值,所以数据加载之后,我们将键值转换为整数类型来替代字符串类型。

    for k,v in json_data.iteritems():
      json_data[int(k)] = json_data.pop(k)

    数据完全载入后,我们创建一个新的枚举类型(enumeration)来存储‘hash值-函数名’的映射。(更多关于的枚举类型的资料与工作原理,可以阅读下面的这个教程

    http://www.cprogramming.com/tutorial/enum.html)

    使用枚举类型,我们能够将一个整数值(如CRC32)与字符串(如函数名)建立映射关系。为了在IDA中建立新的枚举,我们将使用到AddEnum()函数。为了让脚本能处理更多的情况,我们先使用GetEnum()函数来校验枚举是否已经存在。

    enumeration = GetEnum("crc32_functions")
    if enumeration == 0xFFFFFFFF:
      enumeration = AddEnum(0, "crc32_functions", idaapi.hexflag())

    这个枚举以后会被修改。 下一步就是找到hash转换函数的交叉引用。这个跟第一部分提到的很相似。当分析参数如何传递到函数中时, 我们可以看到CRC32格式的hash值被作为第二个参数。

    我们将遍历函数调用前面的指令,找到第二个push指令的使用处。一旦找到,我们将会将CRC32的hash值与之前从output.json里面读取出来的JSON数据进行匹配,看是否有匹配的函数名。

    for x in XrefsTo(load_function_address, flags=0):
        current_address = x.frm
        addr_minus_20 = current_address-20
        push_count = 0
        while current_address >= addr_minus_20:
          current_address = PrevHead(current_address)
          if GetMnem(current_address) == "push":
            push_count += 1
            data = GetOperandValue(current_address, 0)
            if push_count == 2:
              if data in json_data:
                name = json_data[data]

    在下面这个步骤中,我们使用 AddConstEx()函数将CRC32 hash值与函数名加入到之前创建的枚举中。

    AddConstEx(enumeration, str(name), int(data), -1)

    一旦数据被加入到枚举中, 我们就能够将CRC32 hash值转换为对应的枚举名。 下面两个函数能够用来获取枚举的第一个实例,以及获取转换为枚举的数据的地址。

    def get_enum(constant):
      all_enums = GetEnumQty()
      for i in range(0, all_enums):
        enum_id = GetnEnum(i)
        enum_constant = GetFirstConst(enum_id, -1)
        name = GetConstName(GetConstEx(enum_id, enum_constant, 0, -1))
        if int(enum_constant) == constant: return [name, enum_id]
        while True:
          enum_constant = GetNextConst(enum_id, enum_constant, -1)
          name = GetConstName(GetConstEx(enum_id, enum_constant, 0, -1))
          if enum_constant == 0xFFFFFFFF:
            break
          if int(enum_constant) == constant: return [name, enum_id]
      return None
      
    def convert_offset_to_enum(addr):
      constant = GetOperandValue(addr, 0)
      enum_data = get_enum(constant)
      if enum_data:
        name, enum_id = enum_data
        OpEnumEx(addr, 0, enum_id, 0)
        return True
      else:
        return False

    这个枚举转换发生之后,我们将会关注到函数后面DWORD加载的地址。

    我们将会从函数开始往后遍历,去找到将eax的值移动到DWORD的指令。找到之后,我们会将DWORD值重命名为函数名。为了避免命名冲突,我们将会使用’d_’来作为名字前缀。

    address = current_address
    while address <= address_plus_30:
      address = NextHead(address)
      if GetMnem(address) == "mov":
        if 'dword' in GetOpnd(address, 0) and 'eax' in GetOpnd(address, 1):
          operand_value = GetOperandValue(address, 0)
          MakeName(operand_value, str("d_"+name))

    将这几个步骤结合在一起,就可以将之前不可读的反汇编代码用更容易理解的形式替换。

    当我们查看用来存储这些信息的DWORDs时,我们将会得到下面这样的函数名列表。这些数据可以被用来进行恶意软件的静态分析。

    完整的IDAPython 脚本代码:

    https://github.com/pan-unit42/public_tools/blob/master/ida_scripts/crc32_conversion.py

    总结:

    我们又一次用使用IDAPython解决了一个困难的问题(将190处CRC32 hash值转换为有意义的函数名)。枚举结构在这个问题的解决过程中起到了非常重要的作用。IDAPython能够很方便的操作枚举变量,进行创建修改,为我们节省大量的时间。另外的,当我们在逆向其他样本遇到相同问题的时候,这些枚举数据可以导出或导入到IDA项目中。

    * 原文链接:researchcenter.paloaltonetworks,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

  • 相关阅读:
    HDU 4539郑厂长系列故事――排兵布阵(状压DP)
    HDU 2196Computer(树形DP)
    HDU 4284Travel(状压DP)
    HDU 1520Anniversary party(树型DP)
    HDU 3920Clear All of Them I(状压DP)
    HDU 3853LOOPS(简单概率DP)
    UVA 11983 Weird Advertisement(线段树求矩形并的面积)
    POJ 2886Who Gets the Most Candies?(线段树)
    POJ 2828Buy Tickets
    HDU 1394Minimum Inversion Number(线段树)
  • 原文地址:https://www.cnblogs.com/blacksunny/p/7300246.html
Copyright © 2011-2022 走看看