Reverse engineering

Understanding Windows Internal Call Structure

D12d0x34X
October 2, 2013 by
D12d0x34X

Microsoft Windows is a modular architecture. Windows Components are split into smaller pieces known as DLL (Dynamic-Link Library) and sys files (system files). These DLL or system files are inter-related to each other and work as a team. A call from a single DLL is forwarded to another DLL function name or other inter-modular calls. Have you ever wondered how Windows API calls are implemented internally? Well, we are going to learn about the Windows internal call structure in this article.

Tools Required:

  • Windows XP machine or virtual Machine.
  • C/C++ compiler.
  • A Debugger like ollydbg.

Understanding the executable format

As an object file is compiled, it gets transformed into a PE (Portable Executable) file (or an exe file). Portable Executable files are well documented by Microsoft. They consist of sections and headers: these sections and headers inform the PE loader how to load the executable file by Windows. For example, some of the fields inform the loader which section is to be marked as executable and which is to be marked as data section. Size of initial heap, stack, TLS (Thread Local Storage) is also determined by PE headers.

Now let's create a minimalistic PE file and check out its headers and sections:

[plain]

Microsoft (R) COFF/PE Dumper Version 8.00.50727.42

Copyright (C) Microsoft Corporation. All rights reserved.

Dump of file sample.exe

PE signature found

File Type: EXECUTABLE IMAGE

FILE HEADER VALUES

14C machine (x86)

3 number of sections

5207B239 time date stamp Sun Aug 11 21:18:09 2013

0 file pointer to symbol table

0 number of symbols

E0 size of optional header

103 characteristics

Relocations stripped

Executable

32 bit word machine

OPTIONAL HEADER VALUES

10B magic # (PE32)

8.00 linker version

200 size of code

400 size of initialized data

0 size of uninitialized data

1000 entry point (00401000)

1000 base of code

2000 base of data

400000 image base (00400000 to 00403FFF)

1000 section alignment

200 file alignment

4.00 operating system version

0.00 image version

4.00 subsystem version

0 Win32 version

4000 size of image

400 size of headers

0 checksum

3 subsystem (Windows CUI)

400 DLL characteristics

No structured exception handler

100000 size of stack reserve

1000 size of stack commit

100000 size of heap reserve

1000 size of heap commit

0 loader flags

10 number of directories

0 [ 0] RVA [size] of Export Directory

2008 [ 28] RVA [size] of Import Directory

0 [ 0] RVA [size] of Resource Directory

0 [ 0] RVA [size] of Exception Directory

0 [ 0] RVA [size] of Certificates Directory

0 [ 0] RVA [size] of Base Relocation Directory

0 [ 0] RVA [size] of Debug Directory

0 [ 0] RVA [size] of Architecture Directory

0 [ 0] RVA [size] of Global Pointer Directory

0 [ 0] RVA [size] of Thread Storage Directory

0 [ 0] RVA [size] of Load Configuration Directory

0 [ 0] RVA [size] of Bound Import Directory

2000 [ 8] RVA [size] of Import Address Table Directory

0 [ 0] RVA [size] of Delay Import Directory

0 [ 0] RVA [size] of COM Descriptor Directory

0 [ 0] RVA [size] of Reserved Directory

SECTION HEADER #1

.text name

81 virtual size

1000 virtual address (00401000 to 00401080)

200 size of raw data

400 file pointer to raw data (00000400 to 000005FF)

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

60000020 flags

Code

Execute Read

RAW DATA #1

00401000: 55 8B EC 51 C7 45 FC 00 00 00 00 68 30 10 40 00 U.ìQÇEü....h0.@.

00401010: FF 15 00 20 40 00 8B 45 FC C6 00 36 33 C0 8B E5 ÿ.. @..EüÆ.63À.å

00401020: 5D C3 CC CC CC CC CC CC CC CC CC CC CC CC CC CC ]ÃÌÌÌÌÌÌÌÌÌÌÌÌÌÌ

00401030: 55 8B EC 83 EC 08 8B 45 08 8B 08 8B 11 89 55 F8 U.ì.ì..E......Uø

00401040: 81 7D F8 05 00 00 C0 75 28 A1 00 30 40 00 83 C0 .}ø...Àu(¡.0@..À

00401050: 01 A3 00 30 40 00 83 3D 00 30 40 00 05 7D 09 C7 .£.0@..=.0@..}.Ç

00401060: 45 FC FF FF FF FF EB 07 C7 45 FC 01 00 00 00 EB Eüÿÿÿÿë.ÇEü....ë

00401070: 07 C7 45 FC 00 00 00 00 8B 45 FC 8B E5 5D C2 04 .ÇEü.....Eü.å]Â.

00401080: 00 .

SECTION HEADER #2

.rdata name

64 virtual size

2000 virtual address (00402000 to 00402063)

200 size of raw data

600 file pointer to raw data (00000600 to 000007FF)

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

40000040 flags

Initialized Data

Read Only

RAW DATA #2

00402000: 38 20 00 00 00 00 00 00 30 20 00 00 00 00 00 00 8 ......0 ......

00402010: 00 00 00 00 56 20 00 00 00 20 00 00 00 00 00 00 ....V ... ......

00402020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

00402030: 38 20 00 00 00 00 00 00 4A 03 53 65 74 55 6E 68 8 ......J.SetUnh

00402040: 61 6E 64 6C 65 64 45 78 63 65 70 74 69 6F 6E 46 andledExceptionF

00402050: 69 6C 74 65 72 00 4B 45 52 4E 45 4C 33 32 2E 64 ilter.KERNEL32.d

00402060: 6C 6C 00 00 ll..

Section contains the following imports:

KERNEL32.DLL

402000 Import Address Table

402030 Import Name Table

0 time date stamp

0 Index of first forwarder reference

34A SetUnhandledExceptionFilter

SECTION HEADER #3

.data name

4 virtual size

3000 virtual address (00403000 to 00403003)

0 size of raw data

0 file pointer to raw data

0 file pointer to relocation table

0 file pointer to line numbers

0 number of relocations

0 number of line numbers

C0000040 flags

Initialized Data

Read Write

Summary

1000 .data

1000 .rdata

1000 .text

[/plain]

Every PE file begins with a 'MZ' signature that acts as an indicator of a valid PE file.

It follows up with a valid MZ section header (where the DOS error program is also located) and will pop out a message if the PE file runs under MS DOS. After the DOS stub pointer is present (which gives out the offset to the PE header),

0000003C D8000000 DD 000000D8 ; Offset to PE signature

It's the RVA from the beginning of MZ header.

We can calculate RV by using the following formula:

RVA = Virtual Address - Base address

If we reach to the offset of the PE signature, we would have four bytes for the PE signature plus IMAGE_PE header.

The following examples are basic but still important points of this header:

000000D8 50 45 00 00 ASCII "PE" ; PE signature (PE)

000000DC 4C01 DW 014C ; Machine = IMAGE_FILE_MACHINE_I386

000000DE 0500 DW 0005 ; NumberOfSections = 5

000000E0 8F9BC44E DD 4EC49B8F ; TimeDateStamp = 4EC49B8F

The machine specifies the architecture for which it was made. NumberOfSections specifies number of sections this portable executable has. TimeDateStamp is the time_t struct value for the date it was compiled.

Other values like 'numberofsection' gives you the list numbers of sections inside a PE file.

This can be enumerated using the IMAGE_DATA_DIRECTORY structure.

One other important member of the PE format is the AddressOfEntryPoint. This RVA points towards the Entry point of the program from where the execution starts.

Following code can be used to get the entry point of the executable

[plain]

unsigned int AddrSPSize(unsigned char *PEfile)

{

FILE *fp = fopen((const char *)PEfile, "rb");

IMAGE_DOS_HEADER DosHdr = {0};

IMAGE_FILE_HEADER FileHdr = {0};

IMAGE_OPTIONAL_HEADER OptHdr = {0};

fread(&DosHdr, sizeof(IMAGE_DOS_HEADER), 0x01, fp);

fseek(fp, (unsigned int)DosHdr.e_lfanew + 4,SEEK_SET);

fseek(fp, sizeof(IMAGE_FILE_HEADER), SEEK_CUR);

fread( &OptHdr, sizeof(IMAGE_OPTIONAL_HEADER) , 0x01, fp);
fclose(fp);

return(OptHdr.SizeOfImage);

}

[/plain]

Thread Information Block (TIB)

The Tread information block consists of a plethora of information regarding the current thread. "Flowing" is the definition of the thread information block and its structures. The base of TEB is located at FS:[0] segment register .

[plain]

struct TEB

typedef struct _TEB

{

NT_TIB NtTib;

PVOID EnvironmentPointer;

CLIENT_ID ClientId;

PVOID ActiveRpcHandle;

PVOID ThreadLocalStoragePointer;

PPEB ProcessEnvironmentBlock;

ULONG LastErrorValue;

ULONG CountOfOwnedCriticalSections;

PVOID CsrClientThread;

PVOID Win32ThreadInfo;

ULONG User32Reserved[26];

ULONG UserReserved[5];

PVOID WOW32Reserved;

ULONG CurrentLocale;

ULONG FpSoftwareStatusRegister;

VOID * SystemReserved1[54];

LONG ExceptionCode;

PACTIVATION_CONTEXT_STACK ActivationContextStackPointer;

UCHAR SpareBytes1[36];

ULONG TxFsContext;

GDI_TEB_BATCH GdiTebBatch;

CLIENT_ID RealClientId;

PVOID GdiCachedProcessHandle;

ULONG GdiClientPID;

ULONG GdiClientTID;

PVOID GdiThreadLocalInfo;

ULONG Win32ClientInfo[62];

VOID * glDispatchTable[233];

ULONG glReserved1[29];

PVOID glReserved2;

PVOID glSectionInfo;

PVOID glSection;

PVOID glTable;

PVOID glCurrentRC;

PVOID glContext;

ULONG LastStatusValue;

UNICODE_STRING StaticUnicodeString;

WCHAR StaticUnicodeBuffer[261];

PVOID DeallocationStack;

VOID * TlsSlots[64];

LIST_ENTRY TlsLinks;

PVOID Vdm;

PVOID ReservedForNtRpc;

VOID * DbgSsReserved[2];

ULONG HardErrorMode;

VOID * Instrumentation[9];

GUID ActivityId;

PVOID SubProcessTag;

PVOID EtwLocalData;

PVOID EtwTraceData;

PVOID WinSockData;

ULONG GdiBatchCount;

UCHAR SpareBool0;

UCHAR SpareBool1;

UCHAR SpareBool2;

UCHAR IdealProcessor;

ULONG GuaranteedStackBytes;

PVOID ReservedForPerf;

PVOID ReservedForOle;

ULONG WaitingOnLoaderLock;

PVOID SavedPriorityState;

ULONG SoftPatchPtr1;

PVOID ThreadPoolData;

VOID * * TlsExpansionSlots;

ULONG ImpersonationLocale;

ULONG IsImpersonating;

PVOID NlsCache;

PVOID pShimData;

ULONG HeapVirtualAffinity;

PVOID CurrentTransactionHandle;

PTEB_ACTIVE_FRAME ActiveFrame;

PVOID FlsData;

PVOID PreferredLanguages;

PVOID UserPrefLanguages;

PVOID MergedPrefLanguages;

ULONG MuiImpersonation;

WORD CrossTebFlags;

ULONG SpareCrossTebBits: 16;

WORD SameTebFlags;

ULONG DbgSafeThunkCall: 1;

ULONG DbgInDebugPrint: 1;

ULONG DbgHasFiberData: 1;

ULONG DbgSkipThreadAttach: 1;

ULONG DbgWerInShipAssertCode: 1;

ULONG DbgRanProcessInit: 1;

ULONG DbgClonedThread: 1;

ULONG DbgSuppressDebugMsg: 1;

ULONG SpareSameTebBits: 8;

PVOID TxnScopeEnterCallback;

PVOID TxnScopeExitCallback;

PVOID TxnScopeContext;

ULONG LockCount;

ULONG ProcessRundown;

UINT64 LastSwitchTime;

UINT64 TotalSwitchOutTime;

LARGE_INTEGER WaitReasonBitMap;

} TEB, *PTEB;

[/plain]

One more important structure that resides inside the TEB is the process environment block, which has some really interesting fields:

[plain]

typedef struct _PEB {

BYTE Reserved1[2];

BYTE BeingDebugged;

BYTE Reserved2[1];

PVOID Reserved3[2];

PPEB_LDR_DATA Ldr;

PRTL_USER_PROCESS_PARAMETERS ProcessParameters;

BYTE Reserved4[104];

PVOID Reserved5[52];

PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;

BYTE Reserved6[128];

PVOID Reserved7[1];

ULONG SessionId;

}

[/plain]

LDR consists of loaded modules by the executable during runtime. The second variable BeingDebugged is set if the process is being debugged otherwise 0.

We can also enumerate the list of loaded process modules using this structure.

Internal Windows Kernel call Structure.

Now let's look at how internal calls to Windows Kernel take place. For this we will use CreateFileA as an example.

HANDLE File = CreateFile("File", FILE_READ_DATA , FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL,

The above code statement calls CreateFileA in kernel32.DLL, after getting inside CreateFileA in kernel32.DLL, let's see what the subsequent calls are being made after that.

As we can see that there are two internal calls being made in kernel32!CreateFileA.

Let's look at the first one which is located at 7C80E2A4

This call takes one argument as file name.

After examining the rtlinitansistring, we can form structs and functions in C equivalent code

[c]

struct __unknwon

{

short len;

short max_len;

void *buffer;

}unknown;

sturct __unknown rtlinitansistring ( struct unknown, unsigned char *buffer)

{

unkown.len = 0;

unknwon.buffer = buffer;

if ( buffer == 0) return unknown;

while(*buffer)

{

unknown.len++;

buffer++;

}

return unknown;

}

[/c]

This function initializes the ANSI string and stores them in a struct.

After this call, another function is called but before it a check regarding OEM string is made

7C80E2C5 CMP DWORD PTR DS:[7C8836E0],0

RtlAnsiStringToUnicodeString function receieves strict __unicode as one of its parameters.

This function basically converts a ANSI string to UNICODE one

After finishing up with Character conversion, CreateFileW is called with File name in unicode rest of the parameters are kept same.

Inside CreateFileW it will always first check if parameter Mode is CREATE_ALWAYS, which in our case, is.

Mode = CREATE_ALWAYS

7C810988 . 48 DEC EAX

7C810989 . 0F84 C9060000 JE kernel32.7C811058

In that case it will set its variable as 5

7C811058 > C745 F8 050000>MOV DWORD PTR SS:[EBP-8],5

After that it again calls RtlInitUnicodeString

7C8109A2 . 56 PUSH ESI ; /Arg2

7C8109A3 . 8D45 E8 LEA EAX,DWORD PTR SS:[EBP-18] ; |

7C8109A6 . 50 PUSH EAX ; |Arg1

7C8109A7 . FF15 4010807C CALL DWORD PTR DS:[<&ntDLL.RtlInitUnicod>; RtlInitUnicodeString

Up to now, lots of heap memory was allocated. Code that will de-allocate all unused memory is called; which happens to be present at 0x7C810A0E

Finally, after the call to NtCreateFile is made, we land inside ntDLL.DLL sys call dispatcher

7C90D682 >/$ B8 25000000 MOV EAX,25
7C90D687 |. BA 0003FE7F MOV EDX,7FFE0300

7C90D68C |. FF12 CALL DWORD PTR DS:[EDX] ; ntDLL.KiFastSystemCall

7C90D68E . C2 2C00 RETN 2C

0x25 is the SYS Call Number for Create File

7C90EB8B >/$ 8BD4 MOV EDX,ESP

7C90EB8D |. 0F34 SYSENTER

References:

http://www.amazon.com/Windows-2000-Native-API-Reference/dp/1578701996

Become a certified reverse engineer!

Become a certified reverse engineer!

Get live, hands-on malware analysis training from anywhere, and become a Certified Reverse Engineering Analyst.

http://www.amazon.com/dp/0735625301

D12d0x34X
D12d0x34X

Rashid Bhat is an Independent Security Researcher as well as a contributor to InfoSec Institute. His areas of expertise include exploitation, malware analysis and reverse engineering. Twitter: http://twitter.com/raashidbhatt