Domains As mentioned in the
previous blog post, TDL4 has a component called
CMD32/CMD64
that fetches JPEG images from the blogs specified in its configuration file. In order to recover the configurations,
CMD32/CMD64
calls
Init()
and
Uninit()
functions that are implemented in the 'missing' component
COM32/COM64
.
Without this component and without knowing what steganography algorithm is used to conceal the text within the images, it is impossible to recover the text.
To download the
COM32
component, the C&C server should be queried with a parameter
mode=mod&filename=com32
. Previous post explained how to encrypt this parameter. The server will also require the 'GeckaSeka' user agent, otherwise it'll ignore us.
The following parameters for
wget
will fetch an encrypted
COM32
module from the C&C server:
wget.exe http://wahinotisifatu.com/?CehOKSsUCKLC3skBxcO9fFpCcXju4dg= -U "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.1) GeckaSeka/20090911 Firefox/3.5.1"
Now, in order to decrypt the received module, the RC4 key #1 will be used, as shown below:
prepare_seed(seed1); // for the received file
decrypt_file("%received_com32_module%", seed1);
where
seed1
, as before, is ripped from the
CMD32
module:
BYTE seed1[256] =
{
0xF7,0xD4,0xE8,0x26,0x43,0xDB,0x7F,0x07,0xD3,0xE2,0x86,0x38,0x78,0x6A,0x77,0x38,
0xB0,0xCA,0xEC,0x96,0x9C,0x55,0xA8,0x26,0xFB,0x45,0x5E,0x4F,0xAF,0x9A,0x32,0xFF,
0xD5,0x82,0x21,0x26,0xF2,0x98,0xDE,0x28,0xC8,0x2D,0xCC,0xCC,0xFA,0xD1,0xE5,0x2E,
0x85,0x92,0xA9,0xCC,0xF2,0x4E,0x10,0xAD,0x63,0x47,0x25,0xA3,0x91,0x53,0x6F,0xBD,
0xF1,0x1C,0x3D,0x7E,0xD5,0x1A,0x49,0x75,0x44,0x76,0x04,0xD2,0xA3,0xD3,0xE1,0x92,
0x3A,0xA4,0x11,0x96,0x6A,0x97,0x5D,0x3A,0x76,0x3B,0xF0,0xC6,0xF7,0x5F,0xB4,0xCC,
0x0B,0x7B,0x0A,0xE5,0xCF,0x6D,0xAD,0x25,0xA0,0x86,0xC1,0x54,0xC4,0x42,0x85,0x46,
0x6C,0x8A,0x84,0x98,0x5C,0x23,0x93,0x58,0x5E,0x6C,0x36,0xC7,0x3A,0xB5,0x96,0xD4,
0xEA,0xB6,0x16,0x3F,0xF2,0xC1,0x4D,0x1B,0xFC,0x91,0x5D,0xF8,0x24,0xFD,0x99,0x4A,
0xA4,0x61,0x07,0x12,0x40,0xEC,0x43,0xBF,0x51,0x36,0xEE,0x4E,0xE9,0x58,0x87,0xBF,
0x1E,0xF0,0xBF,0x0A,0x32,0xE3,0xB8,0xB2,0x52,0xB3,0x49,0x3D,0x53,0x57,0x19,0xA8,
0x68,0xD0,0x0B,0xD5,0x50,0xD6,0x3A,0x0E,0x6E,0x3B,0xBF,0xD6,0x1C,0x6B,0x0C,0x80,
0x05,0x43,0x8D,0xD0,0x77,0xF9,0x64,0xA8,0x6B,0xB5,0xF6,0x0D,0xA0,0x9A,0x3D,0x2F,
0x00,0x52,0x3E,0x39,0xD0,0x48,0x2B,0xE7,0x55,0xE4,0x47,0x57,0x46,0x34,0xE3,0x1E,
0xFA,0xBE,0x0A,0x45,0xAF,0xCD,0x39,0xD3,0xA1,0x81,0xC2,0x35,0x50,0x21,0x65,0x70,
0x8C,0x3D,0x1B,0x3A,0xFC,0xC9,0x6A,0x96,0x65,0x18,0xC6,0x67,0x3A,0x70,0x97,0xE1,
};
With the same RC4 decryptor, the file decryption routine is implemented as:
void decrypt_file(LPTSTR szFileName, LPBYTE seed)
{
HANDLE hFile = NULL;
HANDLE hMap = NULL;
LPBYTE lpbyBase = NULL;
DWORD dwSize = 0;
BYTE bRet = 0;
if ((hFile = CreateFile(szFileName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL)) != INVALID_HANDLE_VALUE)
{
if (((dwSize = GetFileSize(hFile, NULL)) != INVALID_FILE_SIZE) &&
((hMap = CreateFileMapping(hFile,
NULL,
PAGE_READWRITE,
0,
0,
NULL)) != NULL))
{
if ((lpbyBase = (LPBYTE)MapViewOfFile(hMap,
FILE_MAP_ALL_ACCESS,
0,
0,
0)) != NULL)
{
RC4KEY rc4_key;
rc4_init(seed, 256, &rc4_key);
rc4_crypt(lpbyBase, dwSize, &rc4_key);
UnmapViewOfFile(lpbyBase);
}
CloseHandle(hMap);
}
CloseHandle(hFile);
}
}
The decrypted file is indeed a DLL file that exports
Init()
and
Uninit()
APIs. Without even trying to understand the steganography algorithm implemented in it, let's load it up and try to call its exports in order to decrypt the JPEG images posted into the blogs, specified in the
MAIN
configuration file as:
[jpeg_begin]
http://Skylaco[censored].livejournal.com/|m6dj7aA9mhQKdI8X3jy9
http://miqefic[censored].wordpress.com/|jt5G/KE25R1VSaYny0rr
[jpeg_end]
Needless to say, the
COM32
Dll should always be loaded in the controlled environment (treated as a malware) as the online version of it might be updated with malicious code any time.
In order to call
Init()
and
Uninit()
, first we need to understand what parameters are expected by these functions.
As seen in the disassembled code below, the
Init()
function accepts 5 parameters: a pointer into JPEG buffer, its size, pointer into the address of the decoded configuration data, its returned size, and finally, a JPEG steganography password.
.text:10003782 mov ecx, [esi] ; decrypted JPEG password
.text:10003784 push ecx
.text:10003785 lea edx, [esp+62D4h+Size] ; returned configuration size
.text:10003789 push edx
.text:1000378A lea eax, [esp+62D8h+lpConfig] ; pointer into configuration
.text:1000378E push eax
.text:1000378F push ebp ; JPEG file (buffer) size
.text:10003790 push ebx ; pointer into JPEG raw buffer
.text:10003791 call [esp+62E4h+lpfnInit]
JPEG steganography password is recovered by decrypting the righ-hand part of the blog URL specified in the configuration (as shown above). For example, to decrypt all images from the
Skylaco[censored].livejournal.com blog
, the string
m6dj7aA9mhQKdI8X3jy9
should be decrypted with the RC4 key #1, and then passed to the
Init()
function within
COM32
Dll.
The
Init()
function will allocate memory where it will unpack the configuration. As shown on the listing below, it will then save the recovered configuration back into the memory section of the infected host process, then pass the pointer of the allocated memory buffer to
Uninit()
function in order to de-allocate the memory:
.text:100037DF mov eax, [esp+62D0h+lpConfig] ; get config pointer
.text:100037E3 push offset aMain_0 ; "main"
.text:100037E8 call save_into_host_image
.text:100037ED test eax, eax
.text:100037EF jz start_over_again
...
.text:100037F5 mov eax, [esp+62D0h+lpConfig] ; get config pointer
...
.text:100037F9 push eax
.text:100037FA call [esp+62D4h+lpfnUninit] ; pass it to Uninit()
Knowing exactly what parameters are used for
Init()
and
Uninit()
, let's declare the prototype for these functions:
typedef WINADVAPI BYTE (WINAPI *FINIT)(LPBYTE abyJpegBuffer,
DWORD dwJpegSize,
LPDWORD lpdwConfigPointer,
LPDWORD lpdwSize,
LPSTR szJpegKey);
typedef WINADVAPI BYTE (WINAPI *FUNINIT)(DWORD dwConfigPointer);
FINIT lpfnInit = NULL;
FUNINIT lpfnUninit = NULL;
Next, let's call the function that will decrypt the downloaded JPEG image, passing it the JPEG steganography password that is specified in the configuration:
decrypt_jpeg("%downloaded_jpeg_file%", "jt5G/KE25R1VSaYny0rr");
where
decrypt_jpeg()
function is implemented as shown below:
void decrypt_jpeg(LPSTR szFileName, LPSTR szJpegKeyBase64)
{
char zJpegKey[MAX_PATH];
decrypt(szJpegKeyBase64, szJpegKey, seed1);
HANDLE hFile = NULL;
HANDLE hMap = NULL;
LPBYTE lpbyBase = NULL;
DWORD dwSize = 0;
DWORD dwConfigPointer;
DWORD dwConfigSize;
DWORD dwBytesWritten;
char szConfigTxt[MAX_PATH];
HANDLE hConfigTxt = NULL;
HINSTANCE hCom32 = LoadLibrary("%decrypted_com32_module%");
if (hCom32 == NULL)
{
return;
}
lpfnInit = (FINIT)GetProcAddress(hCom32, "Init");
lpfnUninit = (FUNINIT)GetProcAddress(hCom32, "Uninit");
if ((lpfnInit == NULL) || (lpfnUninit == NULL))
{
FreeLibrary(hCom32);
return;
}
if ((hFile = CreateFile(szFileName,
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL)) != INVALID_HANDLE_VALUE)
{
if (((dwSize = GetFileSize(hFile, NULL)) != INVALID_FILE_SIZE) &&
((hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL)) != NULL))
{
if ((lpbyBase = (LPBYTE)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0)) != NULL)
{
dwConfigSize = 0;
dwConfigPointer = 0;
if (lpfnInit(lpbyBase,
dwSize,
&dwConfigPointer,
&dwConfigSize,
szJpegKey))
{
if (dwConfigPointer && dwConfigSize)
{
sprintf_s(szConfigTxt, MAX_PATH, "%s.txt", szFileName);
if ((hConfigTxt = CreateFile(szConfigTxt,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL)) != INVALID_HANDLE_VALUE)
{
WriteFile(hConfigTxt,
(LPVOID)dwConfigPointer,
dwConfigSize,
&dwBytesWritten,
NULL);
CloseHandle(hConfigTxt);
}
}
lpfnUninit(dwConfigPointer);
}
UnmapViewOfFile(lpbyBase);
}
CloseHandle(hMap);
}
CloseHandle(hFile);
}
FreeLibrary(hCom32);
}
Applying this function over an image downloaded from one of the blogs above (the actual image below doesn't have an embedded text - it was stripped as the image was processed, the original image is available
here):
reveals full configuration file that includes new C&C servers in it:
Applying this function over all JPEG images from the 2 previously mentioned blogs, allows assembling the C&C domain list below:
http://andianralway.com
http://ardchecksys.com
http://arevidenlo.com
http://asdron.com
http://aspirefotbal.com
http://atisedir.com
http://ciselwic.com
http://docietyofa.com
http://doproter.com
http://ecavesiyc.com
http://ersitycardio.com
http://farepala.com
http://healthclini.com
http://icaidspenp.com
http://lacuricub.com
http://listofvoteri.com
http://mecarinariniz.com
http://merialedilasuc.com
http://njmedicaice.com
http://nucerecat.com
http://playpitchca.com
http://ramofgrenca.com
http://rentalprope.com
http://ricardogoe.com
http://sardpuitsmea.com
http://sdhcardusba.com
http://shuttleserv.com
http://silverlakem.com
http://tilesnightc.com
http://tobenri.com
http://uclanedical.com
http://uindirected.com
http://uluniwiming.com
http://usibetsou.com
http://vaneriledcas.com
http://wacardeuse.com
http://wahinotisifatu.com
http://waoninstofnatine.com
http://washutubs.com
http://wideoexpre.com
http://wieremien.com
http://yonseiuniver.com
Once the new C&C servers go live, TDL4 will visit them and request updated configuration from them. The new configuration may specify different blogs with the different posted JPEG images, and new configuration data embedded in them, pointing into the new domains. This vicious cycle may potentially go on indefinitely. Until there is at least one live domain or one live blog, the masterminds behind the botnet have a chance to inject a new portion of the domains and blogs into this deadly whirlpool, preserving full control over the victims.