Rubato and Chord

Reiley's technical blog

December, 2011

  • Rubato and Chord

    Process and Job Objects

    • 0 Comments

    Just like we mentioned in The Main Thread Problem, some questions do not have direct answer just because they are invalid by definition.

    Today, the invalid question would be:

    How do I kill a process tree in Windows?

    Unfortunately, the question is invalid, since Windows by design doesn't keep a tree of process creation relationship. Each process does have a parent process ID (except for the Windows Session Manager SMSS.exe), however this information is not going to change when the parent process got terminated.

    To verify this, simply run tlist.exe -t and see the rootless processes, at least the following processes don't have a parent on my Win7 machine:

    csrss.exe (536)
      conhost.exe (6036) CicMarshalWnd
      conhost.exe (8432) CicMarshalWnd
    winlogon.exe (672)
    explorer.exe (5640) Program Manager

    Now back to the question, probably the intention was to kill all processes spawned from or forked by a certain process. If this is the case, Job Object might help. But please be cautious:

    1. It is possible to use CreateProcess with CREATE_BREAKAWAY_FROM_JOB.
    2. One process can be assigned to only one job.
    3. Job objects cannot be nested.

    2 and 3 are subject to change in the near future.

    Another option I could think of is to hook process creations and maintain our own data structure.

    This could be done in either user mode or kernel mode, one possible approach could be:

    #include <ddk/ntddk.h>
    
    extern "C" DDKAPI NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath);
    static DDKAPI VOID CreateProcessNotifyRoutine(HANDLE hPPID, HANDLE hPID, BOOLEAN bCreate);
    static DDKAPI VOID DriverUnload(PDRIVER_OBJECT pDriverObject);
    
    DDKAPI NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
    {
      NTSTATUS retval;
      pDriverObject->DriverUnload = DriverUnload;
      retval = PsSetCreateProcessNotifyRoutine(CreateProcessNotifyRoutine, FALSE);
      return retval;
    }
    
    DDKAPI VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
    {
      PsSetCreateProcessNotifyRoutine(CreateProcessNotifyRoutine, TRUE);
    }
    
    DDKAPI VOID CreateProcessNotifyRoutine(HANDLE hPPID, HANDLE hPID, BOOLEAN bCreate)
    {
      DbgPrint("%s(PPID=%u, PID=%u)", bCreate ? "CreateProcess" : "TerminateProcess", hPPID, hPID);
    }
    

    I wouldn't recommend the driver way for several reasons:

    1. Modern Windows doesn't allow you to install an unsigned driver module.
    2. Antivirus software may complain since they are making use of similar approaches.
    3. It's easier to crash the whole system if you pay less attention (e.g. the sample code itself has a bug of race condition during unload, I'll leave this as a pop quiz to our readers).

     

  • Rubato and Chord

    CRT Startup

    • 0 Comments

    In my previous blog Early Debugging, we've demonstrated how early can you get using a user mode debugger.

    Normally we don't want to be such early, there are some other places we would want to start with:

    • OEP (Original Entry Point) of the EXE module. WinDBG has a predefined Pseudo-Register called $exentry which makes it a lot easier, as we already mentioned previously in Data Breakpoint.
    • The startup or initialization of runtime. I've covered the managed runtime startup in Yet Another Hello World.

    Now let's talk a bit about the native C/C++ Runtime. When you start writing applications using C/C++ on Windows, normally you would be using CRT already, unless you explicitly tell the linker not to use it, like what I did in A Debugging Approach to IFEO.

    The CRT (C Runtime Library) comes with Windows and Visual C++ Redistributable (let's not talk about the special version which serves CLR), also you can link a static version into your EXE/DLL.

    CRT provides the fundamental C++ runtime support, some obvious features are:

    • setup the C++ exception model
    • making sure the constructor of global variables get called before entering main function
    • parse command line arguments, and call the main function
    • initialize the heap
    • setup the atexit chain

    Let's get to the code:

    /* crtexport.cpp */
    
    #define WIN32_LEAN_AND_MEAN
    
    #include <Windows.h>
    
    class CFoobar
    {
    public:
      CFoobar()
      {
        OutputDebugString(TEXT("CFoobar::CFoobar()\n"));
      }
      ~CFoobar()
      {
        OutputDebugString(TEXT("CFoobar::~CFoobar()\n"));
      }
    };
    
    CFoobar g_foobar;
    
    __declspec(dllexport)
    BOOL WINAPI Foobar()
    {
      return TRUE;
    }
    
    BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvContext)
    {
      switch(fdwReason)
      {
      case DLL_PROCESS_ATTACH:
        OutputDebugString(TEXT("DLL_PROCESS_ATTACH\n"));
        break;
      case DLL_PROCESS_DETACH:
        OutputDebugString(TEXT("DLL_PROCESS_DETACH\n"));
        break;
      case DLL_THREAD_ATTACH:
        OutputDebugString(TEXT("DLL_THREAD_ATTACH\n"));
        break;
      case DLL_THREAD_DETACH:
        OutputDebugString(TEXT("DLL_THREAD_DETACH\n"));
        break;
      default:
        DebugBreak();
      }
      return TRUE;
    }
    

    Note: don't put DebugBreak inside DLL entry point as I do, unless you understand that the loader lock would make JIT debugger unhappy.

    /* crtimport.cpp */
    
    #define WIN32_LEAN_AND_MEAN
    
    #include <Windows.h>
    
    BOOL WINAPI Foobar();
    
    int main()
    {
      Foobar();
      return 0;
    }
    
    

    cl.exe /LD /Zi crtexport.cpp

    cl.exe /Zi crtimport.cpp crtexport.lib

    Set two breakpoints, one at DllMain and one at the main function, then launch the application in Visual Studio Debugger:

    Since our DLL is statically imported, the entry point of DLL is executed before the entry point of EXE.

    As you might have noticed, the actual OEP is _DllMainCRTStartup. You can double click on the crtexport.dll!_DllMainCRTStartup frame and bring up the CRT startup code to start reading - on my machine the startup code is located at C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\dllcrt0.c.

    Also, by taking a look at the Output window, we can see that CFoobar::CFoobar() has already been called, which means the global object was initialized before entering our DllMain. This is of course done by the CRT initialization code in __DllMainCRTStartup, which understands the contract between compiler and runtime.

    Now you understand how the constructor of global variables gets called, think about the destructor semantic:

    1. Is it possible that global variable got destructed in a different thread?
    2. What if there is an exception thrown from the global variable constructor/destructor invocation?

    The actual OEP for the EXE is __tmainCRTStartup. You can double click on the crtimport.exe!__tmainCRTStartup frame and take a look at the code - on my machine the startup code is located at C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\crt0.c.

    As we mentioned in The Main Thread Problem, __tmainCRTStartup runs in the "main thread", and would kill all the other threads before it is going to destroy the global variables. One thing to mention is that CRT makes use of _endthreadex instead of calling ExitThread directly, since _endthreadex would destruct objects constructed on the stack and free the related TLS data, while ExitThread knows nothing about the _tiddata block.

    A few more questions:

    1. What if different versions of CRT are loaded into a single process?
      1. mixing debug and release version of CRT
      2. mixing static and dynamic version of CRT
      3. mixing different major version of CRT
    2. What would happen if there is an exception thrown across module boundary (e.g. from a DLL function to the caller which belongs to EXE)?
    3. Can I use CRT functions without initializing CRT?

     

Page 1 of 1 (2 items)