恶意代码分析与实战lab5

实验目的

(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’

image-20241013190526256

Q2

使用Imports窗口并浏览到gethostbyname, 导入函数定位到什么地址?

image-20241014094328743

  • 经过寻找,发现在idata:100163CC

Q3

有多少函数调用了gethostbyname?

  • 查看交叉引用图

image-20241014094632003

  • 可以看出有五条线直接指向它,说明有五个函数调用了。
  • 且一共被引用九次

Q4

将精力集中在位于0x10001757处的对gethostbyname的调用,你能找出那个DNS请求将被触发吗?

  • 跳转到该处

image-20241014095157403

image-20241014095231755

  • 可以看到,在调用函数之前,eax被压入栈中
  • 根据上面的代码可以计算出eax的值:1001904D

image-20241014100025690

image-20241014095955356

  • 1001904D找不到有意义的值,但可以发现10019040处是一个字符串指针,指向

  • [This is RDO]pics.praticalmalwareanalysis.com

  • 跳过00Dh位之后,刚好是

  • pics.praticalmalwareanalysis.com

Q5

IDA Pro识别了在0x10001656处的子过程中的多少个局部变量?

  • 定位过去

image-20241014100720586

  • 数了一下,有23个局部变量

Q6

IDA Pro识别了在0x10001656处的子过程中的多少个参数?

  • 一个参数在子过程中有arg_0这个参数 lpThreadParameter

image-20241014100720586

Q7

使用Strings窗口,来在反汇编中定位字符串\cmd.exe /c。 它位于哪?

  • 在view->subview中找到字符串窗口

image-20241014101116237

  • 找到了

image-20241014101310271

  • 定位过去

image-20241014101334184

在xdoors_d:10095B34

Q8

在引用\cmd.exe /c的代码所在的区域发生了什么?

  • 引用\cmd.exe /c的代码所在的区域:

image-20241014101505040

  • 查看aCmd_exeC的引用图

image-20241014101719785

  • 这里将其压入栈中,然后跳转:

image-20241014102237596

  • 跳转到这里:

可以看出将aCmd_exeC与其他字符串链接,应该是用于打开命令行的指令

image-20241014102400145

  • 综上推测,可能用于调用命令行

Q9

在同样的区域,在0x100101C8处,看起来好像dword_1008E5C4是一个全局变量,它帮助决定走哪条路径。那恶意代码是如何设置dword_1008E5C4的呢?(提示:使用dword_1008E5C4的交叉引用。)

  • 跳转到0x100101C8:

image-20241014103105434

  • 查看交叉引用:

是将一个函数调用的结果赋值了

image-20241014103324627

  • 查看调用的函数:

返回值为 0 或 1,取决于平台 ID 是否为 Windows NT 系列。

image-20241014103432208

Q10

在位于0x1000FF58处的子过程中的几百行指令中,一系列使用memecmp来比较字符串的比较。如果对robotwork的字符串比较是成功的(当memcmp返回0),会发生什么?

  • 查看0x1000FF58处的子过程

image-20241014104039905

  • 找到robot work的地方

image-20241014104253874

  • 接着查看成功分支:

首先压入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

image-20241014104811896

可以看到此处对注册表中的注册表项 HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion

的值进行了查询,而后通过 push [ebp+s] 的命令将其返回给 socket 套接字用于进一步通讯。

Q11

PSLIST导出函数做了什么?

  • 定位到PSLIST

一来就调用sub_100036C3函数

image-20241014105549993

  • 定位到sub_100036C3函数

可以看出,总体上是在进行操作系统的版本查询与比较。

image-20241014105718103

  • 之后调用的代码块:

    CreateToolhelp32Snapshot 函数,从相关字符串和 API 调用来看,用于获得一个进程列表,通过 send 将进程列表通过 socket 发送。

    image-20241014110435635

Q12

使用图模式来绘制出对sub_10004E79的交叉引用图。当进入这个函数时,那个API函数可能被调用?仅仅基于这些API函数,你会如何重命名这个函数?

image-20241014110804748

image-20241014110835955

  • 根据图中的API,
    • GetSystemDefaultLangID
    • send
  • 可以将send改名为SendSystemDefaultLangID

Q13

DLLMain直接调用了多少个Windows API? 多少个在深度为2时被调用?

  • 在左侧找到DLLMain跳转

因为我按G跳转一直跳转不过去

image-20241014111908434

  • 查看其交叉引用表:

太复杂了

image-20241014112020035

  • 从view->graph->user xref chart 打开

调整深度为1和2

image-20241014113231863

image-20241014113323300

image-20241014113532070

Q14

在0x10001358处,有一个对Sleep(一个使用一个包含要睡眠的毫秒数的参数的API函数)的调用。顺着代码向后看,如果这段代码执行,这个程序会睡眠多久?

  • 跳转至0x10001358

image-20241014114139879

3E8h是十进制1000atoi调用结果乘以1000
eaxoff_10019020赋来的,进入off_10019020'[This is CTI]30',再偏移0Dh,指向30.
程序休眠时间为30X1000=30000毫秒 30秒

Q15

在0x10001701处是一个对socket的调用。它的3个参数是什么?

  • 跳转至0x10001701

image-20241014114346312

显而易见: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,分别替换成实际的常量名,如图:

image-20241014121201295

Q17

搜索in指令(opcode 0xED)的使用。这个指令和一个魔术字符串VMXh用来进行VMware检测。这在这个恶意代码中被使用了吗?使用对执行in指令函数的交叉引用,能发现进一步检测VMware的证据吗?

  • 进行搜索

Search > Sequence of Bytes>Find All Occurrences

image-20241014121601231

只有这一条用了in

image-20241014121733835

image-20241014121806476

  • 果然发现了VMXh

image-20241014121924667

  • 查看这个代码段的交叉引用

image-20241014122619590

  • InstallSB,InstallSA,InstallPT中的字符串可以说明

image-20241014122557936

image-20241014123033788

image-20241014123004841

Q18

将你的光标跳转到0x1001D988处,你发现了什么?

  • 发现了一堆字符

可能是加密了,或者是另一个字符集的。

image-20241014125915825

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

image-20241014165507879

再运行一次有就变回去了

image-20241014171800925

Q20

将光标放在同一位置,你如何将这个数据转成一个单一的 ASCII 字符串?

  • 放上去就变成字符串了

xdoor is this backdoorstring decoded forPractical Malware Analysis Lab :)1234

image-20241014165801757

Q21

使用一个文本编辑器打开这个脚本。它是如何工作的?

  • 打开阅读代码

image-20241014170040422

  • 就是一个简单的异或进行解密

Yara

字符串查看

有很多,只展示部分

image-20241014171139672

规则编写

根据字符串中一些可以字符串编写规则

值得一提的是:‘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
}

规则验证

image-20241014172155329

IDA PYTHON

简单示例

在网上查询资料了,我编写了一些简单的功能,并进行了调试和完善

  • 我发现的一点事,有很多错误都是因为python版本过低引起的,在编程时注意一些特性的兼容性,可以有效避免这些错误。

image-20241014175525484

image-20241014175545552

image-20241014175614359

  • 程序代码
# -*- 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()

  • 验证:

确实是正确的

image-20241014175806555

image-20241014175847684

image-20241014175954956

正式编写

  • 以上实现的简单功能在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()

验证

  • 经过一番调试之后,得到了正确的输出:
    image-20241014193852556
  • 也生成了yara文件:
    image-20241014193949183

image-20241014200240324

实验心得

​ 在本次实验过程中,我收获了许多。

  • 我理解了如何使用IDA进行动态分析
  • 我学会了IDA python的基础使用,不过在win10上使用时,由于版本过低,出现了许多报错,令人疲惫。
  • 使用IDA python生成yara的过程也加深了我对yara的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值