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:

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

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

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);
}

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 .

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;

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

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;
}

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

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;
}

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
0×25 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

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

Want to learn more?? The InfoSec Institute Reverse Engineering course teaches you everything from reverse engineering malware to discovering vulnerabilities in binaries. These skills are required in order to properly secure an organization from today's ever evolving threats. In this 5 day hands-on course, you will gain the necessary binary analysis skills to discover the true nature of any Windows binary. You will learn how to recognize the high level language constructs (such as branching statements, looping functions and network socket code) critical to performing a thorough and professional reverse engineering analysis of a binary. Some features of this course include:

  • CREA Certification
  • 5 days of Intensive Hands-On Labs
  • Hostile Code & Malware analysis, including: Worms, Viruses, Trojans, Rootkits and Bots
  • Binary obfuscation schemes, used by: Hackers, Trojan writers and copy protection algorithms
  • Learn the methodologies, tools, and manual reversing techniques used real world situations in our reversing lab.