Code for Concinnity


Tutorial: Using WinDBG to bypass specific functions — WinDBG kung-fu series

Continuing our series, this time we’ll try to use WinDBG to bypass certain functions at runtime. In this example, we’ll try to use WinDBG to ignore assert errors. Example program

#include <cassert>
#include <iostream>
 
int main()
{
    int x = 10 - 10;
    assert(x);
 
    std::cout << "hello world" << std::endl;
 
    return 0;
}

Objective

The assertion dialog box should not pop up, and the console should show “hello world”. Here we go

> cl /EHsc /Zi /Fefred.exe /Fdfred.pdb fred.cpp > windbg fred.exe

First we need to set a breakpoint at the function we want to bypass. We’ve located the function name to be MSVCR100D!_wassert. You can find this out by looking at the call stack k

0:000> bp MSVCR100D!_wassert 0:000> g

Now our breakpoint’s been hit, let’s look at what we’ve got on the stack:

0:000> kp1 ChildEBP RetAddr
0028f754 002a14d6 MSVCR100D!_wassert(wchar_t * expr = 0x002a7840 "x", wchar_t * filename = 0x002a7848 "k:\vsprojects\fred\fred\fred.cpp", unsigned int lineno = 7) [f:\dd\vctools\crt_bld\self_x86\crt\src\assert.c @ 113]

Nice, let’s try printing out the arguments

0:000> .printf "assertion error: %mu, %mu, %N", @@(expr), @@(filename), @@(lineno) assertion error: x, k:\vsprojects\fred\fred\fred.cpp, 00000007

Now how do we go about “bypassing” this function? There are basically 2 ways to do it:

1. Jumping to the end of the function, i.e. the `ret` instruction.
2. Popping the stack manually and jumping back to the return address.

You can choose whichever method you want depending on what information you have. #1 requires you to know the exact offset of the ret instruction; #2 requires you to know the calling convention. Neither is much more robust than the other, but #2 requires a little more assembly knowledge. So we’ll do the easier approach with #1 here:

Now, let’s get a disassembly by u:

0:000> u MSVCR100D!_wassert+0x2d [f:\dd\vctools\crt_bld\self_x86\crt\src\assert.c @ 121]: 60494afd 83c404 add esp,4 60494b00 85c0 test eax,eax 60494b02 0f85fc000000 jne MSVCR100D!_wassert+0x134 (60494c04) 60494b08 833df01d4e6001 cmp dword ptr [MSVCR100D!__app_type (604e1df0)],1 60494b0f 0f85ef000000 jne MSVCR100D!_wassert+0x134 (60494c04) 60494b15 6af4 push 0FFFFFFF4h 60494b17 ff152c103860 call dword ptr [MSVCR100D!_imp__GetStdHandle (6038102c)] 60494b1d 8985b8fbffff mov dword ptr [ebp-448h],eax

Arg… We need to look further down. Let’s use a real text editor for the job.

0:000> .shell -ci "u eip eip+0x1000" gvim - 0:000> * If you don't have vi installed, you're doomed. Too bad.

In my case, I found the return address here:

MSVCR100D!_wassert [f:\dd\vctools\crt_bld\self_x86\crt\src\assert.c @ 113]: 60494ad0 8bff mov edi,edi 60494ad2 55 push ebp ..... 60495656 c3 ret

So 0x60495656 - 0x60494ad0 = 0xb86. We’ve got our offset. All we need to do is to rig eip to point the that instruction:

0:000> r eip = eip+0xb86 0:000> g

The thing should have run and we’ve been greeting with a nice “hello world.”

To sum it up, we could have converted the assertion error dialog box to console output with a one liner:

bp MSVCR100D!_wassert ".printf \"assertion error: %mu, %mu, %N\", @@(expr), @@(filename), @@(lineno); .echo; r eip = eip+0xb86; g"

The above would print “assertion error” message in the debug output and continue as if nothing happened.

Published by kizzx2, on November 17th, 2010 at 11:27 pm. Filled under: UncategorizedNo Comments

No comments yet.

Leave a Reply