Skip to content

Commit

Permalink
VMICore: Introduce memory mapping API
Browse files Browse the repository at this point in the history
  • Loading branch information
Dorian Eikenberg authored and rageagainsthepc committed Jan 11, 2024
1 parent e59b933 commit cfbe9d4
Show file tree
Hide file tree
Showing 21 changed files with 311 additions and 272 deletions.
2 changes: 2 additions & 0 deletions vmicore/src/include/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ target_sources(vmicore-public-headers INTERFACE
vmicore/callback.h
vmicore/vmi/IBreakpoint.h
vmicore/vmi/IIntrospectionAPI.h
vmicore/vmi/IMemoryMapping.h
vmicore/vmi/MappedRegion.h
vmicore/vmi/events/IInterruptEvent.h
vmicore/vmi/events/IRegisterReadable.h
vmicore/filename.h
Expand Down
17 changes: 10 additions & 7 deletions vmicore/src/include/vmicore/plugins/PluginInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "../vmi/BpResponse.h"
#include "../vmi/IBreakpoint.h"
#include "../vmi/IIntrospectionAPI.h"
#include "../vmi/IMemoryMapping.h"
#include "../vmi/events/IInterruptEvent.h"
#include <functional>
#include <memory>
Expand All @@ -22,19 +23,21 @@ namespace VmiCore::Plugin
class PluginInterface
{
public:
constexpr static uint8_t API_VERSION = 15;
constexpr static uint8_t API_VERSION = 16;

virtual ~PluginInterface() = default;

/**
* Reads a region of contiguous virtual memory from a process. The starting offset as well as the size must be
* 4kb page aligned.
* Map a guest memory region into the address space of the introspection application. See IMemoryMapping.h for
* more details.
*
* @return A unique pointer to a byte vector containing the memory content. Subregions that could not be
* extracted (e.g. because they are paged out) will be replaced by a single all zero padding page.
* @param baseVA Start of the virtual address range inside the guest.
* @param dtb Process dtb
* @param numberOfPages Size of the region in 4kb pages.
* @return An instance of IMemoryMapping. See IMemoryMapping.h for details.
*/
[[nodiscard]] virtual std::unique_ptr<std::vector<uint8_t>>
readProcessMemoryRegion(pid_t pid, addr_t address, size_t numberOfBytes) const = 0;
[[nodiscard]] virtual std::unique_ptr<IMemoryMapping>
mapProcessMemoryRegion(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) const = 0;

/**
* Obtain a vector containing an OS-agnostic representation of all currently running processes.
Expand Down
41 changes: 41 additions & 0 deletions vmicore/src/include/vmicore/vmi/IMemoryMapping.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#ifndef VMICORE_IMEMORYMAPPING_H
#define VMICORE_IMEMORYMAPPING_H

#include "MappedRegion.h"
#include <stdexcept>

namespace VmiCore
{
class MemoryMappingError : public std::runtime_error
{
using std::runtime_error::runtime_error;
};

/**
* Class representing chunks of memory that were able to be mapped within in a given virtual address range.
*/
class IMemoryMapping
{
public:
virtual ~IMemoryMapping() = default;

/**
* Retrieves a set of memory mapping descriptors. See MappedRegion.h for details. Elements are ordered from
* lowest to highest guest VA.
*
* @throws MemoryMappingError Will occur if unmap has already been called.
*/
[[nodiscard]] virtual std::span<const MappedRegion> getMappedRegions() const = 0;

/**
* Will unmap all mappings. This function will also be called as soon as an instance of this class goes out of
* scope.
*/
virtual void unmap() = 0;

protected:
IMemoryMapping() = default;
};
}

#endif // VMICORE_IMEMORYMAPPING_H
54 changes: 54 additions & 0 deletions vmicore/src/include/vmicore/vmi/MappedRegion.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef VMICORE_MAPPEDREGION_H
#define VMICORE_MAPPEDREGION_H

#include "../os/PagingDefinitions.h"
#include "../types.h"
#include <cstdint>
#include <span>
#include <stdexcept>

namespace VmiCore
{
/**
* Represents a chunk of contiguous memory that has been mapped into the address space of the introspection
* application.
*/
struct MappedRegion
{
/// A virtual address representing the start of the memory region inside the guest.
addr_t guestBaseVA;
/// Size of the memory region in pages. Will always be the finest granularity, even if the guest uses large
/// pages.
std::size_t num_pages;
/// Base address of the mapped memory region inside the introspection application's address space.
void* mappingBase;

MappedRegion(addr_t guestBaseVA, std::size_t num_pages, void* mappingBase)
: guestBaseVA(guestBaseVA), num_pages(num_pages), mappingBase(mappingBase)
{
}

MappedRegion(addr_t guestBaseVA, std::span<uint8_t> mapping)
: guestBaseVA(guestBaseVA),
num_pages(mapping.size() / PagingDefinitions::pageSizeInBytes),
mappingBase(static_cast<void*>(mapping.data()))
{
if (mapping.size() % PagingDefinitions::pageSizeInBytes != 0)
{
throw std::invalid_argument("Mapping has to be page aligned");
}
}

/**
* Convenience method for safe access to the mapped memory.
*/
[[nodiscard]] std::span<const uint8_t> asSpan() const
{
return {static_cast<uint8_t*>(mappingBase), num_pages * PagingDefinitions::pageSizeInBytes};
}

bool operator==(const MappedRegion&) const = default;
};
}

#endif // VMICORE_MAPPEDREGION_H
1 change: 1 addition & 0 deletions vmicore/src/lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_library(vmicore-lib OBJECT
vmi/InterruptEventSupervisor.cpp
vmi/InterruptGuard.cpp
vmi/LibvmiInterface.cpp
vmi/MemoryMapping.cpp
vmi/SingleStepSupervisor.cpp
vmi/VmiInitData.cpp
vmi/VmiInitError.cpp)
Expand Down
1 change: 1 addition & 0 deletions vmicore/src/lib/os/windows/ActiveProcessesSupervisor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <fmt/core.h>
#include <string>
#include <vmicore/filename.h>
#include <vmicore/os/PagingDefinitions.h>

namespace VmiCore::Windows
{
Expand Down
64 changes: 7 additions & 57 deletions vmicore/src/lib/plugins/PluginSystem.cpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
#include "PluginSystem.h"
#include "../vmi/MemoryMapping.h"
#include "PluginException.h"
#include <bit>
#include <cstdint>
#include <dlfcn.h>
#include <fmt/core.h>
#include <utility>
#include <vmicore/filename.h>
#include <vmicore/os/PagingDefinitions.h>

namespace VmiCore
{
namespace
{
bool isInstanciated = false;
constexpr char const* paddingLogFile = "memoryExtractionPaddingLog.txt";
}

PluginSystem::PluginSystem(std::shared_ptr<IConfigParser> configInterface,
Expand Down Expand Up @@ -42,62 +43,11 @@ namespace VmiCore
isInstanciated = false;
}

std::unique_ptr<std::vector<uint8_t>>
PluginSystem::readPagesWithUnmappedRegionPadding(uint64_t pageAlignedVA, uint64_t cr3, uint64_t numberOfPages) const
std::unique_ptr<IMemoryMapping>
PluginSystem::mapProcessMemoryRegion(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) const
{
if (pageAlignedVA % PagingDefinitions::pageSizeInBytes != 0)
{
throw std::invalid_argument(
fmt::format("{}: Starting address {:#x} is not aligned to page boundary", __func__, pageAlignedVA));
}
auto vadIdentifier(fmt::format("CR3 {:#x} VAD @ {:#x}-{:#x}",
cr3,
pageAlignedVA,
(pageAlignedVA + numberOfPages * PagingDefinitions::pageSizeInBytes)));
auto memoryRegion = std::make_unique<std::vector<uint8_t>>();
auto needsPadding = true;
for (uint64_t currentPageIndex = 0; currentPageIndex < numberOfPages; currentPageIndex++)
{
auto memoryPage = std::vector<uint8_t>(PagingDefinitions::pageSizeInBytes);
if (vmiInterface->readXVA(pageAlignedVA, cr3, memoryPage, memoryPage.size()))
{
if (!needsPadding)
{
needsPadding = true;
logger->info("First successful page extraction after padding",
{{WRITE_TO_FILE_TAG, paddingLogFile},
{"vadIdentifier", vadIdentifier},
{"pageAlignedVA", fmt::format("{:#x}", pageAlignedVA)}});
}
memoryRegion->insert(memoryRegion->cend(), memoryPage.cbegin(), memoryPage.cend());
}
else
{
if (needsPadding)
{
memoryRegion->insert(memoryRegion->cend(), PagingDefinitions::pageSizeInBytes, 0x0);
needsPadding = false;
logger->info("Start of padding",
{{WRITE_TO_FILE_TAG, paddingLogFile},
{"vadIdentifier", vadIdentifier},
{"pageAlignedVA", fmt::format("{:#x}", pageAlignedVA)}});
}
}
pageAlignedVA += PagingDefinitions::pageSizeInBytes;
}
return memoryRegion;
}

std::unique_ptr<std::vector<uint8_t>>
PluginSystem::readProcessMemoryRegion(pid_t pid, addr_t address, size_t count) const
{
if (count % PagingDefinitions::pageSizeInBytes != 0)
{
throw std::invalid_argument("Size of memory region must be page size aligned.");
}
auto numberOfPages = count >> PagingDefinitions::numberOfPageIndexBits;
auto process = activeProcessesSupervisor->getProcessInformationByPid(pid);
return readPagesWithUnmappedRegionPadding(address, process->processDtb, numberOfPages);
return std::make_unique<MemoryMapping>(
loggingLib, vmiInterface, vmiInterface->mmapGuest(baseVA, dtb, numberOfPages));
}

void PluginSystem::registerProcessStartEvent(
Expand Down
8 changes: 2 additions & 6 deletions vmicore/src/lib/plugins/PluginSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include "../os/IActiveProcessesSupervisor.h"
#include "../vmi/InterruptEventSupervisor.h"
#include "../vmi/LibvmiInterface.h"
#include "PluginException.h"
#include <cstdint>
#include <functional>
#include <map>
Expand Down Expand Up @@ -78,11 +77,8 @@ namespace VmiCore

[[nodiscard]] std::unique_ptr<std::string> getResultsDir() const override;

[[nodiscard]] std::unique_ptr<std::vector<uint8_t>>
readPagesWithUnmappedRegionPadding(uint64_t pageAlignedVA, uint64_t cr3, uint64_t numberOfPages) const;

[[nodiscard]] std::unique_ptr<std::vector<uint8_t>>
readProcessMemoryRegion(pid_t pid, addr_t address, size_t numberOfBytes) const override;
[[nodiscard]] std::unique_ptr<IMemoryMapping>
mapProcessMemoryRegion(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) const override;

[[nodiscard]] std::unique_ptr<std::vector<std::shared_ptr<const ActiveProcessInformation>>>
getRunningProcesses() const override;
Expand Down
21 changes: 21 additions & 0 deletions vmicore/src/lib/vmi/LibvmiInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "VmiException.h"
#include "VmiInitData.h"
#include "VmiInitError.h"
#include <source_location>
#include <utility>
#include <vmicore/filename.h>

Expand Down Expand Up @@ -182,6 +183,26 @@ namespace VmiCore
return true;
}

mapped_regions_t LibvmiInterface::mmapGuest(addr_t baseVA, addr_t dtb, std::size_t numberOfPages)
{
mapped_regions_t regions{};
auto accessContext = createVirtualAddressAccessContext(baseVA, dtb);
std::lock_guard lock(libvmiLock);
if (vmi_mmap_guest_2(vmiInstance, &accessContext, numberOfPages, PROT_READ, &regions) != VMI_SUCCESS)
{
throw VmiException(fmt::format("{}: Unable to create memory mapping for VA {:#x} with number of pages {}",
std::source_location::current().function_name(),
baseVA,
numberOfPages));
}
return regions;
}

void LibvmiInterface::freeMappedRegions(const mapped_regions_t& mappedRegions)
{
vmi_free_mapped_regions(vmiInstance, &mappedRegions);
}

void LibvmiInterface::write8PA(addr_t physicalAddress, uint8_t value)
{
auto accessContext = createPhysicalAddressAccessContext(physicalAddress);
Expand Down
8 changes: 8 additions & 0 deletions vmicore/src/lib/vmi/LibvmiInterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ namespace VmiCore

virtual void clearEvent(vmi_event_t& event, bool deallocate) = 0;

virtual mapped_regions_t mmapGuest(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) = 0;

virtual void freeMappedRegions(const mapped_regions_t& mappedRegions) = 0;

virtual void write8PA(addr_t physicalAddress, uint8_t value) = 0;

virtual void eventsListen(uint32_t timeout) = 0;
Expand Down Expand Up @@ -79,6 +83,10 @@ namespace VmiCore
[[nodiscard]] bool
readXVA(addr_t virtualAddress, addr_t cr3, std::vector<uint8_t>& content, std::size_t size) override;

mapped_regions_t mmapGuest(addr_t baseVA, addr_t dtb, std::size_t numberOfPages) override;

void freeMappedRegions(const mapped_regions_t& mappedRegions) override;

void write8PA(addr_t physicalAddress, uint8_t value) override;

void eventsListen(uint32_t timeout) override;
Expand Down
50 changes: 50 additions & 0 deletions vmicore/src/lib/vmi/MemoryMapping.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "MemoryMapping.h"
#include <vmicore/filename.h>

// Currently implemented in GNU libstdc++ but missing in LLVM libc++.
#ifdef __cpp_lib_is_layout_compatible
#include <type_traits>

static_assert(std::is_layout_compatible_v<mapped_region, VmiCore::MappedRegion>,
"Layout of libvmi mapped_region not compatible with VmiCore MappedRegion");
#endif

namespace VmiCore
{
MemoryMapping::MemoryMapping(const std::shared_ptr<ILogging>& logging,
std::shared_ptr<ILibvmiInterface> vmiInterface,
mapped_regions_t mappedRegions)
: logger(logging->newNamedLogger(FILENAME_STEM)),
vmiInterface(std::move(vmiInterface)),
libvmiMappings(mappedRegions)
{
}

MemoryMapping::~MemoryMapping()
{
if (isMapped)
{
unmap();
}
}

std::span<const MappedRegion> MemoryMapping::getMappedRegions() const
{
if (!isMapped)
{
throw MemoryMappingError("Cannot retrieve mappings for regions that have already been unmapped");
}

return {std::bit_cast<MappedRegion*>(libvmiMappings.regions), libvmiMappings.size};
}

void MemoryMapping::unmap()
{
if (isMapped)
{
vmiInterface->freeMappedRegions(libvmiMappings);

isMapped = false;
}
}
}
Loading

0 comments on commit cfbe9d4

Please sign in to comment.