Friday, September 16, 2016

Intro To Windows Internals

How is a program executed on windows?

All that happens is that the right system calls get executed. In Windows, the system-calls all revolve around the COM model. What is COM? Well, let's take it from the horse's mouth.
COM defines the essential nature of a COM object. In general, a software object is made up of a set of data and the functions that manipulate the data. A COM object is one in which access to an object's data is achieved exclusively through one or more sets of related functions. These function sets are called interfaces, and the functions of an interface are called methods. Further, COM requires that the only way to gain access to the methods of an interface is through a pointer to the interface.
However, that literally sounds like horse-speak without context. Practically, windows is an event-handling based OS. It waits for users to click on things before any code happens. The COM model just means that you have to register all your code with windows and structure all your interfaces in ways that windows can understand. So, a window would be described in terms of a windows data structure, and what happens when you click on it is described as a data structure that contains some of your code! After windows has registered these components, it gives you a handle to the object. The object being the actual program in memory. You then manipulate the object indirectly with more windows system calls if you so choose. Here's what this looks like:

This struct is basically a form to define a window.

WNDCLASSEX wcex;

wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style          = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc    = WndProc;
wcex.cbClsExtra     = 0;
wcex.cbWndExtra     = 0;
wcex.hInstance      = hInstance;
wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName   = NULL;
wcex.lpszClassName  = szWindowClass;
wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));

This is how we register that form with the OS, so that we can create a window.

if (!RegisterClassEx(&wcex))
{
    MessageBox(NULL,
        _T("Call to RegisterClassEx failed!"),
        _T("Win32 Guided Tour"),
        NULL);

    return 1;
}

This is how we get a handle to the window.

static TCHAR szWindowClass[] = _T("name_of_app");
static TCHAR szTitle[] = _T("window_title_text");
// szWindowClass: the name of the application
// szTitle: the text that appears in the title bar
// WS_OVERLAPPEDWINDOW: the type of window to create
// CW_USEDEFAULT, CW_USEDEFAULT: initial position (x, y)
// 500, 100: initial size (width, length)
// NULL: the parent of this window
// NULL: this application does not have a menu bar
// hInstance: the first parameter from WinMain
// NULL: not used in this application
HWND hWnd = CreateWindow(
    szWindowClass,
    szTitle,
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT,
    500, 100,
    NULL,
    NULL,
    hInstance,
    NULL
);
if (!hWnd)
{
    MessageBox(NULL,
        _T("Call to CreateWindow failed!"),
        _T("Win32 Guided Tour"),
        NULL);

    return 1;
}

This is how we make the window visible, and then update that window after it's changed.

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

Finally, we add functionality by giving the windows handlers for certain events. Ultimately, this just means stuffing all our functionality in a function with the following signature, typically called WndProc, which is called to respond to all events that happen to our window.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

Needless to say, programming at that level is a mess. But now that we understand the COM framework, we can better understand what actually happens at a lower level.

As a binary, an executable will be loaded into memory and run, usually performing some initial memory and resource allocations before showing itself in a window, and probably waiting a while for user input.

Your program will reference libraries. If the libraries were statically linked, it will jump to that part of the code, but if it's been dynamically linked, the OS has to find the appropriate Dynamically Linked Library (DLL) somewhere in its system and hunt down the right function at the right memory address to call! This process is called a symbol lookup, because libraries use symbol tables to efficiently organize their contents for quick access. Hence you'll often see people calling these squished function names stored in libraries symbols.

Linking in windows happens with the LoadLibrary system call is invoked with the name of some dll. GetProcAddress for the appropriate function. .lib files are static versions of windows libraries and are essentially wrappers for DLLs that combine both of these operations into one step.

Let's take a look at what libraries notepad.exe uses.




Using the illustrious dependency walker, we can take a look at which functions from a DLL are being imported into our code. Let's take a look at what's being imported from one of these DLLs.



From KERNEL32.dll, a somewhat important DLL, we import some functions key to the windows "Object Model", such as CloseHandle and GetCurrentProcess. Many libraries link other libraries, which means that even though you didn't explicitly dynamically import a library in your code, it might be required by yet another library! Searching and loading these libraries is done during runtime, when the first time one of its external functions is actually called.

The entire process, in C, looks like this.
   #include <windows.h>    //win32 API, I choose you!!
   BOOL freeResult, runTimeLinkSuccess = FALSE; 
   HINSTANCE dllHandle = NULL;              
   FindArtistType FindArtistPtr = NULL;

   //Load the dll and keep the handle to it
   dllHandle = LoadLibrary("welcome_to_hell.dll");

   // If the handle is valid, try to get the function address. 
   if (NULL != dllHandle) 
   { 
      //Get pointer to our function using GetProcAddress:
      FindArtistPtr = (FindArtistType)GetProcAddress(dllHandle,
         "FindArtist");
      // If the function address is valid, call the function. 
      if (runTimeLinkSuccess = (NULL != FindArtistPtr))
      {
         LPCTSTR myArtist = "Duchamp";
         short retVal = FindArtistPtr(myArtist);
      }
      //Free the library:
      freeResult = FreeLibrary(dllHandle);       
   } else {
      printf("Where do we go now?");
   }
The linker will search its path for the dll, and then scan it for the appropriate symbol. Thankfully, unless we're making our own framework, we generally won't need to explicitly use these system calls to load our functionality. Most of the time, it will all be hidden behind some library when we include its project headers. The win32 API, which is essentially the main API for windows programming, exposes the interface to all these methods in a relatively sane manner.


Now that we have a solid understanding of how a windows program runs, lets look at how the layers of functionality are hidden within the dll's themselves.

The windows architecture consists of the following components:
Bottom Level HAL.dll NTOSKRNL.exe
Middle Level NTDLL.dll WIN32K.sys
WIN32 API Level kernel32.dll advapi32.dll advapi32.dll gdi32.dll user32.dll

The name, NTOS kernel, stems from the history of windows kernels.

1.0 --> 2.0 --> 3.11 --> ... --> 9.x --> NT

NT kernel has been the basis of all windows since its debut with windows NT, receiving consistent updates. The OS knows this pupper as NTOSKRNL.exe, which it runs after the bootloader finishes its job.

NTOSKRNL.exe  relies on HAL.dll, which is the "Hardware Abstraction Layer", doing exactly what it says on the tin.

Here's a quick summary of some very important DLLs

NTDLL.dll : User-code functions like syscall stubs and run-time-library(RTL) code. This is where the infamous "undocumented API" is located, which is really just an internal API
WIN32K.sys : kernel-mode driver implements windowing and grapics
kernel32.dll : Systerm-level Win32 API functions, most are just wrappers around lower-level NTDLL functions, butt NLS & console handling aren't in NTDLL
advapi32.dll : System permissions functions. Stuff related to registry and services
gdi.dll : rudimentary drawing functions & bitmap manipulation, e.g. circles, rectangles, basic shapes
user32.dll : Implements many UI features. Programs, message-boxes, prompts, etc... It calls the syscalls implemented by WIN32K.sys


In closing, let's quickly recap the COM model:
You use the API to give windows pointers to your functions and windows will make calls into your program using the message-loop-architecture. basically calling your code when events need to be handled. In this COM framework all objects have "handled".

This is all important to know, very messy, and it's the reason we use frameworks and 4th generation languages.

No comments :

Post a Comment