memory mapping

i am trying to map an exe file in memory than write to its text section and than write it back to disk. while debugging it shows data successfully written in the mapped text section , but when i try to flush it to disk , it succeeds , but the exe file on the disk is unchanged. data does not gets written on disk file.
here is the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
  int main()
{
	DWORD dwFileSizeLow = 0;
	DWORD dwFileSizeHigh = 0;

	LARGE_INTEGER lg;

	HANDLE x64game = CreateFile(L"F:\\x64PracticeGame.exe", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
		NULL, OPEN_EXISTING, 0, NULL);
	if (x64game == INVALID_HANDLE_VALUE)
	{
		MessageBox(nullptr, L"Create file failed", L"Error", MB_OK | MB_TOPMOST);
		return 1;
	}


	GetFileSizeEx(x64game, &lg);

	dwFileSizeLow = lg.LowPart;
	dwFileSizeHigh = lg.HighPart ;
	

	HANDLE gameMapping = CreateFileMapping(x64game, NULL, PAGE_READWRITE, 0, 0, NULL);  //
	if (gameMapping == NULL)
	{
		MessageBox(nullptr, L"CreateFileMapping failed", L"Error", MB_OK | MB_TOPMOST);
		return 1;
	}

	LPVOID gameMappingAddress = MapViewOfFile(gameMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);  //FILE_MAP_READ | FILE_MAP_WRITE

	LPVOID pBuffer;

	// parsing through mapped file
	PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)gameMappingAddress;
	PIMAGE_NT_HEADERS NtHeader  = (PIMAGE_NT_HEADERS)((UINT64)gameMappingAddress + DosHeader->e_lfanew);


	//parsing the sections 
	for (int i = 0; i < NtHeader->FileHeader.NumberOfSections; i++)
	{
		PIMAGE_SECTION_HEADER SectionHeader = (PIMAGE_SECTION_HEADER)((UINT64)IMAGE_FIRST_SECTION(NtHeader) + ((UINT64)IMAGE_SIZEOF_SECTION_HEADER * i));

		if (!strcmp((char*)SectionHeader->Name, (char*)".text"))
		{
			pBuffer = VirtualAlloc(NULL, SectionHeader->Misc.VirtualSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
			if (pBuffer == NULL)
			{
				MessageBox(nullptr, L"VirtualAlooc failed", L"Error", MB_OK | MB_TOPMOST);
				return 1;
			}

			memcpy(pBuffer, (LPVOID)((UINT64)gameMappingAddress + (UINT64)SectionHeader->VirtualAddress),
				SectionHeader->Misc.VirtualSize);
			if (pBuffer == NULL)
			{
				MessageBox(nullptr, L"memcpy failed", L"Error", MB_OK | MB_TOPMOST);
				return 1;
			}

			// for debugging
			void* pp = (LPVOID)((UINT64)gameMappingAddress + (UINT64)SectionHeader->VirtualAddress);

			DWORD oldprotection = 0;

			VirtualProtect((LPVOID)((UINT64)gameMappingAddress + (UINT64)SectionHeader->VirtualAddress),
				SectionHeader->Misc.VirtualSize, PAGE_EXECUTE_READWRITE, &oldprotection);

			

			memcpy((LPVOID)((UINT64)gameMappingAddress + (UINT64)SectionHeader->VirtualAddress), "\x69\x69\x69", 3);

			if (!FlushViewOfFile(gameMappingAddress, 0))      //if (!FlushViewOfFile((LPVOID)((UINT64)gameMappingAddress + (UINT64)SectionHeader->VirtualAddress), 3))
			{
				MessageBox(nullptr, L"File could not be flushed", L"Error", MB_OK | MB_TOPMOST);
				return 1;
			}
			
			if (!FlushFileBuffers(x64game))
			{
				MessageBox(nullptr, L"File could not be flushed", L"Error", MB_OK | MB_TOPMOST);
				return 1;
			}

			break;
		}
	}


	
	
	UnmapViewOfFile(gameMappingAddress);
	CloseHandle(gameMapping);
	CloseHandle(x64game);
	

	std::cout << "Everything went FINE!!!!\n";
	system("pause");

	return 0;
}
See this:

https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-unmapviewoffile

Particularly the Remarks. So try FlushViewOfFile(...)
already using it line 73
I think neither FlushViewOfFile() nor FlushFileBuffers() is strictly needed. The "dirty" (modified) pages are written back to the disk no later than when they are unmapped. You can enforce this to happen earlier by calling FlushViewOfFile(), but doing so immediately before UnmapViewOfFile() is pretty much redundant.

Just because the modified pages have been "written back to disk" it doesn't mean they have been physically written to the disk yet; they may have only been updated in the disk I/O cache for now. Anyway, at this point, any application reading the file will definitely "see" the updated content! By calling FlushFileBuffers() you ensure that the data is physically written to the disc as soon as possible. That would mostly be relevant in case of power loss or BSOD, because in such cases "pending" data from the disc I/O cache may get lost. For making your changes in the file "visible" to other applications FlushFileBuffers() should be totally irrelevant.

To make a long story short, I don't think your problem is caused by a missing flush operation.

Questions:

What is the purpose of pBuffer in your code, and why you allocate that buffer via low-level VirtualAlloc() instead of more simple/efficent HeapAlloc() or, even simpler, malloc()?

What is the point of VirtualProtect() call you are doing there? If you want to modify memory pages, READWRITE permission is sufficient. EXECUTE permission would only be needed if you want to execute them as code - in the current process! Making those "mapped" pages executable in your current process has no effect on the file. And, when the EXE file is later executed, the OS loader takes care of mapping that file into memory with EXECUTE permission where needed. That will be a completely new process though.

[EDIT]

Regarding this line where you actually modify the mapped data:
memcpy((LPVOID)((UINT64)gameMappingAddress + (UINT64)SectionHeader->VirtualAddress), [...]);

I'm not sure whether this kind of pointer arithmetic (converting pointers to integral type, then doing math on them, finally converting them back to pointer type) is kosher.

Try this more standard approach instead:
1
2
BYTE *baseAddress = (BYTE*) gameMappingAddress;
memcpy(baseAddress + SectionHeader->VirtualAddress, "\x69\x69\x69", 3);


Or maybe:
1
2
3
4
5
BYTE *baseAddress= (BYTE*) gameMappingAddress;
BYTE *sectionHeader = baseAddress + SectionHeader->VirtualAddress;
sectionHeader[0] = 0x69;
sectionHeader[1] = 0x69;
sectionHeader[2] = 0x69;


Also: Are you 100% certain that SectionHeader->VirtualAddress is an offset relative to the beginning of the mapped memory region and not an absolute address?
Last edited on
thanks for the reply, if I make change to the text section in mapped PE's memory, and save the dirty pages to disk, will that change reflect in PE's raw text section? Because I have tried for like 4-5 hours yesterday and its not happening.
i tried using low and high DWORDS here instead of 0,0->HANDLE gameMapping = CreateFileMapping(x64game, NULL, PAGE_READWRITE, 0, 0, NULL); but still no go.

memcpy((LPVOID)((UINT64)gameMappingAddress + (UINT64)SectionHeader->VirtualAddress), "\x69\x69\x69", 3);

yeah the code's not elegant , i will go with your suggested approach.
I have mostly used Virtualalloc(), thats the only reason i used it.. i will read documentation on HeapAlloc() and malloc()
if I make change to the text section in mapped PE's memory, and save the dirty pages to disk, will that change reflect in PE's raw text section?


Yes, if you map any file into memory (type of file doesn't matter), with READWRITE access permission, if you then modify the mapped data in the memory in whatever way you like, and if you finally unmap the file again, then your changes will be reflected in the file on disk (modified pages will be updated on disk).

As said before, an explicit flush is not strictly required.

I suggest you first try to create a minimal example with the basics, then later add the "complicated" stuff.

The following code maps a file into memory and overwrites the first n bytes with a fixed string. This works for me as expected. I think you can verify it works for you and then go on from there:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
int main()
{
	HANDLE hFile = CreateFileW(L"C:\\Temp\\foo.txt", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		MessageBox(NULL, L"CreateFile failed", L"Error", MB_OK | MB_TOPMOST);
		return 1;
	}

	LARGE_INTEGER fileSize;
	if (!GetFileSizeEx(hFile, &fileSize))
	{
		MessageBox(nullptr, L"GetFileSizeEx failed", L"Error", MB_OK | MB_TOPMOST);
		CloseHandle(hFile);
		return 1;
	}
	else if (fileSize.QuadPart < 8LL)
	{
		MessageBox(nullptr, L"The file is too small", L"Error", MB_OK | MB_TOPMOST);
		CloseHandle(hFile);
		return 1;
	}
	else if (fileSize.QuadPart > 134217728LL)
	{
		MessageBox(nullptr, L"The file is too large", L"Error", MB_OK | MB_TOPMOST);
		CloseHandle(hFile);
		return 1;
	}

	HANDLE hMapping = CreateFileMappingW(hFile, NULL, PAGE_READWRITE, fileSize.HighPart, fileSize.LowPart, NULL);
	if (hMapping == NULL)
	{
		MessageBox(nullptr, L"CreateFileMapping failed", L"Error", MB_OK | MB_TOPMOST);
		CloseHandle(hFile);
		return 1;
	}

	BYTE *data = (BYTE*) MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0U, 0U, (SIZE_T)fileSize.QuadPart);
	if (!data)
	{
		MessageBox(nullptr, L"MapViewOfFile failed", L"Error", MB_OK | MB_TOPMOST);
		CloseHandle(hMapping);
		CloseHandle(hFile);
		return 1;
	}

	static const char* const EXAMPLE = "Johnl0ck";
	memcpy(data, EXAMPLE, strlen(EXAMPLE));

	UnmapViewOfFile(data);
	CloseHandle(hMapping);
	CloseHandle(hFile);

	return 0;
}



I have mostly used Virtualalloc()

VirtualAlloc() is for low-level memory allocations and only allocates complete pages. Even if you allocate 1 byte, you'll get a whole page. For some very specific things VirtualAlloc() is needed, but generally HeapAlloc() is simpler and more efficient. But you can just use malloc(), because on the Windows platform malloc() is just a minimal wrapper around HeapAlloc() - plus malloc() is portable!
Last edited on
@kigar64551 so much Thanks!!!! man. you are awesome.. now it works.
yesterday i gaveup hope on this method and started using
1
2
std::ifstream input_file_reader(input_file, std::ios::binary);
std::vector<uint8_t> input_file_buffer(std::istreambuf_iterator<char>(input_file_reader), {});
this way, i was able to change the bytes in meaningfull way in raw code section, and write it back to output file, but resulting exe got corrupted everytime..
Now going back to memory mapping.
Last edited on
I suspect your actual problem was not with the memory mapping itself, but getting your pointers/offsets (memory addresses) calculated right within the memory-mapped PE file.

Always be sure you don't read or write beyond the mapped memory region. Bounds checking is essential!
Last edited on
yes, u r right , actually i was writing to the virtual address of the code section instead of raw address. i thought mapping a PE will sort of emulate how a PE looks in memory when executed..
but anyways , its a learning process...
thought mapping a PE will sort of emulate how a PE looks in memory when executed.

It does. Sort of.

Mapping a file into memory is really no different from allocating an array of appropriate size, reading the entire file into that array and, later, writing the entire array back into the file – except that the OS performs many optimizations for memory-mapped I/O. When using memory-mapped I/O, pages are only actually loaded from disk into memory when they are accessed for the first time (lazy loading); you don't need to care about this, because it happens "transparently" for the application. Also, only the "dirty" (modified) pages will be written back to disk at the end. That is why memory-mapped I/O often is more efficient.

When you run a PE file, the OS loader also maps that file into memory. But some more "magic" is going on before execution starts, such as relocation, as well as resolving the required DLL dependencies:
https://en.wikipedia.org/wiki/Relocation_%28computing%29

You really should not make any assumptions about the "absolute" addresses of a memory-mapped file, because the file may be mapped at any "base" address. The MapViewOfFile() function tells you that "base" address. Only "relative" addresses – offsets relative to the "base" address – are meaningful. Such "relative" addresses are perfectly equal to offsets (positions) within the mapped file. At the same time, it's perfectly possible that the "base" address (and therefore all "absolute" addresses) are different on each run of your program.

When you interpret some "memory addresses" stored in the PE file, you should be extremely careful about whether the address is supposed to be an "absolute" or "relative" address. If it's supposed to be a "relative" address, then relative to what is it to be understood? And, if it's supposed to be an "absolute" address, then keep in mind that the OS loader will "fix up" such addresses during relocation phase.
Last edited on
Topic archived. No new replies allowed.