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.
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
The functions we will use to change permissions are all part of the Windows API.
Using the Code
Below is some pseudo code for changing the access flag from C/C++:
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.
Windbg Extension
I have made a windbg extension that implements the functions Protect
and MemInfo
.Protect
- address size protection
MemInfo
- address
Using the Extension
Start by copying the extension to the Windbg
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
Step by Step Example in windbg
We need to load the extension.
Then we run the command "lm
" to get the list of loaded modules.
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:
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
:
Protect flag is 0x20 (PAGE_EXECUTE_READ
), which seems correct.Let's change the code section to PAGE_NOACCESS
:
Let's continue execution until our breakpoint is hit.
Bam! We hit our breakpoint. Let us now restore the flags and do some single stepping:
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.