
重新加载了一下这个程序,x64dbg的基址变了(不影响),所以我们在继续往下执行,F8进入jmp

这个时候需要对照着payload_C2_v2.bin这个文件看汇编,用ida打开
函数D47D是UAC提权的弹窗(因为我这里尝试过很多此调试,都没法绕过UAC弹窗),所以我打算直接把他nop掉,但是这样的操作可能会导致某些管理员操作(写 HKLM、写 C:\Windows\Temp)会失败,但是不影响我们解密出payload并且后续拿到c2通信的地址。
F8,继续走到test eax eax, 2F8C015 | call 2F8CC9D这个函数执行完发现eax = 0,不需要修改寄存器的值,那我们继续往下走,走到00000000028BC057 | jae 28BC0D8双击进去发现是
这个就是我们在静态分析出来的密钥,在栈上拼接,我们直接看ida里的key是在哪里进行解密的

发现在函数D83D是对key进行解密的,那我直接在x64dbg里面在这个函数打断点
按F9运行到断点处

然后F8,步过函数,我们在内存中看一下0x029A17D0这个堆上的空间是什么
这个时候已经解密完成了,所以我们成功的dump出来payload。
以下的实现都是基于解密第三步动调开始[[#动调]]
ida中会给解密完的payload申请一个可读可写可执行的堆空间

汇编是
所以他的偏移是C4A9,我们直接在x64dbg里直接打断点,按F9执行

然后再F7进入堆

此时该分析最后dump出来的payload了,payload_real_static.bin/payload_real_dyn.bin这两个文件都可以,我们用ida打开,同时x64dbg双击进29A1CF5,说明就是从ida的525函数开始执行的
首先我们研究一下sub_525函数
sub_525是这层 shellcode 的总调度,他要先检测环境然后才会执行后续的功能,如果前面几部验证都没通过那么他会直接退出
这个部分是在不走导入表的情况下,拿到 kernel32!Sleep 的地址。正常程序加载时,Windows 看导入表,把 exe 里那条调用改成 kernel32!Sleep 的地址。之后 exe 一执行到那里,就跳进 kernel32。kernel32!Sleep 再进 ntdll,最后才发系统调用进内核,把这条线程挂起。exe 自己没有睡眠的实现。而这个木马不写导入表,所以加载时没人替它填。它自己用 LoadLibrary + GetProcAddress 问到 Sleep 的地址,再 call 那个地址。结果一样,只是地址是它运行后自己查的。
LoadLibrary 和 GetProcAddress 也是 Windows 的 API,本来就给程序在运行时加载 DLL、按名字查函数用。LoadLibrary("kernel32.dll")返回模块句柄。模块句柄就是这个 DLL 在当前进程里的基址,一个指针。h = LoadLibrary("kernel32.dll") 返回的不是 Sleep,而是 kernel32.dll 被映射到的起始地址,比如 0x7FFE12340000。GetProcAddress 拿着这个基址去翻该 DLL 的导出表,才得到 Sleep 的地址。p = GetProcAddress(h, "Sleep")系统打开这个 DLL 的导出表,找到名字 Sleep,把对应函数地址返回。增加静态分析成本。
sub_B125函数首先先找kernel32!GetModuleFileNameW的地址和ntdll!RtlExitUserProcess的地址,然后再比较当前正在跑的这个 exe 在磁盘上的完整文件名,是不是像C:\myapp.exe和D:\myapp.exe这种就退出。至于为什么是myapp.exe可能就是对抗沙箱。
函数sub_B775把ntdll!NtTraceEvent 的第一个字节改掉,让本进程的 ETW 上报直接返回。NtTraceEvent 平时只读可执行,不能直接改,所以先 VirtualProtect, nullsub_1 一般就是一条 ret(字节C3)。盖上之后,谁再调用 NtTraceEvent,一进来就返回,事件写不出去。NtTraceEvent 是 ntdll.dll 里的一个导出函数,用户态程序通过它把一条日志交给 Windows 的 ETW。程序要记「发生了某件事」时,不自己写文件,而是调用 NtTraceEvent。它再进内核,内核把这条记录放进 ETW 会话。杀软、系统监控可以订这些会话,从而看到进程里的行为。样本改的是自己进程里这个函数的第一个字节:调用还是会进 NtTraceEvent,但一进来就 ret,这条日志发不出去。
函数sub_B365遍历 ntdll.dll 的导出函数名,每个名字用 sub_B0D5 做哈希,和 unk_15 里三个值比。对上才返回那个会让 sub_B915 退出的错误码。
unk_3DD 还是 UTF-16 的 ntdll.dll。v36 指向它,v35 的 Length = 18 表示用前 18 个字节,正好 9 个宽字符:n t d l l . d l l。
unk_15是 sub_B365 用来对比的三个哈希,不是字符串。sub_B0D5 算出的值只要等于其中任何一个,就返回 0xC0000462,上层退出。如果我们想看是哪三个导入表的话,断在 sub_B365 里调用 sub_B0D5 的那一行,sub_B0D5 的第四个参数 a4 就是当前这个导出名的指针。x64 调用约定里它在 r9。对应的汇编就是seg000:000000000000B5DA call sub_B0D5, 由于我们刚进入堆之前的时候地址是00000000029A17D0 | E9 20050000 | jmp 29A1CF5 |所以基地址是0x29A1CF5,sub_525函数在ida中显示的是0x525,所以seg000:000000000000B9C8 call sub_B365偏移应该是29A1cf5 + B9C8 - 525 = 29AD198。我们在x64dbg跳转过去看一眼就知道了。
我们在x64dbg里对着这条汇编按ctrl + r就知道谁引用了这条汇编也就是call 这个函数

我们直接在这里打断点,按F9运行过来然后检查寄存器的值
应该是我们系统中没有它黑名单的三个哈希值0x70CE7692 / 0xD4CE4554 / 0x7A99CFAE,所以我们顺利的通过了
sub_24B5 是加载器的单实例与退出开关。API 名用凯撒(移位 15)还原:ztgcta32.saa → kernel32.dll,RgtpitBjitmP → CreateMutexA,VtiAphiTggdg → GetLastError;OpenMutexA 为明文。
函数先 CreateMutexA(NULL, FALSE, "26f3475fc22")。若 GetLastError() 为 183(ERROR_ALREADY_EXISTS),说明已有实例,内部标志置 0。再 OpenMutexA(MUTEX_ALL_ACCESS, FALSE, "48c47662941"),打开成功则直接返回 0。否则返回该标志。
x64dbg

RAX = 1,说明我通过了sub_24B5这个函数
把弹UAC的函数nop掉

只要把这个函数nop掉,就能动调
我们用ida打开payload_real_static.bin,找到sub_9405这个函数,这个函数就是整只木马的任务调度中心
v741 = "ykkgj://" v730 = ".fjj-te-svzazex.rczpletj.tfd/" v749 = "kru" sub_48C5(v18 → v737) // 追加 sub_48C5(v730 → v737) // 再追加 sub_4815(v737 → v744) // 拷一份备份 sub_48C5(v749 → v737) // 追加 "kru" sub_4575(v737, 17) // 整串凯撒解密,移位 17 sub_4CB5(&v654, v737, &v677) // 按解开的 URL 下载
sub_4815 是拷贝,sub_48C5 是追加。解密前 v737 大致是:ykkgj:// + sub_6675() + .fjj-te-svzazex.rczpletj.tfd/ + kru
移位 17 之后就是url了,所以我们看汇编看偏移是多少
所以我们要在进入堆的地址29A1CF5 + 4575 - 525 = 29A5D45打断点
x64dbg在0x029A5D45打断点直接F9运行过去,就拿到了c2的通信地址的密文

sub_4CB5 是下载器。a4 是刚解开的 URL,成功后把内容放在堆上,指针写进 *a3,长度写进 *a5,返回 1。
同理29A1CF5 + 4CB5 - 525 = 29A6485
在00000000029A6485打断点运行过来,得到地址的明文

检测到 360 后,sub_525 通过 CreateThread 启动 sub_39F5,最多三轮,每轮间隔 Sleep(5000)。该线程用 CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS) 枚举进程,只匹配 360tray.exe、360sd.exe、360safe.exe。
命中后做两件事。一是 EnumWindows(sub_40A5, pid),由回调处理该进程的窗口。二是 FindWindowExA(NULL, NULL, "Q360SafeMonClass", NULL) 查找 360 监控窗口,再 PostMessageA(hwnd, WM_CLOSE, 0, 0)。WM_CLOSE(0x10)与点击窗口关闭按钮相同,只是投递关闭消息,不是 TerminateProcess。窗口过程可以忽略该消息。
因此这一段是针对 360 界面的关闭尝试,不是驱动级结束进程。API 均经 sub_C75 动态解析,导入表中不可见。
sub_8A55 + sub_1375 组合只做“检测火绒在不在”,检测到不杀、直接 shutdown /r /f /t 0 零延迟强制重启