原文: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
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!