Friday, February 10, 2012

Malware Analysis Tutorial 16: Return Oriented Programming (Return to LIBC) Attack

Learning Goals:
  1. Understand Return to LibC attacks
  2. Analyze Return to LibC data payloads
  3. Practice analyzing function call parameters and stack
Applicable to:
  1. Operating Systems
  2. Assembly Language
  3. Operating System Security
1. Introduction
This tutorial shows a very interesting attack called Return to LibC. The general idea is to form a chain of system calls by setting up stack contents properly so that the target process, when returning from one system call, will continue onto the next system call set up by the attacker. A more general form of the Return to LibC attack is the recently very active Return Oriented Programming [1], where any arbitrary program can be implemented by setting up the stack contents.

Usually, Return to LibC attack is applied to buffer overflow attacks so that it can overcome the protections taken by OS to set stack as non-executable (recall that it does not inject a shell code, but a bunch of function parameters and return addresses).

In this tutorial, we show that the Max++ author takes a very clever application of the Return to LibC attack. Instead of setting up the code section of a thread, the author directly implements the desired program logic as the stack contents, which saves a lot of trouble in injecting a new thread into a running process [see background information in Tutorial 14 and 15].

We will analyze the code from 0x3C1390  in this tutorial.


2. Lab Configuration
(0) Start WinXP image in DEBUGGED mode. Now in your host system, start a windows command window and CD to "c:\Program Files\Debugging Tools for Windows (x86)" (where WinDBG is installed). Type "windbg -b -k com:pipe,port=\\.\pipe\com_12" (check the com port number in your VBox instance set up). When WinDbg initiates, types "g" (go) twice to let it continue.

(1) Now launch IMM in the WinXP instance, clear all breakpoints and hardware breakpoints in IMM (see View->Breakpoints and View->Hardware Breakpoints).

(2) Go to 0x4012DC and set a hardware breakpoint there. (why not software bp? Because that region will be self-extracted and overwritten and the software BP will be lost). Pay special attention that once you go to 0x4012DC, directly right click on the line to set hardware BP (currently it's gibberish code).

(3) PressF9 several times run to 0x4012DC. You will encounter several breakpoints before 0x4012DC. If you pay attention, they are actually caused by the int 2d tricks (explained in Tutorial 3 and 4, and 5). Simply ignore them and continue (using F9) until you hit 0x4012DC.

Figure 1 shows the code that you should be able to see. As you can see, this is right before the call of RtlAddVectoredException, where hardware BP is set to break the LdrLoadDll call (see Tutorial 11 for details). At this point, the code at 0x3C24FB has not been extracted. If you go to 0x3C24FB at this moment, IMM will complain that this address is not accessible.
Figure 1: code at 0x4012DC
(4) Now scroll down about 2 pages and set a SOFTWARE BREAKPOINT at 0x401417. This is right after the call of LdrLoadDll("lz32.dll"), where Max++ finishes the loading of lz32.dll. Then hit SHIFT+F9 several times until you reach 0x401417 (you will hit 0x7C90D500 twice, this is somwhere inside ntdll.zwMapViewSection which is being called by LdrLoadDll).



Figure 2: code at 0x401407

(6) Now we will set a breakpoint at 0x3C1390.  Goto 0x3C1390set a SOFTWARE BREAKPOINT. SHIFT+F9 there. Press to run to 0x3C1390. (You may see a warning that this is out range of the code segment, simply ignore the warning).

(Figure 3 shows the code that you should be able to see at 0x3C1390. The first instruction should be PUSH SS:[EBP-8], and he next is a function call: CALL DWORD PTR DS: [3D1128] (see Figure 3). This is a function call to RtlCreateUserThread. We will start our analysis here!
Figure 3. Code Starting from 0x003C1390

Section 3. Creating Target Thread
The first function we are analyzing is located at 0x003C1393 (see Figure 3). RtlCreateUserThread is an undocumented function by MS Windows. But a simple google search can turn out its function declaration as shown following (taken from [2]):

RtlCreateUserThread(
     IN HANDLE ProcessHandle,
     IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
     IN BOOLEAN CreateSuspended,
     IN ULONG StackZeroBits,
     IN OUT PULONG StackReserved,
     IN OUT PULONG StackCommit,
     IN PVOID StartAddress,
     IN PVOID StartParameter OPTIONAL,
     OUT PHANDLE ThreadHandle,
     OUT PCLIENT_ID ClientID
); 

There are many parameters, and the following are interesting to us:
(1) ProcessHandle - the handle of the process to create a user thread in.
(2) StartAddress - when the thread is created, where to begin its execution
Figure 4 shows the stack contents when we get to the CALL instruction at 0x3C1390.
Figure 4: Stack Contents for Call RtlCreateUserThread


You can immediately infer that the ProcessHandle is 0x44. If you follow Tutorial 15, you will notice that it's the handle of the target  process (e.g., smss.exe in our VM instance). The startAddress is 0x7C96149B (note! IMM has already found it for you! It's the entry address of RtlExitUserThread!!!) The situation has got a little bit funny here because the author tries to inject a thread which terminates itself immediately! But he/she has additional tricks. Now let's proceed to the next function call.

Section 4. Get Context Thread
 The next system call we are analyzing is located at 0x3C13B5.It's a call to zwGetContext (as shown in Figure 5). A simple search of zwGetContext or ntGetContext yields the function prototype of zwGetContext. It takes two parameters (1) HANDLE threadHandle, and (2) PCONTEXT pContext. Clearly, the first is the handle of the thread to be examined, and the second (i.e., pContext) is a pointer which holds the entry address of the CONTEXT structure. If you look at Figure 5, you might notice that the pContext value is 0x0012D288.

Figure 5. call of zwGetContext
Now we are interested in looking at the contents of the CONTEXT structure at 0x0012D288. Of course, you can use IMM to examine it in the memory dump directly. There is a better way. Using WinDbg can show kernel data structures nicely. For this purpose, let's start the WinDbg in the VBox Windows imageFile-> Attack Process -> Max++ (note: not the external one in the host) and then click and select to run Noninvasively. (as shown in Figure 6).

Figure 6. Run WinDebug in Noninvasive Mode


Now type "dt _CONTEXT -r2 0x0012D288" we have the complete dump of the _CONTEXT structure. Note that the _CONTEXT is used by OS to keep track of the running context (mainly the value of the registers) of a thread when doing context switch. As shown in Figure 7, the initial context of the new thread in smss.exe has several interesting registers:
(1) First, we could see all hardware BPs are cleared.
(2) All segment registers GS/FS/ES/DS are not set yet.
(3) EIP is set to 0x7c96149b. (If you find it out in IMM->View->Modules->ntdll->Names. You will find that this is the entry address of RtlExitUserThread, matching the description before).
(4) The ESP is set to 0x3fff8 (note: this is within the address space of smss.exe, not Max++.exe).

Figure 7. Initial Dump of the _CONTEXT at 0x0012D288


Section 5. Set up smssexe Stack
Next, Max++ will set up the stack for the new thread properly. Let's look at Figure 8. From 0x003C13C3 to 0x003C1455, Max++ prepares the contents first in its own stack (you could see here there are lots of operations to the EBP based addresses). Then at 0x3C145F, it calls the most important function zwWriteVirtualMemory!

Figure 8. Set up Target Stack of smss.exe's new Thread

Google the documentation of zwWriteVirtualMemory or ntWriteVirtualMemory, we could find that it takes four parameters: (1) target process handle [we could verify that this is the handle of the smss.exe], (2) target address [in our case, it's 0x3FF000!], (3) source address (in Max++ addr space): 0x0012D580, (4) number of bytes to write  0x4c!

Figure 9. Contents to Inject
Now let's take a look at the 0x4c bytes to write into the target process smss.exe. It's listed in Figure 9. Notice that the data is starting from 0x0012D580 and ends at 0x0012D5CC i.e., the first word is the 0x7C90CFD0 (i.e., the first word in the first row of Hex Dump  in figure 9, note the byte sequence) in the first row and then the last word is the 0xFFFFFFFF (the first word in the last row). Notice that when copied to the target address space of smss.exe, starting from 0x3FF000 we should have 0x7C90CFD0 and at 0x3FF048 we should have FFFFFFFF.

Section 6. Set Context of smss.exe thread
As shown in Figure 8, the last trick to play is to call the zwSetContextThread call. Using the same trick we used in section 4, we can easily infer the meaning of all parameters of zwSetContextThread. It changes the value of the ESP register to 0x3FF000, EBP is set to 0x0, EIP register is reset to 0x7C90DF30 (using IMM->View->Modules->Names) we can find that it's zwWaitForSingleObject!

Section 7. Analysis of Return To LibC Attack
Now we are getting to the interesting point. The first instruction to be executed by the new thread is the entry of zwWaitForSingleObject(handle, alertable, timeout) . But were are the parameters? If you read about the calling conventions of these ntdll functions you will find that when executing to the first (entry) instruction of a NTDLL function, we have
   ESP --> the return address
   ESP+4:  1st parameter
   ESP+8:  2nd parameter
   ESP+c:  3rd paramter

Thus we immediately get: the return address is (see Figure 9) 0x7C90CFDO (zwClose). The parameters for zwWaitForSingleObject is:
  handle: 0x54 (what's the handle for? It's a challenge for you)
  alertable: 0
   timeOut: 0.

 When zwWaitForSingleObject finishes, it will jump to zwClose. The parameter for zwClose is 0x54 (still the same handle). When zwClose returns, the next function entry is 0x0x7C90D1F0 (zwDelayExecution)!

Challenge of the Day: Finish the above analysis of ReturnToLibC attack employed by Max++ and analyze what it is trying to do? HINT: again if you could find documentation for zwXYZ(), you can try searching ntXYZ().



References
1. E. Buchanan, R. Roemer,  and S. Savage, "Return-Oriented Programming: Exploits Without Code Injection", Blackhat USA 2008.
2.  "Undocumented Functions of NTDLL", Available at http://undocumented.ntinternals.net/UserMode/Undocumented%20Functions/Executable%20Images/RtlCreateUserThread.html.


3 comments:

  1. Good coverage on the return to libc attack.

    ReplyDelete
  2. This blog awesome and i learn a lot about programming from here.The best thing about this blog is that you doing from beginning to experts level.

    Love from

    ReplyDelete