首页
社区
课程
招聘
[翻译]API Call Tracing - PEfile, PyDbg and IDAPython
发表于: 2012-6-8 22:48 19193

[翻译]API Call Tracing - PEfile, PyDbg and IDAPython

2012-6-8 22:48
19193

原文:http://securityxploded.com/api-call-tracing-with-pefile-pydbg-and-idapython.php#API-CALL-PEfile-PyDbg

API Call Tracing 是非常强大的技术.它可以让我们充分的把握一个可执行文件的运行机制,某些情况下我们只要得到

api的调用日志就可以了解到程序的行为.我经常利用这种自动化分析技术来完成对恶意软件的分析工作.

这篇帖子我将展示一下我所使用的一些方法

以下情况使用这些方法会使效率大大的提高:
1.脱壳
2.程序行为分析
3.找出二进制文件中你所感兴趣的函数

这里,我将使用PyDbg脚本去记录API的调用,最后用IDAPython脚本自动完成一些手工的操作.

API Calls Logging with PEfile & PyDbg

基于以上情形我们需要以下信息来编写我们的脚本:
1.返回地址- 调用这个API位置?
2.API的名字-哪一个函数被调用了?

我们需要在每个API上设置断点,这样的话我们需要API的名字和地址,如果得到API的名称我们就可以去解析他的地址然后

设置断点.有个地址我们就可以直接设置断点,问题是怎么得到API的名称?

这可以用PEfile来解决.我们先枚举可执行文件的导入表然后解析出地址然后通过PyDbg来设置断点.

但这样的话又会有以下问题
1.某些API函数是使用LoadLibrary()加载的(通过导入表得不到地址和名称 译者注).
2.二进制文件被加壳了,导入表是在运行过程中才被创建.

在解决这个问题之前让我们先看看外壳程序在运行时是怎样构建导入表的.

通常会使用LoadLibrary 去加载某个dll,然后用GetProcAddress 获取API的地址.LoadLibrary 和GetProcAddress由

kernel32.dll导出,默认情况下所有的Windows进程都会加载.

所以如果我们中断在GetProcAddress上我们就可以通过此时的堆栈来得到函数的名称,这样的话就可以对每个API设置断

点了.Here I am ignoring the call for GetProcAddress with API Ordinal because it is not a common approach.

不过还有另外一种在运行时构建导入表的方法,以某个恶意软件为代表.

反汇编代码如下

        
        push dword ptr fs:[30h] ; PEB
        pop eax
        mov eax,[eax+0ch] ; LDR
        mov ecx,[eax+0ch] ; InLoadOrderModuleList
        mov edx,[ecx]
        push edx
        mov eax,[ecx+30h] 
'''
Author: Amit Malik
http://www.securityxploded.com
'''

import sys,struct
import pefile
from pydbg import *
from pydbg.defines import *

def log(str):
  global fpp
  print str
  fpp.write(str)
  fpp.write("\n")
  return 
  
def addr_handler(dbg):           
  global func_name
  ret_addr = dbg.context.Eax
  if ret_addr:
    dict[ret_addr] = func_name
    dbg.bp_set(ret_addr,handler=generic)
  return DBG_CONTINUE

def generic(dbg):
  global func_name
  eip = dbg.context.Eip
  esp = dbg.context.Esp
  paddr = dbg.read_process_memory(esp,4)
  addr = struct.unpack("L",paddr)[0]
  addr = int(addr)
  if addr < 70000000:
    log("RETURN ADDRESS: 0x%.8x\tCALL: %s" % (addr,dict[eip])) 
  if dict[eip] == "KERNEL32!GetProcAddress" or dict[eip] == "GetProcAddress": 
    try:
      esp = dbg.context.Esp
      addr = esp + 0x8
      size = 50
      pstring = dbg.read_process_memory(addr,4)
      pstring = struct.unpack("L",pstring)[0]
      pstring = int(pstring)
      if pstring > 500:
        data = dbg.read_process_memory(pstring,size)
        func_name = dbg.get_ascii_string(data)
      else:
        func_name = "Ordinal entry"
      paddr = dbg.read_process_memory(esp,4)
      addr = struct.unpack("L",paddr)[0]
      addr = int(addr)
      dbg.bp_set(addr,handler=addr_handler)
    except:
      pass
  return DBG_CONTINUE


def entryhandler(dbg):
  getaddr = dbg.func_resolve("kernel32.dll","GetProcAddress")  
  dict[getaddr] = "kernel32!GetProcAddress"
  dbg.bp_set(getaddr,handler=generic)
  for entry in pe.DIRECTORY_ENTRY_IMPORT:
    DllName = entry.dll
    for imp in entry.imports:          
      api = imp.name
      address = dbg.func_resolve(DllName,api)
      if address:
        try:
          Dllname = DllName.split(".")[0]
          dll_func = Dllname + "!" + api
          dict[address] = dll_func
          dbg.bp_set(address,handler=generic)
        except:
          pass
  
  return DBG_CONTINUE    

def main():
  global pe, DllName, func_name,fpp
  global dict
  dict = {}
  file = sys.argv[1]
  fpp = open("calls_log.txt",'a')
  pe = pefile.PE(file)
  dbg = pydbg()
  dbg.load(file)
  entrypoint = pe.OPTIONAL_HEADER.ImageBase + pe.OPTIONAL_HEADER.AddressOfEntryPoint
  dbg.bp_set(entrypoint,handler=entryhandler)
  dbg.run()
  fpp.close()

if __name__ == '__main__':
  main()
    RETURN ADDRESS: 0x004030e8  CALL: kernel32!GetModuleHandleA
    RETURN ADDRESS: 0x004030f3  CALL: kernel32!GetCommandLineA
    RETURN ADDRESS: 0x00404587  CALL: kernel32!GetModuleHandleA
    RETURN ADDRESS: 0x00404594  CALL: kernel32!GetProcAddress
    RETURN ADDRESS: 0x004045aa  CALL: kernel32!GetProcAddress
    RETURN ADDRESS: 0x004045c0  CALL: kernel32!GetProcAddress

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 7
支持
分享
最新回复 (9)
雪    币: 693
活跃值: (108)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
2
这个给力,必须顶
2012-6-8 23:09
0
雪    币: 506
活跃值: (70)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
这个好。

补一补
2012-6-9 08:56
0
雪    币: 1844
活跃值: (35)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4
恩,翻译辛苦了,收
2012-6-9 09:06
0
雪    币: 4902
活跃值: (120)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
翻译不容易啊
谢谢了哈……
2012-6-9 14:08
0
雪    币: 347
活跃值: (25)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
好文章,感谢~
2012-6-9 15:32
0
雪    币: 7
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
lz的翻译的这篇文章很有用,但我运行代码的时候出现了下面的错误:
pydbg.pdx.pdx: Failed setting breakpoint at 01012d6c
位于函数main()的这一行:dbg.bp_set(entrypoint,handler=entryhandler)
搜一篇帖子http://www.openrce.org/forums/posts/760说是
pydbg不能设置延期断点,不是太明白,想问lz是怎么解决的?
2013-1-13 15:41
0
雪    币: 284
活跃值: (34)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
好贴,顶起!
2013-1-13 15:49
0
雪    币: 7
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
我从win7换到xp下运行脚本就没有问题了。
2013-1-14 09:36
0
雪    币: 156
活跃值: (27)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
好文章。现在才看到。。。
2013-1-16 14:36
0
游客
登录 | 注册 方可回帖
返回
//