Article directory
Windows memory basics
- Each Windows process occupies 4GB of virtual address space exclusively and cannot be accessed by other processes.
- The page size is 4KB.
- The smallest granularity of memory protection attributes is the page.
- The PAGE_GUARD memory protection modifier can be used as a one-time page access warning.
Typical Windows memory layout
Memory fuzz testing example
Target application:
- Waiting in a loop for client connections.
- A new thread is created after receiving the connection, and the new thread uses recv() to receive the client's request.
- Call unmarshal() to decompress or decrypt.
- Pass parameters to parse(), and parse() calls other functions for processing and returns.
The idea of memory fuzz testing is no longer to transmit fuzz test data through the network, but to skip this link and directly perform fuzz testing on the parse() function where vulnerabilities are most likely to occur, which can greatly improve efficiency.
The general idea: hook the position in front of the parse() function in the target application, modify its input and let it run.
Method 1: Variation loop insertion
First, manually locate the start and end positions of the parse() function through reverse engineering, and then use the mutation loop insertion tool to insert a mutation function and two unconditional jump instructions into the target memory space. The mutation function is the mutate() function. It is responsible for modifying the data obtained by the parse() function. The function of the unconditional jump instruction is as shown in the figure. It is responsible for connecting mutate() and parse() from beginning to end, so as to loop to parse() Function passes parameters.
Method 2: Snapshot recovery mutation
First, manually locate the start and end of the parse() function through reverse engineering. When the snapshot recovery mutation tool reaches the starting position of parse(), it creates a snapshot for the target process. After parse() ends, it restores the process snapshot and performs the original The data is mutated and parse() is re-executed with the new data.
Snapshot recovery mutation (SRM) tool development
How to place a hook at a specific point
Hooks can be placed in the process through debugger breakpoints, here we choose software breakpoints.
Suppose you need to set a breakpoint as shown in the figure below 0xDEADBEEF
:
-
First use
ReadProcessMemory()
read the raw byte value of the target address8B
and save it. -
Then use WriteProcessMemory() to write the INT3 instruction to the target address. Since
8B FF
the first byte of the original instruction was modified toCC
, itFF
is disassembled together with the following bytes tocall [ebp-75]
. -
At this time, when execution reaches the breakpoint, the INT3 instruction will trigger a debugging event with an
EXCEPTION_BREAK_POINT
exception codeEXCEPTION_DEBUG_EVENT
, which can be captured by the debugger. -
But there are two problems at this time: first, the original instruction
8B FF
cannot be executed; second, after the INT3 instruction is executed, EIP points0xDEADBEF0
.
-
To solve the first problem: just use
WriteProcessMemory()
write8B
back to the original location. -
Solve the second problem: The structure
GetThreadContext()
of the current thread can be obtained by callingCONTEXT
, which includes the instruction pointer EIP. After modifying it, useSetThreadContext()
writeback.
How to generate a process snapshot
When the process is running, many things will change. When the process is running, new threads will be created, old threads will be terminated, files will be opened and closed, etc. Here we only consider changes in the thread context and memory in the process.
Steps to generate a snapshot:
- Saves the context of each thread in the target process,
GetThreadContext()
implementation dependent. - Saves the contents of all memory that may change. In the process
0x00000000 ~ 0x7FFFFFFF
, some pages will change, and some will not. Specifically, the pages with the following attributes will not. In addition, the pages where the executable code is located are unlikely to change, so we do not treat these pages. consider.Use
VirtualQueryEx()
to access all available memory pages one by one. If we find a page that we want to include in the snapshot, we can use toReadProcessMemory
read the content and page information of the page and store it in the snapshot list.
Existing problem: If the attribute of a certain page is when taking a snapshot, PAGE_READONLY
it will not be saved according to this method, but it may be that the attribute of the page is modified to be writable immediately, then it may occur in the snapshot. Non-existent change. The author ignored this problem, but gave an idea: hook the API that can modify the page attributes. In the hook, make corresponding modifications to the previous snapshot according to the parameters of the API.
where to place hook
The first is the snapshot point: where in execution is the state of the target process saved?
The second is the recovery point: where do we roll back the process state, mutate our input, and continue the instrumented execution loop?