Let’s Learn: Progression of APT28/Sofacy Golang Zebrocy Loader ‘Project2.Go’: WMIC & Hex Decode

Goal: Document the progression of the Zebrocy (aka Zepakab) Golang loader as leveraged by the APT28/Sofacy group.

Source:
UPX-compressed APT28 Zebrocy sample (MD5: 6bc5f53d4082f12dd83aca45bae81e64)
Outline:

I. Background & Summary
II. Zebrocy main* Functions
A. main_asduiwom663721 (WMIC disk parser function)
B. main_dassdwdfgsd3321 (aka "main_getfilename")
C. main_xcmkksja1 (aka "main_CreateSysinfo")
III. Yara Signature

Background & Summary:

Since the previous analysis of the “ProjectC1Dec” Zebrocy loader, the APT28 (also known as Sofacy, Fancy Bear, STRONTIUM, Pawn Storm, and Sednit) group appears to have modified the code to leverage WMIC for host profiling rather than leveraging the GitHub code, taken from the open source repository iamacarpet/go_win64api (thanks to  / &  for the original hunting discovery).

The APT28 group continues to leverage UPX rather for binary compression to deliver the Golang Zebrocy binary.

This project debugging path of the “GoLand” project appears to be as follows:
“C:/=!=/GoLand/Project1_HEX/Project2.go” 

While the previous one was as follows:
C:/!Project/C1/ProjectC1Dec/main.go

The main differences appear to be the usage of hex encoding throughout the application to hide various variables as well as changed main function names coupled with the usage of “WMIC” to query system information. It also appears that the latest Golang Zebrocy dropped the usage of the GitHub code from iamacarpet/go_win64api (ProcessList, InstalledSoftwareList, ListLoggedInUsers,SessionDetails/FullUser), shirou_gopsutil (host_Info) while maintained the screenshot support from the open source library kbinani/screenshot(NumActiveDisplays, GetDisplayBounds, CaptureRect).

It is also notable that the command-and-control server continues to be hosted on the Qhoster 89.37.226.* range ASN AS60068. The previous server was hosted at .*123 while the latest one is at *.148.
Additionally, the observed “Run” registry key is “Driveupd”
The malware capabilities include installation in HKCU registry as “Driveupd” and locally in %APPDATA%, leveraging “WMIC” execution via “cmd”, profiling a victim system (in this case, leveraging “WMIC“, obtaining desktop screenshots, and sending the data to the server. The specific host profiling WMIC command the malware passes is “wmic logicaldisk get caption,description,drivetype,providername,size“.

The client-server communication passes the “support?6t1d %” parameter to the URL  hxxp://89[.]37[.]226[.148/technet-support/library/online-service-description.php?id_name=.

II. Zebrocy main* Functions

This Project2.go compiled sample contains changed function names as mapped to the previous sample from the previous analysis:

main_main
main_init
main_asduiwom6633 -> new hex decoder function
main_asduiwom663721 -> WMIC disk parser
main_dassdwdfgsd3321 -> main_getfilename
main_asduiwom663723 -> parser function
main_asduiwom66372 -> main_CMDRunAndExit
main_xcmkksja1 -> main_CreateSysinfo
main_jjdskasdhhfud00 -> main_ParseData
main_sg383820kks -> main_SendPOSTRequests
main_Bubugaga -> main_Screen       
main_uuettdh666 -> main_GetSND
main_asduiwom66311 -> convert to string
Additionally, the malware heavily relies on the hex decoder function with multiple xrefs to decode hex-encoded parameters.
A. main_asduiwom663721 (WMIC disk parser function)
The “main_asduiwom663721″ function parses host for driver information leveraging the following command executing via os/exec Golang library:

wmic logicaldisk get caption,description,drivetype,providername,size

The pseudo-coded C++ function of the Golang compiled binary is as follows:

////////////////////////////////////////////////
//// APT28 main WMIC disk parser Function //////
////////////////////////////////////////////////

int main_asduiwom663721()
{

...

  while ( (unsigned int)&retaddr <= *(_DWORD *)(*(_DWORD *)__readfsdword(20) + 8) )
    runtime_morestack_noctxt();
  main_asduiwom6633((int)&byte_63BCBB, 6);
  main_asduiwom6633((int)byte_63B871, 4);
  main_asduiwom6633((int)&word_64CD0E, 136); 
// "wmic logicaldisk get caption,description,drivetype,providername,size"
  v12 = v6;
  v13 = v8;
  v14 = v6;
  v15 = v8;
  os_exec_Command(v6, v8, &v12, 2, 2);
  runtime_newobject(dword_61A320, v0);
  *v5 = 1;
  v1 = v11;
  v2 = *(_BYTE *)v11;
  if ( dword_790550 )
  {
    runtime_writebarrierptr();
    v1 = v11;
  }
  else
  {
    *(_DWORD *)(v11 + 76) = v5;
  }
  os_exec__ptr_Cmd_CombinedOutput(v1, v5, v7, v9, v10);
  runtime_slicebytetostring(0);
  return v3;
}
B. main_dassdwdfgsd3321 (aka “main_getfilename“)
The “main_dassdwdfgsd3321” function (previously “main_getfilename”) simply checks for the existence of itself in ‘%APPDATA%\Identities\{83AF1378-986F-1673-091A-02681FA62C3B’} as ‘w32srv.exe’.
The pseudocoded C++ Go function is as follows:
////////////////////////////////////////////////
////// APT28 Zebrocy Go main_dassdwdfgsd3321 ///
////// GetFileName Function ////////////////////
////////////////////////////////////////////////

int main_dassdwdfgsd3321()
{

...

  while ( (unsigned int)&retaddr <= *(_DWORD *)(*(_DWORD *)__readfsdword(20) + 8) )
    runtime_morestack_noctxt();
  // %APPDATA%
  main_asduiwom6633((int)byte_63E145, 14);      
  os_Getenv();
  // \\Identities\\{83AF1378-986F-1673-091A-02681FA62C3B}
  main_asduiwom6633((int)&word_64C8DA, 104);   
  runtime_concatstring2(0, v2, v3, v2, v3);
  os_MkdirAll();
  main_asduiwom6633((int)byte_63E145, 14);
  os_Getenv();
  main_asduiwom6633((int)&word_64CB06, 128);    // \\w32srv.exe
  runtime_concatstring2(0, 1, v0, 1, v0);
  return v4;
}

C. main_xcmkksja1 (aka “main_CreateSysinfo”)
The “main_xcmkksja1” function (previously “main_CreateSysinfo”) formats concatenates host information with the appended “systeminfo”, “tasklist”, and “PrgStart: “
The pseudocoded C++ Go function is as follows:

////////////////////////////////////////////////
////// Zebrocy Go Function /////////
////////////////////////////////////////////////

int main_xcmkksja1()
{

...

  while ( (unsigned int)&v24 <= *(_DWORD *)(*(_DWORD *)__readfsdword(20) + 8) )
    runtime_morestack_noctxt();
  main_asduiwom6633(byte_6409C9, 20, v6, v7);   // 'systeminfo'
  main_asduiwom663723(v6, v7, v6, v7);
  v13 = v7;
  v19 = v6;
  main_asduiwom6633("7461736B6C697374: value of type :VerifyDNSLengthAddDllDirectory", 16, v6, v7);
  main_asduiwom663723(v6, v7, v6, v7);          // 'tasklist'
  v16 = v7;
  v22 = v6;
  os_Executable(v4, v5, v6, v7);
  if ( v6 )
  {
    v0 = 6;
    v1 = &byte_63BD1B;
  }
  else
  {
    v1 = v4;
    v0 = v5;
  }
  v15 = v0;
  v21 = v1;
  main_asduiwom6633(byte_6409B5, 20, v6, v7);   // 'PrgStart: '
  runtime_concatstring2(&v18, v6, v7, v21, v15, v9, v10);
  v21 = v9;
  v15 = v10;
  main_asduiwom663721(v4, v5);
  v20 = v4;
  v14 = v5;
  time_Now((char)v4);
  sub_44CE6E();
  sub_44CE6E();
  time_Time_Format(
    (char)v4,
    v5,
    v6,
    v7,
    v8,
    "[2006-01-02-15.04.05]block device requiredbufio: negative countcheckdead: runnable gcommand not supportedconcurrent map writesdecompression failuredefer on system stackexec: already startedfindrunnable: wrong pgcprocs inconsistencyhttp: Handler timeouthttp: nil Request.URLimage: unknown formatinvalid ALPN protocolinvalid named capturekey is not comparablelink has been severednot enough pixel datapackage not installedpanic on system stackpng: invalid format: read-only file systemreflect.Value.Complexreflect.Value.Pointerreleasep: invalid argruntime: confused by runtime: newstack at runtime: newstack sp=runtime: work.nwait= sequence tag mismatchstale NFS file handlestartlockedm: m has pstartm: m is spinningstate not recoverablestopg: invalid statustrace/breakpoint trapunknown empty Contextuser defined signal 1user defined signal 2wglCreateLayerContextwglDescribeLayerPlane from SOCKS5 proxy at %SystemRoot%\\system32\\%v.WithValue(%#v, %#v)/lib/time/zoneinfo.zip4656612873077392578125Aleutian Standard TimeAtlantic Standard TimeCaucasus Standard TimeConvertSidToStringSidWConvertStringSidToSidWCreateCompatibleBitmapCreateIoCompletionPortDEBUG_HTTP2_GOROUTINESDateline Standard TimeEKU not permitted: %#vGeorgian Standard TimeGetEnvironmentStringsWGetTimeZoneInformationHawaiian Standard TimeInscriptional_ParthianMAX_CONCURRENT_STREAMSMountain Standard TimeNtWaitForSingleObject",
    21,
    v11,
    v12);

III. Yara Signature

rule apt28_win_zebrocy_golang_loader_modified {
   meta:
      description = "Detects unpacked modified APT28/Sofacy Zebrocy Golang."
      author = "@VK_Intel"
      date = "2018-12-23; updated: 2018-12-25" 
   strings:
   // Go build
    $go = { 47 6f 20 62 75 69 6c 64 20 49 44 3a 20 }
    $init = { 6d 61 69 6e 2e 69 6e 69 74 }
    $main = "main" ascii wide fullword
    $scr_git = {67 69 74 68 75 62 2e 63 6f 6d 2f 6b 62 69 6e 61}
    $s0 = "os/exec.(*Cmd).Run" fullword ascii
    $s1 = "net/http.(*http2clientConnReadLoop).processHeaders" fullword ascii
    $s2 = "os.MkdirAll" fullword ascii
    $s3 = "os.Getenv" fullword ascii
    $s4 = "os.Create" fullword ascii
    $s5 = "io/ioutil.WriteFile" fullword ascii
   condition:
    uint16(0) == 0x5a4d and $go and $init and all of ($s*) and #main > 10 and #scr_git > 5
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s