Detailed explanation of kernel PE structure VA and FOA conversion in driver development

Abstract: This article will explore the relevant content of parsing PE files in the kernel.

This article is shared from Huawei Cloud Community " Driver Development: Kernel PE Structure VA and FOA Conversion ", author: LyShark.

This chapter will explore the relevant content of parsing PE files in the kernel. The conversion between FOA, VA, and RVA in PE files is also very important. The so-called FOA is the address in the file, VA is the virtual address after the memory is loaded, and RVA It is the relative offset between the memory base address and the current address. This chapter still needs to use the encapsulated KernelMapFile() mapping function. After mapping, the corresponding PE format is analyzed and the conversion function is implemented.

First, let’s demonstrate how the memory VA address and FOA address are converted to each other. By using WinHEX to open a binary file, after opening, we only need to pay attention to the following blue annotations for the recommended image loading base address, and yellow annotations for the image after loading. RVA offset.

From the above screenshot combined with the PE file structure diagram, we can know that 0000158B is the RVA offset after the image is loaded into the memory, followed by 00400000 is the suggested loading base address of the image, why is it recommended instead of absolute? Don't rush to explain later.

Through the above known conditions, we can calculate the entry address of the program after it is actually loaded into the memory. The formula is as follows:

VA (actual loading address) = ImageBase (base address) + RVA (offset) => 00400000 + 0000158B = 0040158B

After finding the OEP of the program, let’s judge which section this 0040158B belongs to. Taking the .text section as an example, we can see by observing the section in the figure below that the first orange position is 00000B44 (section size), and the second The purple position is 00001000 (section RVA), the third is 00000C00 (file alignment size), the fourth is 00000400 (offset in the file), and the fifth is 60000020 (section attribute).

After obtaining the relevant data of the above text section, we can determine which section area the OEP of the program falls in. Here we take the .text section as an example, and the calculation formula is as follows:

Virtual address start position: section base address + section RVA => 00400000 + 00001000 = 00401000
virtual address end position: text section address + section size => 00401000 + 00000B44 = 00401B44

After calculation, it is known that the .text section is in the range (401000 - 401B44). As long as your loaded VA address 0040158B is in the range, it proves that it is in this section area. The VA address here is in the range of 401000 - 401B44, which means it Belongs to the .text section.

After the calculation of the above formula, we know that the OEP position of the program falls in the .text section. At this time, you are excited to open x64DBG to verify whether the formula is calculated correctly. Unexpectedly, this address does not start with 400000 at all. What the hell is this? ?

The situation in the above picture is about the random base address. There is an option on the new version of the VS compiler whether to enable the random base address (enabled by default). As for the function of the random base address, the guess may be to prevent Crap like buffer overflows.

In order to facilitate our debugging, we need to kill it manually. It corresponds to the structure in the PE file as IMAGE_NT_HEADERS -> IMAGE_OPTIONAL_HEADER -> DllCharacteristics. : 6081 changed to 2081 Compared to x86: 4081 changed to 0081 Take the X86 program as an example, as shown in the figure below after modification.

After the above modification of the flag bit, the program can stop at the position of 0040158B when it is loaded again, which is the OEP of the program. Next, we will calculate the position corresponding to the OEP in the file through the formula.

.text(section header address) = ImageBase + section area RVA => 00400000 + 00001000 = 00401000
VA(virtual address) = ImageBase + RVA(offset) => 00400000 + 0000158B = 0040158B
RVA(relative offset) = VA - ( .text section head address) => 0040158B - 00401000 = 58B
FOA (file offset) = RVA + .text section corresponds to the offset in the file => 58B + 400 = 98B

After the calculation of the formula, we found that the virtual address 0040158B corresponds to the position of 98B in the file. Through WinHEX to locate the past, we can see the machine code instruction at the OEP.

Next, let's calculate the end address of the .text section. The end address of the .text section can be obtained by adding the file offset and the file alignment size 400+C00=1000. Then we mainly calculate the file offset as (98B - 1000) to find a blank space in this interval, here I found a blank area before the file offset is 1000, as shown in the figure below:

Then we use the formula to calculate the position where the file offset is 0xF43, which corresponds to the VA virtual address. The formula is as follows:

.text (section header address) = ImageBase + section area RVA => 00400000 + 00001000 = 00401000
VPK (actual size) = (text section header address - ImageBase) - actual offset => 401000-400000-400 = C00
VA (virtual address) = FOA(.text section) + ImageBase + VPK => F43+400000+C00 = 401B43

After the calculation, jump directly to X64DBG, we fill all the positions from 00401B44 to 90 (nop), and then save the file directly.

Use WinHEX again to view the position where the file offset is 0xF43, and you will find that all of them have been replaced with 90 instructions, indicating that the calculation is correct.

At this point, the conversion between file offset and virtual offset is over, so how to implement these functions, and then we will implement these conversion details in this way.

Convert FOA to VA:  First, convert the FOA address to VA address. This code is very simple to implement, as shown below, here the dwFOA address 0x84EC00 is converted to the virtual address of the corresponding memory.

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pBaseAddress = NULL;
UNICODE_STRING FileName = { 0 };
// 初始化字符串
RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe");
// 内存映射文件
status = KernelMapFile(FileName, &hFile, &hSection, &pBaseAddress);
if (!NT_SUCCESS(status))
{
return 0;
}
// 获取PE头数据集
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHeaders);
PIMAGE_FILE_HEADER pFileHeader = &pNtHeaders->FileHeader;
DWORD64 dwFOA = 0x84EC00;
DWORD64 ImageBase = pNtHeaders->OptionalHeader.ImageBase;
DWORD NumberOfSectinsCount = pNtHeaders->FileHeader.NumberOfSections;
DbgPrint("镜像基址 = %p | 节表数量 = %d \n", ImageBase, NumberOfSectinsCount);
for (int each = 0; each < NumberOfSectinsCount; each++)
{
DWORD64 PointerRawStart = pSection[each].PointerToRawData;                                // 文件偏移开始位置
DWORD64 PointerRawEnds = pSection[each].PointerToRawData + pSection[each].SizeOfRawData;  // 文件偏移结束位置
// DbgPrint("文件开始偏移 = %p | 文件结束偏移 = %p \n", PointerRawStart, PointerRawEnds);
if (dwFOA >= PointerRawStart && dwFOA <= PointerRawEnds)
{
DWORD64 RVA = pSection[each].VirtualAddress + (dwFOA - pSection[each].PointerToRawData);     // 计算出RVA
DWORD64 VA = RVA + pNtHeaders->OptionalHeader.ImageBase;                                     // 计算出VA
DbgPrint("FOA偏移 [ %p ] --> 对应VA地址 [ %p ] \n", dwFOA, VA);
}
}
ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
ZwClose(hSection);
ZwClose(hFile);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

The running effect is as follows. The reason why there are two results here is that it is not returned in time. Generally, the first result is the most accurate;

Convert VA to FOA:  Convert VA memory address to FOA file offset, the code is basically the same as above.

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pBaseAddress = NULL;
UNICODE_STRING FileName = { 0 };
// 初始化字符串
RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe");
// 内存映射文件
status = KernelMapFile(FileName, &hFile, &hSection, &pBaseAddress);
if (!NT_SUCCESS(status))
{
return 0;
}
// 获取PE头数据集
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHeaders);
PIMAGE_FILE_HEADER pFileHeader = &pNtHeaders->FileHeader;
DWORD64 dwVA = 0x00007FF6D3389200;
DWORD64 ImageBase = pNtHeaders->OptionalHeader.ImageBase;
DWORD NumberOfSectinsCount = pNtHeaders->FileHeader.NumberOfSections;
DbgPrint("镜像基址 = %p | 节表数量 = %d \n", ImageBase, NumberOfSectinsCount);
for (DWORD each = 0; each < NumberOfSectinsCount; each++)
{
DWORD Section_Start = ImageBase + pSection[each].VirtualAddress;                                  // 获取节的开始地址
DWORD Section_Ends = ImageBase + pSection[each].VirtualAddress + pSection[each].Misc.VirtualSize; // 获取节的结束地址
DbgPrint("Section开始地址 = %p | Section结束地址 = %p \n", Section_Start, Section_Ends);
if (dwVA >= Section_Start && dwVA <= Section_Ends)
{
DWORD RVA = dwVA - pNtHeaders->OptionalHeader.ImageBase;                                    // 计算RVA
DWORD FOA = pSection[each].PointerToRawData + (RVA - pSection[each].VirtualAddress);       // 计算FOA

DbgPrint("VA偏移 [ %p ] --> 对应FOA地址 [ %p ] \n", dwVA, FOA);
}
}
ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
ZwClose(hSection);
ZwClose(hFile);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

The running effect is as follows. The desired result does not appear here because our current VA memory address is not the actual loading address, but only the address in the PE disk. If we replace it with the PE in the memory, we can extract the correct result;

Convert RVA to FOA:  convert relative offset address to FOA file offset address, here is just one more step pNtHeaders->OptionalHeader.ImageBase + dwRVARVA to VA conversion process, the conversion result is consistent with VA to FOA.

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: [email protected]
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
NTSTATUS status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hSection = NULL;
PVOID pBaseAddress = NULL;
UNICODE_STRING FileName = { 0 };
// 初始化字符串
RtlInitUnicodeString(&FileName, L"\\??\\C:\\Windows\\System32\\ntoskrnl.exe");
// 内存映射文件
status = KernelMapFile(FileName, &hFile, &hSection, &pBaseAddress);
if (!NT_SUCCESS(status))
{
return 0;
}
// 获取PE头数据集
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHeaders);
PIMAGE_FILE_HEADER pFileHeader = &pNtHeaders->FileHeader;
DWORD64 dwRVA = 0x89200;
DWORD64 ImageBase = pNtHeaders->OptionalHeader.ImageBase;
DWORD NumberOfSectinsCount = pNtHeaders->FileHeader.NumberOfSections;
DbgPrint("镜像基址 = %p | 节表数量 = %d \n", ImageBase, NumberOfSectinsCount);
for (DWORD each = 0; each < NumberOfSectinsCount; each++)
{
DWORD Section_Start = pSection[each].VirtualAddress;                                  // 计算RVA开始位置
DWORD Section_Ends = pSection[each].VirtualAddress + pSection[each].Misc.VirtualSize; // 计算RVA结束位置
if (dwRVA >= Section_Start && dwRVA <= Section_Ends)
{
DWORD VA = pNtHeaders->OptionalHeader.ImageBase + dwRVA;                                  // 得到VA地址
DWORD FOA = pSection[each].PointerToRawData + (dwRVA - pSection[each].VirtualAddress);    // 得到FOA
DbgPrint("RVA偏移 [ %p ] --> 对应FOA地址 [ %p ] \n", dwRVA, FOA);
}
}
ZwUnmapViewOfSection(NtCurrentProcess(), pBaseAddress);
ZwClose(hSection);
ZwClose(hFile);
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

The running effect is as follows;

 

Click to follow and learn about Huawei Cloud's fresh technologies for the first time~

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4526289/blog/9874808