实验目的
(1)完成教材上lab5的题目;
(2)在样本分析结果的基础上,编写样本的Yara检测规则。
(3)尝试编写IDA Python脚本来辅助样本分析。
实验原理
IDA Python 是 IDA Pro 的强大脚本接口,它允许我们通过 Python 语言与 IDA 的各种分析功能进行交互。IDA Python 提供了丰富的 API 和库来辅助逆向工程和二进制分析。以下是一些常用的 IDA Python 特有函数和库:
1. idc 模块
idc
模块包含了 IDA 中很多通用的低层次函数,例如操作字节、段、名字和标记等。
-
idc.GetDisasm(ea)
: 返回指定地址的反汇编字符串。disasm = idc.GetDisasm(0x401000) print(disasm) # 输出该地址的反汇编指令
-
idc.set_name(ea, name, flags=SN_CHECK)
: 设置指定地址的名称。可以用来标记函数或数据的名称。idc.set_name(0x401000, "start_function")
-
idc.LocByName(name)
: 返回给定名称的地址,如果名称不存在则返回BADADDR
。address = idc.LocByName("main") print(hex(address))
-
idc.MakeCode(ea)
: 强制将指定地址处的字节转换为代码。idc.MakeCode(0x401000)
2. idautils 模块
idautils
模块提供了多种遍历函数、指令、字符串、函数等对象的工具。
-
idautils.Functions(start=0, end=BADADDR)
: 遍历指定地址范围内的所有函数,返回函数的起始地址。for func_addr in idautils.Functions(): print(hex(func_addr)) # 输出每个函数的起始地址
-
idautils.Heads(start, end)
: 遍历指定范围内的所有指令或数据项。for head in idautils.Heads(0x401000, 0x401100): print("Address: {}, Instruction: {}".format(hex(head), idc.GetDisasm(head)))
-
idautils.Strings()
: 遍历 IDA 中提取的所有字符串,可以用于查找硬编码的字符串。for string in idautils.Strings(): print("String: {}, Address: {}".format(str(string), hex(string.ea)))
-
idautils.XrefsTo(ea, flags=0)
: 返回引用指定地址的交叉引用(Xrefs)。可以用来查找对某个函数或变量的调用点。for xref in idautils.XrefsTo(0x401000): print("Reference from: {}".format(hex(xref.frm)))
3. idaapi 模块
idaapi
模块提供了更高层次的接口,允许你与 IDA 的核心功能(如图形界面、调试器、数据流分析等)交互。
-
idaapi.get_func(ea)
: 返回指定地址处的函数对象,如果该地址不是函数的一部分则返回None
。func = idaapi.get_func(0x401000) if func: print("Function start: {}, end: {}".format(hex(func.start_ea), hex(func.end_ea)))
-
idaapi.FlowChart(func)
: 创建一个函数的控制流图(CFG),可以遍历基本块(basic blocks)和分支逻辑。func = idaapi.get_func(0x401000) flowchart = idaapi.FlowChart(func) for block in flowchart: print("Basic Block: start {}, end {}".format(hex(block.start_ea), hex(block.end_ea)))
-
idaapi.decompile(ea)
: 对指定地址处的函数进行反编译,返回伪代码。需要安装 Hex-Rays 插件。cfunc = idaapi.decompile(0x401000) if cfunc: print(cfunc)
-
idaapi.add_hotkey(hotkey, callback)
: 为指定的快捷键添加回调函数。可以方便地扩展 IDA 的功能。def my_hotkey(): print("Hotkey pressed!") idaapi.add_hotkey("Ctrl-Shift-A", my_hotkey)
4. ida_bytes 模块
ida_bytes
模块提供了与内存字节相关的函数,方便对程序的数据部分进行处理。
-
ida_bytes.get_byte(ea)
: 返回指定地址处的字节值。byte_value = ida_bytes.get_byte(0x401000) print("Byte at 0x401000: {}".format(hex(byte_value)))
-
ida_bytes.patch_byte(ea, value)
: 修改指定地址处的字节值(即打补丁)。ida_bytes.patch_byte(0x401000, 0x90) # 将地址 0x401000 处的字节改为 0x90 (NOP)
5. ida_search 模块
ida_search
模块允许我们执行模式匹配和特定内容的搜索。
-
ida_search.find_text(ea, flag, regex)
: 搜索匹配给定正则表达式的文本,返回匹配的位置。addr = ida_search.find_text(0x401000, idaapi.SEARCH_DOWN, "main") if addr != idc.BADADDR: print("Found 'main' at: {}".format(hex(addr)))
-
ida_search.find_bytes(ea, flag, bytes)
: 搜索指定的字节序列,返回匹配的地址。addr = ida_search.find_bytes(0x401000, idaapi.SEARCH_DOWN, "90 90 90") if addr != idc.BADADDR: print("Found byte sequence at: {}".format(hex(addr)))
实验过程
Q1
DllMain的地址是什么?
- 打开IDA,映入眼帘的就是DLLMain的地址:‘.text:1000D02E’
Q2
使用Imports窗口并浏览到gethostbyname, 导入函数定位到什么地址?
- 经过寻找,发现在idata:100163CC
Q3
有多少函数调用了gethostbyname?
- 查看交叉引用图
- 可以看出有五条线直接指向它,说明有五个函数调用了。
- 且一共被引用九次
Q4
将精力集中在位于0x10001757处的对gethostbyname的调用,你能找出那个DNS请求将被触发吗?
- 跳转到该处
- 可以看到,在调用函数之前,eax被压入栈中
- 根据上面的代码可以计算出eax的值:1001904D
-
1001904D找不到有意义的值,但可以发现10019040处是一个字符串指针,指向
-
[This is RDO]pics.praticalmalwareanalysis.com
-
跳过00Dh位之后,刚好是
-
pics.praticalmalwareanalysis.com
Q5
IDA Pro识别了在0x10001656处的子过程中的多少个局部变量?
- 定位过去
- 数了一下,有23个局部变量
Q6
IDA Pro识别了在0x10001656处的子过程中的多少个参数?
- 一个参数在子过程中有
arg_0
这个参数lpThreadParameter
Q7
使用Strings窗口,来在反汇编中定位字符串\cmd.exe /c。 它位于哪?
- 在view->subview中找到字符串窗口
- 找到了
- 定位过去
在xdoors_d:10095B34
Q8
在引用\cmd.exe /c的代码所在的区域发生了什么?
- 引用\cmd.exe /c的代码所在的区域:
- 查看aCmd_exeC的引用图
- 这里将其压入栈中,然后跳转:
- 跳转到这里:
可以看出将aCmd_exeC与其他字符串链接,应该是用于打开命令行的指令
- 综上推测,可能用于调用命令行
Q9
在同样的区域,在0x100101C8处,看起来好像dword_1008E5C4是一个全局变量,它帮助决定走哪条路径。那恶意代码是如何设置dword_1008E5C4的呢?(提示:使用dword_1008E5C4的交叉引用。)
- 跳转到0x100101C8:
- 查看交叉引用:
是将一个函数调用的结果赋值了
- 查看调用的函数:
返回值为 0 或 1,取决于平台 ID 是否为 Windows NT 系列。
Q10
在位于0x1000FF58处的子过程中的几百行指令中,一系列使用memecmp来比较字符串的比较。如果对robotwork的字符串比较是成功的(当memcmp返回0),会发生什么?
- 查看0x1000FF58处的子过程
- 找到robot work的地方
- 接着查看成功分支:
首先压入
robotwork
字符串指针,紧接着压入eax
,调用memcmp,如果两个数相同,返回0,然后add eap, 0Ch
,0Ch是12d,也是4(字节)*3(个),因为push后面跟的是立即数,所以一个数占4字节,然后offset也是4个字节,所以,一开始的push 9,和后面的两次push,加起来一共是3次,所以这里回收了这3个一共12字节的空间
test eax, eax
,如果eax为0,则ZF置为1,JnZ不跳转
所以当字符串比较成功(当memcmp
返回0)时,JNZ不跳转,程序继续按从上到下的顺序执行,下面要执行的就是sub_100052A2
可以看到此处对注册表中的注册表项 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion
的值进行了查询,而后通过 push [ebp+s] 的命令将其返回给 socket 套接字用于进一步通讯。
Q11
PSLIST导出函数做了什么?
- 定位到PSLIST
一来就调用
sub_100036C3
函数
- 定位到
sub_100036C3
函数
可以看出,总体上是在进行操作系统的版本查询与比较。
-
之后调用的代码块:
CreateToolhelp32Snapshot 函数,从相关字符串和 API 调用来看,用于获得一个进程列表,通过 send 将进程列表通过 socket 发送。
、
Q12
使用图模式来绘制出对sub_10004E79的交叉引用图。当进入这个函数时,那个API函数可能被调用?仅仅基于这些API函数,你会如何重命名这个函数?
- 根据图中的API,
- GetSystemDefaultLangID
- send
- 可以将send改名为SendSystemDefaultLangID
Q13
DLLMain直接调用了多少个Windows API? 多少个在深度为2时被调用?
- 在左侧找到DLLMain跳转
因为我按G跳转一直跳转不过去
- 查看其交叉引用表:
太复杂了
- 从view->graph->user xref chart 打开
调整深度为1和2
Q14
在0x10001358处,有一个对Sleep(一个使用一个包含要睡眠的毫秒数的参数的API函数)的调用。顺着代码向后看,如果这段代码执行,这个程序会睡眠多久?
- 跳转至0x10001358
3E8h
是十进制1000
,atoi
调用结果乘以1000
eax
是off_10019020
赋来的,进入off_10019020
,'[This is CTI]30'
,再偏移0Dh
,指向30
.
程序休眠时间为30X1000=30000
毫秒 30秒
Q15
在0x10001701处是一个对socket的调用。它的3个参数是什么?
- 跳转至0x10001701
显而易见:IDA 中显示的三个参数名:af、type、protocol。
Q16
使用MSDN页面的socket和IDA Pro中的命名符号常量,你能使参数更加有意义吗?在你应用了修改以后,参数是什么?
-
在MSDN中找到了原型描述:
-
af:指定地址族。常见的地址族有 AF_INET(IPv4)、AF_INET6(IPv6)等。2 指的是AF_INET。用来设置** IPv4 socket**。
-
type:指定套接字的类型。常见的类型有 SOCK_STREAM、SOCK_DGRAM 等。**1 指的是 SOCK_STREAM。
-
protocol:指定协议。6 指的是IPPROTO_TCP 表示 TCP 协议,这个 socket 会被配置为基于 IPv4 的 TCP 连接在HTTP 中。
-
SOCKET WSAAPI socket(
int af,
int type,
int protocol
);
- 在数字上右键,Use standard symbolic constant,分别替换成实际的常量名,如图:
Q17
搜索in指令(opcode 0xED)的使用。这个指令和一个魔术字符串VMXh用来进行VMware检测。这在这个恶意代码中被使用了吗?使用对执行in指令函数的交叉引用,能发现进一步检测VMware的证据吗?
- 进行搜索
Search > Sequence of Bytes>Find All Occurrences
只有这一条用了in
- 果然发现了VMXh
- 查看这个代码段的交叉引用
- InstallSB,InstallSA,InstallPT中的字符串可以说明
Q18
将你的光标跳转到0x1001D988处,你发现了什么?
- 发现了一堆字符
可能是加密了,或者是另一个字符集的。
Q19
.如果你安装了IDA Python插件(包括IDA Pro的商业版本的插件),运行Lab05-01.py,一个本书中随恶意代码提供的IDA Pro Python脚本,(确定光标是在Ox1001D988处。)在你运行这个脚本后发生了什么?
- 在原来的winXP虚拟机上发现运行不了IDA python,于是在win10上运行
运行之后,发现字符发生了变化,变成可阅读的字符:xdoor is this backdoorstring decoded forPractical Malware Analysis Lab :)1234
再运行一次有就变回去了
Q20
将光标放在同一位置,你如何将这个数据转成一个单一的 ASCII 字符串?
- 放上去就变成字符串了
xdoor is this backdoorstring decoded forPractical Malware Analysis Lab :)1234
Q21
使用一个文本编辑器打开这个脚本。它是如何工作的?
- 打开阅读代码
- 就是一个简单的异或进行解密
Yara
字符串查看
有很多,只展示部分
规则编写
根据字符串中一些可以字符串编写规则
值得一提的是:‘UU-1::’,27h,‘u<&u!=<&u746>1::’,27h,‘yu&!’,27h,‘<;2u106:101u3:’,27h,'u’是加密之前的xdoor is this backdoorstring decoded forPractical Malware Analysis Lab :)1234
rule find
{
strings:
$string1 = "pics.praticalmalwareanalysis.com"
$string2 = "Hi,Master"
$string3 = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"
$string4 = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion"
$string5 = "\\cmd.exe /c"
$string6 = "'UU-1::',27h,'u<&u!=<&u746>1::',27h,'yu&!',27h,'<;2u106:101u3:',27h,'u'"
condition:
all of them
}
规则验证
IDA PYTHON
简单示例
在网上查询资料了,我编写了一些简单的功能,并进行了调试和完善
- 我发现的一点事,有很多错误都是因为python版本过低引起的,在编程时注意一些特性的兼容性,可以有效避免这些错误。
- 程序代码
# -*- coding: utf-8 -*-
import idaapi
import idautils
import idc
# 1. 列出所有函数并打印其名称和地址
def list_functions():
print("Listing all functions:")
for func_addr in idautils.Functions():
func = idaapi.get_func(func_addr) # 获取函数对象
if func:
func_name = idc.Name(func_addr) # 使用 idc.Name 获取函数名称
print("Function: {0}, Address: {1}".format(func_name, hex(func_addr))) # 明确指定字段编号
# 2. 提取样本中的所有字符串
def extract_strings():
print("\nExtracting all strings:")
for string in idautils.Strings():
print("String: {0}, Address: {1}".format(str(string), hex(string.ea))) # 明确指定字段编号
# 3. 查找网络相关API调用及其交叉引用
def find_network_apis(api_name):
print("\nFinding references to {0}:".format(api_name)) # 明确指定字段编号
api_addr = idc.LocByName(api_name) # 使用 LocByName 来获取 API 的地址
if api_addr == idc.BADADDR:
print("API {0} not found!".format(api_name))
return
# 打印该API的交叉引用
for xref in idautils.XrefsTo(api_addr):
print("Reference to {0} at {1}".format(api_name, hex(xref.frm))) # 明确指定字段编号
# 主函数执行
def main():
list_functions() # 列出所有函数
extract_strings() # 提取样本中的字符串
find_network_apis("gethostbyname") # 查找 gethostbyname 的引用
if __name__ == "__main__":
main()
- 验证:
确实是正确的
正式编写
-
以上实现的简单功能在IDA中已经实现,并未带来便利,所以我根据一些需求,编写了新的python脚本
我的想法是自动提取所有字符串,为其打造量身定做的yara rule
-
第一次成功运行之后,发现提取的字符串有许多不适合写入yara规则的,进行了一些筛选
跳过包含多个连续空格的字符串:
- 使用正则表达式
re.search(r'\s{2,}', clean_string)
来检测是否有两个或以上的连续空格。此类字符串常常是无效或多余的,不适合写入 Yara 规则。
去除字符串中的多余空格:
- 我们用
re.sub(r'\s+', ' ', clean_string)
去除多余的空格,使字符串变得更加规范。在实际分析中,字符串中多余的空白字符会影响匹配的效率与准确性。
字符串长度限制:
- 长度过短的字符串(小于 4 个字符)可能意义不大,长度过长的字符串(超过 50 个字符)通常包含多余的信息,因而都被过滤掉。
过滤特殊字符:
- 跳过包含
'"'
、'\\'
、\n
、\t
等特殊字符的字符串,以避免生成的 Yara 规则中出现非法字符,确保规则的语法正确。
跳过纯数字或十六进制数字串:
- 使用
clean_string.isdigit()
检测纯数字串,并使用re.match(r'0x[0-9a-fA-F]+', clean_string)
跳过十六进制数字。这些数字串一般没有足够的特征性,容易导致误判。
- 使用正则表达式
# -*- coding: utf-8 -*-
import idaapi
import idautils
import idc
import string
import re
# 提取所有字符串
def extract_strings():
print("\nExtracting all strings:")
strings = []
for string_item in idautils.Strings():
raw_string = str(string_item)
clean_string = ''.join([c for c in raw_string if c in string.printable])
clean_string = clean_string.strip()
# 跳过包含两个或两个以上连续空格的字符串
if re.search(r'\s{2,}', clean_string):
continue
clean_string = re.sub(r'\s+', ' ', clean_string) # 去除多余空格
# 过滤条件
if len(clean_string) < 4 or len(clean_string) > 50: # 字符串长度限制
continue
if any(c in clean_string for c in ['"', '\\', '\n', '\t', '\r']): # 跳过包含特殊字符的字符串
continue
if clean_string.isdigit() or re.match(r'0x[0-9a-fA-F]+', clean_string): # 跳过十六进制和数字串
continue
if re.match(r'^\s*$', clean_string): # 跳过空字符串
continue
strings.append((clean_string, hex(string_item.ea)))
print("String: {0}, Address: {1}".format(clean_string, hex(string_item.ea)))
return strings
# 自动生成 Yara 规则
def generate_yara_rules(strings):
rule = "rule auto_generated_rule {\n"
rule += " strings:\n"
for i, (string, _) in enumerate(strings):
rule += ' $s{0} = "{1}"\n'.format(i, string)
rule += " condition:\n"
rule += " all of them\n"
rule += "}"
# 保存规则
with open("auto_generated_rule.yar", "w") as f:
f.write(rule)
print("\nYara rule generated and saved as 'auto_generated_rule.yar'")
# 主函数调用
def main():
strings = extract_strings() # 提取字符串
generate_yara_rules(strings) # 生成Yara规则
if __name__ == "__main__":
main()
验证
- 经过一番调试之后,得到了正确的输出:
- 也生成了yara文件:
实验心得
在本次实验过程中,我收获了许多。
- 我理解了如何使用IDA进行动态分析
- 我学会了IDA python的基础使用,不过在win10上使用时,由于版本过低,出现了许多报错,令人疲惫。
- 使用IDA python生成yara的过程也加深了我对yara的理解。