吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 2715|回复: 35
收起左侧

[PC样本分析] 【木马分析】vshell木马分析

  [复制链接]
ctcaozhe 发表于 2025-7-23 19:44
使用论坛附件上传样本压缩包时必须使用压缩密码保护,压缩密码:52pojie,否则会导致论坛被杀毒软件等误报,论坛有权随时删除相关附件和帖子!
病毒分析分区附件样本、网址谨慎下载点击,可能对计算机产生破坏,仅供安全人员在法律允许范围内研究,禁止非法用途!
禁止求非法渗透测试、非法网络攻击、获取隐私等违法内容,即使对方是非法内容,也应向警方求助!

vshell木马分析

前言

搬运一下自己博客文章,分享一下,其实里面还有很多细节,特别是TEB和PEB通过hash查询函数的方法,我还是有点云里雾里,只知道个大概,而且没成功复现过,有兴趣的大佬可以指点一下
要是有理解错的地方也请指出
原文地址:https://cz.caozhexxgweb.cn/?id=139

今天来分析一下这个vshll的shellcode加载器,首先我们导出bin,文件本地很小只有1kb,
image.png

image.png

因为是汇编,所以直接拖入ida查看即可

入口

因为是shellcode,所以代码先保存了寄存器和空间

push r.. × 8    保存所有通用寄存器,防止被后续破坏。
lea rbp,[rsp-0x298]
sub rsp,0x398   在原来栈顶以下再开 0x398 字节的大栈帧。后面所有局部变量/拼字符串都放这里。

image.png

sub_45C函数

先说一下,这函数主要是通过函数hash来查找出这个函数的虚拟地址,这里的726774Ch就是kernel32.dll里的LoadLibraryA

image.png

我们进去sub_45C函数,想按F5发现F5大法失败了,说是要设置语言但实际不行,可能出bug了,只能硬着头皮看汇编了

image.png

起始部分

mov rax, rsp    暂存当前栈顶地址到 RAX,方便直接在红区里写数据。
mov [rax+8], rbx
mov [rax+10h], rbp
mov [rax+18h], rsi
mov [rax+20h], rdi  把调用的 4 个寄存器存在栈指针下方的红区(未改变RSP他就bu不保存了)。
看样子是比平时一个个 push 更快。(其实我也不知道这样的设计的意义是什么)
push r14    额外保存 R14(后面要当循环计数器)。
sub rsp, 10h    腾出 0x10 字节的局部变量区,IDA 叫 var_18。

image.png

起始后的栈指针
内容 栈指针
存的 rbx/rbp/rsi/rdi ← RSP+0x0
var_18 (16 B) ← RSP-0x10
保存的 r14 ← RSP-0x18

取得 PEB 与第一模块(重点)

从刚刚开始我就一直好奇一个事情,就是我们可以看到这个exe的导入表都是空,那么怎么执行外部函数的呢

image.png

这里简单带一下TEB和PEB的知识(网上都有)

主要是我不知道mov rax, qword ptr gs:loc_5C+4是什么查了一下才豁然开朗。

  • TEB
    • TEB是每个线程都有,保存 TLS、异常链、自旋计数等纯线程级信息。通过 GS(x64)或 FS(x86)直接寻址。
  • PEB
    • 每个进程 1 份,被所有线程共享。字段很多,但最常用的是:
    • PEB->Ldr → PEB_LDR_DATA:维护已加载模块链表。
    • PEB->ProcessParameters:命令行、环境变量。
    • 指向主进程堆、TLS 位图、异常处理列表、调试标志

也就是说他这里只要拿到 PEB,不调用任何 WinAPI 就能做到下面的操作:

  1. 枚举出 kernel32.dll、ntdll.dll 等在 内存中的真实基址;
  2. 解析它们的导出表,手动拿到 LoadLibraryA、GetProcAddress 等地址;
  3. 进而做到免IAT、免明文字符串免API调用监控,做到免杀的效果。(但这个木马已经被杀烂了)

==下面借用一下别人的说明==

  • Windows 每个进程的 PEB(Process Environment Block) 中有一个字段 PEB->Ldr。

    • Ldr 里有三条双向链表:

    • InLoadOrderModuleList

    • InMemoryOrderModuleList ← 本函数用的

    • InInitializationOrderModuleList

  • 每个节点不是简单结构,而是大号 LDR_DATA_TABLE_ENTRY,里面既有链表指针,也记录 DLL 的各种信息。

  • 重要偏移(Win10 x64 常见布局):

字段 偏移 作用
InMemoryOrderLinks 0x20 当前链表的 LIST_ENTRY
DllBase 0x30 模块基址(HMODULE)
BaseDllName 0x58 UNICODE_STRING,DLL 文件名(不含路径)
  • InMemoryOrderModuleList 是循环链表:最后一个节点再指向 Ldr 里的头结点哑元;哑元的 DllBase 字段恒为 0。
  • ==上面借用一下别人的说明==

我们继续看代码

; 获取 PEB
mov     rax, gs:[0x60]          ; TEB → PEB  (= qword ptr gs:loc_5C+4)

; 保存调用参数
mov     ebp, ecx                ; 把目标哈希存进 EBP,后面循环都要用

; 计数寄存器清零
xor     r14d, r14d              ; r14d = 0,既代表“false”也当循环计数起点

; 走到 Ldr 模块链表
mov     rdx, [rax+0x18]         ; RDX = PEB->Ldr
mov     r8,  [rdx+0x10]         ; R8  = Ldr->InMemoryOrderModuleList.Flink
                                ;        (链表第 1 个 LDR_DATA_TABLE_ENTRY)

接下去的代码
就是不断循环查找链表然后计算hash是否和入参一致(忽然发现F5大法恢复了,瞬间舒服了)

image.png

  • 一共有两个return

    • return 0就说明已把所有模块都扫完,都没有这个函数,然后返回说明错误了cmp [r8+30h], r14 → jz loc_54C这边做的比较然后跳到loc_54CEAX0

    • return v6,也就是循环的下一个模块的基址,v6在循环的时候就被定义了。

image.png

; 0x0495  外层循环取模块基址
mov     r9, [r8+30h]        ; R9 ← DllBase        → v6 = v5[6]

; 0x0519  函数名哈希命中后
lea     eax, [rbx+rdx]
cmp     eax, ebp
jz      loc_52E             ; 跳到构造返回值

; 0x052E  计算最终地址
mov     eax, [r10+24h]      ; AddressOfNameOrdinals
add     rax, r9             ;   ↑ 这里 r9 仍为 DLL 基址
…
mov     eax, [rcx+rdx*4]    ; Function RVA
add     rax, r9             ; RAX = r9 + RVA  → 返回

获取函数基指

了解了sub_45C我们看看很清楚了,他获取了一堆函数,然后吧基指放在了rbp中,有的也直接放在了r13、r15等寄存器里

mov ecx, <hash>
call sub_45C
mov  <某寄存器/栈>, rax

image.png

对照后续调用可以推断出大致映射(直接问ai了):

保存位置 真实 API 用途
rbx(第二次) socket/WSASocketA 创建 TCP socket
r13 connect 连接远程主机
rsi setsockoptselect 调整 sock 参数
rdi send 发送握手数据
r15 recv 下载主体数据
[rbp-20h] VirtualAlloc 申请可执行内存
[rbp-18h] closesocket 收尾清理
r14 sprintf / _snprintf 字符串拼装
(其余为 SleepGetVersion, VirtualProtect 等)

调用部分

准备回连

image.png

最后将调用获取函数

image.png

上报

下面调用了很多次的rdi也就是发送送握手数据,来发送设备的识别码[rbp-80h] = 0x20343677 → "w64 4";紧跟空格和 0 组成 w64 64。告诉 C2 这是 Windows-64 位客户端
image.png

加载大马

最后就是加载大马的部分

image.png

木马整体流程

到这里整体流程已经被摸清楚了

  1. 启动与栈帧准备
    入口只做两件事:

    • 保存全部通用寄存器,防止被破坏;
    • 在 RSP 下方一次性开 0x398 字节大栈帧,供后续拼接字符串和存放临时数据。
  2. 动态解析 API

    • 自带的 sub_45C 函数按“ROR-13 + 大小写无关”遍历 PEB 中所有模块的导出名哈希。
    • 仅凭 32 位哈希值就能拿到 LoadLibraryAsocketconnectrecvVirtualAlloc 等真实地址,完全省掉了明文字符串。(但是没有用,还是杀爆)
  3. 加载必需 DLL
    利用刚解析出的 LoadLibraryA 动态加载 user32.dll, ws2_32.dll, msvcrt.dll,用作网络通信、字符串处理、内存管理

  4. 一次性解析所有关键 API
    socket / connect / send / recv / VirtualAlloc / closesocket / _snprintf … 等 API 地址分别保存到寄存器或栈槽里,后面直接调用。

  5. 拼接关键字符串

    • C2 地址:硬编码字节组合成 xxx.xxx.xxx.141,端口 55841
    • 客户端指纹"w64 <本机IP> <OS版本>"
    • 日志文件名log_<日期>.ed
      这些都用 _snprintf 现场拼出,避免静态特征。
  6. 建立 TCP 连接并上报信息

    • socket(AF_INET, SOCK_STREAM)connect 到 C2。
    • 依次 send 客户端指纹、日志名等,完成“注册/握手”。
  7. 申请可执行内存
    VirtualAlloc(NULL, 0x1C9C380 ≈ 30 MB, MEM_COMMIT, PAGE_EXECUTE_READWRITE) 为后续载荷准备 RWX 缓冲区。

  8. 循环下载并异或解密

    • 每次 recv 0x64000 字节。
    • 对收到的数据逐字节 XOR 0x99 写回缓冲区。
    • 循环直到 recv 返回 < 要求长度(服务器主动断流)。
  9. 收尾并执行载荷

    • closesocket 关闭连接。
    • 直接 call 指向缓冲区首地址 → 跳入已解密的 Stage-2 Shellcode,不落地文件,纯内存执行。
  10. 退出 Loader
    如果第二阶段代码返回,则恢复之前保存的寄存器并 retn;否则进程控制权彻底交给后续载荷。

这是一个只有 0x56C 字节的Stage-0 TCP Loader:通过 API 哈希隐藏所有函数名,联网到硬编码 C2,下载 XOR 加密的下一阶段并在内存中直接执行,为远控恶意模块有点类似反射加载dll。

免费评分

参与人数 6威望 +1 吾爱币 +25 热心值 +6 收起 理由
uarn + 1 + 1 我很赞同!
tooor + 1 + 1 用心讨论,共获提升!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
junjia215 + 1 + 1 用心讨论,共获提升!
smjp + 1 + 1 我很赞同!
UUoh + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| ctcaozhe 发表于 2025-8-1 11:31
temls 发表于 2025-8-1 10:29
mov ax, rsp是啥意思?

是指mov rax, rsp吧,ax是16位寄存器,rsp是64位寄存器,直接mov是会有问题的。
汇编的话,单独来看意思是没有什么特殊的含义的,需要结合上下文一起来看
正常来说的化mov     rax, rsp是保存一下当前栈顶指针或者用push来保存,但后面又被mov     rax, qword ptr gs:loc_5C+4覆盖掉了,所以这行代码没有实际作用,具体可以看下面的代码片段。
[Asm] 纯文本查看 复制代码
seg000:000000000000045C                 mov     rax, rsp
seg000:000000000000045F                 mov     [rax+8], rbx
seg000:0000000000000463                 mov     [rax+10h], rbp
seg000:0000000000000467                 mov     [rax+18h], rsi
seg000:000000000000046B                 mov     [rax+20h], rdi
seg000:000000000000046F                 push    r14
seg000:0000000000000471                 sub     rsp, 10h
seg000:0000000000000475                 mov     rax, qword ptr gs:loc_5C+4
seg000:000000000000047E                 mov     ebp, ecx
seg000:0000000000000480                 xor     r14d, r14d
seg000:0000000000000483                 mov     rdx, [rax+18h]
seg000:0000000000000487                 mov     r8, [rdx+10h]


这里主要是shellcode,所以没有通用性,我这边拓展一下举个简单的例子作为堆栈提升然后恢复的部分,用的是我以前学习的实验,所以寄存器都是32位寄存器,每一行我都做了详细的备注:

c代码如下:
[C] 纯文本查看 复制代码
int plus(int x, int y) {
        return x + y;
}
int main()
{
        plus(1, 2);
        return 0;
}

汇编代码如下:

[Asm] 纯文本查看 复制代码
main函数:
# 传参
push 2
push 1
call plus  # 调用函数

add esp,8  # 外平栈

puls函数:
# 提升堆栈
push ebp  # 将ebp「栈底」存进去,便于返回
mov ebp,esp  # 将ebp「栈底」同步至esp「栈顶」
sub esp,40h  # 将esp「栈顶」减少40h 提升之后的40h就是缓冲区

# 保存当前寄存器状态
push ebx
push esi
push edi

# 将缓冲区添CC
lea edi,[ebp-40h]  # 设置串复制地址
mov ecx,10h  # ecx「计数器」设置stos计数器次数
mov eax,0CCCCCCCCh  # 设置填充内容
rep stos dword ptr [edi]  # stos指令,将EAX的值存入内存,stos 指令会将 eax 寄存器中的值存储到 edi 寄存器指向的内存地址中,然后根据当前的地址大小标志位(DF,在 EFLAGS 寄存器中)决定增加或者减少 edi。

# 执行return x + y;
mov eax,dword ptr [ebp+8]
add eax,dword ptr [ebp+0Ch]  # 返回值存放在eax寄存器中

# 恢复寄存器状态
pop edi
pop esi
pop edx
# 恢复ebp状态
mov esp,ebp
pop ebp
#返回函数
ret
 楼主| ctcaozhe 发表于 2025-7-28 08:44
zhangsanlisi321 发表于 2025-7-27 22:33
学习这方面的知识需要那些基础呀。

我当初跟着滴水逆向教程学的,他有一整套的系统课,但我也没全部看完。
然后就了解一下pe和elf文件结构,和各种系统api,还有些c语言中在汇编的体现。
主要很多操作其实都是编译器再做优化的汇编代码,看多了就可以吧汇编代码合在一起一块一块的看,千万别一行一行看汇编,会很累,我刚开始就一行行看的,深有体会。
然后就多看木马样本或者些别人写的分析文档,自己跟一下熟能生巧,中途缺有什么看不懂的地方就网上查。
小书匠 发表于 2025-7-24 11:04
NeoRainbow 发表于 2025-7-24 11:41
分析的非常好,到时候我也跟着去尝试一下,感谢分享
kimi2005yyds 发表于 2025-7-24 12:34
来找大佬学习,
wasd2025 发表于 2025-7-24 12:42
来学习学习,虽然有些地方看不懂,先研究一下
zjhzxhz 发表于 2025-7-24 13:30
感谢分享~ 上一次看汇编还是10年前
Qchi 发表于 2025-7-24 13:35
学习了,感谢分享
phan70m 发表于 2025-7-24 13:56
请教一个问题
```
mov rax, rsp    暂存当前栈顶地址到 RAX,方便直接在红区里写数据。
mov [rax+8], rbx
mov [rax+10h], rbp
```
这部分代码的解释是在红区写数据,但红区不是在栈顶之下的128字节吗
但这里保存在栈顶之上,如果保存在栈顶之上那么这段该如何解释
gsfgood 发表于 2025-7-24 16:56
我的头像怎么没了,谢谢楼主
zhangsanlisi321 发表于 2025-7-24 22:16
感谢分享,完全看不懂
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-1-16 14:42

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表