Skip to content

Commit

Permalink
Merge pull request #80 from mrexodia/memory-partial-release
Browse files Browse the repository at this point in the history
Memory partial release
  • Loading branch information
mrexodia authored May 22, 2023
2 parents 84cb895 + 149d536 commit 80975aa
Show file tree
Hide file tree
Showing 16 changed files with 41,497 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ jobs:
- name: 'Test: DumpulatorTests'
run: |
cd tests
python harness-tests.py
python run-tests.py
- name: 'Test: ExceptionTest_x64'
run: |
Expand Down
42 changes: 33 additions & 9 deletions src/dumpulator/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ def find_free(self, size: int, allocation_align=True) -> Optional[int]:
return info.base
return None

def reserve(self, start: int, size: int, protect: MemoryProtect, memory_type: MemoryType = MemoryType.MEM_PRIVATE, info: Any = None) -> None:
def reserve(self, start: int, size: int, protect: MemoryProtect, memory_type: MemoryType = MemoryType.MEM_PRIVATE, info: Any = None) -> MemoryRegion:
assert isinstance(protect, MemoryProtect)
assert isinstance(memory_type, MemoryType)
assert size > 0 and self.align_page(size) == size
Expand All @@ -203,6 +203,7 @@ def check_overlaps(idx):
check_overlaps(index - 1)
check_overlaps(index)
self._regions.insert(index, region)
return region

def _decommit_region(self, parent_region: MemoryRegion, decommit_region: MemoryRegion):
assert decommit_region in parent_region
Expand All @@ -227,19 +228,42 @@ def _decommit_region(self, parent_region: MemoryRegion, decommit_region: MemoryR
del self._committed[release_start + i * PAGE_SIZE]
parent_region.commit_count -= 1

def release(self, start: int) -> None:
def release(self, start: int, size: int = 0) -> None:
assert self.align_allocation(start) == start
assert self.align_allocation(size) == size

parent_region = self.find_region(start)
if parent_region is None:
parent = self.find_region(start)
if parent is None:
raise KeyError(f"Could not find parent for {hex(start)}")
if parent_region.start != start:
raise KeyError(f"You can only release the whole parent region")

self._decommit_region(parent_region, parent_region)
if size == 0:
size = parent.size
if start + size > parent.start + parent.size:
raise KeyError(f"You can only release part of the parent region")

decommit = MemoryRegion(start, size)
self._decommit_region(parent, decommit)
self._regions.remove(parent)

before = MemoryRegion(parent.start, decommit.start - parent.start)
after = MemoryRegion(decommit.end, parent.end - decommit.end)

if parent.size == decommit.size:
# Make sure the whole region was freed
assert parent.commit_count == 0

if before.size > 0:
before = self.reserve(before.start, before.size, parent.protect, parent.type, parent.info)
for page in before.pages():
if page in self._committed:
before.commit_count += 1
if after.size > 0:
after = self.reserve(after.start, after.size, parent.protect, parent.type, parent.info)
for page in after.pages():
if page in self._committed:
after.commit_count += 1

assert parent_region.commit_count == 0
self._regions.remove(parent_region)
assert before.commit_count + after.commit_count == parent.commit_count

def commit(self, start: int, size: int, protect: MemoryProtect = MemoryProtect.UNDEFINED) -> None:
assert isinstance(protect, MemoryProtect)
Expand Down
3 changes: 1 addition & 2 deletions src/dumpulator/ntsyscalls.py
Original file line number Diff line number Diff line change
Expand Up @@ -1820,11 +1820,10 @@ def ZwFreeVirtualMemory(dp: Dumpulator,
size = RegionSize.read_ptr()
if FreeType == MEM_RELEASE:
print(f"release {hex(base)}[{hex(size)}]")
assert size == 0
region = dp.memory.find_region(base)
if region is None:
return STATUS_MEMORY_NOT_ALLOCATED
dp.memory.release(base)
dp.memory.release(base, size)
return STATUS_SUCCESS
elif FreeType == MEM_DECOMMIT:
print(f"decommit {hex(base)}[{hex(size)}]")
Expand Down
4 changes: 1 addition & 3 deletions tests/DumpulatorTests/ExceptionTest/ExceptionTest.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#include <Windows.h>
#include <cstdio>

#include "../Tests/debug.h"
#include <cstdlib>

static LONG WINAPI VectoredHandler(struct _EXCEPTION_POINTERS* ExceptionInfo)
{
Expand Down
6 changes: 5 additions & 1 deletion tests/DumpulatorTests/Loader/Loader.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <Windows.h>
#include "../Tests/debug.h"
#include <cstdio>
#include <cstdlib>

int main(int argc, char** argv)
{
Expand All @@ -9,6 +10,9 @@ int main(int argc, char** argv)
auto dll = "Tests_x86.dll";
#endif // _WIN64
auto hLib = LoadLibraryA(dll);
auto p_DebugPrintf = (void**)GetProcAddress(hLib, "DebugPrintf");
if (p_DebugPrintf != nullptr)
*p_DebugPrintf = printf;
if (argc < 2)
{
// TODO: implement enumerating all exports and running them
Expand Down
2 changes: 1 addition & 1 deletion tests/DumpulatorTests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The harness dumps were created as follows:

## Adding a new test

Add a new `mytest.cpp` file to the `Tests` project. The tests are exported as `bool <Prefix>_<description>Test();` and the result indicates whether the test was successful or not. If you need a custom environment add the following in `tests/harness-tests.py`:
Add a new `mytest.cpp` file to the `Tests` project. The tests are exported as `bool <Prefix>_<description>Test();` and the result indicates whether the test was successful or not. If you need a custom environment add the following in `tests/run-tests.py`:

```python
class <Prefix>Environment(TestEnvironment):
Expand Down
20 changes: 19 additions & 1 deletion tests/DumpulatorTests/Tests/DllMain.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
#include <Windows.h>
#include "debug.h"

inline ULONG
STDAPIVCALLTYPE
DbgPrintNop(
_In_z_ _Printf_format_string_ PCSTR Format,
...
)
{
return 0;
}

#ifdef _WIN64
#pragma comment(linker, "/EXPORT:DebugPrintf=DebugPrintf")
#else
#pragma comment(linker, "/EXPORT:DebugPrintf=_DebugPrintf")
#endif // _WIN64

extern "C" decltype(&DbgPrint) DebugPrintf = DbgPrintNop;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
Expand Down
5 changes: 3 additions & 2 deletions tests/DumpulatorTests/Tests/HandleTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ extern "C" __declspec(dllexport) bool Handle_ReadFileTest()
if (file_handle == INVALID_HANDLE_VALUE)
{
DebugPrint(L"Failed to open file");
return ret_value;
return false;
}

ret_value = ReadFile(
Expand All @@ -70,7 +70,8 @@ extern "C" __declspec(dllexport) bool Handle_ReadFileTest()
FALSE
);


CloseHandle(file_handle);

return ret_value;
return ret_value && memcmp(read_buffer, "Lorem ipsum", 11) == 0;
}
43 changes: 43 additions & 0 deletions tests/DumpulatorTests/Tests/MemoryTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "debug.h"

extern "C" __declspec(dllexport) bool Memory_PartialReleaseTest()
{
PVOID Base = 0;
SIZE_T RegionSize = 0x1c0000;
auto status = NtAllocateVirtualMemory(NtCurrentProcess(), &Base, 0, &RegionSize, MEM_RESERVE, PAGE_READWRITE);
DebugPrintf("status: 0x%08X, base: %p, size: %p\n", status, Base, RegionSize);
if (!NT_SUCCESS(status))
return false;

SIZE_T PartialSize = RegionSize - 0x10000;
status = NtFreeVirtualMemory(NtCurrentProcess(), &Base, &PartialSize, MEM_RELEASE);
DebugPrintf("status: 0x%08X, base: %p, size: %p\n", status, Base, PartialSize);
if (!NT_SUCCESS(status))
return false;

SIZE_T PageSize = 0x1000;
PVOID PartialBase = (char*)Base + PartialSize;
status = NtAllocateVirtualMemory(NtCurrentProcess(), &PartialBase, 0, &PageSize, MEM_COMMIT, PAGE_READWRITE);
DebugPrintf("status: 0x%08X, base: %p, size: %p\n", status, PartialBase, PageSize);
if (!NT_SUCCESS(status))
return false;
return true;
}

extern "C" __declspec(dllexport) bool Memory_MiddleReleaseTest()
{
PVOID Base = 0;
SIZE_T RegionSize = 0x30000;
auto status = NtAllocateVirtualMemory(NtCurrentProcess(), &Base, 0, &RegionSize, MEM_RESERVE, PAGE_READWRITE);
DebugPrintf("status: 0x%08X, base: %p, size: %p\n", status, Base, RegionSize);
if (!NT_SUCCESS(status))
return false;

SIZE_T MiddleSize = 0x10000;
PVOID MiddleBase = (char*)Base + 0x10000;
status = NtFreeVirtualMemory(NtCurrentProcess(), &MiddleBase, &MiddleSize, MEM_RELEASE);
DebugPrintf("status: 0x%08X, base: %p, size: %p\n", status, MiddleBase, MiddleSize);
if (!NT_SUCCESS(status))
return false;
return true;
}
4 changes: 4 additions & 0 deletions tests/DumpulatorTests/Tests/Tests.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@
<ItemGroup>
<ClCompile Include="DllMain.cpp" />
<ClCompile Include="HandleTest.cpp" />
<ClCompile Include="MemoryTest.cpp" />
<ClCompile Include="ntstatusdb.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="debug.h" />
<ClInclude Include="ntstatusdb.h" />
<ClInclude Include="phnt.h" />
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
Expand Down
12 changes: 12 additions & 0 deletions tests/DumpulatorTests/Tests/Tests.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,22 @@
<ClCompile Include="DllMain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="MemoryTest.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ntstatusdb.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="debug.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="phnt.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ntstatusdb.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
17 changes: 4 additions & 13 deletions tests/DumpulatorTests/Tests/debug.h
Original file line number Diff line number Diff line change
@@ -1,22 +1,11 @@
#pragma once

#include <Windows.h>
#include <winternl.h>

#ifdef __cplusplus
extern "C"
#endif // __cplusplus
NTSYSCALLAPI
NTSTATUS
NTAPI
NtDisplayString(
PUNICODE_STRING String
);
#include "phnt.h"

#define WIDEN_EXPAND(str) L ## str
#define WIDEN(str) WIDEN_EXPAND(str)

#ifdef __c1plusplus
#ifdef __cplusplus
// Helper function to directly call NtDisplayString with a string
// This simplifies the trace output of Dumpulator
template<size_t Count>
Expand All @@ -38,3 +27,5 @@ static void DebugPrint(const wchar_t* str)
NtDisplayString(&ustr);
}
#endif // __cplusplus

extern "C" decltype(&DbgPrint) DebugPrintf;
Loading

0 comments on commit 80975aa

Please sign in to comment.