IDAPython 教程2
Using IDAPython to Make Your Life Easier: Part 2
原文:https://unit42.paloaltonetworks.com/using-idapython-to-make-your-life-easier-part-2/
继续使用IDAPython简化逆向工程师生活的主题,我将要解决一个非常普遍的问题:shellcode和使用散列算法混淆加载的函数和库的恶意软件。该技术被广泛使用,分析人员经常会碰到它。使用IDAPython,我们将解决这个具有挑战性的问题并轻松解决它。
背景
反向工程师最常在shellcode中遇到混淆的函数名称。整个过程非常简单。该代码最初将在运行时加载kernel32.dll库。然后,它将继续使用此加载的映像来标识和存储LoadLibraryA函数,该函数用于加载其他库和函数。此特定技术采用了一种哈希算法,该算法用于识别功能。哈希算法通常为CRC32,但是其他变体(例如ROR13)也很常见。
在对一个恶意软件进行反向工程时,我遇到了以下技术:
图1使用CRC32哈希值动态加载恶意软件
在上面的示例中,我们能够快速识别CRC32算法使用的常数0xEDB88320。
图2识别的CRC32算法
现在已经确定了算法和函数,我们可以查看交叉引用的数量来确定调用此函数的次数。在此特定样本中,此函数总共被调用190次。显然,解码所有这些哈希值并在我们的IDA Pro文件中手动重命名不是我们想要执行的操作。因此,我们可以使用IDAPython来简化生活。
IDAPython中的脚本
第一步实际上并没有使用IDAPython,但确实使用了Python。为了确定什么哈希等于什么函数,我们需要在Microsoft Windows操作系统上生成最常见的函数哈希的列表。为此,我们可以简单地获取Windows操作系统使用的常见库的列表,然后遍历它们的函数名。
1
2
3
4
5
6
7
8
9
10
11
|
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哈希算法。
1
2
|
def calc_crc32(string):
return int(binascii.crc32(string) & 0xFFFFFFFF)
|
最后,我们将结果写入JSON格式的文件,我将其命名为“ output.json”。此JSON数据文件包含一个大型字典,使用以下格式:
1
|
HASH => NAME
|
可以在这里找到此脚本的完整副本。
生成此文件后,我们可以返回IDA,在此完成脚本的其余部分。我们脚本的第一步将是从先前创建的“ output.json”中读取JSON数据。不幸的是,JSON对象不支持使用整数作为键值,因此在加载此数据后,我们修改键以表示整数而不是字符串。
1
2
|
for k,v in json_data.iteritems():
json_data[int(k)] = json_data.pop(k)
|
正确加载此数据后,我们将创建一个新的枚举,该枚举将存储哈希到函数名称的映射。(有关枚举及其工作方式的更多信息,建议您阅读本教程。)
使用枚举,我们可以将整数值(例如CRC32哈希)映射到字符串表示形式(例如函数名称)。为了在IDA中创建新的枚举,我们使用AddEnum()函数。为了使脚本更具通用性,我们首先使用GetEnum()函数检查枚举是否已经存在。
1
2
3
|
enumeration = GetEnum("crc32_functions")
if enumeration == 0xFFFFFFFF:
enumeration = AddEnum(0, "crc32_functions", idaapi.hexflag())
|
稍后将修改此枚举。下一步将确定负责将散列转换为函数的函数具有哪些交叉引用。对于阅读过第1部分的人来说,应该看起来很熟悉。在查看如何将函数传递给函数的结构时,我们看到提供了CRC32哈希作为第二个参数。
图3将参数传递给load_function
这样,我们将遍历导致函数调用的前一条指令,寻找push指令的第二个实例。一旦发现,我们将根据先前从output.json加载的JSON数据检查CRC32哈希值,以确保此值具有与其关联的函数名称。
1
2
3
4
5
6
7
8
9
10
11
12
|
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哈希和函数名称添加到先前创建的枚举中。
1
|
AddConstEx(enumeration, str(name), int(data), -1)
|
将此数据添加到枚举后,我们可以将CRC32哈希转换为其枚举名称。以下两个函数可用于获取数字的第一个实例以进行枚举,以及将某个地址处的数据转换为该枚举。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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的方法,该DWORD包含了已加载函数在加载后的地址。
图4加载后将功能地址存储到DWORD
为此,我们将不遍历函数,而是遍历后,寻找将eax移至DWORD的指令。发现此错误后,我们将将此DWORD重命名为正确的函数名称。为避免命名冲突,我们将在名称前加上“ d_”。
1
2
3
4
5
6
7
|
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))
|
综上所述,我们能够将以前的不可读反汇编更新为更具可读性的内容。
图5运行脚本后的更改
现在,当我们查看用于存储此信息的DWORD列表时,将获得实际函数名称的列表。然后,可以使用此数据对所涉及的恶意软件执行其他静态分析。
图6脚本运行后的DWORD命名
完整的IDAPython脚本可在此处找到。
结论
我们再次能够承担一个相当艰巨的任务,为它提供190个函数名的CRC32哈希表示,并使用IDAPython提取有意义的数据。遇到这种问题时,枚举可能是一种强大的机制。使用IDAPython可以轻松地执行创建和修改枚举以及为变量分配枚举的过程,从而节省了宝贵的时间。此外,如果分析人员发现相同的挑战而对另一个样本进行反向工程,则可以将这些枚举导出并导入其他IDA项目。
附录2015年12月31日: 正如 Alex Hanel 在Twitter上向我指出的那样,如果将功能重命名为其实际功能名称,IDA Pro将自动执行参数传播。这为分析人员增加了更多级别的信息,从而使静态分析更加容易。对本博文前面提到的脚本进行简单的修改将使分析师可以执行此操作。如果名称已经存在,只需在名称中添加“ _”或“ _1”即可避免冲突。