Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Hacking with Windbg - Part 1 (Notepad)

4.83/5 (24 votes)
31 May 2023CPOL10 min read 31.5K  
Assembly inspection and hacking with windbg

Introduction

Debugging is a very important skill when it comes to software development and every software developer would have had times where they had to either debug something they wrote or something that was written by someone else. Depending on the platform you're working on, there are a bunch of tools that can help you with debugging with windbg being one of the most popular debugging tools for Windows. Windbg is something that I use heavily during my day to day work and I feel it's one of the most powerful tools ever written for Windows. There are many tutorials out there that give you an introduction to debugging with windbg, so this series will not focus on that. Instead, we will go through a few examples of how you can use windbg to do some reverse engineering and hacking and in the process, understand more about the different things you can do with windbg. While this particular part focuses on hacking Notepad through windbg, you can use similar techniques to hack something else. In future parts, I will show some advanced hacking and reverse engineering including some kernel mode examples.

Background

While I will try to go over every single debugging command used here, this tutorial doesn't aim to be a starting point for learning windbg or learning assembly. Some familiarity with x64 assembly and windows programming is assumed. I use the word "windbg" to refer to the GUI tool but the same commands apply even if you're using CDB or NTSD.

Hacking Notepad

In most of the windbg tutorials I have seen, Notepad seems to be the favorite demo application so for part 1, we will stick with that. The behavior we are trying to hack here is something you might have often seen and it annoys me a lot when I run into it. I saw a similar demo a couple of years back when someone else was doing it but I don't remember how they did it so I re-tried the same myself.

Let's say you open Notepad and then you click on "File->open" which opens up a dialog box to select the file you wish to open. You might have noticed while this dialog box is open, you cannot bring to the foreground the actual editing window. Clicking on the editing area throws an alert sound without bringing that window in foreground.

The rest of this tutorial will focus on how we can hack Notepad using windbg to change this behavior and actually keep switching back and forth between the two windows while both of them are open simultaneously.

The first step is to open Notepad from windbg so we can control and change its behavior. You can start windbg and then either choose File -> Open Executable -> <path to notepad.exe> or File->Attach to a process->notepad.exe if notepad.exe is already running.

The difference between these two options is in the first one (open executable), the debugger breaks in before the actual process starts executing its main routine. The debugger application basically calls CreateProcess API with a special flag that gives it control back after the application has been loaded in the memory but before it actually starts executing. In the second option (Attach to process), the debugger calls DebugActiveProcess API which attaches the debugger to the process which is already running and breaks-in.

How exactly the debugger communicates with the process being debugged is outside the scope of this tutorial but curious folks can read about it in the Advanced Windows Debugging book.

Once we have broken in, the next logical step is to first find out what is the function we want to hack. For example, what is the function that gets called when we click the open dialog box from the File Menu. For doing this, we will need symbols for notepad.exe and luckily, the Microsoft public server does have public symbols for it.

You can use the .symfix command to point the symbol path to the Microsoft public server and then do a .reload to actually load the public symbols.

0:011> .symfix
0:011> .reload

Without knowing the exact function name, we don't know what to search for but we can make an educated guess. If you were the developer writing the code for Notepad, what would you name the function that gets called when you click the open dialog box?. Well, I hope it atleast has the word open in it!

So using the x command, we can search for functions inside notepad.exe which has the word open in it. Here is what it looks like:

0:011> x notepad!*open*
00007ff7`0307182c notepad!ShowOpenSaveDialog (<no parameter info>)
00007ff7`03092310 notepad!<wbr />pszEDPFileOpenErrorHeader = <no type information>
00007ff7`0308a6f8 notepad!_imp_GetOpenFileNameW = <no type information>
00007ff7`030923d8 notepad!szOpenCaption = <no type information>
00007ff7`0308b190 notepad!CLSID_FileOpenDialog = <no type information>
00007ff7`03074860 notepad!NpOpenDialogHookProc (<no parameter info>)
00007ff7`0308a890 notepad!_imp_OpenSemaphoreW = <no type information>
00007ff7`0308aad0 notepad!_imp_OpenClipboard = <no type information>
00007ff7`0308ace0 notepad!_imp_OpenPrinterW = <no type information>
00007ff7`030842d4 notepad!TraceFileOpenComplete (<no parameter info>)
00007ff7`03094ac0 notepad!szOpenFilterSpec = <no type information>
00007ff7`0308a688 notepad!_imp_RegOpenKeyExW = <no type information>
00007ff7`03092300 notepad!g_ftOpenedAs = <no type information>
00007ff7`0307199c notepad!InvokeOpenDialog (<no parameter info>)
00007ff7`03093438 notepad!g_isFileDragOpen = <no type information>
00007ff7`03092308 notepad!pszEDPFileOpenError = <no type information>
00007ff7`0308a650 notepad!_imp_OpenProcessToken = <no type information>

The first function highlighted here in bold, ShowOpenSaveDialog, looks exactly like something we are looking for!

Let's see if this is indeed the one that gets called when we click open. In order to verify this, we will put a breakpoint on it.

0:011> bp notepad!ShowOpenSaveDialog
0:011> g

The first command bp is actually putting the software breakpoint on our function (int 3) and the next command g, which stands for go, releases the debugger control and Notepad starts executing again.

We now click on File->Open and you should see Notepad freeze when you do this. The debugger will have broken-in saying it hit our breakpoint and we can check the call stack at this time.

Breakpoint 0 hit
notepad!ShowOpenSaveDialog:
00007ff7`0307182c 48895c2408      mov     qword ptr [rsp+8],
           rbx ss:00000073`74d2f310=<wbr />0000000000000000
0:000> k
 # Child-SP          RetAddr           Call Site
00 00000073`74d2f308 00007ff7`03071aeb notepad!ShowOpenSaveDialog
01 00000073`74d2f310 00007ff7`030721fa notepad!InvokeOpenDialog+0x14f
02 00000073`74d2f370 00007ff7`030738d6 notepad!NPCommand+0x4a2
03 00000073`74d2f6f0 00007fff`664b6d41 notepad!NPWndProc+0x726
04 00000073`74d2f9f0 00007fff`664b6713 USER32!<wbr />UserCallWinProcCheckWow+0x2c1
05 00000073`74d2fb80 00007ff7`03073bdb USER32!DispatchMessageWorker+<wbr />0x1c3
06 00000073`74d2fc10 00007ff7`03089333 notepad!WinMain+0x27f
07 00000073`74d2fd10 00007fff`68ea3034 notepad!__mainCRTStartup+0x19f
08 00000073`74d2fdd0 00007fff`69073691 KERNEL32!BaseThreadInitThunk+<wbr />0x14
09 00000073`74d2fe00 00000000`00000000 ntdll!RtlUserThreadStart+0x21

Great! We found the function of interest relatively easily, but now what? Once we have this, what do we do with it?

If you're familiar with Windows programming, you will know that Windows heavily uses the concept of objects and handles. Whenever you want to use an object, you always get a handle to it. If we think of the open dialog box and the editor area as two separate "window", then they would be two objects. Since we can't bring the editor window in the foreground till this dialogbox is open, that also suggests a parent-child relationship. At this stage, all this is speculation since we don't have the source code for Notepad but if our speculation is true, then this function will somehow know that its parent window is the editor window.

So based on this logic, my guess here is that this function takes a handle to the parent window as an argument to establish this relationship.

Without the private symbols, you can't dump local variables or parameters passed using the dv command but if you know your Microsoft x64 calling convention (https://msdn.microsoft.com/en-us/library/ms235286.aspx), you would know that the first 4 parameters are passed in the rcx, rdx, r8 and r9 registers. So let's dump these using the r command.

0:000> r
rax=0000000000000000 rbx=0000000000000000 rcx=000000000004094e
rdx=00000213f5020968 rsi=000000499cb1f338 rdi=00000213f5021c20
rip=00007ff70307182c rsp=000000499cb1f288 rbp=000000499cb1f2d0
 r8=00000213f4ffe9d6  r9=000000499cb1f338 r10=00000ffee060e246
r11=0000000000014140 r12=0000000000000000 r13=0000000000000001
r14=000000000004094e r15=00000000ffffffff
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
notepad!ShowOpenSaveDialog:
00007ff7`0307182c 48895c2408      mov     qword ptr [rsp+8],
rbx ss:00000049`9cb1f290=<wbr />0000000000000000

I have highlighted the 4 registers in the output above. If you have used Window objects before which are represented by HWND (Handle to Window) and looked at the values it can have, you would instantly notice that only the rcx register has something that even remotely resembles a HWND.

But for the sake of completeness, let's assume you don't know what a handle value is actually supposed to look like.

To check this, we can close this instance of Notepad, and then open windbg again and this time, choose Open executable option pointing to the Notepad path. We want to see what's the HWND value that gets returned when we actually create a window in Notepad. So to do this, we will again use our friend x to search for functions that might resemble creating a window.

0:000> x notepad!*create*window*
00007ff7`0308a6c8 notepad!_imp_<wbr />CreateStatusWindowW = <no type information>
00007ff7`0308ab78 notepad!_imp_CreateWindowExW = <no type information>

_imp_ represents that the actual function is inside the address of this _imp_CreateStatusWindowW. So we can dump the pointer value inside _imp_CreateStatusWindowW and unassemble it to get the actual function.

0:000> x notepad!*create*window*
00007ff7`0308a6c8 notepad!_imp_<wbr />CreateStatusWindowW = <no type information>
00007ff7`0308ab78 notepad!_imp_CreateWindowExW = <no type information>
0:000> dq 00007ff7`0308ab78
00007ff7`0308ab78  00007fff`664a4710 00007fff`664c5780
00007ff7`0308ab88  00007fff`664ae980 00007fff`664a4660
00007ff7`0308ab98  00007fff`664b8e80 00007fff`664b5710
00007ff7`0308aba8  00007fff`664b8540 00007fff`664b3f10
00007ff7`0308abb8  00007fff`664c7c20 00007fff`664a26e0
00007ff7`0308abc8  00007fff`664a8800 00007fff`664c8410
00007ff7`0308abd8  00007fff`664d06c0 00007fff`664d0b20
00007ff7`0308abe8  00007fff`664c1500 00007fff`664cffb0
0:000> u 00007fff`664a4710
USER32!CreateWindowExW:
00007fff`664a4710 4c8bdc          mov     r11,rsp
00007fff`664a4713 4881ec88000000  sub     rsp,88h
00007fff`664a471a 33c0            xor     eax,eax
00007fff`664a471c 6689442478      mov     word ptr [rsp+78h],ax
00007fff`664a4721 89442470        mov     dword ptr [rsp+70h],eax
00007fff`664a4725 c744246800000040 mov     dword ptr [rsp+68h],40000000h
00007fff`664a472d 89442460        mov     dword ptr [rsp+60h],eax
00007fff`664a4731 488b8424e8000000 mov     rax,qword ptr [rsp+0E8h]

If you check MSDN for CreateWindowExW, you will see that it returns HWND, just what we are looking for! So let's put a breakpoint on this function and see what is the return value we get. Once we hit our breakpoint, we need to let this function execute and return back to the caller so we can use gu to go back one level in the stack to the function which actually called our function and then check the return value at this stage.

The return value itself is stored in the rax register which again is part of the calling convention.

0:000> g
ModLoad: 00007fff`66f10000 00007fff`66f3d000   C:\WINDOWS\System32\IMM32.DLL
Breakpoint 0 hit
USER32!CreateWindowExW:
00007fff`664a4710 4c8bdc          mov     r11,rsp
0:000> k
 # Child-SP          RetAddr           Call Site
00 00000083`2f57f958 00007fff`6670afbe USER32!CreateWindowExW
01 00000083`2f57f960 00007fff`666cefb6 combase!InitMainThreadWnd+0x56
02 00000083`2f57f9d0 00007fff`666ce004 combase!ThreadFirstInitialize+<wbr />0x12a
03 00000083`2f57fa30 00007fff`666cecd6 combase!_CoInitializeEx+0x158 
04 00000083`2f57fb30 00007ff7`03073ab1 combase!CoInitializeEx+0x36 
05 00000083`2f57fb80 00007ff7`03089333 notepad!WinMain+0x155
06 00000083`2f57fc80 00007fff`68ea3034 notepad!__mainCRTStartup+0x19f
07 00000083`2f57fd40 00007fff`69073691 KERNEL32!BaseThreadInitThunk+<wbr />0x14
08 00000083`2f57fd70 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:000> gu
combase!InitMainThreadWnd+<wbr />0x56:
00007fff`6670afbe 48890563af2000  mov     qword ptr [combase!ghwndOleMainThread (00007fff`66915f28)],
rax ds:00007fff`66915f28=<wbr />0000000000000000
0:000> r@rax
rax=00000000000d071c

As we see, the highlighted value represents an HWND value and from the previous dump of the ShowOpenSaveDialog routine, we can verify that only rcx indeed resembles something like an HWND value.

So how do we break this parent-child relationship between the two window objects? One easy way would be just clear the rcx register so the HWND value is 0. Of course, this may lead to a crash or not, but we don't have anything better to try at this stage.

Here is how we can overwrite the register and confirm it is actually 0.

0:000> r @rcx=0
0:000> r
rax=0000000000000000 rbx=0000000000000000 rcx=0000000000000000
rdx=00000213f5020968 rsi=000000499cb1f338 rdi=00000213f5021c20
rip=00007ff70307182c rsp=000000499cb1f288 rbp=000000499cb1f2d0
 r8=00000213f4ffe9d6  r9=000000499cb1f338 r10=00000ffee060e246
r11=0000000000014140 r12=0000000000000000 r13=0000000000000001
r14=000000000004094e r15=00000000ffffffff
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
notepad!ShowOpenSaveDialog:
00007ff7`0307182c 48895c2408      mov     qword ptr [rsp+8],
rbx ss:00000049`9cb1f290=<wbr />0000000000000000

We let the program resume now by hitting g and now if you click on the editor window while this dialog box is open, voila! It worked! Assuming you followed the steps listed here properly, you should be able to switch back and forth between the two windows now.

But wait! We don't want to keep setting this breakpoint everytime and changing the value of rcx everytime we open the dialogbox. Can we change the code to do this automatically for us from the debugger? You bet we can.

So for this, we need to make sure when the caller calls ShowOpenSaveDialog, it zeroes out the rcx register. The caller in this case is the InvokeOpenDialog function. Let's look at the assembly code just before it calls ShowOpenSaveDialog. You can use the ub command for this which unassembles backwards from a given address.

0:000> k
 # Child-SP          RetAddr           Call Site
00 00000049`9cb1f288 00007ff7`03071aeb notepad!ShowOpenSaveDialog
01 00000049`9cb1f290 00007ff7`030721fa notepad!InvokeOpenDialog+0x14f
02 00000049`9cb1f2f0 00007ff7`030738d6 notepad!NPCommand+0x4a2
03 00000049`9cb1f670 00007fff`664b6d41 notepad!NPWndProc+0x726
04 00000049`9cb1f970 00007fff`664b6713 USER32!<wbr />UserCallWinProcCheckWow+0x2c1
05 00000049`9cb1fb00 00007ff7`03073bdb USER32!DispatchMessageWorker+<wbr />0x1c3
06 00000049`9cb1fb90 00007ff7`03089333 notepad!WinMain+0x27f
07 00000049`9cb1fc90 00007fff`68ea3034 notepad!__mainCRTStartup+0x19f
08 00000049`9cb1fd50 00007fff`69073691 KERNEL32!BaseThreadInitThunk+<wbr />0x14
09 00000049`9cb1fd80 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:000> ub notepad!InvokeOpenDialog+0x14f
notepad!InvokeOpenDialog+<wbr />0x133:
00007ff7`03071acf 8bd8            mov     ebx,eax
00007ff7`03071ad1 85c0            test    eax,eax
00007ff7`03071ad3 782c            js      notepad!InvokeOpenDialog+<wbr />0x165 (00007ff7`03071b01)
00007ff7`03071ad5 4c8b05fc080200  mov     r8,qword ptr [notepad!szOpenCaption (00007ff7`030923d8)]
00007ff7`03071adc 4c8bce          mov     r9,rsi
00007ff7`03071adf 488b5538        mov     rdx,qword ptr [rbp+38h]
00007ff7`03071ae3 498bce          mov     rcx,r14
00007ff7`03071ae6 e841fdffff      call    notepad!ShowOpenSaveDialog (00007ff7`0307182c)

We see that rcx is getting the value from r14 which is highlighted above. What if instead of this mov instruction, we had something like xor ecx,ecx?. We just need to zero out the ecx register since HWND is a type of HANDLE datatype which are 32 bit values in practice even though it is defined as a PVOID. So essentially, we need something like this:

ASM
mov rdx,qword ptr [rbp+38h]

xor ecx,ecx

call notepad!ShowOpenSaveDialog

We can use the a (assemble) command in windbg to achieve this.

0:000> a 00007ff7`03071ae3
00007ff7`03071ae3 xor ecx,ecx
xor ecx,ecx
00007ff7`03071ae5 nop
nop
00007ff7`03071ae6 

0:000> ub notepad!InvokeOpenDialog+0x14f
notepad!InvokeOpenDialog+<wbr />0x135:
00007ff7`03071ad1 85c0            test    eax,eax
00007ff7`03071ad3 782c            js      notepad!InvokeOpenDialog+<wbr />0x165 (00007ff7`03071b01)
00007ff7`03071ad5 4c8b05fc080200  mov     r8,qword ptr [notepad!szOpenCaption (00007ff7`030923d8)]
00007ff7`03071adc 4c8bce          mov     r9,rsi
00007ff7`03071adf 488b5538        mov     rdx,qword ptr [rbp+38h]
00007ff7`03071ae3 31c9            xor     ecx,ecx
00007ff7`03071ae5 90              nop
00007ff7`03071ae6 e841fdffff      call    notepad!ShowOpenSaveDialog (00007ff7`0307182c)

When using the a command, we must take care that the next instruction that follows the one which we are modifying, starts at the original offset. For example, the mov instruction before started at ae3 offset and the call instruction was at ae6. When we assemble xor ecx,ecx at ae3, it is a 2 byte instruction so we need to pad another 1 byte instruction at ae5. For this, we just use the nop instruction which doesn't really do anything.

Now using the ub command again, we see that our code is successfully modified with what we need. We can now disable all the breakpoints and resume Notepad. Since we didn't change rcx this time in the current invocation, we will not be able to switch back and forth between the windows yet. We can close this open dialog box this time and from the next subsequent times whenever we open it, we should be able to switch back and forth between the two windows now.

You should note that this code modification is done in the memory. So the actual binary doesn't get affected. Whenever you close Notepad and restart it, this hack won't be there so you will have to do it again if you wish to.

Conclusion

So here, we saw how we can use windbg to understand how the program execution works, search for functions inside an application and even change the behavior of an application whose source code we don't have! Hopefully, this gives you a greater sense of appreciation for windbg to see the different things you can do with it. In the next part, we will pick another application and do some more advanced hacking.

History

  • 10th February, 2019: Initial version

Click this link to read the next article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)