How does it work ?
Proof of concept
The proof of concept is open source and can be found here : https://github.com/recastrodiaz/formGrabber/. It includes instructions on how to run it on your machine.
The POC (Proof of Concept) has been successfully tested on Windows XP SP3, Windows 7 32 bits and Windows 7 64 bits with Firefox 11.0 and 12.0. Nevertheless, it has failed to work on at least one Windows 7 64 bits computer.
The following image shows an example of network connections created by Firefox when logging in to a Facebook account. The first line represents the encrypted data sent over a secure tunnel between the web browser and facebook.com (namely HTTPS). The second is a copy of the first but sent in plain text to localhost/postDemo.php. It contains the User’s email and password : “myMail@mail.com” and “guessMe”.
There are at least three ways of injecting code to a running process:
- Using windows hooks.
- By forcing the process to load a custom dll.
- By writing the code in the same context of the process and forcing the process to use it.
The first two methods and even the third to some extent, are very well explained in the following Robert Kuster’s article. Three Ways to Inject Your Code into Another Process.
The latter method is chosen because of its flexibility and its straight forward implementation.
In order to modify how a Windows process works, we need to change its “binary” code. (Un)Fortunately, Windows prevents unrelated processes to modify each other, notably by the use of Virtual Address Spaces. However, methods exist that circumvent this protection.
The key here, is to inject some code at the beginning of the PR_Write function located in nspr4.dll that will force custom code to be executed before the real PR_Write.
The code injection algorithm can be separated in 3 steps :
I. Virtual set up
- Locating the Firefox process. It must be running when the FormGrabber is executed: CreateToolhelp32Snapshot(…), Process32Next(…)
- Allocate space in the Firefox virtual space for our injected code and data: VirtualAllocEx(firefoxHandle,…,MEM_RESERVE, PAGE_EXECUTE_READWRITE)
- Copy the “to be injected” code at the previously allocated space and init all necessary data used by that code. WriteProcessMemory(firefoxHandle, …, Hook,…)
- Launch the remote injected code: CreateRemoteThread(firefoxHandle, pRemoteProgram, pRemoteMemory)
- Wait the hook to be set up: WaitForSingleObject(…)
II. The remote injected code
The remote code is in fact a function that takes one previously initialized data parameter. It accomplishes the following :
- Find the PR_Write function: GetProcAddress( GetModuleHandle(“nspr4.dll”), “PR_Write” )
- Replace the first binary instruction of PR_Write by a near Jump (JMP) pointing to a section of our remote code. (aka the Hook): VirtualProtect( PR_Write, …, PAGE_EXECUTE_READWRITE, …); *pData->PR_Write = HOOK_JMP
- By this time, the Form Grabber process is terminated.
III. The hook
- The hook will be called each time PR_Write is called with the right parameters. It extracts the second and third parameters, making a copy and sending it to a remote evil server with the help of the wininet library.
- Once the hook is executed, the instructions replaced by the previous JMP overwrite must be executed.
- The program flow is given to the real PR_Write code as if nothing had happened.
To be considered
Writing software that mixes C and Assembler is not easy. In our case, Mixing C function calls and ASM is cumbersome and error prone. Our POC code relies on assembler c calling conventions, but the compiler is free to change and optimize them at his will. Even though dll calling conventions should remain steady, nothing guarantees the code won’t break in the future. Moreover, debugging injected code within Visual Studio is practically impossible. Chances are you’ll crash the target process even with very simple lines of code such as: a += 1; Mixing assembler and c function calls is like playing with fire. Fortunately, debugging tools such as IDA will make your life easier, even if that means to debug assembly code.
In the other hand, we allocate space and handles but we never free them. From a malicious point of view this shouldn’t be a problem. But from a programmer’s point of view, we should free all allocated resources when nspr4.dll is unloaded.
Finally, this type of attack is not limited to web browsers. Intercepting dll functions calls can be achieved using this same approach.
Coming up next : Who’s guilty and conclusion.