📜 ⬆️ ⬇️

Experiment: looking for int i = 0xDEADBEEF in a physical memory dump

KDPV


The study of the virtual address space and address translation algorithm is much simpler if we start with a simple practical example. To do this, we write a simple program that displays the address of a local variable:


int main() { unsigned i = 0xDEADBEEF; std::cout << "address of i is " << std::hex << &i; std::cin.get(); //    return 0; } 

Then we try to find the physical address and view the value at that address.



We will consider 32-bit Windows (without any Physical Address Extension), because 64-bit is more complicated. The description of the transformation is simplified, but enough for our experiment. I recommend checking in virtual. It doesn't matter which one, but in the end I'll show you how to unload a memory dump in VirtualBox.


The basics


In my case, the address turned out to be 0x22FF2C. In general, it may vary each time the program is started (see ASLR ). Other processes at this address may have their own values, because this is not a physical, but a virtual address. Perhaps the main purpose of a virtual address space is the ability to provide each process with its own address space in which it would not interfere with others. The size of the virtual address space depends on the platform. For x86, the theoretical maximum size is 4 GB. By default, the first half (0 - 0x7FFFFFFF) is the user process space in which the image of the current process executable file is located, its stack, heap, and so on. The second half (0x80000000 - 0xFFFFFFFF) is the system half. With some reservations, we can assume that the user process space is unique for each process, and only one system. Address 0x22FF2C, obviously, hit the first half.


The virtual address space is divided into 0x100000 (1048576) pages of 4096 bytes each. Physical memory is also paginated of the same size, called page blocks. Pages (not all, of course) are mapped onto page blocks, so each page needs information about its location in physical memory. All 0x100000 pages correspond to the same 4-byte entries, called PTE (page table entry). In virtual space, they are located in the address range 0xC0000000 - 0xC03FFFFF, and occupy 1024 pages, called page tables. Getting an entry is simple: the k-th page corresponds to the k-th entry.


Virtual addressing
Orange marked page tables.


 virtual_address = 0x22FF2C page_index = virtual_address / 4096 pte_addr = 0xC0000000 + page_index * 4 

Multiply by 4 because PTE is 4-byte. We get that in our case pte_addr = 0xC00008BC


Naive attempt


The PTE address is, try to find out what is there:


 std::cout << "PTE is " << std::hex << *(unsigned*)0xC00008BC; 

Well, oh. Hardware exception. And all because we tried to read from the system space. ReadProcessMemory does not help either. Calling VirtualQuery will tell us PAGE_NOACCESS. Access can only be obtained by obtaining kernel mode privileges. Perhaps the easiest way for our research task is to use the kernel debugger.


Using the kernel debugger


We put KD and LiveKd . LiveKD allows you to run the Microsoft Kd and Windbg kernel debuggers, included in the debugging tools for Windows package, in the actual system in local mode. The last link is also a small help for installation and help.


We start our example (let it be called main.exe). Launch LiveKd. We write " !process 0 0 " to display a list of all running processes, or immediately " !process 0 0 main.exe "


 0: kd> !process 0 0 main.exe PROCESS 86530118 SessionId: 1 Cid: 0dcc Peb: 7ffdd000 ParentCid: 0428 DirBase: 2402e000 ObjectTable: 8879f430 HandleCount: 16. Image: main.exe 

We are interested in the address after the word PROCESS (this is the address on the EPROCESS structure containing the process attributes). Connect to the process:


 0: kd> .process 86530118 Implicit process is now 86530118 

We check the content at 0x22FF2C to make sure that everything is done correctly:


 0: kd> dd 22FF2C L1 0022ff2c deadbeef 

Hexadecimal numbers are used by default. The dd displays several 4-byte values ​​starting at the specified virtual address. L1 - output only one value.


PTE reading


 0: kd> dd C00008BC L1 c00008bc 6612f847 

It was possible not to consider:


 dd C0000000 + (22FF2C >> 0xC) * 4 L1 c00008bc 6612f847 

In the PTE value of the record 6612f847, the first 20 bits (5 hex digits) are the index of the page block, the rest are various flags. To get the address of the page block, you need to multiply the index by the block size - 4096 bytes.


 page_block_index = 0x6612F page_block_address = page_block_index << 12 = 0x6612F000 //  4096 

The byte order inside the page and page block is the same, so it is necessary to calculate the offset inside the page and add to the address of the page block:


 virtual_adress = 0x22FF2C offset = virtual_adress & 0xFFF = 0xF2C //  hex- phisycal_address = page_block_address + offset = 0x6612FF2C 

Checking:


 0: kd> !dd 6612FF2C L1 #6612ff2c deadbeef 

The !dd similar to dd , only accepts physical addresses.


We found out that our address can be represented as follows:


 0x22FF2C = b 00000000001000101111 111100101100 20  12  page_index byte_offset 

But also note that the PTE found is in the 0th page table with the index 0x22F inside it. And our address can be represented as follows:


 0x22FF2C = b 0000000000 1000101111 111100101100 10  10  12  table_idx PTE_index byte_offset 

We need to go deeper (PDE)


Using virtual PTE addresses is unsportsmanlike. After all, they, too, are the usual pages that need to find page blocks. And if so, then we simply find our PTE for these pages. In total, we have 1024 such pages (called page tables) and all PTE for them are placed in one page. This page is called the page directory and it contains 1024 entries (called PDE - page directory entry) with addresses on the page tables.


Virtual addressing
Blue marks the catalog of tables, orange - tables of pages.


We act in the same way as we have already done:


 pte_addr = 0xC00008BC page_index = pte_addr / 4096 = 0xC0000 pde_addr = 0xC0000000 + page_index * 4 = 0xC0300000 

We got the address PDE = 0xC0300000 (all PDEs are stored in the page at 0xC0300000, we hit the zero PDE). Check the content:


 0: kd> dd C0300000 L1 c0300000 0b21d867 

Completely similar: A PDE containing 0b21d867 gives us the address 0x0B21D000 of a page block with a page table. It remains to find the right PTE in it. Recall that address 0x22FF2C corresponds to PTE with index 0x22F in the 0th table (with offset 0x22F * 4). This means that PTE is located at 0x0B21D000 + 0x22F * 4.


 0: kd> !dd 0b21d000 + 0x22f * 4 # b21d8bc 6612f847 

With the address 6612f847 we have already worked.


It remains to find out where the directory is in physical memory (since we received the PDE using virtual addressing). The address was specified in DirBase when we reviewed the process information with the " !process 0 0 main.exe Process !process 0 0 main.exe " !process 0 0 main.exe . In our case, DirBase = 2402e000


 0: kd> !dd 2402e000 #2402e000 0b21d867 

Final formula


 0x22FF2C = b 0000000000 1000101111 111100101100 10  10  12  PDE_index PTE_index byte_offset pde_addr = DirBase + PDE_index * 4 pte_addr = ((*pde_addr) & 0xFFFFF000) | (PTE_index * 4) value_addr = ((*pte_addr) & 0xFFFFF000) | byte_offset 

Looking for a dump


I think removing a dump from a working system is somewhat problematic, so we’ll remove it from VirtualBox. To do this, you need to run in debug mode:


 VirtualBox.exe --dbg --startvm VM_name 

Select in the menu "Debugging" -> "Command line ..." and type:


 .pgmphystofile "path_to_dump_file" 

Open the file (I use HxD), go to 6612ff2c:


View in dump


Knowing DirBase and the virtual address, you can search for the value immediately in the dump, without a debugger. In general, in the dump you can find the value of DirBase by the name of the process, but this is another story.


')

Source: https://habr.com/ru/post/322636/


All Articles