1.环境与工具搭建 自行百度
2.恶意样本分析目的 自己想
3.初步信息收集 1、样本的威胁情报,什么家族?什么类型?
2、样本中有什么函数值得关注,是否有C2外联?是否存在创建文件?是否有注册表操作?等等。。。
3、样本是否存在加壳?
4、多实践发现
4.加壳/脱壳 4.1 加壳的目的 1、它使恶意代码“隐藏”在杀软中。当然,它并没有那么隐蔽,但它是一种软逃避技术,使分析师的工作变得更加困难,并最终给防御带来一些问题。(也算是免杀的一种)
2、由于需要规避许多反分析技术(反调试器和反虚拟机技巧),因此动态解密可能会很困难。
3、恶意软件通常使用定制进程将有价值的代码加密成多层
4、最终,整个恶意软件或者只有脱壳代码可能是多态的
4.2 加壳的特点 1、 IAT(导入地址表)可能已被删除,或者最多只有一个导入函数
2、大多数字符都是加密的
3、从静态来看,内存完整性经过检查和保护,因此无法从内存中转储干净的可执行文件,因为原始指令未完全解码
4、混淆是基于堆栈的,因此使用静态方法处理虚拟化代码相当困难
5、 有数千行虚假的“推送”指令,当然,其中许多指令包含死代码和无用代码
6、这些保护器使用无条件跳转实现代码重新排序
7、所有这些现代打包器都使用代码扁平化、许多反调试和反虚拟机技术
8、大多数情况下,函数的序言和结尾不会被虚拟化
9、原始代码部分可能会被“分割”和/或分散在程序中
10、引用导入函数的指令可能会被清零,甚至被 NOP 替换,因此在这种情况下,这些“引用”将被动态恢复。有时这些相同的引用不会清零,但被使用 RVA 跳转到相同导入地址的指令所取代,即所谓的“IAT 混淆”
11、 与在 shellcode 和常见恶意软件中一样, API 名称经过哈希处理
12、使用混淆技术,例如常量展开、基于模式的混淆、控制间接、内联函数、代码复制和主要不透明谓词
4.3 脱壳前的思考 1、该恶意软件真的被打包了吗?
2、代码打包的证据有哪些?
3、恶意软件是否执行自我注入或远程注入?
4、该恶意软件是否会执行自我覆盖?
5、有效载荷被写入哪里?
6、有效载荷将如何执行?
7、脱壳后有什么证据表明代码被脱壳了?
8、是否有额外的填充层?
4.4 证明加壳的证据条件 1、 二进制样本导入的DLL和函数较少。
2、混淆的字符串较多
3、存在特定的系统调用
4、非标准的部分名称
5、非通用可执行二进制部分(只有 .text/.code 部分应该是可执行的)
6、意外的可写部分
7、高熵部分(通常高于7.0,但并非总是如此
8、某个部分的原始大小和虚拟大小之间存在很大差异
9、缺少与网络通信相关的 API
10、缺乏恶意软件功能所需的基本 API
11、不寻常的文件格式和标题
12、入口点指向 .text/.code 部分以外的其他部分。
13、代码中资源部分(.rsrc 部分)的大小很大,后面跟着LoadResource( )函数
14、在IDA Pro 上打开它并在彩色条上观察大量数据或未探索的代码
仅出现上述列表中的一个特征并不能确定恶意软件已加壳。因此,考虑两个或更多特征非常重要
4.5 深入观察 1、大多数样本使用LoadLibrary()和GetProcAddress( )动态解析其 API
2、网络 API也可以动态解析。
3、格式错误的标头在第一次分析时可能有点难以检测
4、大资源部分可能不相关,因为它可能仅包含 GUI 工件和数字证书
5、加密/模糊字符串和纯文本字符串可能会混合在一起,因此更难判断二进制文件是否被打包。
4.6 脱壳需要的关注的技术难点 1、反调试技术(时间检查、CPUID、堆检查、调试标志检查、NtSetInformationThread( )等),因此建议使用反调试器
2、针对 VMware、VirtualBox、Hyper‑V 和 Qemu 工件进行反虚拟机技巧检查
3、文件名、主机名和帐户检查(避免使用哈希作为文件名)
4、虚拟机上的可用磁盘大小(建议至少 100 GB)
5、测试虚拟机上的处理器数量(两个或更多是合适的)
6、正常运行时间(尝试保留正常运行时间超过 20 分钟的虚拟机快照)
7、许多无意义的调用(结果不再使用)和不存在的 API(假 API)。
8、异常处理程序被用作反调试技术。
9、软件断点被清除,寄存器(DR#)被操纵(反断点技术)
10、使用典型算法的哈希函数(例如,crc32、conti、add_ror13 等)
11、对知名工具(例如 Process Hacker、Process Explorer、Process Monitor等)进行恶意代码检查(建议在使用这些可执行二进制文件之前重命名它们)
12、反虚拟机技巧和反调试器技术并不总是可以通过插件来处理,我们必须设法使用调试器来绕过它们,在这种情况下,我们有一个有趣的可能性,那就是使用不同的调试器(如WinDbg)来管理一些需要 Ring 3 调试器的恶意软件威胁
13、仅限内核调试器而非内核调试器(最近的案例是 GuLoader 恶意软件)
4.7 解密后会出现的问题 1、DOS/PE 头可能在内存中被破坏或者被压缩库修改。
2、入口点(EP)可能已被清零或者错误
3、解密后的二进制文件的导入表可能由于已被转储而被破坏,并且其地址引用的是虚拟地址(映射版本而非未映射版本),因此会显示未对齐的部分或无部分
4、基地址错误
5、PE 格式的字段存在一些不一致
6、确定 OEP(原始入口点)可能很困难,它通常在使用间接调用(例如, call [eax]或jmp [eax] )从解包器代码转换后出现
此外,存在未解析的 API 可能是恶意代码尚未到达 OEP 的证据。准时: OEP 是可执行文件在被打包之前的入口点 (EP)。打包后,新的 EP 将与打包程序本身关联。
7、互斥锁被用作两个解包层之间的一种“解锁钥匙”。在这种情况下,如果第一阶段没有发生,第二阶段的解包就不会发生,如果发生了,那么互斥锁的存在就得到了确认
8、代码可能正在执行自我覆盖
9、脱壳代码的第一阶段不从任何目录运行,而只从特定目录运行
10、您可能已经提取了诱饵二进制文件。在许多实际情况下,恶意软件作者会将一个或多个无用的可执行文件打包为诱饵,以消耗分析师的时间。因此,最好不要相信您第一次尝试就从内存中解压了正确的二进制文件
4.8 解决方法 1、从另一个可执行文件(或从自己的恶意软件样本)复制一个好的 PE 头,并根据解压后的二进制文件是未映射的(.text 部分通常从 0x400 开始)还是映射的(.text 部分通常从 0x1000 开始)来对齐部分。
2、通过修复各自的原始地址和原始大小来对齐解压二进制文件(映射寻址)的各个部分。此操作通常可以修复导入表,并可以毫无问题地可视化导入函数。注意可能的“陷阱”:某些解压二进制文件在对齐其部分之前不会显示其导入表。但是,其他恶意软件威胁即使在解压二进制文件后也不会在导入表中显示任何函数,因此这并不意味着您犯了任何错误,而意味着恶意软件会动态解析其所有 API。
3、重建IAT并强制OEP (原始入口点)。
4、如果您在查找 OEP 时遇到问题,请记住OEP 很可能是在 IAT 解析之后出现的。在这种情况下,一种可能的方法是检查 IAT 是否已解析(检查 x64dbg 或 OllyDbg 上的 Intermodular Calls)或在关键 API 上设置断点,该 API 将在恶意软件的关键操作期间执行(例如,勒索软件威胁中的 CryptoAcquireContext()),因为当执行到达这些关键 API 时,IAT 肯定会被解析。之后,建议寻找无条件跳转到特定内存地址甚至间接调用(例如,call [eax])。另一种有趣的方法是使用调试器的图形可视化(x64dbg 上的“g”)并在最后的“代码块”中检查这些转换点(间接调用或内存地址的无条件跳转)。最后,一个专门的工具可能会帮助您找出 OEP。正如您所注意到的,没有一种方法可以做到这一点
5、调整基地址以与从内存转储的段基地址匹配
6、为了检测恶意软件是否执行自我覆盖,我们可以尝试在.text/.code 部分。在这种情况下,我们可以选择在代码编写或执行期间触发此断点。
7、在两阶段脱壳的情况下,第一个脱壳的二进制文件可能是一个 DLL。因此,根据具体情况,将 DLL 二进制文件转换为可执行文件可能会很有用,并且有很多方法可以完成此任务,但我最喜欢的方法是编辑 PE 标头以更改 Characteristics 字段并将导出函数的条目作为入口点
脱壳只是恶意软件分析过程中的第一个障碍,许多其他艰难的挑战,例如字符串反混淆、API 解析、C2 配置提取、C2 模拟和其他主题会陆续讲到
5.代码注入审查 5.1 注入的概念 代码注入是 Windows 系统支持的操作,当然,这是一种非常有用的规避方法,因为恶意软件能够将恶意代码注入(写入)进程本身(自我注入)或远程进程(远程注入)的内存区域(有些人使用术语“段”),并且此有效负载将在目标上下文中执行,就像它是其中的一部分一样,不会留下太多证据。此外,源进程(恶意软件)可以干净地终止自身
而恶意负载则继续在所谓的正常进程中运行(例如 explorer.exe 和 svchost.exe)。归根结底,这是一种逃避安全防御的隐秘方法
从 Windows 8.1(主要是 Windows 10 和 11)开始,就存在一系列缓解和保护措施,例如代码完整性保护、扩展点禁用策略、控制流保护、代码完整性保护、动态代码限制和任意代码保护(动态代码限制的一种更新),在这些 Windows 版本上执行代码注入并不那么容易不会被检测到和阻止
5.2 注入的类型 1、 DLL 注入:这种老技术用于强制进程加载 DLL。主要可能涉及的 API: OpenProcess( )、VirtualAllocEx( )、WriteProcessMemory和CreateRemoteThreat | NtCreateThread( ) | RtlCreateUserThread( )。
2、PE注入:该技术会写入恶意代码,然后强制在远程进程甚至自身进程中执行(自我注入)。主要相关 API:
OpenThread( )、SuspendThread( )、VirtualAllocEx( )、WriteProcessMemory( )、SetThreatContext( )和ResumeThreat( ) | NtResumeThread( )
3、反射注入:这种技术类似于 PE 注入,例如LoadLibrary() 和 CreateRemoteThread( )。此方法有许多有趣的派生,其中之一(也用于Cobalt Strike)由以下 API 完成:
CreateFileMapping()、Nt/MapViewOfFile()、OpenProcess()、memcpy()和Nt/MapViewOfSection( )。最后,可以通过调用来执行远程进程上的代码OpenProcess()、CreateThread()、NtQueueApcThread()、CreateRemoteThread()或RtlCreateUserThread()。值得注意的是,变体也可以使用VirtualQueryEx()和ReadProcessMemory()
4、 APC 注入:此代码注入技术允许程序通过附加到 APC 队列来在特定线程中执行代码。当线程退出可警告状态时(由SleepEx( )、SignalObjectAndWait( )、MsgWaitForMultipleObjectsEx( )、WaitForMultipleObjectsEx( ) 或WaitForSingleObjectEx ( ) 等调用发起),将执行注入的代码
因此,还经常可以看到CreateToolhelp32Snapshot()、Process32First()、Process32Next()、Thread32First()、Thread32Next()、QueueUserAPC()和KeInitializeAPC()等 API。参与到这个技术中
5、挖空或进程替换:简而言之,恶意软件使用这种技术“抽干”进程的全部内容,并将恶意内容插入其中。一些涉及的 API 包括CreateProcess( )、NtQueryProcessInformation( )、GetModuleHandle( )、Zw/NtUnmapViewOfSection( )、VirtualAllocEx( )、WriteProcessMemory( )、GetThreadContext( )、SetThreadContext ( )和ResumeThread( )。
6、 AtomBombing:此技术是前一种技术(APC 注入)的变体,其工作原理是将恶意负载拆分为单独的字符串,为每个给定的字符串创建一个 Atom,将它们复制到 RW 段(使用GlobalGetAtomName()和NtQueueApcThread( ))并使NtSetContextThread()设置上下文。因此,以下是进一步的 API 列表:
OpenThread()、GlobalAddAtom()、GlobalGetAtomName()和QueueUserAPC()
7、进程分身:这种技术可以视为进程的一种演化
Hollowing。这两种技术之间的关键区别在于,Process Hollowing 在进程恢复之前替换进程的内容(图像),而 Process Doppelgänging 能够在进程创建之前替换图像,方法是在加载之前用恶意图像覆盖目标图像。这里的关键概念是 NTFS 操作是在事务内执行的,因此事务内的所有这些操作要么一起提交,要么都不提交。同时,恶意图像仅存在,并且在事务内可见,对任何其他进程不可见。因此,恶意图像被加载到内存中,恶意软件会从文件系统中删除恶意负载(通过回滚事务),因为该文件以前从未存在过。此技术涉及一些 API:
CreateTransaction()、CreateFileTransaction()、NtCreateSection、NtCreateProcessEx()、NtQueryInformationProcess()、NtCreateThreadEx()和RollbackTransaction()
8、Process Herpaderping:此技术与 Process Doppelgänging 类似,但其过程略有不同。Process Herpaderping 基于以下事实:安全防御通常通过使用PsSetCreateProcessNotifyRoutineEx( )在内核端注册回调例程或在驱动程序的DispatchCleanup 例程 (IRP_MJ_CLEANUP) 期间监控进程创建,该例程在创建线程后调用。这是关键问题:如果对手创建并映射进程,之后该对手能够修改文件映像,然后创建线程,那么安全产品就能够检测到这种恶意负载
尽管如此,这个检查顺序可以决定攻击者是否能够在磁盘上创建恶意二进制文件、打开它的句柄、使用NtCreateSection将其映射为图像部分函数(包括SEC_IMAGE 标志),使用节句柄创建一个进程(NtCreateProcesEx()),修改文件内容使其听起来不像是恶意的,并使用此“好图像”创建线程(NtCreateThreadEx()) 。
重点是:创建线程时,会触发进程回调并检查磁盘上文件(好文件)的内容,因此安全防御认为一切都很好,因为磁盘上的图像没有危害,但真正的恶意是在内存中。换句话说,安全防御无法有效检测与内存上的图像不同的磁盘上的图像。此技术使用的一些 API: CreateFile()、NtCreateSection()、NtCreateProcessEx()和NtCreateThreadEx()。
9、挂钩注入:要使用此技术,我们将看到涉及挂钩活动的函数(例如SetWindowsHookEx()和PostThreadMessage())用于注入恶意 DLL
10、额外的 Windows 内存注入:恶意软件威胁利用这种技术将代码注入
通过使用额外 Windows 内存(即 EWM)来破坏进程,其大小最多为 40 字节,并在注册 Windows 类时附加类的实例。诀窍在于附加的空间足以存储可能将执行转发给恶意代码的指针。此技术涉及的一些可能 API 是FindWindowsA()、GetWindowThreadProcessId()、OpenProcess()、VirtualAllocEx()、WriteProcessMemory()、SetWindowLongPtrA()和SendNotify()。
11、传播注入:这种技术已被恶意软件威胁所使用,例如 RIG Exploit Kit Smoke Loader 将恶意代码注入 explorer.exe 进程(中等完整性级别)和其他持久进程,它基于枚举方法(EnumWindows() →EnumWindowsProc → EnumChildWindows() → EnumChildWindowsProc → EnumProps() → EnumPropsProc → GetProp)窗口实现SetWindowsSubclass() 一旦找到子类窗口(检查提供子类标头的UxSubclassInfo和/或CC32SubclassInfo ),就可以保留旧的 Windows 过程,但我们也可以通过更新 CallArray 字段为窗口分配一个新的过程。当向目标进程发送事件时,将调用新过程,之后还会调用旧过程保持,恶意软件会将恶意负载(shellcode)插入内存并使用SetPropA( ) 更新子类过程。当调用此新属性(通过 Windows 消息)时,执行将转发到负载。此技术涉及的一些 Windows API 包括FindWindow()、FindWindowEx()、GetProp()、GetWindowThreadProcessId()、OpenProcess()、ReadProcessMemory()、VirtualAllocEx()WriteProcessMemory()、SetProp()和PostMessage()
下面显示了恶意软件威胁的一个非常常见的代码注入序列示例(IDA Pro 的反编译输出):
6.脱壳解密手法 6.1 调试器+特定函数断点 在众所周知的 API 上设置软件断点,其中大多数与内存管理和操作有关,并寻找要从内存中提取的可执行文件和/或 shellcode
6.1.1 常见API VirtualAlloc( )
VirtualAllocEx( )
ZwProtectVirtualMemory( )
WriteProcessMemory( ) | NtWriteProcessMemory( )
ResumeThread( ) | NtResumeThread( )
CryptDecrypt( ) | RtlDecompressBuffer( )
NtCreateSection( ) + MapViewOfSection( ) | ZwMapViewOfSection( )
NtWriteVirtualMemory()
NtReadVirtualMemory()
6.1.2 脱壳后注意事项 1、在恶意软件到达其入口点后(系统断点之后)设置断点。
2、建议使用反调试插件,并在少数情况下忽略从0x00000000 到 0xFFFFFFFF 范围内的所有异常(在 x64dbg 上,转到选项→偏好→例外以包括此范围)
3、有时忽略异常可能不是一个好主意,因为恶意软件可能会利用它们来调用解包过程。此外(与本文内容无关),还存在使用中断和异常来调用 API 的威胁。
4、使用 MSDN了解所有列出的API 及其各自的参数是成功解压恶意软件威胁。
5、如果您正在使用VirtualAlloc( ),建议在其退出点(ret 10) 上设置断点。此外,有时通过设置写入,可以更容易地跟踪转储上分配的内容内存断点
6、在某些情况下,恶意软件会将其有效负载提取到内存中,但会破坏 PE 标头,因此您必须重建整个标头,尽管使用像 HxD 这样的十六进制编辑器可以完成这个过程很简单。
7、提取的有效载荷可能是映射格式或非映射格式。如果是映射格式,那么导入表可能混乱了,你需要通过重新调整节标题来修复它们通过 PEBear 手动操作(最喜欢的方法)或使用 pe_unmapper 之类的工具。您可能需要修复基地址和入口点,无论它是否已归零
8、要重建被破坏的 IAT,建议使用 Scylla (嵌入在 x64dbg 上)。它将进入 OEP 所必需的,找到它的方法之一是通过查找指令给出的代码转换,例如jmp eax、call eax、call [eax]等
9、少数脱壳的恶意软件样本在IAT中没有任何函数,因此有两种可能性:要么部分未对齐(映射版本),要么脱壳的恶意软件动态解析其所有函数
10、使用x64dbg 上的“g”热键可能有助于可视化块中的代码并找到可能的 OEP 转换。
11、在很多情况下,解压后的代码可能只是恶意软件的第一阶段,因此需要重复步骤来解压后续阶段
12、少数恶意软件样本会执行自我覆盖,因此你可能必须在.text上设置断点部分来检测解包后的二进制执行情况
13、根据提取的二进制文件(例如,shellcode),它可能无法在特定的进程上下文之外运行,因此有必要将其注入到正在运行的进程(例如,explorer.exe)中以执行进一步的分析。
14、如何检查提取的恶意软件是否是最终版本?没有明确的答案,通过查找 DLL 中的网络函数(例如WS2_32.dll (Winsock) 和Wininet.dll)、纯文本字符串、加密函数(主要是恶意软件是勒索软件)以及许多其他证据。在重新对齐部分和/或重建 IAT 后,在 IDA Pro 上打开提取的代码是一种很好的方法
6.2 调试器 + DLL 加载时中断 这是一种古老而简单的脱壳恶意软件的技术,通过在每次加载的 DLL 上停止调试器并检查内存中可能提取的 PE 格式文件的内存映射来脱壳(注意:不要只关注 RWX 段,因为许多恶意软件会在 RW 区域中提取其有效载荷,并且在将执行上下文转移到提取的可执行文件之前,它们会使用VirtualProtect( ) 将该区域的权限更改为 RWX)。毫无疑问,这会消耗一些时间,但在很多情况下它仍然是高效的。常见的调试器(x64dbg、OllyDbg和Immunity Debugger)都有一个配置选项可以在每次 DLL 加载时中断。在 x64dbg 上,此选项位于选项→首选项→事件中,并标记DLL 加载。在OllyDbg上,您可以转到选项→调试选项→事件并标记“在新模块(DLL)上中断”
6.3 自动化方法
工具彼此之间有类似的方法,因此应该在隔离的虚拟机中运行恶意软件并执行适当的命令,我在下面展示了一些可用于快速方法
hollow_hunter.exe /pname <文件名> /loop /imp
mal_unpack.exe /exe <文件名> /timeout <超时时间:毫秒>
pe‑sieve64.exe /pid <进程 ID> /dmode 3 /imp 3
6.4 进程DUMP 从内存中提取二进制文件的方法是通过Process Hacker双击正在运行的进程,转到“内存”选项卡,查找有趣的区域/基地址(RWX),双击它并按“保存”按钮。当然,如果是自我注入,找到恶意二进制文件/有效负载会更容易。如果是远程注入,您需要逆向恶意软件以了解要注入的目标进程或做出“有根据的猜测”,并在众所周知的目标(例如 explorer.exe 或 svchost.exe)上寻找注入的代码。再次重申,这是一种有限且简单的方法,有时可以节省时间。
6.5 编写脱壳脚本 虽然这种方法听起来很耗时,但在 shellcode 案例中或处理恶意软件线程使用多种反虚拟机和反调试技术的情况下,编写 Python 代码来完成脱壳是很常见的。此外,在处理类似的恶意软件案例时,我们有一个优势,那就是可以自动化脱壳过程。
7.分析恶意样本案例 7.1 样本信息收集
首先分析样本我们不管是针对远控木马,勒索病毒,窃密木马,挖矿病毒,这些共同特点就是网络通信,二进制加密(勒索偏多),这两个部分是逃不掉的。而这个样本我们看不到有什么DLL或者函数是和加密还有网络相关的,所以可能是被加壳了。
从导出表可知道,这很可能是个DLL文件
通过pestudio查看,.data明显原始大小和内存大小有很大区别,所以基本能确定加壳了
7.2 脱壳提取内存
"C:\Windows\SysWOW64\rundll32.exe" C:\Users\xiaoyun\Desktop\sample_1.bin,Callrun
更改命令行加载第一个函数
下断点在VirtualAlloc,与Protect,
总共会命中三次内存申请,可以每次都放在内存监视器中,毕竟实际情况你也不知道哪个可能才是,这个样本第三次的内存申请,我们发现了内存3存在M8Z,这表明它使用了 aPLib 压缩
跳转到内存布局准备将其拖出
7.3 文件修复
用二进制编辑器找到This program字符串
找到真正的可执行MZ头,M8Z为恶意混淆
选中MZ之前的光标进行清除
再将其放到PEBear就能看到网络相关IAT了,明显也比之前的多
现在我们可以关掉调试器了
7.4 逆向分析代码
我们找到未探索区域的起点
这里能看到这里被进行了交叉引用,这里可能是加密部分的参数,
byte_10004000(16字节)
pbData(8字节)
unk_10004018(可能2000h)
一般pbData是 Microsoft Crypto API 中几个 API 的重要参数,在许多知名恶意软件样本中发现的另一种模式是结构密钥 + 加密数据,因此即使我对这种情况没有任何进一步的线索,也可以假设“pbData”是某个密钥(长度为 8 个字节),尽管有时它不是最终密钥,因为恶意威胁使用KDF(密钥派生函数)来生成
根据提供的密码获取最终密钥。根据我们的分析,“unk10004018”可能指的是潜在的加密数据
跳到交叉引用部分,可以看到pbData作为参数压入堆栈作为sub_10002131的参数使用了
还有其他几个参数分别被11A4,1214两个call调用
进入调用byte_10004000的子程序,发现在申请内存进行分配
进入sub_10002131,发现CryptAcquireContextA函数,虽然已经被弃用,但是依然不少恶意软件会使用。此 API 用于获取具有 CSP(加密服务提供程序)的密钥容器的句柄,它使用默认密钥容器名称和用户默认提供程序,因为两个参数均为零(来自 xor edi, edi)。获取dwFlags(0x0F0000000)并在wincrypt.h 上搜索此值,我们知道它指的是CRYPT_VERIFYCONTEXT 提供程序类型,这在使用临时密钥或不需要访问持久私钥的应用程序中很常见。实际上,正如您将了解到的,此恶意软件样本使用了一种密钥派生
这三个API需要详细进行分析了。
CryptCreateHash:
这里Algid使用了8004h在https://docs.microsoft.com/en‑us/windows/win32/seccrypto/alg‑id上搜索我们能够了解到0x8004h表示CALG_SHA1,因此该恶意软件正在操纵SHA1 哈希(160 位/20 字节)。此函数的返回值是 CSP 哈希对象的句柄(保存在phHash 参数中,该参数最初为零),该句柄将在下一个函数中使用
CryptHashData:
此函数将特定大小(大小由dwDataLen 参数指定)的数据(数据由pbData 参数指定)添加到CryptCreateHash()返回的哈希对象中。这两个参数都是子程序sub_10002131的第三和第四个参数。紧随其后的是子程序sub_10001CB7 ,它显示大小为 8 个字节并指向可能的密钥(pbData)。此时, CryptHashData() 函数将可能的密钥(8 个字节)提取到哈希对象中,从而生成SHA1 哈希。换句话说,恶意软件的代码正在从一个条目生成哈希输出,并且CryptCreateHash()返回的句柄指向保存此哈希数据的CSP 哈希对象
CryptDeriveKey:
此函数根据给定的种子数据值生成会话密钥,并且根据其定义,它保证使用相同的基础数据生成相同的会话密钥,因此它完全适合我们的情况,因为我们正在解密配置数据。这里有两个有趣的参数:Algid 是 0x6801 ,dwFlags 是 0x280011
二个参数(Algid == 0x6801)标识对称加密算法,根据文档( https://docs.microsoft.com/en‑us/windows/win32/seccrypto/alg‑id),0x6801表示CALG_RC4 。
第四个参数(dwFlags == 0x280011)需要进一步详细说明。根据文档(https://docs.microsoft.com/en‑us/windows/win32/api/wincrypt/nf‑wincrypt‑cryptderivekey):“密钥大小(表示密钥模数的长度,以位为单位)由该参数的高 16 位设置。因此,如果要生成 128 位 RC4 会话密钥,则值 0x00800000 将与任何其他 dwFlags 预定义值进行按位或运算”。因此,我们知道 RC4 密钥有 40 位(0x28),所以它有 5 个字节。MSDN文档的另一部分告诉我们“该参数的低 16 位可以为零,或者您可以使用按位或运算符将它们组合起来,从而指定以下一个或多个标志”。从 MSDN 页面获取可能的值并在wincrypt.h 文件中搜索它们(ReacOS为我们提供了一个示例: https://doxygen.reactos.org/d7/d4a/wincrypt_8h_source.html)我们发现0x11表示CRYPT_NO_SALT + CRYPT_EXPORTABLE ,拆解开来就是0x28==40位,0x00==低十六位可为0,0x11==CRYPT_NO_SALT + CRYPT_EXPORTABLE 即为0x280011
恶意软件的作者使用SHA1 作为 KDF(密钥推导函数)生成最终的解密密钥,这可以通过以下方式解释
pbData = C58B00157F8E9288 (记住:要导出数据,您应该使用SHIFT‑E)
pbData → SHA1 (20 字节)
SHA1 → CryptHashData() → CryptDeriveKey() → RC4 密钥(5 个字节)
CryptDecrypt:
此函数负责使用提供的密钥句柄(hKey) 解密加密数据。提供的其他参数包括hHash (由CryptCreateHash( ) 生成的哈希对象的句柄)、 Final (值等于 1,因为这是要解密的最后一个且唯一的部分)、 pbData (包含要解密的数据的缓冲区)和pbwDataLen (指向 DWORD 的指针,该 DWORD 指示pbData缓冲区的长度,在本例中为0x2000)。
pbwDataLen(0x2000)是正在分析的函数的第二个参数( sub_10002131(BYTE *rc4_encrypted_data,DWORD pdwDataLen,BYTE *pbData,DWORD dwDataLen) )。因此,最终结果(RC4解密数据)保存到pbData(地址)中,相应的大小保存到pdwDataLen参数中。
初始密钥:C58B00157F8E9288(.data 部分的前 16 个字节之后)也就是pbData
初始数据地址: 0x10004018
数据大小: 0x2000
哈希算法: SHA1(20字节)
解密算法: RC4
RC4 密钥大小: 5 个字节
我们知道这个密码解密顺序是,编码成sha1,那我们可以先进行sha1编码得到密钥
因为密钥是5字节所以提取前十位,11f668918f
提取要解密的数据
最后进行rc4解密
8.总结 整体其实不难,主要就是最后解密环节,当慢慢理清顺序就很简单了,尤其是知道密钥当作解密私钥的时候是以什么形式(sha1),这个用的什么解密(rc4),最后就成了:
初始密钥->sha1转换->提取五字节作为密钥->解密初始数据(rc4)->C2地址配置。
最后就是dll文件的特性主要是调用其中构造的函数或子程序也好,不像exe按顺序走,但我们也可以用导入导出表大体猜出。
最后于 2小时前
被PinkKey编辑
,原因:
上传的附件: