近期,火绒工程师在日常关注安全动态时发现,Lumma Stealer 木马家族会利用 Go 语言编写注入器,通过 AES 解密创建傀儡进程并注入恶意代码窃取用户信息,其中恶意代码经过控制流混淆、常量加密以及手动调用系统调用号等方式使代码复杂度提高,更难以破解。分析发现该样本会利用 Steam 账户名称、动态获取远程服务器域名,根据下载的 JSON 配置窃取相应应用程序数据,例如浏览器数据等,此外还会窃取邮箱、Steam、Discord、TXT 文件等数据。目前,火绒安全产品可对上述窃密木马进行拦截查杀,请广大用户及时更新病毒库以提高防御能力。(文末有彩蛋掉落)
查杀图
Lumma Stealer 是一种商业窃密木马,该木马于 2023 年开始在论坛上公开售卖。从 Hack Forums 论坛这篇帖子上可以发现该木马更新频率高,可对某些失效的窃密手段进行及时修补,且会经常更新代码混淆器,进行免杀。
售卖帖
样本执行流程图如下所示:
流程图
一、样本分析
该样本以 Go 为注入器,注入步骤主要由解密 Lumma Stealer ShellCode、创建傀儡进程、注入 Lumma Stealer ShellCode 组成。
注入模块
解密 Lumma Stealer 数据:
将字符串 a2f045451b2c742c6e17d6d046804e73 进行 MD5 计算,得出 AES 密钥 48901d3cdcfd61d216a912a7bd4a17a7,使用该密钥对长度为 310300 的数据进行解密。解密过程使用了 Go 标准库中的 crypto/cipher 和 crypto/aes,最终获得 Lumma Stealer 的数据。
解密秘钥的来源
解密 Demo 如下:
解密 Demo
创建傀儡进程:
利用库函数 path/filepath.Walk 遍历 C:\Windows\ 目录,遍历过程中通过检查文件后缀名找到第一个以 .exe 为结尾的文件后,使用 CreateProcessW 创建进程。
遍历文件并判断文件名后缀
创建进程
注入代码:
随后在创建的进程中调用 VirtualAllocEx 函数分配可读、可写、可执行的内存,调用 NtSetContextThread 函数指定 EIP 为 ShellCode 入口点,调用 NtWriteVirtualMemory 函数写入 ShellCode,最后调用 NtResumeThread 函数使线程继续执行,从而执行 ShellCode 代码。
分配内存
修改 EIP
写入 ShellCode
恢复线程执行
窃密模块
窃密模块分为以下阶段:
准备阶段:准备哈希/调用号表、检测沙箱、检测语言等操作。
通过配置窃取信息:从远程服务器下载 JSON 配置,通过动态获取到的规则窃取应用程序敏感信息。
固定窃取:窃取邮件、Outlook、ThunderBird、Steam、Discord、TXT 文件、剪切板、截屏、系统信息等数据。
自删除:如果下载的 JSON 配置中键 "ad" 值为 true 时将会进行自删除,具体方法为 CreateProcessW 创建进程 cmd.exe "start /min cmd.exe "/c timeout /t 3 /nobreak & del "当前进程路径"。
准备阶段
通过 fs:[30] 获取 PEB 地址,随后遍历模块链表,通过比对模块名获取 ntdll.dll 的模块基址。
PEB 地址获取
随后遍历导出表中的函数,获取函数名计算其哈希值。然后将以 Nt 开头的函数名哈希值和对应的系统调用号存入内存,以便之后通过函数名哈希值查找系统调用号,并通过 Wow64Transition 手动调用系统调用。
获取函数名哈希值和调用号
函数名哈希值与系统调用号内存布局
函数调用逻辑
之后利用上述调用系统调用的方法调用 NtOpenSection 获取 \KnownDlls32\ntdll.dll 句柄,然后调用 NtMapViewOfSection 函数将文件数据映射到内存中。再次重复将函数名哈希值和系统调用号写入内存的操作,推测这一步是为了防止安全软件修改过进程启动时加载的 ntdll.dll,所以选择以内存共享区域中的 \KnownDlls32\ntdll.dll 为标准再次获取系统调用号。
打开 \KnownDlls32\ntdll.dll 内存映射句柄
利用 NtSetInformationProcess 传递 ProcessInstrumentationCallback 参数设置回调为空,用于防止被安全软件检测到样本的系统调用。
调用 NtSetInformationProcess
通过遍历模块名计算哈希值判断是否在沙箱中,如果存在就会终止程序。
下图是通过网上比较常见的沙箱会加载的模块名计算比较得出的结果,其中一个没有被识别到。
沙箱列表
以下是上述检测沙箱时会用到的模块名哈希算法,第一个参数是模块名,第二个参数会传入固定值 0x18D40B1A。
计算模块名哈希值
还会通过调用 GetUserDefaultUILanguage 函数检测语言,具体代码为 0x419:俄罗斯。如果是俄罗斯语言则不会进行窃密操作,如果不是则继续。
不支持警告
下载配置信息并开始窃密
获取远程服务器:
该样本通过域名轮询的方式,循环连接多个候选服务器,直到成功建立连接。如果所有候选服务器都无法连接,样本会访问 https://steamcommunity.com/profiles/76561199724331900,获取页面中的 HTML 数据,使用 <span class="actual_persona_name"> 和 </span> 标签定位 Steam 账号名称。随后通过提取的 Steam 账号名称解密出远程服务器域名,目前分析时解密出的域名为 sergei-esenin.com。
由于 Steam 账号名称可以随时修改,木马作者能够通过动态更新 Steam 名称来控制和更新远程服务器的域名,从而保证样本的远程服务器持续存活。
动态获取函数地址
获取远程服务流程
Steam 名称
Steam 名称 URL 解密代码
下载配置信息并利用:
样本通过 POST 请求向 https://sergei-esenin.com/api 发送数据,请求参数包括 act=recive_message(用于指定服务器行为)、ver=4.0(版本)、lid=xAeOdp--mainteam 与 j=15f7911c5c73e2c263a9b433eb55ff31。服务器响应返回加密的 JSON 配置数据,包含需要窃取的应用程序配置信息。之后对返回的加密数据进行 Base64 解码,随后通过异或运算与位运算的组合还原解密出 JSON 配置。
JSON 数据解密算法
可以看出 JSON 数据中包含重要文件、比特币钱包、浏览器配置信息,密码数据库等,该木马会按照该配置中的路径获取文件,最后会打包成压缩文件并发送到服务器中。
部分 JSON 配置信息
下图是其中一个 Chrome 用户信息的压缩包:
压缩包
如果解析 JSON 数据时获取 se 的值为 true 则会截屏发送当前画面。
截屏逻辑
获取邮件文件:
利用 RtlExpandEnvironmentStrings 展开 %LocalAppData%,查看该文件夹下的 Packages 文件夹是否存在。
如果存在则利用通配符 microsoft.windowscommunicationsapps* 遍历文件夹,进入该文件夹递归的遍历 *.eml 邮件文件,最终将文件压缩到 Windows10Mail 压缩包中。
如果 Packages 文件夹不存在则会检查 %LocalAppData%\Microsoft\Windows Mail\Local Folders 文件夹是否存在,如果存在则遍历 *.eml 邮件文件,最终将文件压缩到 Windows10MailAlternative 压缩包中。
压缩包
下图是利用通配符遍历 *.eml 文件并压缩到 Mails/Windows10Mail 的函数调用图。
函数调用图
获取 Outlook:
遍历 HKEY_USERS 注册表,并从中检测以下注册表路径:
· Microsoft\Windows Messaging Subsystem\Profiles\9375CFF0413111d3B88A00104B2A6676
· Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\Outlook\9375CFF0413111d3B88A00104B2A6676
· Microsoft\Office\13.0\Outlook\Profiles\Outlook\9375CFF0413111d3B88A00104B2A6676
· Microsoft\Office\14.0\Outlook\Profiles\Outlook\9375CFF0413111d3B88A00104B2A6676
· Microsoft\Office\15.0\Outlook\Profiles\Outlook\9375CFF0413111d3B88A00104B2A6676
从这些路径中循环获取 00000001 ~ 00000004 键中的值,最后将敏感信息写入 Applications/Outlook/Profiles.txt 文件中压缩。
获取 ThunderBird 文件:
利用 RtlExpandEnvironmentStrings 展开 %AppData%\Thunderbird\Profiles,之后通过 NtQueryAttributesFile 查看该文件夹是否存在。如果存在则会利用通配符 * 遍历所有文件夹,并从中获取以下内容:
· key4.db(账户密码)
· cert9.db(数字证书)
· formhistory.sqlite(历史记录)
· cookies.sqlite(cookie)
· logins.json(登录凭据)
· places.sqlite(书签)
最后写入 Thunderbird 文件夹中并压缩。
压缩包
获取 Steam Token:
通过上述遍历进程名的方法查看 steam.exe 是否存在,如果存在则利用 NtOpenProcess 打开进程句柄并通过 NtReadVirtualMemory 读取内存查看是否存在 eyAidHlwIjogIkpXVCIsICJhbGciOiAiRWREU0EiIH0 字符串,如果有则获取该 Token 并解密 Base64 提取出 "sub" 的值,相加保存到 Applications/Steam/Tokens.txt 压缩。
压缩包和 Tokens.txt
获取 Steam Token 的流程与 2024 年 4 月火绒安全实验室发布的《窃密木马借"壁纸引擎"传播,Steam "再中招"》文章中提到的盗取 Token 逻辑相同,具体细节可查看往期分析报告。
获取 Discord Token:
Discord 有三种版本,分别对应路径:
· %AppData%\Discord(稳定版本)
· %AppData%\DiscordCanary(测试版本)
· %AppData%\DiscordPTB(介于中间的版本)
三者都会读取 %AppData%\[DiscordName]\Local State 文件,获取其中的 os_crypt.encrypted_key 并通过 Base64 解密,然后调用 CryptUnprotectData 函数解密后将数据写入 [DiscordName]\dp.txt 文件并压缩。
获取 NotePad++ TXT 文件:
利用 RtlExpandEnvironmentStrings 展开 %AppData%\Notepad++\session.xml,利用 NtOpenFile 打开句柄,利用 NtQueryInformationFile 获取文件大小,之后通过 NtReadFile 读取文件内容,从中读取 filename=" 开头和 " 结尾的 TXT 文件,并存储到 Important Files/Notepad++/ 压缩。
压缩包
获取已安装列表:
利用 NtOpenKeyEx 打开注册表句柄 \REGISTRY\MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall 并利用 NtQueryKey 获取键的数量。
之后利用 NtEnumerateKey 遍历注册表,通过 NtQueryValueKey 获取 DisplayName,最后写入 Software.txt 并压缩。
压缩包和 Software.txt
获取进程列表:
利用 NtQuerySystemInformation 函数传入参数 SystemProcessInformation 获取进程信息,将进程名写入到 Processes.txt 中并压缩。
压缩包和 Processes.txt
获取系统信息:
以下是获取到的系统信息以及大致方法。
· Path:通过 fs:[30] 获取 PEB,通过偏移 PEB->ProcessParameters->ImagePathName.Buffer 获取当前进程路径
· OS Version/Local Date/Time Zone/Install Date:通过 WMI 命名空间 ROOT\CIMV2,使用 WQL 查询 SELECT * FROM Win32_OperatingSystem,分别获取 Caption(版本名)、LocalDateTime(时间)、CurrentTimeZone(时区)、InstallDate(安装时间)
· 位数:mov eax, cs 判断
· Elevated:NtQueryInformationToken 传入参数 TokenElevation 获取是否已提权
· Computer:GetComputerNameA 获取计算机名
· User:GetUserNameA 获取用户名
· Domain/Hostname/NetBIOS:GetComputerNameExA 分别传入参数 ComputerNamePhysicalDnsDomain/ComputerNamePhysicalDnsHostname/ComputerNamePhysicalNetBIOS
· Language:GetUserDefaultLocaleName 获取系统语言
· Anti Virus:通过 WMI 命名空间 ROOT\SecurityCenter2,使用 WQL 查询 SELECT * FROM AntiVirusProduct 获取 displayName ( 防病毒软件的名称)
· HWID:通过 WMI 命名空间 ROOT\SecurityCenter2,使用 WQL 查询 SELECT * FROM Win32_BIOS 获取 SerialNumber(序列号),再经过计算获得
· RAM Size:GetPhysicallyInstalledSystemMemory 获取物理内存大小
· CPU 相关:通过 cpuid 获取
· GPU:EnumDisplayDevicesW 获取设备信息
· Display resolution:EnumDisplaySettingsW 获取分辨率
下图是包含了系统信息的 System.txt 文件,可以看出该 Lumma Stealer 是 2024 年 9 月 2 号编译的。
系统信息
获取剪切板数据:
利用 GetClipboardData 获取当前剪切板数据,将数据写入到 Clipboard.txt 文件后压缩。
剪切板数据
二、附录
C&C:
HASH:
讲点大白话:
有的小伙伴表示没有学过计算机知识,看不太懂这篇文章,那么你可以参考如下说明。
简单来说,网络世界存在一个对你个人信息图谋不轨的“小偷”,盗窃木马Lumma Stealer就像是它的“偷窃工具”,这个“小偷”很狡猾,它用一种叫做Go语言的编程语言来充当自己的伪装工具,伪装“偷窃工具”的“合法身份”,并通过一些加密手法来隐藏工具的行踪,使得它难以被发现和抓住。同时“偷窃工具”会悄悄潜入你的电脑,偷看你的个人信息,比如你的邮箱、Steam账户、Discord账户,甚至包括你电脑里的TXT文件、剪切板、截屏等数据。它还会根据“小偷”的指令来决定要偷哪些数据。
但是别担心,目前火绒已经可以拦截和清除这种木马了。就像有警察保护我们不受“小偷”侵害一样,所以,记得更新你的火绒病毒库,守护你的电脑安全哦~