IDAPython是IDA的一个功能强大的扩展特性,对外提供了大量的IDA API调用。另外,还能在使用python 脚本语言的过程中获得能力提升,所以我强烈推荐所有的逆向工程师使用它。

然而不幸的是,除了下面这几项,关于IDAPython的信息和教程实在太少了。

· “The IDA Pro Book” by Chris Eagle
· “The Beginner’s Guide to IDAPython” by Alex Hanel
· “IDAPython Wiki” by Magic Lantern

为了增加IDAPython相关的教程资料,在该系列中我将会提供我写的一些有趣的实例代码。 而在第一部分,我会通过编写脚本来解码一个恶意软件里面的大量被混淆字的符串。

背景

在逆向恶意样本的过程中,我遇到了下面这个函数:

基于过去的经验,我觉得这个函数应该是用来解密二进制数据的。这个函数的交叉引用次数证明了我的猜测:

正如图2所示,这个特殊的函数被调用了116次。这个函数的每一次调用,都有一个二进制数据对象通过ESI寄存器作为参数传入。

通过这一分析,我更加确定这个函数是恶意软件运行时用来解密字符串的。面对这一情况,我可以选择下面这几种解决方案:

1. 手动解密并重命名这些被混淆的字符串
2. 运行这些样本,遇到这些字符串的时候进行重命名
3. 编写一个脚本来解密并重命名这些字符串

如果恶意软件只是解密少量的字符串,我会选择第一种或者第二种方案。然而,正如我们前面了解到的,这个函数被调用了116次,所以编写一个脚本似乎更有意义。

编写IDAPYTHON脚本

解决混淆字符串问题的第一个步骤是找到并重写解密函数。幸运的是,这里的解密函数比较简单。这个函数简单的将二进制数组中的第一个字符与剩下的数据逐字节的进行异或。

E4 91 96 88 89 8B 8A CA 80 88 88

在上面这个例子中,取出0XE4跟剩下的其他数据进行异或。解密的结果是’urlmon.dll’。我们可以用python这样实现:

def decrypt(data):
  length = len(data)
  c = 1
  o = ""
  while c < length:
    o += chr(ord(data[0]) ^ ord(data[c]))
    c += 1
  return o

运行这段代码,我们得到了我们预期的结果

>>> from binascii import *
>>> d = unhexlify("E4 91 96 88 89 8B 8A CA 80 88 88".replace(" ",''))
>>> decrypt(d)
'urlmon.dll'

接下来就是找出代码中引用了解密函数的地方,提取作为参数出入的数据。 通过IDA找到函数的引用比较简单,IDA提供的API 函数XrefsTo()完美的解决了这个问题。下面这个脚本中,我将地址硬编码到解密脚本中。 下面的代码能够找出解密函数的引用地址。下面这个测试,我简单的将地址用16进制的格式打印出来。

for addr in XrefsTo(0x00405BF0, flags=0):
  print hex(addr.frm)
  
Result:
0x401009L
0x40101eL
0x401037L
0x401046L
0x401059L
0x40106cL
0x40107fL
<truncated>

从交叉引用处识别参数并提取原始数据稍微复杂一点,但显然不是不可能的。首先我们要得到字符串解密函数调用点之前最近的一个 ‘mov esi, offset unk_??’指令的偏移地址。为了达到这个目的,我们会对字符串解密函数的每一处调用,逐条指令的回溯去查找 ‘mov esi, offset [addr]’ 指令。我们可以使用GetOperandValue()函数(API)来获取真正的偏移地址。

下面是代码实现:

def find_function_arg(addr):
  while True:
    addr = idc.PrevHead(addr)
    if GetMnem(addr) == "mov" and "esi" in GetOpnd(addr, 0):
      print “We found it at 0x%x” % GetOperandValue(addr, 1)
      break
      
Example Results:
Python>find_function_arg(0x00401009)
We found it at 0x418be0

现在我们只要简单的将偏移地址处的字符串提取出来。 通常我们会使用GetString()函数,然而,由于混淆过的字串是原始的二进制数据,这个函数无法得到我们期望的结果。 所以我们通过逐字节的重复读取,直到遇到null(0×00)结束符为止。

下面是代码实现:

def get_string(addr):
  out = ""
  while True:
    if Byte(addr) != 0:
      out += chr(Byte(addr))
    else:
      break
    addr += 1
  return out

接下来就是将之前实现的功能整合到一起:

def find_function_arg(addr):
  while True:
    addr = idc.PrevHead(addr)
    if GetMnem(addr) == "mov" and "esi" in GetOpnd(addr, 0):
      return GetOperandValue(addr, 1)
  return ""
  
def get_string(addr):
  out = ""
  while True:
    if Byte(addr) != 0:
      out += chr(Byte(addr))
    else:
      break
    addr += 1
  return out
  
def decrypt(data):
  length = len(data)
  c = 1
  o = ""
  while c < length:
    o += chr(ord(data[0]) ^ ord(data[c]))
    c += 1
  return o
  
print "[*] Attempting to decrypt strings in malware"
for x in XrefsTo(0x00405BF0, flags=0):
  ref = find_function_arg(x.frm)
  string = get_string(ref)
  dec = decrypt(string)
  print "Ref Addr: 0x%x | Decrypted: %s" % (x.frm, dec)
  
Results:
[*] Attempting to decrypt strings in malware
Ref Addr: 0x401009 | Decrypted: urlmon.dll
Ref Addr: 0x40101e | Decrypted: URLDownloadToFileA
Ref Addr: 0x401037 | Decrypted: wininet.dll
Ref Addr: 0x401046 | Decrypted: InternetOpenA
Ref Addr: 0x401059 | Decrypted: InternetOpenUrlA
Ref Addr: 0x40106c | Decrypted: InternetReadFile
<truncated>

我们可以不用运行恶意软件也能看到所有加密后的字符串。下一个步骤我们可以将解密后的字符串以注释的形式写到引用处,让字符串明文与密文同时存在,这样就很便于分析了。我们使用 MakeComm() 函数来实现这一功能。将下面这两行代码加入到上面代码print语句的后面:

MakeComm(x.frm, dec)
MakeComm(ref, dec)

如下图所示,我们能看到的,通过这一额外的步骤,我们可以结合交叉引用看得更清楚。现在我们能够很容易的找到被引用的特殊字符串。

另外的,通过反编译,我们可以看到解密后的字符串作为注释存在与代码中。

总结

通过使用IDAPython,我们完成一个困难的任务—恶意二进制样本中的161个加密字符串的解密。正如我们看到的,IDAPython对于逆向工程来说是一款强大的工具,简化任务,节省宝贵的时间。

*原文链接:researchcenter.paloaltonetworks,东二门陈冠希/编译,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

IDApython教程(一)的更多相关文章

  1. IDApython教程(五)

    我们继续IDAPython让生活更美好序列,这一部分我们解决逆向工程师日常遇到的问题:提取执行的内嵌代码. 恶意软件会用各种方式存储内嵌可执行代码,有些恶意软件将内嵌代码加到文件附加段,包括PE资源区 ...

  2. IDAPython教程(二)

    继续我们的主题—使用IDAPython 让逆向工程师的生活变得更美好. 这一部分,我们将着手处理一个非常常见的问题:shellcode和恶意软件使用hash算法混淆加载的函数和链接库,这项技术被广泛使 ...

  3. IDApython教程(四)

    前三部分已经验证了用IDAPython能够让工作变的更简单,这一部分让我们看看逆向工程师如何使用IDAPython的颜色和强大的脚本特性. 分析者经常需要面对越来越复杂的代码,而且有时候无法轻易看出动 ...

  4. IDAPython教程(三)

    在过去两个部分中,我们已经讨论了使用IDAPython让逆向工程更容易一些.这一部分我们来看一下条件断点. 当在IDA中调试时,分析者经常会遇到希望可以在一个特殊的地址中断下来的情况,但这只有在一些特 ...

  5. IDAPython安装

    转载:All Right   (本人没有测试过) 关于IDAPython的安装教程网上的资料非常少,也不是很详细,我费了好长时间才装好,现在和大家分享一下. 注意事项 下面几点关系到安装是否成功 ID ...

  6. Angular2入门系列教程7-HTTP(一)-使用Angular2自带的http进行网络请求

    上一篇:Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数 感觉这篇不是很好写,因为涉及到网络请求,如果采用真实的网络请求,这个例子大家拿到手估计还要自己写一个web ...

  7. Angular2入门系列教程6-路由(二)-使用多层级路由并在在路由中传递复杂参数

    上一篇:Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数 之前介绍了简单的路由以及传参,这篇文章我们将要学习复杂一些的路由以及传递其他附加参数.一个好的路由系统可以使我们 ...

  8. Angular2入门系列教程5-路由(一)-使用简单的路由并在在路由中传递参数

    上一篇:Angular2入门系列教程-服务 上一篇文章我们将Angular2的数据服务分离出来,学习了Angular2的依赖注入,这篇文章我们将要学习Angualr2的路由 为了编写样式方便,我们这篇 ...

  9. Angular2入门系列教程4-服务

    上一篇文章 Angular2入门系列教程-多个组件,主从关系 在编程中,我们通常会将数据提供单独分离出来,以免在编写程序的过程中反复复制粘贴数据请求的代码 Angular2中提供了依赖注入的概念,使得 ...

随机推荐

  1. [SCOI2014]方伯伯的OJ(线段树)

    方伯伯正在做他的Oj.现在他在处理Oj上的用户排名问题.Oj上注册了n个用户,编号为1-n“,一开始他们按照编号排名. 方伯伯会按照心情对这些用户做以下四种操作,修改用户的排名和编号: 1.操作格式为 ...

  2. SHOI2008仙人掌图(tarjan+dp)

    Solution 好题啊没的说. 本题需要求出仙人掌的直径,但仙人掌是一个带有简单环的一张图无法直接用树形dp求解,但它有一个好东西就是没有类似环套环的东西,所以我们在处理时就方便了一些. 思路:ta ...

  3. Jupyter ~ 像写文章般的 Coding (附:同一个ipynb文件,执行多语言代码)

    前面用了好久Notebook来交互式编程了,这次说说几个其他的选项: Notebook Markdown 这次选Markdown模式(关于Markdown基础可以看之前写的Markdown Base) ...

  4. 洛谷P1041 传染病控制

    解:搜索...... 我们可以每次选择分支少的搜索,或者说,贪心的搜索当前更优的决策. 每一层把能剪的点搞出来,按照度数/SIZ排序,然后依次搜索.加个最优化剪枝就完事了. #include < ...

  5. 【【洛谷P2678 跳石头】——%%%ShawnZhou大佬】

    {dalao传送门} 这道题如果要使用暴力搜索直接求解会严重超时.实际上,我们可以发现,这个所谓的最短跳跃距离显然不能超过一个范围,而这个范围题目上已经给了出来.也就是说,答案是有一个确定的范围限制的 ...

  6. 真的无语, 今天遇到一个奇葩的事情: http 会话劫持

    今天一个用户反应说软件一运行就提示错误,然后关闭. 然后用户截屏发给我看了一下,我一看,无语了. 在软件中显示的正常一段文字说明尾部出现 <script src="http://ad. ...

  7. 第十八篇-Spinner下拉列表的使用

    效果图 MainActivity.java package com.example.aimee.spinnertest; import android.support.v7.app.AppCompat ...

  8. C connect实现Timeout效果(Linux)

    C connect函数是阻塞的,现要实现非阻塞式的connect. int SocketClient::connectTimeOut(const int &connect_fd, const ...

  9. c#线程2

    多线程中很有可能存在争夺一个变量资源而产生死锁或者不被期望的结果. 测试类; class TestClass { ; private object objLock = new object(); pu ...

  10. 利用/dev/urandom文件创建随机数

    1:/dev/urandom和/dev/random是什么 这两个文件记录Linux下的熵池,所谓熵池就是当前系统下的环境噪音,描述了一个系统的混乱程度,环境噪音由这几个方面组成,如内存的使用,文件的 ...