Fuzz: memory fuzz testing

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

Insert image description here

Memory fuzz testing example

Insert image description hereTarget application:

  1. Waiting in a loop for client connections.
  2. A new thread is created after receiving the connection, and the new thread uses recv() to receive the client's request.
  3. Call unmarshal() to decompress or decrypt.
  4. 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

Insert image description here
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

Insert image description here

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:

  1. First use ReadProcessMemory()read the raw byte value of the target address 8Band save it.Insert image description here

  2. Then use WriteProcessMemory() to write the INT3 instruction to the target address. Since 8B FFthe first byte of the original instruction was modified to CC, it FFis disassembled together with the following bytes to call [ebp-75].Insert image description here

  3. At this time, when execution reaches the breakpoint, the INT3 instruction will trigger a debugging event with an EXCEPTION_BREAK_POINTexception code EXCEPTION_DEBUG_EVENT, which can be captured by the debugger.

  4. But there are two problems at this time: first, the original instruction 8B FFcannot be executed; second, after the INT3 instruction is executed, EIP points 0xDEADBEF0.
    Insert image description here

  5. To solve the first problem: just use WriteProcessMemory()write 8Bback to the original location.

  6. Solve the second problem: The structure GetThreadContext()of the current thread can be obtained by calling CONTEXT, which includes the instruction pointer EIP. After modifying it, use SetThreadContext()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:

  1. Saves the context of each thread in the target process, GetThreadContext()implementation dependent.
  2. 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. Insert image description hereUse 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 to ReadProcessMemoryread 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_READONLYit 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?

Guess you like

Origin blog.csdn.net/weixin_43249758/article/details/126375463