Working with DLLs - Malware Analyst’s Cookbook and DVD: Tools and Techniques for Fighting Malicious Code (2011)

Malware Analyst’s Cookbook and DVD: Tools and Techniques for Fighting Malicious Code (2011)

Chapter 12. Working with DLLs

Windows exposes a majority of its Application Programming Interface (API) in Dynamic Link Library (DLL) files. Thus, the functions that processes need to interact with the file system, Registry, network, and GUI interface are contained within DLLs. When a process wants to call an API function, it must first load a copy of the DLL that exports the API into its private memory space. The fact that DLLs execute in the context of a process makes their use very desirable to malware authors. By distributing malicious code as DLLs instead of EXEs, the malware can run inside any process (henceforth known as the target or host process), including winlogon.exe, csrss.exe, or explorer.exe. Not only does this capability help malware conceal its actions (any actions the malware performs will then appear to originate from the host process), but it gives the malware access to the entire addressable memory range owned by the host process.

If the host process is a browser, the malware can steal credentials from SSL-secured transactions before encryption takes place. If the host process accepts user input, the malware can record keystrokes or mouse movements. Of course, there are other ways to perform these malicious actions, but from a programmer’s perspective, creating a DLL that contains the functionality and then injecting the DLL into a host process is extremely easy. Attackers are attracted to easy solutions, because they save time. Another reason attackers use DLLs is because researchers and analysts aren’t as familiar with DLLs as they are with EXEs. For example, many people had trouble performing dynamic analysis of Conficker samples when it was first discovered, because they didn’t know how to execute Conficker’s malicious DLL. This chapter discusses some of the challenges involved with analyzing DLLs and shows how you can overcome the challenges. As always, you should analyze suspicious DLLs within a virtual environment or on a Unix-based system.

Recipe 13-1: Enumerating DLL Exports

Many attackers assign meaningful names to the functions that their malicious DLLs export, thus giving you a quick and easy first impression of the DLL’s capabilities. Other attackers may use misleading or random names to intentionally trick you. This recipe shows you a few techniques for enumerating exported functions. The DLL used in the examples is a component of the 4DW4R3 rootkit described on the Sysinternals forums.1

CFF Explorer

Daniel Pistelli’s CFF Explorer2 is a robust PE viewer/editor for Windows-based platforms. If you open a PE file that exports functions, you’ll be able to click the Export Directory button, as shown in Figure 13-1. The application displays the following information for each function:

· Ordinal: An index into the Export Address Table (EAT) that contains information on the exported function

· Function RVA: The relative virtual address (i.e., offset from the image base of the DLL) where the function’s code can be found in memory.

· Name: The function’s name

Figure 13-1: Using CFF Explorer to view a DLL’s exports

f1301.tif

Pefile

If you want you enumerate exports using a Python script on multiple platforms (for example, to process a large number of DLLs at once), you can use Ero Carrera’s pefile (see Recipe 3-8 for an introduction). The following code shows the commands you can use:

$ python

Python 2.5.1 (r251:54863, Feb 6 2009, 19:02:12)

>>> import pefile

>>> pe = pefile.PE("4DW4R3c.dll")

>>> if hasattr(pe, 'DIRECTORY_ENTRY_EXPORT'):

... for exp in pe.DIRECTORY_ENTRY_EXPORT.symbols:

... print hex(pe.OPTIONAL_HEADER.ImageBase + exp.address), \

... exp.name, exp.ordinal

...

0x10002415 FileDownload 1

0x1000249b HideConnection 2

0x10002484 InjectorAdd 3

0x1000234c ModuleDownload 4

0x10002504 SetCmdDelay 7

0x10002509 SetRedirUrl 8

0x10002255 _ModuleLoad@4 5

0x100021e3 _ModuleUnload@4 6

Notice that the output shows the VA (virtual address) of the exported functions rather than the RVA, as CFF Explorer shows. That is because we added the function’s RVA to the DLL’s image base (thus creating the VA) before printing the address. Assuming the DLL receives its preferred image base (0x10000000 in this case) when it is loaded into a process, you can expect to find the start of the FileDownload function at 0x10002415.

IDA Pro

Performing static analysis in IDA Pro is one of the best ways to research a DLL’s potential behaviors. Don’t jump to conclusions about how a function behaves based on its name. Instead, inspect the code for each exported function. To do this, open a malicious DLL in IDA Pro and navigate to the Exports tab as shown in Figure 13-2. From the Exports tab, you can click the name of a function to view a disassembly of the function. In the example, we also used the Hex-Rays plug-in to decompile the HideConnection function.

Figure 13-2: Analyzing a DLL’s exports with IDA Pro and Hex-Rays

f1302.eps

As you can see, IDA Pro reveals critical information for reverse-engineering the DLL. It shows that HideConnection accepts one parameter, which is a character pointer that the function passes to gethostbyname. Additionally, it shows that the function references the h_addr_list member of the value returned by gethostbyname. This h_addr_list member contains a list of IP addresses for a host. Thus, the argument to HideConnection is a hostname (i.e. www.hidethisaddress.com) that the malware should hide on the victim system.

Common and Uncommon Export Names

As you may have gathered, attackers can choose any names for the functions exported by their DLLs. In fact, even if the names are blank or contain non-ASCII characters, a process can still find and call the functions based on the functions’ ordinal values. Therefore, you’ll run into all sorts of names during your research. Here are a few examples of descriptive names:

· HideProcess

· ExecuteFile

· KillProcess

· BindIEBrowser

· StartHook

· ResetSSDT

Here are a few examples of generic names that malware authors frequently use:

· Install

· Launch

· Init

· Load

· Start

· ServiceMain

· Hook

Here are a few examples of names that are unique (and borderline funny), but not descriptive:

· KIIsSes__McafEe

· Kisses_To_Trojanhunter

· _GetAwayFromMe

· _CreateSweetPlace

· YouTalkingTooMuch

· IFoundTreasure

· ByeByeMyLove

· TheirKnifeIsSharp

· _BangBangBang

Lastly, here are a few examples of random names:

· Lymomohu

· WanoRivacyde

· KenyjybopymoJo

· AddCvqidsd

· Kepibagipefowo

One thing you might do with all the DLLs you have in your malware collection is use a pefile script to dump all the export names into a database. Then you can query the database whenever you receive new DLLs and try to match new samples with old samples based on exported function names or other attributes.

1 http://forum.sysinternals.com/topic21838_page1.html

2 http://www.ntcore.com/exsuite.php

Recipe 13-2: Executing DLLs with rundll32.exe

Unlike executable programs, you cannot simply double-click a DLL in order to run it because a DLL is not a standalone entity—it requires a host process, or container, to operate. Windows ships with a program called rundll.exe (16-bit version) or rundll32.exe (32-bit version) that serves as a generic host process for executing arbitrary DLLs (for more information, see Windows Rundll and Rundll32 Interface3). Both versions of the program use the following syntax, but we’ll focus on rundll32.exe in this recipe.

C:\> rundll32 <dllpath>,<export> [optional arguments]

Here is a description of the parameters:

· The dllpath parameter should be the full path to the DLL on disk (but without any spaces or special characters).

· The export parameter is the name of an exported function to call after the DLL is loaded.

· There must be a comma (but no spaces) between the dllpath and export parameters.

· You can optionally supply arguments to the export function by placing them last on the command line.

The following steps explain how rundll32.exe works:

1. It calls GetCommandLineW to get the command-line parameters that you supplied.

2. It validates the command-line parameters and exits if your syntax is incorrect.

3. It loads the specified DLL by calling LoadLibraryW. This step automatically executes the code in the DLL’s entry point (keep this in mind, it is very important).

4. It attempts to obtain the address of the export function by calling GetProcAddress and exits if the function cannot be found.

5. It calls the export function, supplying any optional arguments that are provided.

The rundll32.exe syntax is quite simple, but many people have trouble getting it right. Here are a few tips for common mistakes:

Tip #1:

The mistake in the following command is that an export function was not specified. As a result, the syntax check will fail and rundll32.exe will exit before calling LoadLibraryW.

C:\>rundll32 malicious.dll

Assuming you want to load a DLL and only call its entry point function (i.e., not any exports), then you can use the following command:

C:\>rundll32 malicious.dll,ThisIsFake

In the example, your syntax is valid, so rundll32.exe proceeds to call LoadLibraryW. As previously mentioned, LoadLibraryW invokes the DLL’s entry point function automatically. Thus, the entry point function executes before rundll32.exe gets to Step 3 in order to check if ThisIsFake exists.

Tip #2:

The following command contains an error:

C:\>rundll32 kernel32.dll,Sleep 100

The mistake is that you can only call functions that do not require arguments or that expect to receive arguments in string form (i.e., a pointer to an ANSI or UNICODE buffer). The Sleep API call accepts an integer value representing the number of milliseconds to sleep. In the example, Sleep actually receives the address in memory where the string “100” exists, and the rundll32.exe process will end up sleeping for some unpredictable amount of time rather than 100 milliseconds.

As you may recall from Recipe 13-1, the HideConnection function accepted a hostname in string form. You can legitimately call that function in the following manner:

C:\>rundll32 4DW4R3c.dll,HideConnection www.hidethisaddress.com

Monitoring DLLs Dynamically

You can use any of the dynamic analysis tools from Chapter 9 to monitor the DLL’s behaviors. If you’re using Process Monitor, consider setting a filter based on the process name of rundll32.exe. Additionally, consider creating a script that enumerates exported functions in a DLL and calls each export in sequence, so that you are sure to trigger all possible entry points.

3 http://support.microsoft.com/kb/164787

Recipe 13-3: Bypassing Host Process Restrictions

One of the obvious limitations to rundll32.exe is that the host process for the DLL will always be rundll32.exe. Many malicious DLLs only operate in a specific host process, and they will exit or behave differently if you try to run them anywhere else. For example, Figure 13-3 shows a decompilation (produced by the Hex-Rays plug-in for IDA Pro) of the code found in the DLL of the Clod/Sereki4 trojan.

Figure 13-3: Hex-Rays view of Clod’s host process checks

f1303.tif

As you can see, if the host process is explorer.exe, the malware creates a thread that installs a proxy server on the victim machine. Then it checks for any installed point of sale (POS) software and will attempt to exfiltrate credentials. If the host process is not explorer.exe, iexplore.exe, regedit.exe, regedt32.exe, or firefox.exe, then the DLL calls the Cleanup function and returns. If you execute a DLL with rundll32.exe and it doesn’t behave the way you expect it to, then you may have found a DLL with host process restrictions. In these cases, you can leverage static analysis to determine the list of processes that trigger the desired behavior. Keep in mind that the host process list is not always a list of strings in cleartext. Attackers may pack the DLL to obfuscate the strings in addition to using the following tricks:

memset(name, 0, MAX_PATH);

GetModuleFileNameA(NULL, name, MAX_PATH);

if (strrchr(name, '\\') != NULL) {

name = (char *)(strrchr(name, '\\') + 1);

}

if ((name[2] == 'x' && name[4] == 'l') || // Matches iexplore.exe

(name[0] == 'f' && name[3] == 'e') || // Matches firefox.exe

(name[1] == 'p' && name[2] == 'e')) // Matches opera.exe

{

intarget = TRUE;

}

The code matches iexplore.exe, firefox.exe, and opera.exe, but it is much harder to figure that out from an analyst’s perspective. Instead of checking the entire process name, which leaves visible strings in the binary, malware will often just make sure that a few of the letters are in the required position.

Bypassing Host Process Restrictions

One simple way to get around the host process check is to rename rundll32.exe to iexplore.exe (or whatever host process the DLL requires) before calling it on the command line. That bypasses the name check, but other behaviors of the DLL might actually require that you run it inside a real Internet Explorer process. In these cases, you can use RemoteDLL,5 as shown inFigure 13-4, to inject your DLL into an existing IE process.

Once the DLL is running in one of its target host processes, you can analyze the processes’s behavior using file system monitors, registry monitors, packet capture utilities, and so on (see the dynamic analysis techniques discussed in Chapter 9). Another step you might take is scanning with an anti-rootkit tool (see Recipe 10-6) to see if the DLL attempts to hook any API functions in the host process.

Figure 13-4: Injecting a DLL into IE with RemoteDLL

f1304.tif

4 http://www.threatexpert.com/threats/backdoor-win32-sereki-b.html

5 http://securityxploded.com/remotedll.php

Recipe 13-4: Calling DLL Exports Remotely with rundll32ex

dvd1.eps

You can find supporting material for this recipe on the companion DVD.

As previously mentioned, a limitation of rundll32.exe is that you cannot choose the host process for your DLL. A limitation of RemoteDLL is that you cannot specify an exported function to call once the DLL is loaded. This recipe shows how (and why) we created a tool called rundll32ex that allows you to both specify a host process and call an exported function.

The Need for a New Tool

The DLL that you saw in Recipe 13-1 exported a function named SetRedirUrl. Using IDA Pro, you can verify that SetRedirUrl takes one parameter—a character pointer. The Hex-Rays decompiler shows the following code for SetRedirUrl:

char *__stdcall SetRedirUrl(const char *Source)

{

sub_10003DF2(Source);

return strncpy(Dest, Source, 0x64u);

}

Let’s assume, based on the function’s name, that SetRedirUrl takes a URL or hostname as its one parameter. You can try to analyze the DLL dynamically by calling the exported function with rundll32.exe. However, as shown in Figure 13-5, you’ll encounter an error that states a DLL initialization routine failed.

Figure 13-5: Calling SetRedirUrl from rundll32 results in an error.

f1305.tif

To troubleshoot the initialization error, you can analyze the DLL’s entry point function using IDA Pro. LoadLibrary will report failure if a DLL’s entry point function returns FALSE. Therefore, to determine the possible causes for the failure, you can inspect the code for any statements that would force the function to return 0 (FALSE) instead of 1 (TRUE). Figure 13-6 shows a Hex-Rays decompilation of the code in question:

Figure 13-6: Troubleshooting the DLL initialization error

f1306.eps

Based on the code shown in Figure 13-6, you can make the following conclusions about the DLL’s 'text-align:justify;line-height:normal; background:#EEEEEE'>· It calls GetModuleFileNameA to retrieve the full path to the host process (for example, C:\WINDOWS\system32\rundll32.exe).

· It calls PathFindFileNameA to strip the file name from the file path. PathFindFileNameA returns a value such as rundll32.exe.

· It checks if the host process is svchost.exe, and, if so, it calls the call_on_svchost function and continues.

· If the host process is not svchost.exe, it begins to cycle through a list of targets (target_list) until the list is empty or PathMatchSpecA returns TRUE when comparing an entry in the list with the host process name.

· It returns 0 (FALSE) if the host process is not matched with an entry in the target list. Otherwise, it calls the call_on_target function and continues. This is your primary point of failure. Most likely, rundll32.exe is failing the host process check. To verify your findings, you can look at the target_list variable and see what it contains. Figure 13-7 shows the list entries:

Figure 13-7: The DLL’s list of target host processes

f1307.tif

As you can see, the DLL is programmed to only execute in svchost.exe, Windows Explorer (matches *explore*), and various popular browsers. Recipe 13-3 showed you an easy method of injecting the DLL into a target process. However, as previously mentioned, RemoteDLL does not allow you to call an exported function (much less supply an optional argument to an exported function). Hence, you must use a different tool, such as the one presented in this recipe.

Using rundll32ex

rundll32ex uses a very common method of injection involving the CreateRemoteThread API. Unfortunately, the behavior of this API is not uniform across all versions of Windows (for more information, see Injecting Code Into Privileged Win32 Processes6 or Win7 and CreateRemoteThread7). As a result, the tool may only work on Windows XP. rundll32ex accepts the following parameters:

· The PID of the target process

· The full path to the DLL to inject

· The name of an exported function to call once the DLL is loaded (optional)

· The argument to pass the exported function (optional)

Figure 13-8 shows the syntax and usage for rundll32ex. In the example, rundll32ex injected 4DW4R3c.dll into IEXPLORE.EXE (PID 3924) and called the DLL’s exported SetRedirUrl function. Additionally, it passed the argument http://testing.com to SetRedirUrl.

Figure 13-8: Using rundll32ex to invoke SetRedirUrl from IE

f1308.eps

The output from rundll32ex shows some technical information, such as the address in the remote process where the DLL loaded. However, the most useful information comes from monitoring tools like Process Monitor (see Recipe 9-1). Before executing rundll32ex, you can set a filter for IEXPLORE.EXE. Figure 13-9 shows the results. In particular, you can see the API calls made by IEXPLORE.EXE immediately after launching rundll32ex. The process used RegSetValue to write the string http://testing.com to HKLM\SOFTWARE\4DW4R3c\redirurl.

In this recipe, you learned how to investigate and then bypass a malicious DLL’s host process restriction. Furthermore, you learned how to invoke a very specific function in the DLL and isolated its behavior with Process Monitor. In the end, you ultimately learned that the SetRedirUrl function takes whatever argument you pass and writes it to a particular location in the Registry.

Figure 13-9: Isolating the SetRedirUrl behavior in Process Monitor

f1309.eps

6 http://mnin.blogspot.com/2007/05/injecting-code-into-privileged-win32.html

7 http://www.ivanlef0u.tuxfamily.org/?p=395

Recipe 13-5: Debugging DLLs with LOADDLL.EXE

So far, in this chapter, you have learned how to execute DLLs using a variety of techniques. The key aspect of DLL analysis that is missing up to this point is how to debug them. This will give you the ability to unpack DLLs, modify their default behaviors, and answer questions about the DLLs that are not evident using dynamic analysis.

Loading the DLL in Your Debugger

To debug a DLL, you can simply drag and drop the file over Immunity Debugger or OllyDbg’s icon. Both debuggers include a generic host process named LOADDLL.EXE, which serves as a container for executing your DLL (in much the same way as rundll32.exe works). Figure 13-10 shows what you will see after dragging and dropping a DLL into Immunity Debugger.

Notice the top of the application’s window shows that your primary debugging target is 0040.DLL, but the current module is LOADDLL. In the CPU pane, you can see that LOADDLL calls GetCommandLineA and subsequently LoadLibraryA. This should give you a sense for how the debugger works when you open a DLL. The debugger just executes LOADDLL with the path to your DLL as a command-line argument.

Figure 13-10: Debugging a DLL with the generic LOADDLL.EXE process

f1310.eps

Reaching the DLL’s Entry Point

In order to get to the entry point of the DLL, you need to hit F9 (or click Debug⇒ Run) once. LOADDLL will call LoadLibrary and automatically place a breakpoint on the DLL’s AddressOfEntryPoint instruction. If you accidentally hit F9 more than once, then you will play past the entry point and possibly infect your system. Figure 13-11 shows how the debugger appears once you have reached the entry point of the DLL. The debugger calculated the entry point address by adding the AddressOfEntryPoint value in the DLL’s PE header (0x55EC in this case) to the base address of the DLL loaded in the memory (0x360000 in this case).

Figure 13-11: You reach the DLL’s entry point by clicking the play button once.

f1311.tif

Now that you’ve reached the DLL’s entry point, you can debug it as you would debug any other program.

Recipe 13-6: Catching Breakpoints on DLL Entry Points

This recipe shows how to debug a DLL inside a specific host process, rather than the generic LOADDLL.EXE. You can do this by starting a new instance, or attaching to an existing instance, of the desired host process using your debugger (see Recipe 11-1). Then, you can inject the DLL into the debugged process with RemoteDLL, rundll32ex, or Immunity Debugger’s built-in inject_dll function. Regardless of the method you use, you will encounter the same problem—the code in the DLL’s entry point function will execute before you get a chance to debug it.

Why does this happen? Well, you cannot set a breakpoint on the DLL’s entry point unless you know the entry point’s address. You cannot calculate the entry point’s address without the DLL’s image base, which LoadLibrary returns after loading the DLL. However, before LoadLibrary returns, it automatically calls the DLL’s entry point function (this concept was discussed in Recipe 13-2). Therefore, by the time you figure out where to set the breakpoint, it is already too late.

Breaking on New Modules

To configure your debugger to catch breakpoints on the entry point function of newly loaded DLLs, follow these steps:

1. Click Options⇒ Debugging Options⇒ Events and place a check in the box labeled “Break on new module (DLL),” as shown in Figure 13-12.

Figure 13-12: Configuring the debugger to break on new DLLs

f1312.tif

2. Open a Python shell (it’s the button with a snake and >>> on it) in Immunity Debugger and inject the DLL, as shown in the code that follows.

*** Immunity Debugger Python Shell v0.1 ***

Immlib instanciated as 'imm' PyObject

READY.

>>>thread_id = imm.inject_dll("C:\\0040.DLL")

>>>print "DLL-loading thread ID: 0x%x" % thread_id

DLL-loading thread ID: 0x8bc

3. At this point, the DLL is loaded into the process, but its entry point function has not executed yet. Your host process should be paused due to the change you made in Step 1. The following code shows how to set a breakpoint at the DLL’s entry point function that will trigger when you resume the host process.

>>>mod = imm.getModule("0040.DLL")

>>>print "DLL loaded at 0x%x" % mod.baseaddress

DLL loaded at 0x1e00000

>>>print "DLL entry point at 0x%x" % mod.entrypoint

DLL entry point at 0x1e055ec

>>>imm.setBreakpoint(mod.entrypoint)

4. Resume the host process by typing imm.Run() into your Python shell or clicking the debugger’s Play button.

Recipe 13-7: Executing DLLs as a Windows Service

dvd1.eps

You can find supporting material for this recipe on the companion DVD.

A service DLL has a special entry point that only executes properly if the DLL is running as a Windows service. This is similar to a host process restriction, except the primary factor is the context in which the DLL executes and other environmental factors, as opposed to the name of the host process. It is inevitable that you will need to perform behavioral analysis on service DLLs. Many trojans drop or download a DLL, load the DLL as a service, and then delete the dropper component. As a result, when you perform a forensic investigation, in most cases you will only find the DLL. This recipe shows how you can overcome the challenges of service DLLs.

Service DLL Entry Points

Most malware samples create a service of type SERVICE_WIN32_SHARE_PROCESS for their malicious service DLLs. This service type indicates that the DLL should run within a generic host process (svchost.exe) that can be shared with other DLLs also running services. When a particular service is activated by a call to the StartService API function, the svchost.exe process loads the service DLL and calls an exported function named ServiceMain. Now you know how to distinguish a service DLL from a normal DLL—just look for an export named ServiceMain.

Note Distinguishing service DLLs, based on the existence of an export named ServiceMain, works almost 100 percent of the time. However, the name of the service entry point can be configured per service by modifying the service’s configuration in the registry such as: HKLM\System\CurrentControlSet\Service\<SERVICENAME>\Parameters\ServiceMain = "AlternateFunction". In this case, you may find a service DLL that exports a function named AlternateFunction instead of ServiceMain.

Service Initialization

The Service Control Manager (SCM), which is the services.exe process, requires that all newly started services must perform the following actions within the first few seconds of their execution:

· Register its control handlers by calling RegisterServiceCtrlHandler

· Report a status of SERVICE_RUNNING by calling SetServiceStatus

The initialization procedure is the crux of why you cannot execute service DLLs outside of a service context. For example, when you use StartService, the SCM becomes aware that a service should be starting. If you try to load a service DLL using a command such as

C:\> rundll32 malicious.dll,ServiceMain

the DLL’s calls to RegisterServiceCtrlHandler will fail because the SCM is not expecting a service to start. In almost all cases, if the call to RegisterServiceCtrlHandler fails, the DLL will just exit, as shown in Figure 13-13.

Figure 13-13: The DLL exits if RegisterServiceCtrlHandler fails.

f1313.eps

Likewise, you also cannot run a normal DLL in a service context. In other words, if the DLL does not export a function named ServiceMain, or if the ServiceMain function does not perform the required initialization tasks, then the SCM will assume the service has hung and forcefully terminate the host process.

Installing Service DLLs

At this point, you should understand how to distinguish service DLLs from normal DLLs and why you must run service DLLs in a proper service context. You can install the DLL as a service on your analysis machine by creating a simple batch script such as the following:

REM

REM Usage: install_svc.bat <SERVICENAME> <DLLPATH>

REM

@echo off

set SERVICENAME=%1

set BINPATH=%2

sc create "%SERVICENAME%" binPath= "%SystemRoot%\system32\svchost.exe \

-k %SERVICENAME%" type= share start= auto

reg add "HKLM\System\CurrentControlSet\Services\%SERVICENAME%\Parameters" \

/v ServiceDll /t REG_EXPAND_SZ /d "%BINPATH%" /f

reg add "HKLM\Software\Microsoft\Windows NT\CurrentVersion\SvcHost" \

/v %SERVICENAME% /t REG_MULTI_SZ /d "%SERVICENAME%\0" /f

sc start %SERVICENAME%

Of course, before running install_svc.bat, you can set up your dynamic analysis tools to capture the service’s behavior.

Passing Arguments to Services

The only issue with the batch script is that you cannot pass custom arguments to the service. A ServiceMain function conforms to the following specification per Microsoft, which means it can accept a variable number of string-type arguments.

VOID WINAPI ServiceMain(

__in DWORD dwArgc

__in LPTSTR *lpszArgv

);

dwArgc [in]

The number of arguments in the lpszArgv array.

lpszArgv [in]

The null-terminated argument strings passed to the service by the

call to the StartService function that started the service. If

there are no arguments, this parameter can be NULL. Otherwise, the

first argument (lpszArgv[0]) is the name of the service, followed

by any additional arguments (lpszArgv[1] through lpszArgv[dwArgc-1]).

In many cases, the ServiceMain function will not accept arguments and you can start the service from a batch script, the services.msc snap-in, or Process Hacker. However, consider you find a DLL with the following code in its ServiceMain function:

VOID WINAPI ServiceMain(

__in DWORD dwArgc

__in LPSTR *lpszArgv)

{

// hard-coded password somewhere in the DLL binary

LPSTR specialPass = "myPass";

// exit if no parameters were passed

if (dwArgc < 2)

return;

// exit if the password does not match

if (strcmp(lpszArgv[1], specialPass) != 0)

return;

//Perform malicious activity

}

The previous code prevents a service from executing properly if the second argument is not equal to the hard-coded special password. This is a simplified version of what you might see in the wild, but that is the point—extremely simple things can prevent you from analyzing the service DLL’s behavior. If you find a DLL with a ServiceMain export, examine the function in IDA to see if it accepts any arguments and if so, how it uses them. If you need to supply specific arguments to the DLL when starting the service, you can use the install_svc.py script, which is on the DVD that accompanies this book.

import win32service

import win32con

import win32api

import sys

if len(sys.argv) < 3:

print 'Usage: %s <SVCNAME> <DLLPATH> [arg1 arg2 ...]' % sys.argv[0]

sys.exit()

ServiceName = sys.argv[1]

ImagePath = sys.argv[2]

ServiceArgs = sys.argv[3:]

hscm = win32service.OpenSCManager(

None, None, win32service.SC_MANAGER_ALL_ACCESS)

try:

hs = win32service.CreateService(hscm,

ServiceName,

"",

win32service.SERVICE_ALL_ACCESS,

win32service.SERVICE_WIN32_SHARE_PROCESS,

win32service.SERVICE_DEMAND_START,

win32service.SERVICE_ERROR_NORMAL,

"C:\\WINDOWS\\System32\\svchost.exe -k " + ServiceName,

None,

0,

None,

None,

None)

except:

print "Cannot create service!"

sys.exit()

key = win32api.RegCreateKey(win32con.HKEY_LOCAL_MACHINE,

"System\\CurrentControlSet\\Services\\%s\\Parameters" % ServiceName)

try:

win32api.RegSetValueEx(key,

"ServiceDll",

0,

win32con.REG_EXPAND_SZ,

ImagePath);

finally:

win32api.RegCloseKey(key)

key = win32api.RegCreateKey(win32con.HKEY_LOCAL_MACHINE,

"Software\\Microsoft\\Windows NT\\CurrentVersion\\SvcHost")

try:

win32api.RegSetValueEx(key,

ServiceName,

0,

win32con.REG_MULTI_SZ,

[ServiceName, '']);

finally:

win32api.RegCloseKey(key)

win32service.StartService(hs, ServiceArgs)

win32service.CloseServiceHandle(hs)

win32service.CloseServiceHandle(hscm)

You can use the install_svc.py script to pass special arguments to a service DLL like this:

C:\> python install_svc.py testsvc C:\windows\system32\svc.dll myPass

Using the tricks described in this recipe, you can dynamically analyze DLLs that only run in a service context and that require specific arguments.

Recipe 13-8: Converting DLLs to Standalone Executables

dvd1.eps

You can find can find supporting material for this recipe on the companion DVD.

There are many reasons why you may not want to execute a DLL exactly as the authors intended. For example, the DLL may contain anti-debugging tricks, noisy network communications, time-consuming sleep loops, or several functions that you need to bypass. Perhaps you only want to execute the function that extracts an embedded EXE to disk or that generates a random domain name to contact. This recipe describes how you can convert a DLL into an EXE and change its entry point to skip certain functions that you don’t want to execute.

Consider the following example DLL:

BOOL Install(void)

{

if (DecodeEmbeddedEXE() && DropEmbeddedEXE())

return TRUE;

return FALSE;

}

BOOL APIENTRY DllMain( HMODULE hModule,

DWORD ul_reason_for_call,

LPVOID lpReserved)

{

switch (ul_reason_for_call)

{

case DLL_PROCESS_ATTACH:

if (DebuggerActive() || !C2Active())

return FALSE;

// Other insignificant code or anti-rce tricks

// ...

Install();

case DLL_THREAD_ATTACH:

case DLL_THREAD_DETACH:

case DLL_PROCESS_DETACH:

break;

}

return TRUE;

}

In the DllMain routine, the DLL calls DebuggerActive (code not shown), which presumably returns TRUE if the malware detects the presence of a debugger. It also calls C2Active, which presumably returns TRUE if the malware can successfully contact its command and control server. If there are no debuggers attached to the DLL and the command and control server is active, the DLL calls the Install function to drop an executable. Otherwise, the DLL simply exits.

The purpose of this demonstration is to show how you can force execution of the Install function, without running the code in DllMain. Here are the steps you can follow:

1. Determine the relative virtual address (RVA) of the function you want to execute (see Recipe 13-1 for how to do this). Figure 13-14 shows that the RVA of the Install function is 0x10C0.

Figure 13-14: The RVA of the Install function is 0x10C0.

f1314.tif

2. Use the dll2exe.py script, which you can find on the DVD that accompanies this book, to convert the DLL into an EXE and change the AddressOfEntryPoint value to the RVA of the Install function. To use the script, call it on the command line like this:

$ python dll2exe.py example.dll 0x10C0

Converting example.dll from DLL to EXE

Characteristics 0x2102 => 0x102

Entry point RVA 0x1853 => 0x10C0

Saved new file as example.dll.exe

3. If you do not want to debug the function, you can execute example.dll.exe from cmd.exe. If you want to debug the function, open example.dll.exe in your debugger and it should automatically break at the new entry point. Figure 13-15 shows an example of what you’ll see. The first instruction to be executed is at 0x100010C0, which is the beginning of the Installfunction. You bypassed all of the anti-debugging code in DllMain!

Figure 13-15: We bypassed DllMain and reached the Install function.

f1315.eps

Here is the code for dll2exe.py:

#!/usr/bin/python

import pefile

import sys, os

IMAGE_FILE_DLL = 0x2000

if len(sys.argv) < 2 or not os.path.isfile(sys.argv[1]):

print "\nUsage: dll2exe.py <filename> [EntryPoint RVA (hex)]\n"

sys.exit()

else:

FileName = sys.argv[1]

pe = pefile.PE(FileName)

OldChars = pe.FILE_HEADER.Characteristics

NewChars = OldChars - (OldChars & IMAGE_FILE_DLL)

pe.FILE_HEADER.Characteristics = NewChars

print "\nConverting %s from DLL to EXE" % FileName

print "Characteristics 0x%x => 0x%x" % (OldChars, NewChars)

if len(sys.argv) == 3:

OldEP = pe.OPTIONAL_HEADER.AddressOfEntryPoint

NewEP = int(sys.argv[2], 16)

pe.OPTIONAL_HEADER.AddressOfEntryPoint = NewEP

print "Entry point RVA 0x%x => 0x%x" % (OldEP, NewEP)

ExeFileName = FileName + ".exe"

pe.write(ExeFileName)

print "Saved new file as %s\n" % ExeFileName

The method described in this recipe is not always as simple as it sounds. For example, if you want to force execution of a function that requires parameters, you will have to manually place those parameters on the stack before allowing the program to run. Additionally, if you redirect the entry point of a DLL or EXE that performs required startup routines or initializes global variables referenced by the function you want to execute, then you could run into serious issues. So, be aware of the caveats, but don’t forget about the possibility of using this trick in the future.