Click here to Skip to main content
15,860,943 members
Articles / Programming Languages / C++

Extending windbg with Page Fault Breakpoints

Rate me:
Please Sign up or sign in to vote.
4.93/5 (16 votes)
1 May 2011Ms-PL6 min read 77.9K   1.8K   43   19
Take advantage of the memory page access flag, and set a new kind of breakpoint
Screenshot from windbg

Introduction

Have you ever needed to set a breakpoint on an entire module or memory region?

This is particularly useful if you want break on code execution in a module without specifying any function names directly. Let's say that between point A and point B, some code calls into module C, and corrupts its data.

In Visual Studio, you can set data breakpoints, which will break the debugger when a value changes. Very useful indeed, but you cannot break on a read access.

In the powerful Windbg, we are equipped with hardware breakpoints via the "ba" command (break on access), which works more or less the same as the page fault breakpoint I will explain. It has unfortunately a very strict limitation regarding the size. According to the documentation, the size can only be 1, 2 or 4 bytes, except when it concerns PAGE_EXECUTE, in that case the maximum size is 1 byte. This makes it harder to break over a large memory area.

In its strict sense, we are not going to set a breakpoint, but what we do can be used as a breakpoint, since we are making the debugger break on certain conditions which we can control.

What we will do is to change the access flag of memory pages, from PAGE_EXECUTE_* into PAGE_NOACCESS or PAGE_READONLY. When a function call is made into non-executable pages, it results in an access violation, which will break the debugger. At that point, you can inspect the callstack, variables, and memory regions. If you want to continue executing, you simply restore the access flag to its previous value, and tell the debugger to continue executing.

The same principle can be applied for data. Normally, memory pages where data is stored are marked PAGE_READWRITE. If you suspect memory corruption, you can simply mark the pages PAGE_READONLY, so whenever someone tries to modify the data, you will get an access violation.

Background

There exists a debugger called Ollydbg which is an exceptional tool for doing reverse-engineering, where this functionality is already built-in.

Screenshot from Ollydbg

Unfortunately, Ollydbg doesn't work well in Windows 7. So I switched to using Windbg, but I noticed that Windbg was missing this functionality. What could I do about it? Well, I had to write a Windbg extension implementing the same functionality.

How It Works

In order to implement memory protection and virtual memory, modern operating systems are organised around memory pages. Protection is implemented by marking these pages with an access flag, e.g. PAGE_READONLY or PAGE_EXECUTE. Whenever the protection is violated, the program will halt (access violation).

The page size is the same for all pages, and is typically of a size between 4-32 kb, a 1 MB program is fitted into 256 4KB pages.

A program consists of several segments or sections:

  • A .rdata section, where typically constants are stored. PAGE_READONLY
  • A .data section, where your variables are stored. PAGE_READWRITE
  • A .text section, where the executable code is stored. PAGE_EXECUTE

Normally, you cannot modify the .text section, execute code in the .data section or modify constants in the .rdata section. The page access flag doesn't allow you. Any attempts will result in an access violation. But by adding and removing permissions on memory pages, we can create "breakpoints".

The functions we will use to change permissions are all part of the Windows API.

C++
BOOL WINAPI VirtualProtect(
  __in   LPVOID lpAddress,
  __in   SIZE_T dwSize,
  __in   DWORD flNewProtect,
  __out  PDWORD lpflOldProtect
);

BOOL WINAPI VirtualProtectEx(
  __in   HANDLE hProcess,
  __in   LPVOID lpAddress,
  __in   SIZE_T dwSize,
  __in   DWORD flNewProtect,
  __out  PDWORD lpflOldProtect
);

SIZE_T WINAPI VirtualQuery(
  __in_opt  LPCVOID lpAddress,
  __out     PMEMORY_BASIC_INFORMATION lpBuffer,
  __in      SIZE_T dwLength
);

SIZE_T WINAPI VirtualQueryEx(
  __in      HANDLE hProcess,
  __in_opt  LPCVOID lpAddress,
  __out     PMEMORY_BASIC_INFORMATION lpBuffer,
  __in      SIZE_T dwLength
);

Protection flags:
  PAGE_EXECUTE = 0x10
  PAGE_EXECUTE_READ = 0x20
  PAGE_EXECUTE_READWRITE = 0x40
  PAGE_EXECUTE_WRITECOPY = 0x80
  PAGE_NOACCESS = 0x01
  PAGE_READONLY = 0x02
  PAGE_READWRITE = 0x04
  PAGE_WRITECOPY = 0x08		

Using the Code

Below is some pseudo code for changing the access flag from C/C++:

C++
DWORD oldProtection;
DWORD newProtection = 1; // PAGE_NOACCESS
// The call changes all pages that are affected by the size argument.
// oldProtection only contains the protection mask of the first page.
// So be careful :)
VirtualProtect(address, size, newProtection, &oldProtection);

// To restore the protection:
VirtualProtect(address, size, oldProtection, &oldProtection);

Since a Windbg extension is running inside the process of Windbg, and not in the process I would like to modify. I had to use VirtualProtectEx, which takes as its first argument, a process handle. We obtain a process handle by calling OpenProcess on the pid (process id) of the debugging target.

C++
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,pId);
BOOL result = VirtualProtectEx
	(hProcess, address, size, newProtection, &oldProtection);
CloseHandle(hProcess);

Windbg Extension

I have made a windbg extension that implements the functions Protect and MemInfo.

  • Protect address size protection - This function changes the protection flag of memory page(s).
  • MemInfo address - This function displays information about a memory page, e.g. the current protection flag.

Using the Extension

Start by copying the extension to the Windbg extension folder.

On my machine, it is C:\Program Files (x86)\Debugging Tools for Windows (x86)\winext.

In the following scenario, we will break on code execution.

Step by step instructions:

  • Locate the baseaddress of the module by running the command "lm"
  • Use the obtained baseaddress, and execute the command "!dh baseaddress"
  • Locate the section named .text in the output of the previous command
  • Find the virtual address
  • Find the virtual size
  • !Protect baseaddress+virtualaddress virtualsize 1 would mark the page as non-accessible.

Step by Step Example in windbg

We need to load the extension.

0:000> .load debugext.dll

Then we run the command "lm" to get the list of loaded modules.

0:000> lm
start    end        module name
00dd0000 00dd6000   PFDebug    (private pdb symbols)
6e460000 6e503000   MSVCR90    (deferred)             
6eb00000 6eb8e000   MSVCP90    (deferred)             
6eb90000 6eb96000   BuggyLib   (deferred)             
751a0000 751e6000   KERNELBASE   (deferred)             
76660000 76760000   kernel32   (deferred)             
77440000 775c0000   ntdll      (export symbols)

We obtained the start and end address of the modules. We are interested in the start address of module BuggyLib, which is 6eb90000.

Let's find the relative address of the code section and its size. We do this by looking in the PE header. All executables and DLLs have a PE header. Basically, it tells Windows how to load the module into memory. Among other things, it contains the addresses and sizes of the .text, .data, .rdata sections.

The !dh command displays the PE Header:

0:000> !dh 6eb90000
..... cut down for readability .....
SECTION HEADER #2
   .text name
    960 virtual size
   1000 virtual address
     A00 size of raw data
     400 file pointer to raw data
       0 file pointer to relocation table
       0 file pointer to line numbers
       0 number of relocations
       0 number of line numbers
60000020 flags
         Code
         (no align specified)
         Execute Read
....

We obtained the virtual size 0x960, the virtual address 0x1000, and the protection is Execute Read, but let's double-check that by calling MemInfo:

0:000> !MemInfo 6eb90000+1000
MEMORY_BASIC_INFORMATION
  BaseAddress = 0x6EB91000
  AllocationBase = 0x6EB90000
  AllocationProtect = 0x80
  RegionSize = 0x1000 (4096)
  State = 0x1000
  Protect = 0x20
  Type = 0x1000000

Protect flag is 0x20 (PAGE_EXECUTE_READ), which seems correct.

Let's change the code section to PAGE_NOACCESS:

0:000> !Protect 6eb90000+1000 960 1
New protection (1)
Old protection (20)

Let's continue execution until our breakpoint is hit.

0:000> g
(db4.ad0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0031fc68 ebx=00000000 ecx=6e4fb6f8 edx=00000000 esi=00000001 edi=00dd3384
eip=6eb91000 esp=0031fc40 ebp=0031fcb8 iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010286
BuggyLib!SetLimits:
6eb91000 ??              ???

Bam! We hit our breakpoint. Let us now restore the flags and do some single stepping:

0:000> !Protect 6eb90000+1000 960 20

New protection (20)
Old protection (1)
0:000> p
(db4.ad0): Access violation - code c0000005 (!!! second chance !!!)
eax=0031fc68 ebx=00000000 ecx=6e4fb6f8 edx=00000000 esi=00000001 edi=00dd3384
eip=6eb91000 esp=0031fc40 ebp=0031fcb8 iopl=0         nv up ei ng nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010286
BuggyLib!SetLimits:
6eb91000 33c0            xor     eax,eax
0:000> p
eax=00000000 ebx=00000000 ecx=6e4fb6f8 edx=00000000 esi=00000001 edi=00dd3384
eip=6eb91002 esp=0031fc40 ebp=0031fcb8 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
BuggyLib!SetLimits+0x2:
6eb91002 66a35833b96e    mov     word ptr [BuggyLib!data+0x4 (6eb93358)],
ax ds:002b:6eb93358=0000

When single stepping, we stepped into a second-chance exception. The first-chance exception stopped the debugger, the second-chance exception is a left over from the first exception. That is why we have to do "double" step to continue.

Points of Interest

There exist tools for hunting down memory corruption issues. They use memory access flags as a means to implement it. In order to detect a memory overwrite, they allocate a new page for every allocation, no matter how small it is, and gives you an address relative to the end of the memory page. The following page they mark as PAGE_NOACCESS. So when a buffer overrun is made, the next byte is in a non accessible page, resulting in an access violation.

History

  • 23rd April, 2011: Initial version
  • 27th April, 2011: Minor update
    • Updated the section "Step by Step Example in windbg". A reader noticed that some memory addresses were incoherent in the output. That was due to copy & pasting from different runs, when writing the article. Now the whole output is from the same run.
  • 1st May, 2011: Updated download files
    • Implementation was extended with windbg x64 support
    • Demo project was updated with debugext.dll binary for x64

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)


Written By
Architect Visma Software AB
Sweden Sweden
Mattias works at Visma, a leading Nordic ERP solution provider. He has good knowledge in C++/.Net development, test tool development, and debugging. His great passion is memory dump analysis. He likes giving talks and courses.

Comments and Discussions

 
QuestionCool example, alas won't work with remote debugging Pin
strictlymike28-Jul-17 19:13
strictlymike28-Jul-17 19:13 
GeneralProblem Pin
Member 79785653-Jun-11 14:13
Member 79785653-Jun-11 14:13 
GeneralRe: Problem Pin
Mattias Högström4-Jun-11 9:41
Mattias Högström4-Jun-11 9:41 
GeneralThis functionality seems to be present already in the SDbgExt extension Pin
Sasha Goldshtein1-May-11 3:43
Sasha Goldshtein1-May-11 3:43 
GeneralRe: This functionality seems to be present already in the SDbgExt extension Pin
Mattias Högström1-May-11 5:10
Mattias Högström1-May-11 5:10 
GeneralRe: This functionality seems to be present already in the SDbgExt extension Pin
Sasha Goldshtein1-May-11 19:07
Sasha Goldshtein1-May-11 19:07 
GeneralMy vote of 5 Pin
Niklas L28-Apr-11 1:57
Niklas L28-Apr-11 1:57 
GeneralGreat! but what about Windbg x64? Pin
Mr Nukealizer27-Apr-11 13:42
Mr Nukealizer27-Apr-11 13:42 
GeneralRe: Great! but what about Windbg x64? Pin
Mattias Högström27-Apr-11 20:25
Mattias Högström27-Apr-11 20:25 
GeneralThanks Pin
Mr Nukealizer29-Apr-11 14:05
Mr Nukealizer29-Apr-11 14:05 
GeneralRe: Great! but what about Windbg x64? Pin
Mattias Högström1-May-11 4:28
Mattias Högström1-May-11 4:28 
GeneralRelated to your "points of interest" Pin
peterchen26-Apr-11 6:34
peterchen26-Apr-11 6:34 
GeneralRe: Related to your "points of interest" Pin
Mattias Högström27-Apr-11 20:19
Mattias Högström27-Apr-11 20:19 
GeneralMy vote of 5 Pin
ToothRobber26-Apr-11 4:25
professionalToothRobber26-Apr-11 4:25 
GeneralRe: My vote of 5 Pin
Mattias Högström27-Apr-11 20:32
Mattias Högström27-Apr-11 20:32 
GeneralLooks excellent, one question Pin
wtwhite26-Apr-11 0:09
wtwhite26-Apr-11 0:09 
GeneralRe: Looks excellent, one question Pin
Mattias Högström26-Apr-11 11:49
Mattias Högström26-Apr-11 11:49 
GeneralRe: Looks excellent, one question Pin
Ras Winterchill27-Apr-11 0:09
Ras Winterchill27-Apr-11 0:09 
GeneralRe: Looks excellent, one question Pin
Mattias Högström28-Apr-11 9:00
Mattias Högström28-Apr-11 9:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.