Writing your own windows debugger in C

Hello all!

I’m cracking away on various projects and trying to keep focus. As I was going through my old notes, I came across a talk I wanted to give but could not due to my car accident and the subsequent down time caused me to forget. I wanted to cover making your own debugger in windows, but I never completed the project. I had dumping, I had a disassembler, etc, but it never panned out. It was more of a learning experience.

None the less, I will share with you all some snippets I was able to piece together. We’ll go over the basics of “Break & Enter”. There are a few steps involved, but its pretty easy. By break and enter, I mean open a process / thread and break-point on the current instruction, sort of like how you would do when you attach to a process in Immunity / Olly / WinDBG / IDA. Luckily for me, windows provides the means to do so internally and we need only invoke these functions to do our own low level debugging. Disassebmly and dumping however is not supported internally on windows, so we would have to use a 3rd party library or write our own functionality if we wanted to do this. Debugging on the other hand is easy. Perhaps in the future I’ll finish my debugger when time is more permissive and I have more interest.

These are the steps:

Step 1 – get process id – can be done programatically with win32 snapshots / psapi or with task manager

Step 2 – get handle to said process id – OpenProcess() win32 api

Step 3 – getthreadcontenxt and store in structure- Windows api which stores the registers (eax, esi, etc)

Step 4 – Enable debug privileges and suspend the thread so we can mess with it.

Step 5 – call the DebugBreakProcess() function which writes an int3 call into the EIP for the process.

Step 6 – Handle the debug loop. The debug loop is responsible for handling debug events such as single step interrupts, access violations, thread / process creation and the like.

The code is below and I’ve tried my best to comment it and add the appropriate links to MSDN.

DWORD pid = 2182; // PID grabbed from task manager

HANDLE myproc = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION ,TRUE,pid); // open it, may have to do debug for the main loop


void BreakAndEnter(HANDLE proc)
{
    CONTEXT tcx;
    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms679297%28v=vs.85%29.aspx
    // there are 2 ways to do this
    // 1) WriteProcessMemory() and pass he int3 opcode to the stack of the currently executing process
    // 2) DebugBreakProcess() function which does the same thing essentially
    // but first, you NEED to set the debug privilege
    EnableDebugPriv(proc);
    SuspendThread(proc);
    // You need to suspend the thread b4 calling getthreadcontext
    GetThreadContext(proc,&tcx);
    printf("Register info for debugged process: \r\n");
    printf("EAX: %d\r\n",tcx.Eax);
    printf("EBX: %d\r\n",tcx.Ebx);
    printf("ECX: %d\r\n",tcx.Ecx);
    printf("EDX: %d\r\n",tcx.Edx);
    printf("ESI: %d\r\n",tcx.Esi);
    printf("ESP: %d\r\n",tcx.Esp);
    printf("EIP: %d\r\n",tcx.Eip);
    DebugBreakProcess(proc);
    // after calling into this, we need to tell our remote thread to continue
    ContinueDebugEvent();
    // Our debug debug loop will handle any and all exceptions
    system("pause");
    EnterDebugLoop();
    //Applications should call FlushInstructionCache if they generate or modify code in memory. The CPU cannot detect the change, and may execute the old code it cached.
   // FlushInstructionCache(proc,baseaddr,sizeofregion);
    // After all this is called, we can handle our debug events
    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms679302%28v=vs.85%29.aspx


}

BOOL EnableDebugPriv(HANDLE proc)
{
	    HANDLE hToken;
	    LUID sedebugnameValue;
	    TOKEN_PRIVILEGES tkp;
	    // pass our opened process handle
	    OpenProcessToken(proc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
        LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &sedebugnameValue);
	    tkp.PrivilegeCount = 1;
	    tkp.Privileges[0].Luid = sedebugnameValue;
	    tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
	    if(AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof tkp, NULL, NULL))
	    {
        CloseHandle(hToken);
	    return TRUE;
	    }
	    else
	    {
	        MessageBox(NULL,"Bro, you gotta be an admin to set privs like this", "Shit", MB_OK);
	        CloseHandle(hToken);
	        return FALSE;
	    }


}

void EnterDebugLoop(const LPDEBUG_EVENT DebugEv)
{
   DWORD dwContinueStatus = DBG_CONTINUE; // exception continuation

   for(;;)
   {
   // Wait for a debugging event to occur. The second parameter indicates
   // that the function does not return until a debugging event occurs.

      WaitForDebugEvent(DebugEv, INFINITE);

   // Process the debugging event code.

      switch (DebugEv->dwDebugEventCode)
      {
         case EXCEPTION_DEBUG_EVENT:
         // Process the exception code. When handling
         // exceptions, remember to set the continuation
         // status parameter (dwContinueStatus). This value
         // is used by the ContinueDebugEvent function.

            switch(DebugEv->u.Exception.ExceptionRecord.ExceptionCode)
            {
               case EXCEPTION_ACCESS_VIOLATION:
               // First chance: Pass this on to the system.
               // Last chance: Display an appropriate error.
                  break;

               case EXCEPTION_BREAKPOINT:
               // First chance: Display the current
               // instruction and register values.
                  break;

               case EXCEPTION_DATATYPE_MISALIGNMENT:
               // First chance: Pass this on to the system.
               // Last chance: Display an appropriate error.
                  break;

               case EXCEPTION_SINGLE_STEP:
               // First chance: Update the display of the
               // current instruction and register values.
                  break;

               case DBG_CONTROL_C:
               // First chance: Pass this on to the system.
               // Last chance: Display an appropriate error.
                  break;

               default:
               // Handle other exceptions.
                  break;
            }

            break;

         case CREATE_THREAD_DEBUG_EVENT:
         // As needed, examine or change the thread's registers
         // with the GetThreadContext and SetThreadContext functions;
         // and suspend and resume thread execution with the
         // SuspendThread and ResumeThread functions.

            dwContinueStatus = OnCreateThreadDebugEvent(DebugEv);

            break;

         case CREATE_PROCESS_DEBUG_EVENT:
         // As needed, examine or change the registers of the
         // process's initial thread with the GetThreadContext and
         // SetThreadContext functions; read from and write to the
         // process's virtual memory with the ReadProcessMemory and
         // WriteProcessMemory functions; and suspend and resume
         // thread execution with the SuspendThread and ResumeThread
         // functions. Be sure to close the handle to the process image
         // file with CloseHandle.

            dwContinueStatus = OnCreateProcessDebugEvent(DebugEv);
            break;

         case EXIT_THREAD_DEBUG_EVENT:
         // Display the thread's exit code.

            dwContinueStatus = OnExitThreadDebugEvent(DebugEv);
            break;

         case EXIT_PROCESS_DEBUG_EVENT:
         // Display the process's exit code.

            dwContinueStatus = OnExitProcessDebugEvent(DebugEv);
            break;

         case LOAD_DLL_DEBUG_EVENT:
         // Read the debugging information included in the newly
         // loaded DLL. Be sure to close the handle to the loaded DLL
         // with CloseHandle.

         //   dwContinueStatus = OnLoadDllDebugEvent(DebugEv); busted
            break;

         case UNLOAD_DLL_DEBUG_EVENT:
         // Display a message that the DLL has been unloaded.

//            dwContinueStatus = OnUnloadDllDebugEvent(DebugEv); busted
            break;

         case OUTPUT_DEBUG_STRING_EVENT:
         // Display the output debugging string. doesnt fucking work

//            dwContinueStatus = OnOutputDebugStringEvent(DebugEv);
            break;

         case RIP_EVENT:
//            dwContinueStatus = OnRipEvent(DebugEv); doesnt fucking work

         //   http://msdn.microsoft.com/en-us/library/windows/desktop/ms681675%28v=vs.85%29.aspx
          //  http://support.microsoft.com/kb/121093
            break;
      }

   // Resume executing the thread that reported the debugging event.

   ContinueDebugEvent(DebugEv->dwProcessId,DebugEv->dwThreadId,dwContinueStatus);
   }
}

Happy hacking!
k9AnYwF

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.