aphex - dissection of a backdoor

38
Back in the day when backdoors still had room to grow, Aphex was signed on with a major publisher to write a book on the subject. He missed a few deadlines and they ended up axing the project. In 2004, He sent me a few chapters to check out. Here is chapter two for the internet museum (hope he doesn’t mind). -stm Remember: akcom, drocon, archphase, MrJinxy, Olympus, PrinceAli, d-one, nelix, k0nsl, aza, b0b, slash, illy and everyone else that experienced the #trinity days. Chapter 2, 1

Upload: munawir-bin-syamsuddin

Post on 27-Nov-2014

129 views

Category:

Documents


7 download

TRANSCRIPT

Page 1: Aphex - Dissection of a Backdoor

Back in the day when backdoors still had room to grow, Aphex was signed on with a major publisher to write a book on the subject. He missed a few deadlines and they ended up axing the project. In 2004, He sent me a few chapters to check out. Here is chapter two for the internet museum (hope he doesn’t mind).

-stm

Remember:

akcom, drocon, archphase, MrJinxy, Olympus, PrinceAli, d-one, nelix, k0nsl, aza, b0b, slash, illy and everyone else that experienced the #trinity days.

Chapter 2, 1

Page 2: Aphex - Dissection of a Backdoor

Chapter 2

Dissection of a Backdoor

Components of a Backdoor

Backdoors consist of many different parts that enable them to achieve their ultimate goal, complete access to a system. As the previous chapter described, these components are common for most backdoors. This chapter briefly details each one and demonstrates how their simplest forms behave. In addition, this chapter will provide you with an overview of how backdoors work and prepare you for the following chapters.

Physical: Executable Formats

In order for backdoors to transfer from the attacker to the victim, they need to be in a standard package that can run on the victim. The portable executable (PE) format is the most common form of transportation and execution. The PE format defines how an application should store its code and dependencies in a single file. This executes with the same results across multiple versions of Windows operating system and usually without any extra libraries needed.

When a user double clicks a file icon in Explorer, many things can happen depending on the type of file. The way in which Explorer determines the type of file is by the file extension. For example, in the filename “server.exe” the “exe” is the file extension. The extension tells Explorer that this is an executable file and it should create a new process for it when a user double clicks it. Explorer opens other file extensions such as “mp3” and “wav” with an associated program such as Windows Media Player. Explorer knows to do this by checking information located in the registry. The setting for EXE files is under the root key, HKEY_CLASSES_ROOT, located in \exefile\shell\open\command.

This key instructs how Explorer is to open executable file types. In this case, it should simply execute it and pass the remaining parameters to it. Interestingly enough, backdoors can modify this key so that Explorer executes the backdoor every time a user opens an EXE. The backdoor is then responsible for loading the executables. If an antivirus deletes the backdoor without repairing this registry value, executable files will not be able to load. The “exe” extension is just one of many executable extensions. Table 2.1 lists every default alternate executable extension on Windows XP.

Table 2.1Alternate Executable Extensions

Chapter 2, 2

Page 3: Aphex - Dissection of a Backdoor

Extension Name Type

BAT MS-DOS Batch File Batch shell scripts

CMD Windows NT Command Script Shell script, NT specific batch file

COM MS-DOS Application MS-DOS console application

PIF Shortcut to MS-DOS Application Links to MS-DOS applications

SCR Screen Saver Executable screen saver application

If an attacker renames a standard EXE to any of these extensions, it will still work the same. The only difference is the file icon, which may change to the icon of the associated format. These different formats can confuse users into opening applications that would otherwise be suspicious. An interesting note about the PIF extension is that Explore hides it regardless of the “Hide extensions for known file types” option selected or not. This can confuse users into running an application that appears as “server.mp3” when its name is actually “server.mp3.pif”. Several worms have made use of this fact. Because of this, the PIF extension has grown in common user knowledge as being an executable file type.

Beyond EXE Files

Many other file formats can lead to a backdoor installing and executing. Windows provides a built-in scripting engine called Windows Script Hosting (WSH). This engine allows scripts to fulfill many dynamic and useful purposes. Unfortunately, it also opens yet another avenue for attackers to exploit. WSH comes with two scripting languages installed by default. These are Visual Basic (VB) and Java or VBScript and JScript respectfully. These scripts are able to manipulate various system aspects such as the file system and registry. They are also able to execute applications. Some attackers will embed a standard EXE file inside of a script. When the script executes, it writes the EXE to disk and executes it. Attackers call this “dropping” an executable. Many tools allow attackers to create droppers for different file formats. Table 2.2 details some of the formats attackers use to drop EXE files.

Table 2.2Scripting Executable ExtensionsExtension Name Type

VBS VBScript Script File Visual Basic script

VBE VBScript Encoded Script File Encoded Visual Basic script

JS JScript Script File Java script

SHS Scrap Object OLE scrap object

HTA HTML Application Trusted form of HTML files

Windows trusts all of these extensions and does not display any warning or prompt the user to allow prevention of performing malicious activities. They are as dangerous as any EXE file. Like PIF extensions, Explorer automatically hides the SHS extension by default regardless of the folder settings. Shell scraps are also interesting because Windows can embed executable files in them using only Wordpad. The result is an executable that appears to be a harmless text file with only a slightly different file icon.

Chapter 2, 3

Page 4: Aphex - Dissection of a Backdoor

Another form of PE files is Dynamic Link Libraries (DLL). These DLL files are a special type of PE that allows applications to share code. Typically DLL files are loaded by an application using Windows built-in loader, LoadLibrary(). Double clicking on a DLL file will not cause it to execute. However, some extensions will inadvertently cause a DLL to be loaded. One of these is the CPL extension. These CPL files are control panel extensions and control.exe loads them when they are double clicked. Control panel extensions have a few special requirements to work properly but unfortunately, control.exe only verifies these requirements after they are loaded and the DLL has already had a chance to execute its code.

Persistence: Surviving Shutdowns

For a backdoor to be useful, the attacker must be able to connect to it. If the backdoor is not running because Windows has been shutdown, the attacker will not be able to connect. To overcome this, the backdoor must have a way to execute each time Windows starts. There are many methods to accomplish this. The backdoor can use one of Windows standard methods that it provides to applications. If the backdoor wants to be even more insidious, it can piggyback on top of another application that automatically starts each time Windows loads. Table 2.3 details the standard methods for having an application load with Windows in the order that they are loaded.

Table 2.3Standard Startup MethodsStartup Method Windows Versions Descriptions

Config.sys 95, 98 This INI format file provides management of real mode devices during the boot process.

Autoexec.bat 95, 98 Config.sys loads this file unless otherwise specified.

Winstart.bat 95, 98 Halfway between DOS and Windows, this file is loaded and parsed.

Registry Run All Versions This is by far the most popular method for loading applications every time Windows loads.

Wininit.ini All Versions Installers usually use this to delete or rename files after rebooting.

System.ini All Versions This INI handles loading drivers and configuring system settings.

Win.ini All Versions Win.ini is similar to System.ini except that it is for non-system applications.

NT Services NT, 2000, XP, 2003 Services are the newest and most powerful startup method. Services execute with the highest privileges and exist in separate from the logged in user.

Startup Folder All Versions The startup folder is easiest to use. Any applications or shortcuts located in this folder will

Chapter 2, 4

Page 5: Aphex - Dissection of a Backdoor

automatically execute each time a user logs in.

Active Setup All Versions This is one of the least known methods. It is only available with Internet Explorer 5 or later.

Using Registry Run Example

The following application demonstrates how to create a registry key to load an application each time Windows starts.

1 program regrun1;23 {$APPTYPE CONSOLE}45 uses6 Windows;78 var9 Key: HKEY;1011 const12 Path: 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run';13 App: 'run.exe';1415 begin16 RegOpenKey(HKEY_LOCAL_MACHINE, Path, Key);17 RegSetValue(Key, App, REG_SZ, App, Length(App) + 1);18 RegCloseKey(Key);19 end.

Lines 1-6: Options and dependencies

This section defines the program name, compiler options and other needed units to compile this application. This example’s binary output is very small because it does not use Delphi’s visual component library (VCL). Instead, it only needs Windows. This is an advantage for file size but it makes developing applications a lot more work. There are other options if the developer is willing to sacrifice executable size for ease of use.

Lines 8-9: Global variables

Global variables are accessible from any place in the application. Variables allow applications to store temporary information to use in computational tasks. Complex or multi-threaded applications sparsely use global variables because of synchronization issues. Windows threads can execute at the same time as each other and if more than one thread tries to access the same variable, the threads may interrupt each other and cause unexpected errors. To overcome

Chapter 2, 5

Page 6: Aphex - Dissection of a Backdoor

the issue Windows offers several “bottle necks” that allow multiple threads to access global variables one at a time.

Line 16: Open the key

Before the application is able to write to the registry, it must first open a handle to a key or create one. The registry is similar in format to an ordinary file system. There is a root key and one or more subkeys. Each subkey can hold multiple values. Each value can have a different format such as a string or binary value. To open a registry key it uses the RegOpenKey() function.

function RegOpenKey( hKey: HKEY; lpSubKey: PChar; var phkResult: HKEY): Longint; stdcall;

The RegOpenKey() function accepts 3 parameters. The hKey parameter is the root key containing the desired subkey. The lpSubKey parameter contains the location of the subkey in a string format. The phkResult receives the opened key if the function is successful. If the function succeeds, the result is 0. If the function fails, the result is a nonzero error code.

Line 17: Set the value

Now that the application has opened a key, it can write its values to it. The type of value required for a startup registry key is REG_SZ or REG_EXPAND_SZ.

function RegSetValue( hKey: HKEY; lpSubKey: PChar; dwType: DWORD; lpData: PChar; cbData: DWORD): Longint; stdcall;

The RegSetValue() function accepts 5 parameters. The hKey parameter is the previously opened registry key. The lpSubKey parameter is the value to write the data. The dwType parameter specifies the format of the value, in this case REG_SZ. The lpData parameter is a pointer to the data to write to the value. The cbData parameter is the number of bytes to write. If the function succeeds, the result is 0. If the function fails the results is a nonzero error code.

Line 18: Close the key

Chapter 2, 6

Page 7: Aphex - Dissection of a Backdoor

Once the application has written the value to the registry, it needs to clean up by closing all open keys. This is done with the RegCloseKey() function.

function RegCloseKey( hKey: HKEY): Longint; stdcall;

The RegCloseKey() function accepts 1 parameter. The hKey parameter is the registry key to close. If the function succeeds, the result is 0. If the function fails, the result is a nonzero error code.

Installing Backdoors

The other half of the persistence puzzle is choosing exactly where to locate the backdoor executable. It would not be wise if the backdoor created a registry key pointing to its current location. Typically, the first execution location of a backdoor is only temporary. If the backdoor were to rely on this location, it would most likely be deleted or raise suspicion. An ideal location would be in a Windows system file directory. Because of the numerous system files located in these folders, the backdoor is more likely to go unnoticed. When a backdoor chooses this approach, it will often set its file time to match a genuine system file. This gives the backdoor an appearance of not being any newer than the surrounding cover files. Some startup methods are location dependent. For example, the startup folder method requires the executable to be located in its folder.

Copying To System Example

The system folder is an ideal location for backdoors. Windows provides several functions for locating important folders. Once the system folder is located, the backdoor compares its current path with the system folder path. If they do not match, the backdoor creates a copy of itself in the system folder and executes the copy. After moving to the system folder, the original copy exits and allows the copy to continue execution from where the original halted. The following example demonstrates the functions involved in this procedure.

1 program sysinstall1;23 {$APPTYPE CONSOLE}45 uses6 Windows, Sysutils;78 var9 CurrentPath: pchar;10 SystemPath: pchar;11 Location: pchar;1213 function GetSystemPath: string;

Chapter 2, 7

Page 8: Aphex - Dissection of a Backdoor

14 var15 Path: array [0..MAX_PATH - 1] of Char;16 begin17 GetSystemDirectory(Path, Sizeof(Path));18 Result := string(Path) + '\';19 end;2021 begin22 CurrentPath := pchar(ExtractFilePath(ParamStr(0)));23 SystemPath := pchar(GetSystemPath);24 WriteLn(ParamStr(0));25 ReadLn;26 if lstrcmpi(CurrentPath, SystemPath) <> 0 then27 begin28 Location := pchar(string(SystemPath) + '\test.exe');29 CopyFile(pchar(ParamStr(0)), Location, False);30 WinExec(Location, SW_SHOW);31 end;32 end.

Line 22-23: Get current and system path

The current path is located within the command line passed to the current process. Each new process receives a copy of the command line consisting of the current executable path and the parameters passed to the application. The application can access the first item in the command line using ParamStr(0).

The system path is located using the GetSystemDirectory() function. In the example, a wrapper function, GetSystemPath() has been created to simplify using the function.

function GetSystemDirectory( lpBuffer: PChar; uSize: UINT): UINT; stdcall;

The GetSystemDirectory() function accepts 2 parameters. The lpBuffer parameter is a buffer used to store the resulting path string. The uSize parameter specifies the size of the buffer in bytes. If the function succeeds, the result is length in bytes returned in lpBuffer. If the length is greater than the size of the buffer, the result returns the number of bytes needed to contain the string. If the function fails, the result is 0. To get extended error information use GetLastError().

Line 26: Compare paths

Windows file paths are case insensitive. Because of this, the application uses the case insensitive string compare function, lstrcmpi().

Chapter 2, 8

Page 9: Aphex - Dissection of a Backdoor

function lstrcmpi( lpString1: PChar; lpString2: PChar): Integer; stdcall;

The lstrcmpi() function accepts 2 parameters. The lpString1 parameter is the first string to compare. The lpString2 is the second string to compare. If the strings are equal, the result is 0. If the first string is greater than the second is, the result is positive. If the first string is less than the second is, the result is negative.

Line 29: Create a copy

To copy a file from one path to another, the CopyFile() function is used. This function is very straightforward.

function CopyFile( lpExistingFileName: PChar; lpNewFileName: PChar; bFailIfExists: BOOL): BOOL; stdcall;

The CopyFile() function accepts 2 parameters. The lpExistingFileName parameter is the path of the file the application wishes to copy. The lpNewFileName is the target path for the new copy. The bFailIfExits parameter instructs whether or not the CopyFile() function should return false if the file already exists at the location for the new copy. If the function succeeds, the result is True. If the function fails, the result is False.

Line 30: Execute the copy

Once the application creates the copy, it can execute the copy and allow it to continue execution. These steps happen in a split second. To the user, it appears as if the original application was never in use. There are several methods available for executing a new application. One very simple function is WinExec().

function WinExec( lpCmdLine: LPCSTR; uCmdShow: UINT): UINT; stdcall;

Chapter 2, 9

Page 10: Aphex - Dissection of a Backdoor

The WinExec() function accepts 2 parameters. The lpCmdLine parameter is the desired application to run. It can simply be a path to an executable or it can also specify parameters to pass to the executable. The uCmdShow parameter specifies whether the process should execute visibly to the user. Backdoors will typically use SW_HIDE to create a hidden process but for demonstration purposes, the example uses SW_SHOW instead. If the function succeeds, the result is greater than 31. If the function fails, it can be one of several error codes, 0, ERROR_FILE_NOT_FOUND, ERROR_PATH_NOT_FOUND or ERROR_BAD_FORMAT.

Execution: Process Hijacking and DLL Injection

Once a backdoor has taken all the necessary precautions to ensure its persistence, it is time for the backdoor to decide where it should execute. The simplest choice is to stay in the current process and continue execution. However, this is not very stealthy because any process viewer will be able to view the extra running process unless it takes steps to hide its presence. To do so, backdoors use remote execution. The backdoor will copy its code into another process and execute from there.

The most useful form of remote execution is DLL injection. A backdoor will contain its code inside of a DLL and force another application to load it. When the application loads the DLL, it executes its entry point. This allows the DLL to create a thread to continue execution while passing control back to the calling process. To have a DLL loaded in another process, the backdoor must force the other process to call LoadLibary() with the path to its DLL. The following example demonstrates the functions involved in this procedure.

Another method of remote execution is process hijacking. This is when the backdoor executable takes an existing process and copies its code directly into the process. The application then forces the process to execute its code by either creating a new thread for it or changing the execution point of an existing thread.

DLL Injection Example

1 program dllinject1;23 uses4 Windows;56 var7 PID, BytesWritten: dword;8 Process, Thread, ThreadId: dword;9 hKernel: dword;10 pLoadLibrary, Paramaters: pointer;11 DLL: pchar;1213 begin14 DLL := 'c:\Inject\Library.dll';15 PID := 1784;16 Process := OpenProcess(PROCESS_ALL_ACCESS,

Chapter 2, 10

Page 11: Aphex - Dissection of a Backdoor

17 False,18 PID);1920 Paramaters := VirtualAllocEx(Process,21 nil,22 4096,23 MEM_COMMIT,24 PAGE_READWRITE);2526 WriteProcessMemory(Process,27 Paramaters,28 Pointer(DLL),29 4096,30 BytesWritten);3132 hKernel := GetModuleHandle('KERNEL32.DLL');3334 pLoadLibrary := GetProcAddress(hKernel,35 'LoadLibraryA');3637 Thread := CreateRemoteThread(Process,38 nil,39 0,40 pLoadLibrary,41 Paramaters,42 0,43 ThreadId);4445 WaitForSingleObject(Thread, INFINITE);4647 VirtualFreeEx(Process,48 Paramaters,49 0,50 MEM_RELEASE);5152 CloseHandle(Thread);53 CloseHandle(Process);54 end.

Lines 14-15: Set DLL path and target process

First, the application must select a target process. This is beyond the scope of this example. Later chapters cover target selection in detail. For now, the only concern is how the DLL gets loaded. The path to the DLL must be the full path unless it is located in the system folder. This is because LoadLibrary() only checks a few known locations and the DLL will most likely not be in the same directory as the target application. The PID is the Process ID of the target.

Lines 16-18: Open target process

Before the application can interact with the target process, it must first open a handle to it. This is done using the OpenProcess() function.

Chapter 2, 11

Page 12: Aphex - Dissection of a Backdoor

function OpenProcess( dwDesiredAccess: DWORD; bInheritHandle: BOOL; dwProcessId: DWORD): THandle; stdcall;

The OpenProcess() function accepts 3 parameters. The dwDesiredAccess parameter is the type of access requested for the process. The level of access available depends on the current user and account settings. For injection, PROCESS_ALL_ACCESS is required. The bInheritHandle parameter specifies whether the application should inherit the target’s handles. The example does not need this for injection. The dwProcessId parameter is the process ID of the target. If the function succeeds, the result is an open handle to the process. If the function fails, the result is 0,

Lines 20-24: Allocate memory in the target

The LoadLibrary() function requires, among other things, a pointer to a null terminated string containing the path to the DLL to be loaded. Since pointers are only valid in the same process, the application must allocate a new chunk of memory in the target to store the DLL path string. Windows NT 4 and later versions provide the VirtualAllocEx() function for this purpose.

function VirtualAllocEx( hProcess: THandle; lpAddress: Pointer; dwSize: DWORD; flAllocationType: DWORD; flProtect: DWORD): Pointer; stdcall;

The VirtualAllocEx() function accepts 5 parameters. The hProcess parameter is an open handle to the target process. The lpAddress parameter is the preferred address for the newly allocated memory. If the application specifies an address of nil, Windows will automatically select a suitable address. The dwSize parameter is the number of bytes of allocated memory required. The flAllocationType parameter specifies the type of allocation. In order to access the memory it must first be committed. The flProtect specifies the type of operations allowed. Since the example is merely using the memory for data storage and not executable code, the value of PAGE_READWRITE is used. If the function succeeds, the result is a pointer to the newly allocated memory. If the function fails, the result is nil.

Lines 26-30: Write the DLL path to the target

Chapter 2, 12

Page 13: Aphex - Dissection of a Backdoor

As mentioned before, pointers are not valid across multiple processes. This means that the pointer to the memory previously allocated is only valid in remote process. Fortunately, Windows provides access to pointers located in other processes using certain debug functions. To write data to the memory, the application uses WriteProcessMemory().

function WriteProcessMemory( hProcess: THandle; const lpBaseAddress: Pointer; lpBuffer: Pointer; nSize: DWORD; var lpNumberOfBytesWritten: DWORD): BOOL; stdcall;

The WriteProcessMemory() function accepts 5 parameters. The hProcess parameter is the previously opened handle to the target process. The lpBaseAddress parameter is the starting address of the memory the application is modifying. The lpBuffer parameter is a pointer to the data that the application is writing. The nSize parameter specifies the number of bytes contained in the buffer. The lpNumberOfBytesWritten parameter will contain the number of bytes actually written to the memory when the function returns. If the function succeeds, the result is nonzero. If the function fails, the result is 0.

Lines 37-43: Force the target to load the DLL

Once the DLL path has been successfully copied into the target process, the target can then be forced to call LoadLibrary(). To do this, the application creates a new thread in the target process. The new thread’s starting address is set to the address of LoadLibrary() and the parameter passed to it is the pointer to the allocated memory containing the DLL path.

function CreateRemoteThread( hProcess: THandle; lpThreadAttributes: Pointer; dwStackSize: DWORD; lpStartAddress: TFNThreadStartRoutine; lpParameter: Pointer; dwCreationFlags: DWORD; var lpThreadId: DWORD): THandle; stdcall;

The CreateRemoteThread() function accepts 7 parameters. The hProcess parameter is the previously opened handle to the target process. The lpThreadAttributes specifies the security parameters for the thread. A value of 0 results in the default security descriptor. The dwStackSize parameter specifies the initial size of the thread’s stack. A value of 0 results in the default stack size. The lpStartAddress parameter is the address of the code to execute when the thread loads. To load a DLL this should be set to the address of LoadLibrary(). When the

Chapter 2, 13

Page 14: Aphex - Dissection of a Backdoor

thread loads, Windows passes the lpParameter parameter to the thread. This should be set to the path of the DLL to load. The dwCreatingFlags parameter specifies different options to use when creating the thread. The example does not need any no additional options. If the default value of 0 is specified. The lpThreadId parameter will contain the thread ID of new thread when the function returns. If the function succeeds, the result is a handle to the new thread. If the function fails, the result is 0.

Line 45: Wait for the DLL to load

Before the application can clean up it must wait for the thread to exit and the DLL to load. Windows provides a function for just this purpose.

function WaitForSingleObject( hHandle: THandle; dwMilliseconds: DWORD): DWORD; stdcall;

The WaitForSingleObject() function accepts 2 parameters. The hHandle parameter is a handle that the application wishes to wait for. The function waits until the handle is in a signaled state or it is no longer valid. The dwMilliseconds parameter specifies the time in milliseconds to wait on the handle. To wait forever the application specifies a value of INFINITE. If the function succeeds, the result is the event that caused the function to return. If the function fails, the result is WAIT_FAILED.

Lines 47-53: Clean up

After the DLL is loaded, the application frees the memory to spare resources from being wasted. This process is basically the opposite of VirtualAllocEx().

function VirtualFreeEx( hProcess: THandle; lpAddress: Pointer; dwSize: DWORD; dwFreeType: DWORD): Pointer; stdcall;

The VirtualFreeEx() function accepts 4 parameters. The hProcess parameter is the previously opened handle to the target process. The lpAddress is the starting address of the allocated memory. The dwSize parameter is the number of bytes to free. The dwFreeType parameter is the type of freeing operation. In the case of the example, it needs to be decommitted. For some strange reason, Borland has declared the return type of VirtualAllocEx() as a

Chapter 2, 14

Page 15: Aphex - Dissection of a Backdoor

point. However, according to the Windows SDK the result should be of a BOOL type. If the function is nil, it has failed. Otherwise, the result is nonzero.

Communications: Clients and Servers

Client and server relationships form the basis of most of the technologies we use on the internet. A server provides a service and a client enables us to make use of that service. The most common type is a HTTP server. As a service, it provides web pages and other content such as images for the HTTP client. Your web browser is the HTTP client. It utilizes HTTP to communicate with the server and retrieve documents. In order for the client and server to communicate there must be a set of rules or a protocol that defines the roles and behaviors of the client and server. Most developers design protocols on paper first and then later translate it into source code. This allows developers to solve problems in abstract first without rewriting many lines of source code. However, backdoor protocols are anything but standard and most developers write their protocols on the fly. The actual format of the messages exchanged can be simple or complex depending on the intended usage and the developer’s level of programming skill. A simple message protocol may look something like the following.

“Command + Separator + Parameter 1 + Separator + Parameter 2…”

The client and server use the separator to break the message into usable pieces. Each different command specifies the number and type of parameters expected according to the protocol. This type of approach works fine as long as binary data is not expected. Otherwise, the binary data might contain the separator or null characters and make correctly parsing the data impossible.

In most backdoor protocols, there are two types of messages, request and reply. The client sends requests to the server to request a service. This can be anything from the server status to a listing of files. The server sends back the results of the request in the form of a reply message. The message may contain a message indicating the success or failure of the command, or it can contain the requested data such as a file listing.

Before the client and server exchange messages, they form a connection between each other. Most Windows applications use Winsock to accomplish this. Winsock is a set of API that allows applications to send data over any network protocol. It defines a specification for a socket similar to the BSD Unix version. Applications create a socket and receive a descriptor to it, much like a file handle or a named pipe. Assuming the protocol is connection oriented the application makes a connection. Upon a successful connection, the application reads and writes data to the socket descriptor similar to windows file API. The applications in the following sections demonstrate how to use Windows sockets.

Client Application Example

A common task for a HTTP client is to download a web page from a HTTP server. This may seem like a daunting chore but requesting a file is actually simple thanks to the clear and logical format of all the protocols involved. The following console application first initializes Winsock and creates a socket. Then the socket connects using Microsoft’s address and sends a GET

Chapter 2, 15

Page 16: Aphex - Dissection of a Backdoor

request to the server. As the server sends the reply, the application prints the output to STDOUT. After the server completes the transfer of the requested page, the client closes the socket and shuts Winsock down.

1 program wget1;23 {$APPTYPE CONSOLE}45 uses6 Windows,7 Winsock;89 var10 wsa: TWSAData;11 sck: TSocket;12 sin: TSockAddrIn;13 hent: PHostEnt;14 bytes: integer;15 buf: array [0..1023] of char;1617 const18 Address: pchar = 'www.microsoft.com';19 Port: integer = 80;20 Request: pchar =21 'GET / HTTP/1.1' + #13#10 +22 'Host: www.microsoft.com' + #13#10 +23 'Connection: close' + #13#10#13#10;2425 begin26 WSAStartUp($101, wsa);2728 sck := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);29 if sck = INVALID_SOCKET then Exit;3031 sin.sin_family := AF_INET;32 sin.sin_port := htons(Port);33 sin.sin_addr.s_addr := inet_addr(Address);3435 if sin.sin_addr.s_addr = INADDR_NONE then36 begin37 hent := gethostbyname(Address);38 if hent = nil then39 begin40 closesocket(sck);41 Exit;42 end;43 sin.sin_addr.s_addr := PLongint(hent^.h_addr_list^)^;44 end;4546 if connect(sck, sin, SizeOf(sin)) = SOCKET_ERROR then Exit;4748 send(sck, pointer(Request)^, Length(Request), 0);4950 while True do

Chapter 2, 16

Page 17: Aphex - Dissection of a Backdoor

51 begin52 ZeroMemory(@buf, SizeOf(Buf));53 bytes := recv(sck, buf, SizeOf(buf), 0);54 if bytes = 0 then Break;55 if bytes = SOCKET_ERROR then Break;56 Write(buf);57 end;5859 closesocket(sck);60 WSACleanup;6162 ReadLn;63 end.

Lines 17-23: Global constants

Global constants are special variables that never change. They are useful for storing important settings and because they are global, they are accessible from any location in the application. Since constants never change, they are safe to use even in multi-threaded applications.

Line 26: Initialize Winsock

Before an application is able to create a socket, it must first initialize Winsock. There are two major versions of Winsock but in order to maintain compatibility with future versions, Winsock requires the application to negotiate the minimum version required against the maximum version supported. Older versions, such as Windows 95 and NT 3.x, come with Winsock 1.1 default. All following versions include Winsock 2 but are also able to support Winsock 1.1. Unless your application specifically needs Winsock 2 features, it is safer to use Winsock 1.1. This will enable your application to support older versions of Windows.

function WSAStartup( wVersionRequired: word; var WSData: TWSAData): Integer; stdcall;

Take a look at the WSAStartup() function. The first parameter, wVersionRequired, is the highest supported version of Winsock by the application. The second parameter, WSData, is a variable used to hold information about the version of Winsock negotiated. If the function succeeds the return value is 0, if not it will return one of several Winsock error codes. MSDN maintains full specification of this and all other Winsock functions online at http://msdn.microsoft.com/library/en-us/winsock/winsock/winsock_reference.asp.

Lines 28-29: Create a socket to use

Chapter 2, 17

Page 18: Aphex - Dissection of a Backdoor

Once Winsock as been initialized, the application creates a socket for TCP/IP usage. Winsock can support any network protocol but for reliable and long-lived connections, TCP/IP is the ideal choice. To specify the network protocol our socket should use, the application calls the Winsock socket() function with SOCK_STREAM and IPPROTO_TCP options. Other options regarding the socket can be controlled once it is created using setsocketopt() and ioctlsocket() but these are more advanced topics and most applications will not make use of but a few additional features.

function socket( af: Integer; Struct: Integer; protocol: Integer): TSocket; stdcall;

The socket() function accepts 3 parameters, the first is the address family. This tells the Winsock what type of address the socket should use. In able to use IP the application must specify AF_INET for the address family. This applies to UDP, TCP and raw IP sockets. The second parameter is the type of socket required. SOCK_STREAM and SOCK_DGRAM are for TCP and UDP respectively and are the only two supported by Winsock 1.1. The last parameter is the protocol to use with the socket. IPPROTO_TCP obviously specifies TCP. If the function succeeds, the return value is a socket descriptor. If an error occurs the return value is the constant INVALID_SOCKET and WSAGetLastError() contains extended error information.

Lines 31-33: Fill in the address structure

Before the application can connect to a host, it needs to know the address. Socket addresses are contained within a special structure defined by Winsock. The structure contains the address, address family and if applicable, the port number to connect to.

sockaddr_in = record case Integer of 0: (sin_family: u_short; sin_port: u_short; sin_addr: TInAddr; sin_zero: array[0..7] of Char); 1: (sa_family: u_short; sa_data: array[0..13] of Char)end;TSockAddrIn = sockaddr_in;

The structure of TSockAddrIn may seem a little confusing. This is because it is really two structures in one, known as a structure union. The most important structure is the first one. It contains the three important properties, sin_family, sin_port and sin_addr. The family, as was mentioned earlier, is AF_INET. The port number is the port of the service running on the

Chapter 2, 18

Page 19: Aphex - Dissection of a Backdoor

server. In the case of this application, it is port 80, the standard HTTP port. The address is another structure in itself but most applications do not manipulate its properties directly. Instead, applications access it as a single 32-bit value or convert it from dotted notation. The function used to convert a dotted notation IP address into this structure is inet_addr().

Lines 35-44: Resolve address

If the address supplied to inet_addr() is a host name, as in this case, and not a dotted notation IP address, the function will fail and return INADDR_NONE. This means that the application must resolve the host name to an IP address before it can connect. The protocol used to resolve a host name to an IP address is DNS. DNS can be a complex protocol but thankfully, Windows provides an easy method for resolving host names.

function gethostbyname( name: PChar): PHostEnt; stdcall;

The gethostbyname() function accepts a hostname, in this case “www.microsoft.com” and performs the DNS request on the applications behalf. If the function succeeds, the result is a pointer to a seemingly complex THostEnt structure defined by Winsock. If the function fails, the return value is nil.

PHostEnt = ^THostEnt;{$EXTERNALSYM hostent}hostent = record h_name: PChar; h_aliases: ^PChar; h_addrtype: Smallint; h_length: Smallint; case Byte of 0: (h_addr_list: ^PChar); 1: (h_addr: ^PChar)end;THostEnt = hostent;

At first glance this structure seems complex but we are really only interested to the h_addr_list property. It contains a list of null terminated IP addresses for the requested host. Most hosts have only one IP and for the application’s purposes, it only retrieves the first.

Line 46: Connect the socket to the server

Chapter 2, 19

Page 20: Aphex - Dissection of a Backdoor

Now that the application has taken all the steps to prepare the socket, it creates the connection to the HTTP server. This is done using the connect() function which accepts the created socket and address structure and attempts the connection.

function connect( s: TSocket; var name: TSockAddr; namelen: Integer): Integer; stdcall;

The connect() function accepts 3 parameters. The first parameter, s, is a socket that was created with the socket() function. The name is an address structure filled with the destination address in compliance with the chosen address family. Finally, the namelen parameter specifies the size of the address structure. If the function succeeds the result is 0 if it fails the result is SOCKET_ERROR and WSAGetLastError() contains extended error information.

Line 48: Send the request

Now that the socket has connected the client, it can tell the server what document it is requesting. This is where the HTTP protocol is involved. To request a document the application must form it in the form of a HTTP GET request. For this simple client the request has been “hard coded” into the client using the Request constant.

function send( s: TSocket; var Buf; len: Integer; flags: Integer): Integer; stdcall;

The send() function accepts 4 parameters. The first, as you should know, is the same socket descriptor used earlier in the application. The buf parameter is the buffer that the application wishes to send. In this case, it is an array of 1024 bytes. The len parameter is the number of bytes contained in the buffer. The final parameter is used for advanced functions and in this case is set to 0. If the send() function succeeds the result is the amount of bytes actually sent, which may be lower than the number specified with len. If there are already too many bytes queued to be sent, the send() function returns 0. If an error occurs, the result is SOCKET_ERROR.

Lines 50-57: Receive and display reply

Assuming that the request was valid and the document exists, the server sends back the document. The application uses the recv() function to receive data from the socket descriptor.

Chapter 2, 20

Page 21: Aphex - Dissection of a Backdoor

The data is received in chunks the size of the buffer, 1KB. When the recv() function is called there may or may not be data waiting to be read. The server could still be processing the command or a slow connection could delay the data. In any case, if there is no data waiting the recv() function waits until there is data to be read and only returns afterwards. This is called blocking mode because it blocks further execution. This could be a problem if the application also needs to perform other actions such as processing window messages for the GUI. In these cases, the socket can be set to non-blocking mode or a separate thread made for each socket.

function recv( s: TSocket; var Buf; len: Integer flags: Integer): Integer; stdcall;

The recv() function accepts 4 parameters. The s parameter is the previously connected socket descriptor. Buf is the buffer that receives the data and len is the size of that buffer. The flags parameter has a special purpose and in most cases, it is simply set to 0. If the function succeeds, the result is the number of bytes actually read in to the buffer. If it fails, the return is 0 or SOCKET_ERROR depending on the circumstances. In either case, this is a fatal error and the loop only breaks on either of these results. Since in the HTTP request the connection is set to automatically close, the loop will continue to read the data and dump it to the console using the Write() function until it does so.

Lines 59-63: Shutdown and cleanup

Once the application has finished using Winsock, it is time to clean up. This consists of closing all open sockets and shutting down Winsock. To do this the application uses the closesocket() function to close the socket. Then the application shuts Winsock down and frees any resources allocated by calling WSACleanup().

function closesocket( s: TSocket): Integer; stdcall;

function WSACleanup: Integer; stdcall;

The closesocket() function accepts only 1 parameter. The s is the open socket descriptor to close. If the function succeeds, the result is 0 otherwise it returns SOCKET_ERROR. The WSACleanup() function does not accept any parameters. If the function succeeds, the result is 0 otherwise it returns SOCKET_ERROR.

Chapter 2, 21

Page 22: Aphex - Dissection of a Backdoor

Server Application Example

HTTP servers are very complex compared to the clients. It would not make sense to use one as an example here. Instead, the following application is an echo server. This is simply a server that listens for text and sends back any text it receives. It is easy to test it with a telnet client. Instead of creating connections, a server accepts them. Otherwise, a server application behaves like a client application in many ways. Some of the functions in the example below are identical in usage to the client. The line-by-line explanation following the example skips passed these common functions and focuses on those not yet covered. Refer to the previous example for their explanations.

1 program echo1;23 {$APPTYPE CONSOLE}45 uses6 Windows,7 Winsock;89 var10 wsa: TWSAData;11 sck: TSocket;12 sin: TSockAddrIn;13 clnt: TSocket;14 bytes: integer;15 buf: array [0..1023] of char;1617 begin28 WSAStartUp($101, wsa);2920 sck := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);21 if sck = INVALID_SOCKET then Exit;2223 sin.sin_family := AF_INET;24 sin.sin_addr.s_addr := INADDR_ANY;25 sin.sin_port := htons(80);2627 bind(sck, sin, sizeof(TSockAddrIn));2829 listen(sck, SOMAXCONN);3031 clnt := accept(sck, nil, nil);32 if clnt = INVALID_SOCKET then Exit;3334 while True do35 begin36 bytes := recv(clnt, buf, SizeOf(buf), 0);37 if bytes = 0 then Break;38 if bytes = SOCKET_ERROR then Break;39 bytes := send(clnt, buf, bytes, 0);40 if bytes = 0 then Break;41 if bytes = SOCKET_ERROR then Break;42 end;

Chapter 2, 22

Page 23: Aphex - Dissection of a Backdoor

4344 closesocket(clnt);45 closesocket(sck);46 WSACleanup;47 end.

Lines 23-25: Fill in the address structure

This is the same address structure used in the client example. In this case, its purpose is a little different. Instead of specifying the address and port to connect to, it specifies the address and port to listen for connections on. The address used is INADDR_ANY. This special address is 0.0.0.0 and tells Winsock to accept connections on any address. All systems also have 127.0.0.1, which is the local loop back interface. Only the local system can connect to this special address. If any other host tried to connect to 127.0.0.1, they would only be connecting to themselves.

In addition to specifying INADDR_ANY, an application can use one of the IP addresses assigned to the computer. An interesting fact to note is that any sockets that bind to a specific address take precedence over those that bind to INADDR_ANY. For example, imagine if an HTTP server were to bind a socket to INADDR_ANY on port 80. If another application binds to 192.168.1.100 on port 80, it will receive the connections first. This is useful for backdoors that want to bypass firewall rules that only allow usage of a certain port.

Line 27: Bind the socket

When an application creates a socket, it is not associated with any address. An application using the connect function does not need to do so unless the host is multi-homed, that is, having more than one assigned IP address. However, sockets that will be later used with the listen() function must first call bind() to associate the socket with an address. Not doing so will result in an error.

function bind( s: TSocket; var addr: TSockAddr; namelen: Integer): Integer; stdcall;

The bind function accepts 3 parameters. The s parameter is the open socket descriptor. The addr parameter is the filled in address structure including the address and port the socket should bind to. The namlen is the size of the address structure. If the function succeeds the result is 0, otherwise a result of SOCKET_ERROR is returned.

Line 29: Listen for connections

Chapter 2, 23

Page 24: Aphex - Dissection of a Backdoor

Once an application binds a socket to an address it can listen for incoming connections. To do this the application uses the listen() function to put the socket in a state that is ready to accept connections. As each connection is waiting to be accepted, Winsock adds to a backlog. If the backlog becomes full, Winsock drops all subsequent incoming connections until the backlog is cleared by either accepting the waiting connections or calling listen() again.

function listen( s: TSocket; backlog: Integer): Integer; stdcall;

The listen() function accepts 2 parameters. The first is a descriptor to a socket already associated with an address using bind(). The second is the maximum number of pending connections to be queued. If the value of SOMAXCON is specified it is left up to Winsock to decide on a reasonable number. If the function succeeds, the result is 0. If the function fails, the result is SOCKET_ERROR.

Lines 31-32: Accept a connection

Once the application has the socket listening, it is ready to accept connections. If a connection is not waiting to be accepted, the function blocks until there is one available. This is because the socket is in blocking mode by default. This behaves much like the recv() function does as covered in the client application example. When a connection is accepted, the function returns a socket descriptor that the application uses to communicate with the client connection. The application can use the send() and recv() functions identical to the previous example.

function accept( s: TSocket; addr: PSockAddr; addrlen: PInteger): TSocket; stdcall;

The accept function accepts 3 parameters. The s parameter is the previously listening socket descriptor. The addr parameter is optional and receives the address structure of the incoming connection. The addrlen parameter is also optional and receives the size of the addr structure of the incoming connection. If the function succeeds, the result is a valid socket descriptor. If the function fails, the result is INVALID_SOCKET.

Upon receiving a successful connection, the server is now able to communicate with client and handle requests. In the case of the echo server, the service provided is merely to send back any text that it receives. If the application wishes to accept another connection it must call accept() again. However, because it is still handling the first connection it cannot handle another unless the application uses a method to allow multiple connections. Applications achieve

Chapter 2, 24

Page 25: Aphex - Dissection of a Backdoor

this by using multiple threads, one for each connection, or using asynchronous or non-blocking sockets. This allows the server to handle multiple requests without having to wait on each client to finish. At this point, the server behaves much like a client, sending and receiving data. After it is finished, it too must close each socket and cleanup Winsock.

Notification: Contacting the Attacker

A very important task for the backdoor is to notify the attacker of its presence online. Many victims have a dynamic IP address. Even a cable modem that has a long-term lease on its address may eventually change. Most hosts on the internet acquire their IP address using dynamic host configuration protocol (DHCP). When a system requires an IP address, it uses DHCP to obtain it. Even when the IP address remains the same, the attacker still needs a notification when the victim comes online. Table 2.4 details some of the most common notification methods.

Table 2.4Common Notification MethodsName Description

Instant messaging (IM) Instant messaging is very complex these days. It typically requires a complete client compatible with the IM protocol used. The earliest was ICQ but because of the enormous amount of abuse, ICQ has taken measures to stop this.

Internet Relay Chat (IRC) IRC is ideal for a few reasons. It can service a very large number of notifications. Also the notification is in real time, allowing the attacker to know who is online at any given second. It can also provide the attacker with a means to control a massive number of victims with a single command.

Email Email is the oldest and possibly the slowest form of notification. However, it is also the most cumbersome. Sifting through hundreds of emails to find a target is very inefficient.

Web scripts (CGI/PHP) Web scripts provide the most intuitive means of notification. They organize notifications in an easy to read manner. Combined with the fact that web servers are always online makes it a very reliable method.

Static host Static host notification is really notification and communication in one. Instead of a victim notifying, it simply connects directly to the attacker. This is very useful for bypassing proxies. However because the attacker’s IP address is directly involved it is also the most likely to get the attacker in trouble.

Chapter 2, 25

Page 26: Aphex - Dissection of a Backdoor

Beyond these notification methods are a few others that are not very common. They are not common because of the level of technical skill required to implement them. The most noteworthy is peer-to-peer (P2P) notification. In this scheme, attackers do not rely on a central point for notification. Instead, the victims organize themselves into a network and provide service to each other. When the attacker wishes to access the compromised hosts, he or she joins the network and sends a command. The commands relay from victim to victim until all of them have received the command. There are very few backdoors in the wild using P2P. The first widespread backdoor to make use of P2P was Phatbot, a typical IRC bot with a twist. Phatbot makes use of the WASTE protocol created by Nullsoft to form a victim network without requiring any central server. However, the WASTE protocol is severely limited in scalability and only supports upwards to 50 peers. Aside from protocol issues, P2P networks hold very large promise for attackers wishing to create a very large network of victims. The potential for backdoors is so great because each victim is not consuming bandwidth resources on the network but instead only providing them. In the future, P2P networks will open a new tier of attacks. Currently, a network of a few hundred thousand hosts is nearly impossible to maintain but with the help of P2P protocols, these large numbers could be easily possible. Such large networks could create distributed denial of service (DDoS) attacks large enough to cut off entire countries from the internet.

Stealth: Hiding From the Victim

A very important yet often neglected aspect of backdoors is the ability to hide their presence. As discussed in earlier sections, stealth features cross over into several other components. Everything a backdoor does can be stealthy. The ultimate goal for any backdoor stealth is to remain invisible from the victim. Backdoors achieve this in many ways, from API hooks to direct kernel object manipulation. Only recently have mainstream backdoors started to take notice of these possibilities. As the average user’s intelligence increases so must backdoor’s. In later chapters, this book explores API hooking deeper, exactly how hooks work and what is required to hide objects from Windows.

Functionality: Providing Services to the Attacker

The real meat of a backdoor is the functionality it provides to the attacker. In addition, this is where the backdoor gets its classification. It at this point you will see terms like remote administration tool (RAT). Table 2.5 describes the major classifications of backdoors.

Table 2.5Classes of BackdoorsType Description

Downloader/Dropper/Binder These specialized backdoor’s sole purpose is the placement of another backdoor. Downloaders retrieve the backdoor from a URL. Droppers extract it from their resource section. Binders retrieve it from the end of the file.

Uploader An uploader is a small backdoor designed only

Chapter 2, 26

Page 27: Aphex - Dissection of a Backdoor

to receive and execute files.

Notifier Some backdoors lack adequate notification methods. Stand-alone notifiers provide more notification methods to these backdoors.

Password stealer By its name, the purpose of a password stealer is obvious. They steal a victim’s passwords and send them to an attacker.

Key logger Attackers use key loggers to monitor activity. They work by capturing all keystrokes and usually window captions to a text file. Many key loggers may also send their logs to the attacker once they reach a certain size or age.

IRC bot IRC bots are invisible IRC clients controlled by an attacker. One of most notable features is the ability to perform a single command across multiple victims. This is why IRC bots carry out most DDoS attacks.

Lite RAT A “lite” RAT is very small with minimal functionality. They are useful because they attackers can distribute them faster and provide a backup in case the main backdoor fails.

Full RAT Full RATs are the end-all of backdoors. There is little that they cannot do. They provide an all-in-one base station for an attacker to monitor the victim and make further attacks on other systems.

With the exception of droppers and binders, all of these types of backdoors include a network component. They also include some form of notification. To modify settings most of these backdoors include an editor. The editor can change certain bytes inside the backdoor binary to configure it to use the attacker’s desired settings.

Summary

Backdoors arrive in various forms. Most often, they exist as PE files, yet other non-executable file types are possible. Most antivirus software does not detect every variant of these file types. Some files may provide a false appearance to a user by masking the associated file type. Once backdoors execute, they must relocate to a permanent location. A system directory is an ideal choice because it is certain to exist and provides cover for the backdoor. To heighten the effectiveness of these cover files, the backdoor will often change its file time to that of a system file. This gives the appearance of not being a newer file. Unless a user memorizes hundreds of file names, they are not likely to notice a single extra file. When the backdoor locates a suitable permanent location, it will use some method of restarting its process each time Windows loads. The most common method is using the registry.

Chapter 2, 27

Page 28: Aphex - Dissection of a Backdoor

After a backdoor has taken steps to ensure its persistence, it can then setup any extended features such as injecting into other processes and setting API hooks. The backdoor then informs the attacker that it is online and ready to use. At this point, the attacker has gained complete control of your system. The attacker is free to break any computer laws without fear of punishment. That is because your IP address takes the blame. The police will be knocking on your door, not theirs.

Chapter 2, 28