Thursday, April 28, 2011

Debugging the Native Windows API

We are going to play a little game. We will search inside the Native Windows Application Programming Interface (API) for functions that used internally by the Windows 7 operating system. The use of such functions is not suggested by Microsoft. We are not only going to uncover such functions, but also we will use them and we will examine their results. 
The Native API is behind the Base API that Microsoft suggests to use for compatibility and portability reasons.

The Native API is the last layer (in user mode) that performs direct calls to the windows kernel mode and more specific to the NTOSKRNL.EXE that is the core windows kernel.

I must say that, in my opinion, the method of checking the API of windows is not the easiest thing. I could say that it is more difficult than this in Linux while windows source is not available. Its a closed source.  How then is possible to study a specific API function? Only disassembly code can be extracted by processes that are not belong to core kernel. In case that we want to debug kernel, we will need special programs (a windows  kernel debugger for example), but this is beyond the scope of this article. We will see from a user-mode point of view the procedures and functions (even undocumented) inside the Native API, aka ntdll.dll.

A question that one might ask, is: But why we do this?
Hmm... there are more than one reason:
1. It is a very good elementary lesson for the wannabe operating systems reverser's.
2. We will learn how to administer our operating system's basic internal actions.
3. We will see live the operation of the (somehow) cryptic native windows API.

What knowledge is required to read this article?
Well, not deep.
1. Elementary knowledge of some reversing techniques, for example how to use Olly debugger.
2. Little (yes little!) knowledge of assembly. We will meet inevitability a lot of assembly code in our trip but I am not willing to make this article an assembly listing with explanations! We will see how to achieve our goals without the need to be an assembly programmer.
Lets start!

The native API is implemented inside the ntdll.dll that is located in c:\windows\system32. For the shake of safety lets copy this dll to a work directory say d:\work2. Every test is presented here is performed in a box with Windows 7 Ultimate 64bit.

What I am going to do now is to extract every single function that this DLL contains and to (randomly...?) choose one to examine. In order to extract all the exported functions of ntdll.dll I will use the program dumpbin that is come with Visual Studio 2008.
I enter:
D:\work2> dumpbin ntdll.dll /exports > exports.txt  
and I create (through redirection) the file exports.txt that contains all the functions that ntdll.dll exports.
When I open (using ultraedit) I will see the following:
Image 1: Exported functions of ntdll.dll

Looking inside this file I found this:
    ordinal hint  RVA         name          
        933  398  0003859A    RtlGetVersion 

Hmm... there is a function named RtlGetVersion that is located at RVA (Relative Virtual Address) 3859Α. The RVA is the relative address that the RtlGetVersion function will be loaded when the dll will be loaded into memory and thus gets a Base Address. If, for example, the RVA of a function is 15 and the dll will be loaded at position 100 then the function will be located at address 115.

I would like to see what this function does. So, I will dissaseble it. To do this I have to disassemble the whole ntdll as follows:
D:\work2>dumpbin /disasm ntdll.dll > ntdlldisasm.txt  

The file ntdlldisasm.txt that I create contains the whole assembly code of ntdll. Its a small source file of... 17Mb!
Now, I have to search in this file for my RtlGetVersion function.
This is easy. I just have to apply the rule:
IMAGE_BASE_ADDRESS + Function_RVA = Function Address

I already have the RVA. its 0003859A.
But what is the IMAGE_BASE_ADDRESS of ntdll?
This is easy too. I just enter:

D:\work2>dumpbin /headers ntdll.dll | find "image base" 
        7DE70000 image base (7DE70000 to 7DFEFFFF)      

So 7DE70000 + 0003859A = 7DEA859A.
which means that my function RtlGetVersion located at address 7DEA859A.

Image 2: the ntdll.dll disassembly code

The actual code of RtlGetVersion is the following:
  7DEA859A: 8B FF           mov         edi,edi
  7DEA859C: 55              push        ebp
  7DEA859D: 8B EC           mov         ebp,esp
  7DEA859F: 51              push        ecx
  7DEA85A0: 64 A1 18 00 00 00  mov      eax,dword ptr fs:[00000018h]
  7DEA85A6: 53              push        ebx
  7DEA85A7: 56              push        esi
  7DEA85A8: 8B 75 08        mov         esi,dword ptr [ebp+8]
  7DEA85AB: 57              push        edi
  7DEA85AC: 8B 78 30        mov         edi,dword ptr [eax+30h]
  7DEA85AF: 8B 87 A4 00 00 00  mov      eax,dword ptr [edi+000000A4h]
  7DEA85B5: 89 46 04        mov         dword ptr [esi+4],eax
  7DEA85B8: 8B 87 A8 00 00 00  mov      eax,dword ptr [edi+000000A8h]
  7DEA85BE: 89 46 08        mov         dword ptr [esi+8],eax
  7DEA85C1: 0F B7 87 AC 00 00  movzx    eax,word ptr [edi+000000ACh]
            00
  7DEA85C8: 89 46 0C        mov         dword ptr [esi+0Ch],eax
  7DEA85CB: 8B 87 B0 00 00 00  mov      eax,dword ptr [edi+000000B0h]
  7DEA85D1: 89 46 10        mov         dword ptr [esi+10h],eax
  7DEA85D4: 8B 87 F4 01 00 00  mov      eax,dword ptr [edi+000001F4h]
  7DEA85DA: 85 C0           test        eax,eax
  7DEA85DC: 74 0A           je          7DEA85E8
  7DEA85DE: 66 83 38 00     cmp         word ptr [eax],0
  7DEA85E2: 0F 85 BB 78 05 00  jne      7DEFFEA3
  7DEA85E8: 33 C0           xor         eax,eax
  7DEA85EA: 66 89 46 14     mov         word ptr [esi+14h],ax
  7DEA85EE: 81 3E 1C 01 00 00  cmp      dword ptr [esi],11Ch
  7DEA85F4: 75 5E           jne         7DEA8654
  7DEA85F6: 66 0F B6 87 AF 00  movzx    ax,byte ptr [edi+000000AFh]
            00 00
  7DEA85FE: 66 89 86 14 01 00  mov      word ptr [esi+00000114h],ax
            00
  7DEA8605: 66 8B 87 AE 00 00  mov      ax,word ptr [edi+000000AEh]
            00
  7DEA860C: B9 FF 00 00 00  mov         ecx,0FFh
  7DEA8611: 66 23 C1        and         ax,cx
  7DEA8614: 66 89 86 16 01 00  mov      word ptr [esi+00000116h],ax
            00
  7DEA861B: 66 A1 D0 02 FE 7F  mov      ax,word ptr ds:[7FFE02D0h]
  7DEA8621: 66 89 86 18 01 00  mov      word ptr [esi+00000118h],ax
            00
  7DEA8628: 8D 45 FC        lea         eax,[ebp-4]
  7DEA862B: 8D BE 1A 01 00 00  lea      edi,[esi+0000011Ah]
  7DEA8631: 50              push        eax
  7DEA8632: C6 07 00        mov         byte ptr [edi],0
  7DEA8635: E8 28 00 00 00  call        7DEA8662
  7DEA863A: 84 C0           test        al,al
  7DEA863C: 74 16           je          7DEA8654
  7DEA863E: 8B 45 FC        mov         eax,dword  line ptr [ebp-4]
  7DEA8641: 88 07           mov         byte ptr [edi],al
  7DEA8643: 83 F8 01        cmp         eax,1
  7DEA8646: 75 0C           jne         7DEA8654
  7DEA8648: B8 EF FF 00 00  mov         eax,0FFEFh
  7DEA864D: 66 21 86 18 01 00  and      word ptr [esi+00000118h],ax
            00
  7DEA8654: 5F              pop         edi
  7DEA8655: 5E              pop         esi
  7DEA8656: 33 C0           xor         eax,eax
  7DEA8658: 5B              pop         ebx
  7DEA8659: C9              leave
  7DEA865A: C2 04 00        ret         4

I present this code just for the shake of knowledge and I am not to go in a line by line explanation as I promised. I am going to do something more practical: I will execute the above code using Olly debugger and I will examine the results. Ok, it sounds interesting, but how can I call the specific function that is inside a DLL? With Olly this is easy.
Before I start, I want to check the documentation of this function. Indeed here is the documentation that microsoft provides.

The RtlGetVersion routine returns version information about the currently running operating system.
Syntax
NTSTATUS RtlGetVersion(
  __out  PRTL_OSVERSIONINFOW lpVersionInformation
);


Parameters
lpVersionInformation [out]

    Pointer to either a RTL_OSVERSIONINFOW structure or a RTL_OSVERSIONINFOEXW structure that contains the version
information about the currently running operating system. A caller specifies which input structure is used by setting
the dwOSVersionInfoSize member of the structure to the size in bytes of the structure that is used.

Return Value
RtlGetVersion returns STATUS_SUCCESS.

Remarks
RtlGetVersion is the kernel-mode equivalent of the user-mode GetVersionEx function in the Windows SDK. See the example
in the Windows SDK that shows how to get the system version.


As we can see, this function has no input arguments and once it is called returns its results to the structure RTL_OSVERSIONINFOW. The definition in this structure is here.

dwOSVersionInfoSize
    Specifies the size in bytes of an RTL_OSVERSIONINFOW structure. This member must be set before the structure is used with RtlGetVersion.

dwMajorVersion
    Identifies the major version number of the operating system. For example, for Windows 2000, the major version number is five.
dwMinorVersion
    Identifies the minor version number of the operating system. For example, for Windows 2000 the minor version number is zero.

dwBuildNumber
    Identifies the build number of the operating system.

dwPlatformId
    Identifies the operating system platform. For Microsoft Win32 on NT-based operating systems, RtlGetVersion returns the value VER_PLATFORM_WIN32_NT.

szCSDVersion
    Contains a null-terminated string, such as "Service Pack 3", which indicates the latest Service Pack installed on the system. If no Service Pack has been installed, the string is empty.
 

Ok, now I know what I expect as a result: The structure RTL_OSVERSIONINFOW. Now, I just return to my initial goal: To execute the function via Olly. Because we are talking about a DLL (and to about an executable) I will do the following: After loading the DLL in Olly I will go to the address where my function is located and I will put there a new origin point, as you can see in the next image.

Image 3: The new origin point is the address of RtlGetVersion.

Note that the address of RtlGetVersion is not the same of what I get from the hexdump output. This is not odd and can be explained by the fact that the Base Address in each case is different.
After setting my new origin, I put a break point at address 776D859A and I am now ready to press RUN to meet this breakpoint and then I will go line by line (by pressing F10) examining the function execution and the registers values. In addition, in order to check the results in a more reliable way, I will do the following: I will execute from command line the "winver" system command in order to check (according to Microsoft documentation) the values. The winver command gives me the following:

Image 4: The output of the winver command.


Note the string "Version 6.1 (Build 7600)". I must see the same result after executing the function RtlGetVersion.
Below there is an analysis of the code that I get from Olly with my remarks for the registers values I get. I suppose that the remarks are descriptive enough so no more are necessary.

776D859A > 8BFF             MOV EDI,EDI |
776D859C   55               PUSH EBP    | Standard function prologue
776D859D   8BEC             MOV EBP,ESP |

776D859F   51               PUSH ECX
776D85A0   64:A1 18000000   MOV EAX,DWORD PTR FS:[18]       ====>> return the TEB (Thread Entry Block) address
776D85A6   53               PUSH EBX
776D85A7   56               PUSH ESI
776D85A8   8B75 08          MOV ESI,DWORD PTR SS:[EBP+8]    ====>> return the PEB (Process Entry Block) address
776D85AB   57               PUSH EDI
776D85AC   8B78 30          MOV EDI,DWORD PTR DS:[EAX+30]
776D85AF   8B87 A4000000    MOV EAX,DWORD PTR DS:[EDI+A4]   ===>> eax=6                 (MajorVersion)
776D85B5   8946 04          MOV DWORD PTR DS:[ESI+4],EAX
776D85B8   8B87 A8000000    MOV EAX,DWORD PTR DS:[EDI+A8]   ===>> eax=1                 (MinorVersion)
776D85BE   8946 08          MOV DWORD PTR DS:[ESI+8],EAX
776D85C1   0FB787 AC000000  MOVZX EAX,WORD PTR DS:[EDI+AC]  ===>> eax=00001DB0 i.e. 7600  (BuildNumber) MOV with zero eXtended
776D85C8   8946 0C          MOV DWORD PTR DS:[ESI+C],EAX
776D85CB   8B87 B0000000    MOV EAX,DWORD PTR DS:[EDI+B0]   ===>> eax=2 (platformID)
776D85D1   8946 10          MOV DWORD PTR DS:[ESI+10],EAX
776D85D4   8B87 F4010000    MOV EAX,DWORD PTR DS:[EDI+1F4]
776D85DA   85C0             TEST EAX,EAX
776D85DC   74 0A            JE SHORT ntdll.776D85E8
776D85DE   66:8338 00       CMP WORD PTR DS:[EAX],0
776D85E2   0F85 BB780500    JNZ ntdll.7772FEA3
776D85E8   33C0             XOR EAX,EAX
776D85EA   66:8946 14       MOV WORD PTR DS:[ESI+14],AX
776D85EE   813E 1C010000    CMP DWORD PTR DS:[ESI],11C
776D85F4   75 5E            JNZ SHORT ntdll.776D8654
776D85F6   66:0FB687 AF0000>MOVZX AX,BYTE PTR DS:[EDI+AF]
776D85FE   66:8986 14010000 MOV WORD PTR DS:[ESI+114],AX
776D8605   66:8B87 AE000000 MOV AX,WORD PTR DS:[EDI+AE]
776D860C   B9 FF000000      MOV ECX,0FF
776D8611   66:23C1          AND AX,CX
776D8614   66:8986 16010000 MOV WORD PTR DS:[ESI+116],AX
776D861B   66:A1 D002FE7F   MOV AX,WORD PTR DS:[7FFE02D0]
776D8621   66:8986 18010000 MOV WORD PTR DS:[ESI+118],AX
776D8628   8D45 FC          LEA EAX,DWORD PTR SS:[EBP-4]
776D862B   8DBE 1A010000    LEA EDI,DWORD PTR DS:[ESI+11A]
776D8631   50               PUSH EAX
776D8632   C607 00          MOV BYTE PTR DS:[EDI],0
776D8635   E8 28000000      CALL ntdll.RtlGetNtProductType
776D863A   84C0             TEST AL,AL
776D863C   74 16            JE SHORT ntdll.776D8654
776D863E   8B45 FC          MOV EAX,DWORD PTR SS:[EBP-4]
776D8641   8807             MOV BYTE PTR DS:[EDI],AL
776D8643   83F8 01          CMP EAX,1
776D8646   75 0C            JNZ SHORT ntdll.776D8654
776D8648   B8 EFFF0000      MOV EAX,0FFEF
776D864D   66:2186 18010000 AND WORD PTR DS:[ESI+118],AX
776D8654   5F               POP EDI
776D8655   5E               POP ESI
776D8656   33C0             XOR EAX,EAX
776D8658   5B               POP EBX
776D8659   C9               LEAVE
776D865A   C2 0400          RETN 4

Very well. As you can see, I get the same results.
 
There is another thing that I would like to point out: Pay attention at address 776D8635. In this address a function is called: the RtlGetNtProductType. This RtlGetNtProductType is probably internal and... undocumented. More about undocumented functions we will discuss in a later article...

We achive the following, so far:
1. We see which functions exist inside the ntdll.dll.
2. We select and examine one of them.
3. We execute it and we get its result line by line by examining its source (disassembly) code.

Be carefull when you mess around with native API functions. If something goes wrong it is very possible to freeze your system.


REMARK:
Until now we examine functions with 'Rtl' as prefix. Microsoft choose this naming convention for its internal functions. Rtl means RunTime Library. In addition, inside ntdll we will see another prefix: the Zw. Functions with this prefix make direct calls to the kernel. In general, you must know that the naming convetion used by Microsoft following the formula:
<Prefix><Operation><Object>

What remains to be done is a program in C language that will direct call this function from the native API. This program could be used as a base for more advanced... checks, using even undocumented functions ;)

If you search the internet you will not find many sources of how to call RtlGetVersion
direct from Native API. So, I suppose that this code could put a small brick to cover this gap.
The program is in C++ in Visual Studio 2010.


///////////////////////////////////////////////////////////////////////
// Kernel01.cpp : Call the RtlGetVersion from native API
// © by Thiseas 2011 for www.p0wnbox.com
//
#include "stdafx.h"
#include <Windows.h>

typedef void (WINAPI *pwinapi)(PRTL_OSVERSIONINFOW); //http://www.osronline.com/ddkx/kmarch/k109_452q.htm


int _tmain(int argc, _TCHAR* argv[])
{
    RTL_OSVERSIONINFOW info;
    pwinapi p_pwinapi;

    ZeroMemory(&info, sizeof(RTL_OSVERSIONINFOW));

    p_pwinapi = (pwinapi) GetProcAddress(GetModuleHandle(TEXT("ntdll.dll")), "RtlGetVersion");
    p_pwinapi(&info);

    return(0);
}

There is no need to spend more bytes for comments. Please try this by yourself...
Debug and  disassemble this little program and repeat the procedure that we just describe above.
I will promise you that you will be disappointed... ;)



Happy Reversing!

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.