Saturday, January 16, 2010

Trojan.Hydraq - Part II

Previous post described the installation process of the trojan and its backdoor commands.

Now it's time to inspect its connection details, in particular - where does it retrieve the host name of the remote command-and-control (C&C) server.

The source code of the trojan contains a hard-coded host name 192.168.5.164 that is tried out every 5 seconds, but these values must have been used during testing only - they are replaced with the different ones during the runtime - we must establish which ones.

It is also worth noting that the trojan's code is very fragmented - it is deliberately split into small chunks with the size of a few instructions each, connected with the calls and jumps into a large maze: the code of Trojan.Hydraq contains 1,748 jumps and 922 calls - tracing it requires quite a bit of a patience. Graph image of the disassembled source indeed reminds a serpent-like beast - hence, probably, the name.

Hydraq's call-only graph:



The trojan carries its C&C connection details (server, name, port, retry delay, etc.) inside the internal resource (name is 100, type is 243). The resource is 344 bytes in size, and it is encrypted.

Decryption of the resource is performed in 4 stages:

  • The first 8 bytes of the resource are skipped, the remaining 336 bytes are XOR-ed with 0x99

  • Next, every byte from the 336 input buffer is translated according to the following logics:

    • if the byte is a character from 'A' to 'Z', it is subtracted 'A' value (0x41)

    • if the byte is a character from 'a' to 'z', it is subtracted 'G' value (0x47)

    • if the byte is a character from '0' to '9', it is added 4

    • if the byte is a character '+', its replaced with '>'

    • if the byte is a character '/', its replaced with '?'

    • if the byte is a character '=', its replaced with '\0'

    • otherwise, the byte is left as is

  • The input buffer is then converted into the resulting buffer which is 25% smaller. This is achieved by splitting every byte quartet (bytes 0-3) from the input buffer into 3 pairs (0-1, 1-2, 2-3), performing operations over these three pairs, and writing 3 byte results into the output buffer. This way, the 336 bytes will be converted into 252 bytes (4 -> 3 conversion); in some way, it's similar to unpacking all-ASCII base64 back into binary. The operations performed over the 3 pairs are illustrated below:


  • Finally, the resulting 252 bytes are XOR-ed with 0xAB


The fully decrypted resource is shown below:


Knowing this logics, the decryption can now be reconstructed in a stand-alone tool; a function that retrieves and decrypts the resource from the trojan DLL is provided below (should be run from a virus lab as loading the DLL will invoke its entry point):

#define IDD_RES_NAME   100
#define IDD_RES_TYPE 243

void DecodeHydraqResource()
{
    HMODULE hDll;
    HRSRC hRes;
    HGLOBAL hResLoad;
    BYTE lpBuffer[0x150];
    BYTE lpResult[0x150];
    int iResultOffset;
    int i;
    char szHost[MAX_PATH];
    int dwDelay;
    int dwPort;
    char szAltDnsServer[MAX_PATH];
    char szMessage[MAX_PATH * 4];
    BOOL bOk;

    szHost[0] = '/0';
    dwDelay = 0;
    dwPort = 0;
    szAltDnsServer[0] = '/0';
    bOk = FALSE;
    szMessage[0] = '/0';

    hDll = LoadLibrary(_T("sample.dll"));
    if (hDll)
    {
        hRes = FindResource(hDll, MAKEINTRESOURCE(IDD_RES_NAME), MAKEINTRESOURCE(IDD_RES_TYPE));
        if (hRes)
        {
            hResLoad = LoadResource(hDll, hRes);
            if (hResLoad)
            {
                memset(lpResult, 0, 0x150);
                iResultOffset = 0;

                if (SizeofResource(hDll, hRes) == 0x158)
                {
                    memset(lpBuffer, 0, 0x150);
                    memcpy(lpBuffer, (LPBYTE)hResLoad + 8, 0x150);

                    for (i = 0; i < 0x150; i++)
                    {
                        lpBuffer[i] ^= 0x99;

                        if ((lpBuffer[i] >= 'A') && (lpBuffer[i] <= 'Z'))
                        {
                            lpBuffer[i] -= 'A';
                        }
                        else if ((lpBuffer[i] >= 'a') && (lpBuffer[i] <= 'z'))
                        {
                            lpBuffer[i] -= 'G';
                        }
                        else if ((lpBuffer[i] >= '0') && (lpBuffer[i] <= '9'))
                        {
                            lpBuffer[i] += 4;
                        }
                        else if (lpBuffer[i] == '+')
                        {
                            lpBuffer[i] = '>';
                        }
                        else if (lpBuffer[i] == '/')
                        {
                            lpBuffer[i] = '?';
                        }
                        else if (lpBuffer[i] == '=')
                        {
                            lpBuffer[i] = 0;
                        }
                    }

                    for (i = 0; i < 0x150; i++)
                    {
                        lpResult[iResultOffset++] = (lpBuffer[i] * 4) ^ (lpBuffer[i + 1] / 16);
                        lpResult[iResultOffset++] = (lpBuffer[i + 1] * 16) ^ (lpBuffer[i + 2] / 4);
                        lpResult[iResultOffset++] = (lpBuffer[i + 2] * 64) ^ (lpBuffer[i + 3]);
                        i += 3;
                    }

                    for (i = 0; i < 0x150; i++)
                    {
                        lpResult[i] ^= 0xAB;
                    }

                    i = strlen((LPSTR)lpResult);

                    if ((i > 0) && (i < MAX_PATH))
                    {
                        strcpy(szHost, (LPSTR)lpResult);
                        sprintf(szAltDnsServer,
                                _T("%d.%d.%d.%d"),
                                lpResult[iResultOffset - 4],
                                lpResult[iResultOffset - 3],
                                lpResult[iResultOffset - 2],
                                lpResult[iResultOffset - 1]);
                        dwPort = *(LPDWORD)(lpResult + iResultOffset - 12);
                        dwDelay = *(LPDWORD)(lpResult + iResultOffset - 8);
                        sprintf(szMessage,
                                _T("Remote Host: %s\nAlternative DNS Server: %s\nConnection Port: %d\nDelay between connection attempts: %d sec."),
                                szHost,
                                szAltDnsServer,
                                dwPort,
                                dwDelay);
                        bOk = TRUE;
                    }

                }
            }
        }
        FreeLibrary(hDll);
    }

    if (!bOk)
    {
        MessageBox(NULL, _T("Failed to retrieve any details!"), _T("Error"), MB_OK);
    }
    else
    {
        MessageBox(NULL, szMessage, _T("Success"), MB_OK);
    }

}


Once the trojan knows its C&C server, it attempts to connect to it via the specified port - e.g. server sl1.homelinux.org, port 443.

It starts doing so by trying to resolve its host name first. If this attempt fails, the trojan makes a DNS query by crafting a TCP packet on port 53 of an alternative (legitimate) DNS server, also specified in its resource, in order to resolve the same host name. For example, the analysed sample has alternative DNS server 168.95.1.1 - this is dns.hinet.net server located in Taiwan.

If Hydraq can't connect to the remote server, it falls asleep for the time specified in its resource (2 minutes), then repeats the beaconing again.

If the connection to remote host on port 443 succeeds, the malware prepares a packet to send - it is 20 bytes in size:

00 00 00 00 00 00 FF FF 01 00 00 00 00 00 00 00 00 00 77 00

The packet is encoded by inverting its bytes:

FF FF FF FF FF FF 00 00 FE FF FF FF FF FF FF FF FF FF 88 FF

As soon as the packet is submitted to the live C&C server, it receives the response packet that is also 20 bytes in size. It is encrypted with the XOR 0xCC.

Hydraq decodes the received packet and retrieves the command ID from it - a number from 0 to 18, which is then converted into the backdoor command group - a number from 0 to 10. Conversion rules Command ID -> Backdoor Command Group are shown below:

  • 0 -> 0 (adjust privileges, terminate processes)

  • 1 -> 1 (control services group)

  • 2 -> 2 (remote file execution)

  • 3 -> 3 (registry manipulation group)

  • 4 -> 4 (file system manipulation group)

  • 5 -> 10 (receive more data)

  • 6 -> 5 (system manipulations - power off/reboot/uninstall/clear events)

  • 7 -> 6 (file search)

  • 8 -> 7 (call other components)

  • 9 -> 10

  • 10 -> 8

  • 11 -> 10

  • 12 -> 10

  • 13 -> 10

  • 14 -> 10

  • 15 -> 10

  • 16 -> 10

  • 17 -> 10

  • 18 -> 9 (networks.ics file manipulations)

Next, a specific command from the chosen group is executed. For more details on backdoor groups and commands within them, please check the previous post.

It may be assumed that upon successful connection to the remote C&C server (sl1.homelinux.org), the trojan was designed to be able to update itself. A new copy may have a different C&C server specified in its resource (e.g. yahooo.8866.org, 360.homeunix.com or as in the last seen sample - blog1.servebeer.com) in order to survive the shutdown of the old servers.

The presence of a resource that stores all the connection parameters could potentially indicate an intented cloud-based automation for updating the same template with a newly generated resource without the need to recompile the sample, with the obfuscation step added on top of it to evade existing detections. With the relatively high update frequency of such server-side polymorphism, the C&C server shutdown may always fall behind; given the fact the firewalls let the traffic on port 443 through (HTTPS traffic), a heuristic detection of Trojan.Hydraq (added as Trojan.Hydraq!gen1) is a viable option that reliably breaks this vicious circle.