mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-04-03 05:57:40 -05:00
feat: Added Linux support to the Process Memory Provider (#1331)
<!-- Please provide as much information as possible about what your PR aims to do. PRs with no description will most likely be closed until more information is provided. If you're planing on changing fundamental behaviour or add big new features, please open a GitHub Issue first before starting to work on it. If it's not something big and you still want to contact us about it, feel free to do so ! --> ### Problem description <!-- Describe the bug that you fixed/feature request that you implemented, or link to an existing issue describing it --> Implement a Linux backend for the ProcessMemoryProvider plugin. ### Implementation description <!-- Explain what you did to correct the problem --> Most of the provider code is the same between Windows and Linux. The primary differences are: - enumerate PIDs in `/proc/` to get the process list - use `/proc/<PID>/cmdline` as the process name - parse `/proc/<PID>/maps` to get the module list - reading/writing from memory is done using `process_vm_readv`/`process_vm_writev` NOTE: `sudo setcap CAP_SYS_PTRACE=+eip build/imhex` must be run to give the binary permission to read another process' memory. Running as root user should also work but I would not recommend it. ### Additional things The existing translations keys no longer match since I moved the plugin from `windows` to `builtin`. I'm not well versed in C++ so I attempted to keep my changes rather simple. Feedback is very welcome. --------- Co-authored-by: WerWolv <werwolv98@gmail.com>
This commit is contained in:
@@ -60,6 +60,7 @@ add_imhex_plugin(
|
||||
source/content/providers/intel_hex_provider.cpp
|
||||
source/content/providers/motorola_srec_provider.cpp
|
||||
source/content/providers/memory_file_provider.cpp
|
||||
source/content/providers/process_memory_provider.cpp
|
||||
|
||||
source/content/tools/ascii_table.cpp
|
||||
source/content/tools/base_converter.cpp
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(OS_WINDOWS) || defined (OS_LINUX)
|
||||
|
||||
#include <hex/providers/provider.hpp>
|
||||
#include <hex/api/localization_manager.hpp>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
#include <hex/ui/widgets.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <set>
|
||||
#include <thread>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
#include <windows.h>
|
||||
#elif defined(OS_LINUX)
|
||||
#include <sys/types.h>
|
||||
#endif
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
class ProcessMemoryProvider : public hex::prv::Provider {
|
||||
public:
|
||||
ProcessMemoryProvider() = default;
|
||||
~ProcessMemoryProvider() override = default;
|
||||
|
||||
[[nodiscard]] bool isAvailable() const override {
|
||||
#ifdef _WIN32
|
||||
return this->m_processHandle != nullptr;
|
||||
#elif __linux__
|
||||
return this->m_processId != -1;
|
||||
#endif
|
||||
}
|
||||
[[nodiscard]] bool isReadable() const override { return true; }
|
||||
[[nodiscard]] bool isWritable() const override { return true; }
|
||||
[[nodiscard]] bool isResizable() const override { return false; }
|
||||
[[nodiscard]] bool isSavable() const override { return false; }
|
||||
[[nodiscard]] bool isDumpable() const override { return false; }
|
||||
|
||||
void readRaw(u64 address, void *buffer, size_t size) override;
|
||||
void writeRaw(u64 address, const void *buffer, size_t size) override;
|
||||
[[nodiscard]] u64 getActualSize() const override { return 0xFFFF'FFFF'FFFF; }
|
||||
|
||||
void save() override {}
|
||||
|
||||
[[nodiscard]] std::string getName() const override { return hex::format("hex.builtin.provider.process_memory.name"_lang, this->m_selectedProcess != nullptr ? this->m_selectedProcess->name : ""); }
|
||||
[[nodiscard]] std::vector<Description> getDataDescription() const override {
|
||||
return {
|
||||
{ "hex.builtin.provider.process_memory.process_name"_lang, this->m_selectedProcess->name },
|
||||
{ "hex.builtin.provider.process_memory.process_id"_lang, std::to_string(this->m_selectedProcess->id) }
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] bool open() override;
|
||||
void close() override;
|
||||
|
||||
[[nodiscard]] bool hasLoadInterface() const override { return true; }
|
||||
[[nodiscard]] bool hasInterface() const override { return true; }
|
||||
bool drawLoadInterface() override;
|
||||
void drawInterface() override;
|
||||
|
||||
void loadSettings(const nlohmann::json &) override {}
|
||||
[[nodiscard]] nlohmann::json storeSettings(nlohmann::json) const override { return { }; }
|
||||
|
||||
[[nodiscard]] std::string getTypeName() const override {
|
||||
return "hex.builtin.provider.process_memory";
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<Region, bool> getRegionValidity(u64) const override;
|
||||
std::variant<std::string, i128> queryInformation(const std::string &category, const std::string &argument) override;
|
||||
|
||||
private:
|
||||
void reloadProcessModules();
|
||||
|
||||
private:
|
||||
struct Process {
|
||||
u32 id;
|
||||
std::string name;
|
||||
ImGuiExt::Texture icon;
|
||||
};
|
||||
|
||||
struct MemoryRegion {
|
||||
Region region;
|
||||
std::string name;
|
||||
|
||||
constexpr bool operator<(const MemoryRegion &other) const {
|
||||
return this->region.getStartAddress() < other.region.getStartAddress();
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Process> m_processes;
|
||||
const Process *m_selectedProcess = nullptr;
|
||||
|
||||
std::set<MemoryRegion> m_memoryRegions;
|
||||
ui::SearchableWidget<Process> m_processSearchWidget = ui::SearchableWidget<Process>([](const std::string &search, const Process &process) {
|
||||
return hex::containsIgnoreCase(process.name, search);
|
||||
});
|
||||
ui::SearchableWidget<MemoryRegion> m_regionSearchWidget = ui::SearchableWidget<MemoryRegion>([](const std::string &search, const MemoryRegion &memoryRegion) {
|
||||
return hex::containsIgnoreCase(memoryRegion.name, search);
|
||||
});
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE m_processHandle = nullptr;
|
||||
#elif __linux__
|
||||
pid_t m_processId = -1;
|
||||
#endif
|
||||
|
||||
bool m_enumerationFailed = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -538,6 +538,20 @@
|
||||
"hex.builtin.provider.mem_file.rename.desc": "Enter a name for this memory file.",
|
||||
"hex.builtin.provider.motorola_srec": "Motorola SREC Provider",
|
||||
"hex.builtin.provider.motorola_srec.name": "Motorola SREC {0}",
|
||||
"hex.builtin.provider.process_memory": "Process Memory Provider",
|
||||
"hex.builtin.provider.process_memory.enumeration_failed": "Failed to enumerate processes",
|
||||
"hex.builtin.provider.process_memory.memory_regions": "Memory Regions",
|
||||
"hex.builtin.provider.process_memory.name": "'{0}' Process Memory",
|
||||
"hex.builtin.provider.process_memory.process_name": "Process Name",
|
||||
"hex.builtin.provider.process_memory.process_id": "PID",
|
||||
"hex.builtin.provider.process_memory.region.commit": "Commit",
|
||||
"hex.builtin.provider.process_memory.region.reserve": "Reserved",
|
||||
"hex.builtin.provider.process_memory.region.private": "Private",
|
||||
"hex.builtin.provider.process_memory.region.mapped": "Mapped",
|
||||
"hex.builtin.provider.process_memory.utils": "Utils",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll": "Inject DLL",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll.success": "Successfully injected DLL '{0}'!",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll.failure": "Failed to inject DLL '{0}'!",
|
||||
"hex.builtin.provider.view": "View",
|
||||
"hex.builtin.setting.experiments": "Experiments",
|
||||
"hex.builtin.setting.experiments.description": "Experiments are features that are still in development and may not work correctly yet.\n\nFeel free to try them out and report any issues you encounter!",
|
||||
|
||||
@@ -504,6 +504,20 @@
|
||||
"hex.builtin.provider.mem_file.rename.desc": "이 메모리 파일의 이름을 입력합니다.",
|
||||
"hex.builtin.provider.motorola_srec": "모토로라 SREC 공급자",
|
||||
"hex.builtin.provider.motorola_srec.name": "모토로라 SREC {0}",
|
||||
"hex.builtin.provider.process_memory": "프로세스 메모리 공급자",
|
||||
"hex.builtin.provider.process_memory.enumeration_failed": "프로세스 열거 실패",
|
||||
"hex.builtin.provider.process_memory.memory_regions": "메모리 영역",
|
||||
"hex.builtin.provider.process_memory.name": "'{0}' 프로세스 메모리",
|
||||
"hex.builtin.provider.process_memory.process_id": "PID",
|
||||
"hex.builtin.provider.process_memory.process_name": "프로세스 이름",
|
||||
"hex.builtin.provider.process_memory.region.commit": "커밋",
|
||||
"hex.builtin.provider.process_memory.region.mapped": "맵",
|
||||
"hex.builtin.provider.process_memory.region.private": "프라이빗",
|
||||
"hex.builtin.provider.process_memory.region.reserve": "예약됨",
|
||||
"hex.builtin.provider.process_memory.utils": "도구",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll": "DLL 삽입",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll.failure": "DLL '{0}'을(를) 삽입하지 못했습니다!",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll.success": "DLL '{0}'을(를) 성공적으로 삽입했습니다!",
|
||||
"hex.builtin.provider.view": "보기",
|
||||
"hex.builtin.setting.folders": "폴더",
|
||||
"hex.builtin.setting.folders.add_folder": "새 폴더 추가",
|
||||
|
||||
@@ -504,6 +504,20 @@
|
||||
"hex.builtin.provider.mem_file.rename.desc": "输入此内存文件的名称。",
|
||||
"hex.builtin.provider.motorola_srec": "Motorola SREC",
|
||||
"hex.builtin.provider.motorola_srec.name": "Motorola SREC {0}",
|
||||
"hex.builtin.provider.process_memory": "进程内存提供器",
|
||||
"hex.builtin.provider.process_memory.enumeration_failed": "无法枚举进程",
|
||||
"hex.builtin.provider.process_memory.memory_regions": "内存区域",
|
||||
"hex.builtin.provider.process_memory.name": "'{0}' 进程内存",
|
||||
"hex.builtin.provider.process_memory.process_id": "PID",
|
||||
"hex.builtin.provider.process_memory.process_name": "进程名",
|
||||
"hex.builtin.provider.process_memory.region.commit": "提交",
|
||||
"hex.builtin.provider.process_memory.region.mapped": "映射",
|
||||
"hex.builtin.provider.process_memory.region.private": "私有",
|
||||
"hex.builtin.provider.process_memory.region.reserve": "保留",
|
||||
"hex.builtin.provider.process_memory.utils": "工具",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll": "注入DLL",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll.failure": "无法注入DLL '{0}'!",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll.success": "成功注入DLL '{0}'!",
|
||||
"hex.builtin.provider.view": "独立查看",
|
||||
"hex.builtin.setting.folders": "扩展搜索路径",
|
||||
"hex.builtin.setting.folders.add_folder": "添加新的目录",
|
||||
|
||||
@@ -504,6 +504,20 @@
|
||||
"hex.builtin.provider.mem_file.rename.desc": "Enter a name for this memory file.",
|
||||
"hex.builtin.provider.motorola_srec": "Motorola SREC 提供者",
|
||||
"hex.builtin.provider.motorola_srec.name": "Motorola SREC {0}",
|
||||
"hex.builtin.provider.process_memory": "處理序記憶體提供者",
|
||||
"hex.builtin.provider.process_memory.enumeration_failed": "無法列舉處理序",
|
||||
"hex.builtin.provider.process_memory.memory_regions": "記憶體區域",
|
||||
"hex.builtin.provider.process_memory.name": "'{0}' 處理序記憶體",
|
||||
"hex.builtin.provider.process_memory.process_id": "處理序名稱",
|
||||
"hex.builtin.provider.process_memory.process_name": "PID",
|
||||
"hex.builtin.provider.process_memory.region.commit": "已認可",
|
||||
"hex.builtin.provider.process_memory.region.mapped": "已對應",
|
||||
"hex.builtin.provider.process_memory.region.private": "私人",
|
||||
"hex.builtin.provider.process_memory.region.reserve": "受保留",
|
||||
"hex.builtin.provider.process_memory.utils": "工具",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll": "注入 DLL",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll.failure": "無法注入 DLL '{0}'!",
|
||||
"hex.builtin.provider.process_memory.utils.inject_dll.success": "已成功注入 DLL '{0}'!",
|
||||
"hex.builtin.provider.view": "View",
|
||||
"hex.builtin.setting.folders": "資料夾",
|
||||
"hex.builtin.setting.folders.add_folder": "新增資料夾",
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "content/providers/motorola_srec_provider.hpp"
|
||||
#include "content/providers/memory_file_provider.hpp"
|
||||
#include "content/providers/view_provider.hpp"
|
||||
#include <content/providers/process_memory_provider.hpp>
|
||||
#include "content/popups/popup_notification.hpp"
|
||||
#include "content/helpers/notification.hpp"
|
||||
|
||||
@@ -34,6 +35,10 @@ namespace hex::plugin::builtin {
|
||||
ContentRegistry::Provider::add<MemoryFileProvider>(false);
|
||||
ContentRegistry::Provider::add<ViewProvider>(false);
|
||||
|
||||
#if defined(OS_WINDOWS) ||defined (OS_LINUX)
|
||||
ContentRegistry::Provider::add<ProcessMemoryProvider>();
|
||||
#endif
|
||||
|
||||
ProjectFile::registerHandler({
|
||||
.basePath = "providers",
|
||||
.required = true,
|
||||
@@ -133,4 +138,4 @@ namespace hex::plugin::builtin {
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,407 @@
|
||||
#if defined(OS_WINDOWS) || defined (OS_LINUX)
|
||||
|
||||
#include <content/providers/process_memory_provider.hpp>
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#include <shellapi.h>
|
||||
#elif defined(OS_LINUX)
|
||||
#include <sys/uio.h>
|
||||
#endif
|
||||
|
||||
#include <imgui.h>
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/ui/view.hpp>
|
||||
|
||||
#include <wolv/io/fs.hpp>
|
||||
#include <wolv/io/file.hpp>
|
||||
#include <wolv/utils/guards.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
bool ProcessMemoryProvider::open() {
|
||||
#if defined(OS_WINDOWS)
|
||||
this->m_processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, this->m_selectedProcess->id);
|
||||
if (this->m_processHandle == nullptr)
|
||||
return false;
|
||||
#elif defined(OS_LINUX)
|
||||
this->m_processId = pid_t(this->m_selectedProcess->id);
|
||||
#endif
|
||||
|
||||
this->reloadProcessModules();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProcessMemoryProvider::close() {
|
||||
#if defined(OS_WINDOWS)
|
||||
CloseHandle(this->m_processHandle);
|
||||
this->m_processHandle = nullptr;
|
||||
#elif defined(OS_LINUX)
|
||||
this->m_processId = -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ProcessMemoryProvider::readRaw(u64 address, void *buffer, size_t size) {
|
||||
#if defined(OS_WINDOWS)
|
||||
ReadProcessMemory(this->m_processHandle, reinterpret_cast<LPCVOID>(address), buffer, size, nullptr);
|
||||
#elif defined(OS_LINUX)
|
||||
const iovec local {
|
||||
.iov_base = buffer,
|
||||
.iov_len = size,
|
||||
};
|
||||
const iovec remote = {
|
||||
.iov_base = reinterpret_cast<void*>(address),
|
||||
.iov_len = size,
|
||||
};
|
||||
|
||||
auto read = process_vm_readv(this->m_processId, &local, 1, &remote, 1, 0);
|
||||
|
||||
if (read == -1) {
|
||||
// TODO error handling strerror(errno)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void ProcessMemoryProvider::writeRaw(u64 address, const void *buffer, size_t size) {
|
||||
#if defined(OS_WINDOWS)
|
||||
WriteProcessMemory(this->m_processHandle, reinterpret_cast<LPVOID>(address), buffer, size, nullptr);
|
||||
#elif defined(OS_LINUX)
|
||||
const iovec local {
|
||||
.iov_base = const_cast<void*>(buffer),
|
||||
.iov_len = size,
|
||||
};
|
||||
const iovec remote = {
|
||||
.iov_base = reinterpret_cast<void*>(address),
|
||||
.iov_len = size,
|
||||
};
|
||||
|
||||
auto read = process_vm_writev(this->m_processId, &local, 1, &remote, 1, 0);
|
||||
if (read == -1) {
|
||||
// TODO error handling strerror(errno)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::pair<Region, bool> ProcessMemoryProvider::getRegionValidity(u64 address) const {
|
||||
for (const auto &memoryRegion : this->m_memoryRegions) {
|
||||
if (memoryRegion.region.overlaps({ address, 1 }))
|
||||
return { memoryRegion.region, true };
|
||||
}
|
||||
|
||||
Region lastRegion = Region::Invalid();
|
||||
for (const auto &memoryRegion : this->m_memoryRegions) {
|
||||
|
||||
if (address < memoryRegion.region.getStartAddress())
|
||||
return { Region { lastRegion.getEndAddress() + 1, memoryRegion.region.getStartAddress() - lastRegion.getEndAddress() }, false };
|
||||
|
||||
lastRegion = memoryRegion.region;
|
||||
}
|
||||
|
||||
return { Region::Invalid(), false };
|
||||
}
|
||||
|
||||
bool ProcessMemoryProvider::drawLoadInterface() {
|
||||
if (this->m_processes.empty() && !this->m_enumerationFailed) {
|
||||
#if defined(OS_WINDOWS)
|
||||
DWORD numProcesses = 0;
|
||||
std::vector<DWORD> processIds;
|
||||
|
||||
do {
|
||||
processIds.resize(processIds.size() + 1024);
|
||||
if (EnumProcesses(processIds.data(), processIds.size() * sizeof(DWORD), &numProcesses) == FALSE) {
|
||||
processIds.clear();
|
||||
this->m_enumerationFailed = true;
|
||||
break;
|
||||
}
|
||||
} while (numProcesses == processIds.size() * sizeof(DWORD));
|
||||
|
||||
processIds.resize(numProcesses / sizeof(DWORD));
|
||||
|
||||
auto dc = GetDC(nullptr);
|
||||
ON_SCOPE_EXIT { ReleaseDC(nullptr, dc); };
|
||||
for (auto processId : processIds) {
|
||||
HANDLE processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
|
||||
if (processHandle == nullptr)
|
||||
continue;
|
||||
|
||||
ON_SCOPE_EXIT { CloseHandle(processHandle); };
|
||||
|
||||
char processName[MAX_PATH];
|
||||
if (GetModuleBaseNameA(processHandle, nullptr, processName, MAX_PATH) == 0)
|
||||
continue;
|
||||
|
||||
ImGuiExt::Texture texture;
|
||||
{
|
||||
HMODULE moduleHandle = nullptr;
|
||||
DWORD numModules = 0;
|
||||
if (EnumProcessModules(processHandle, &moduleHandle, sizeof(HMODULE), &numModules) != FALSE) {
|
||||
char modulePath[MAX_PATH];
|
||||
if (GetModuleFileNameExA(processHandle, moduleHandle, modulePath, MAX_PATH) != FALSE) {
|
||||
SHFILEINFOA fileInfo;
|
||||
if (SHGetFileInfoA(modulePath, 0, &fileInfo, sizeof(SHFILEINFOA), SHGFI_ICON | SHGFI_SMALLICON) > 0) {
|
||||
ON_SCOPE_EXIT { DestroyIcon(fileInfo.hIcon); };
|
||||
|
||||
ICONINFO iconInfo;
|
||||
if (GetIconInfo(fileInfo.hIcon, &iconInfo) != FALSE) {
|
||||
ON_SCOPE_EXIT { DeleteObject(iconInfo.hbmColor); DeleteObject(iconInfo.hbmMask); };
|
||||
|
||||
BITMAP bitmap;
|
||||
if (GetObject(iconInfo.hbmColor, sizeof(BITMAP), &bitmap) > 0) {
|
||||
BITMAPINFO bitmapInfo = { };
|
||||
bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bitmapInfo.bmiHeader.biWidth = bitmap.bmWidth;
|
||||
bitmapInfo.bmiHeader.biHeight = -bitmap.bmHeight;
|
||||
bitmapInfo.bmiHeader.biPlanes = 1;
|
||||
bitmapInfo.bmiHeader.biBitCount = 32;
|
||||
bitmapInfo.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
std::vector<u32> pixels(bitmap.bmWidth * bitmap.bmHeight * 4);
|
||||
if (GetDIBits(dc, iconInfo.hbmColor, 0, bitmap.bmHeight, pixels.data(), &bitmapInfo, DIB_RGB_COLORS) > 0) {
|
||||
for (auto &pixel : pixels)
|
||||
pixel = (pixel & 0xFF00FF00) | ((pixel & 0xFF) << 16) | ((pixel & 0xFF0000) >> 16);
|
||||
|
||||
texture = ImGuiExt::Texture(reinterpret_cast<const u8*>(pixels.data()), pixels.size(), bitmap.bmWidth, bitmap.bmHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->m_processes.push_back({ u32(processId), processName, std::move(texture) });
|
||||
}
|
||||
#elif defined(OS_LINUX)
|
||||
for (const auto& entry : std::fs::directory_iterator("/proc")) {
|
||||
if (!std::fs::is_directory(entry)) continue;
|
||||
|
||||
const auto &path = entry.path();
|
||||
u32 processId = 0;
|
||||
try {
|
||||
processId = std::stoi(path.filename());
|
||||
} catch (...) {
|
||||
continue; // not a PID
|
||||
}
|
||||
|
||||
wolv::io::File file(path /"cmdline", wolv::io::File::Mode::Read);
|
||||
if (!file.isValid())
|
||||
continue;
|
||||
|
||||
std::string processName = file.readString(0xF'FFFF);
|
||||
|
||||
this->m_processes.emplace_back(processId, processName, ImGuiExt::Texture());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (this->m_enumerationFailed) {
|
||||
ImGui::TextUnformatted("hex.builtin.provider.process_memory.enumeration_failed"_lang);
|
||||
} else {
|
||||
ImGui::PushItemWidth(500_scaled);
|
||||
const auto &filtered = this->m_processSearchWidget.draw(this->m_processes);
|
||||
ImGui::PopItemWidth();
|
||||
if (ImGui::BeginTable("##process_table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY, ImVec2(500_scaled, 500_scaled))) {
|
||||
ImGui::TableSetupColumn("##icon");
|
||||
ImGui::TableSetupColumn("hex.builtin.provider.process_memory.process_id"_lang);
|
||||
ImGui::TableSetupColumn("hex.builtin.provider.process_memory.process_name"_lang);
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (auto &process : filtered) {
|
||||
ImGui::PushID(process);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Image(process->icon, process->icon.getSize());
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d", process->id);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Selectable(process->name.c_str(), this->m_selectedProcess != nullptr && process->id == this->m_selectedProcess->id, ImGuiSelectableFlags_SpanAllColumns, ImVec2(0, process->icon.getSize().y)))
|
||||
this->m_selectedProcess = process;
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return this->m_selectedProcess != nullptr;
|
||||
}
|
||||
|
||||
void ProcessMemoryProvider::drawInterface() {
|
||||
ImGuiExt::Header("hex.builtin.provider.process_memory.memory_regions"_lang, true);
|
||||
|
||||
auto availableX = ImGui::GetContentRegionAvail().x;
|
||||
ImGui::PushItemWidth(availableX);
|
||||
const auto &filtered = this->m_regionSearchWidget.draw(this->m_memoryRegions);
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
auto availableY = 400_scaled;
|
||||
#else
|
||||
// Take up full height on Linux since there are no DLL injection controls
|
||||
auto availableY = ImGui::GetContentRegionAvail().y;
|
||||
#endif
|
||||
|
||||
if (ImGui::BeginTable("##module_table", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY, ImVec2(availableX, availableY))) {
|
||||
ImGui::TableSetupColumn("hex.builtin.common.region"_lang);
|
||||
ImGui::TableSetupColumn("hex.builtin.common.size"_lang);
|
||||
ImGui::TableSetupColumn("hex.builtin.common.name"_lang);
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (const auto &memoryRegion : filtered) {
|
||||
ImGui::PushID(&memoryRegion);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGuiExt::TextFormatted("0x{0:016X} - 0x{1:016X}", memoryRegion->region.getStartAddress(), memoryRegion->region.getEndAddress());
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(hex::toByteString(memoryRegion->region.getSize()).c_str());
|
||||
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
if (ImGui::Selectable(memoryRegion->name.c_str(), false, ImGuiSelectableFlags_SpanAllColumns))
|
||||
ImHexApi::HexEditor::setSelection(memoryRegion->region);
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
ImGuiExt::Header("hex.builtin.provider.process_memory.utils"_lang);
|
||||
|
||||
if (ImGui::Button("hex.builtin.provider.process_memory.utils.inject_dll"_lang)) {
|
||||
hex::fs::openFileBrowser(fs::DialogMode::Open, { { "DLL File", "dll" } }, [this](const std::fs::path &path) {
|
||||
const auto &dllPath = path.native();
|
||||
const auto dllPathLength = (dllPath.length() + 1) * sizeof(std::fs::path::value_type);
|
||||
|
||||
if (auto pathAddress = VirtualAllocEx(this->m_processHandle, nullptr, dllPathLength, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); pathAddress != nullptr) {
|
||||
if (WriteProcessMemory(this->m_processHandle, pathAddress, dllPath.c_str(), dllPathLength, nullptr) != FALSE) {
|
||||
auto loadLibraryW = reinterpret_cast<LPTHREAD_START_ROUTINE>(reinterpret_cast<void*>(GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryW")));
|
||||
if (loadLibraryW != nullptr) {
|
||||
if (auto threadHandle = CreateRemoteThread(this->m_processHandle, nullptr, 0, loadLibraryW, pathAddress, 0, nullptr); threadHandle != nullptr) {
|
||||
WaitForSingleObject(threadHandle, INFINITE);
|
||||
EventManager::post<RequestOpenErrorPopup>(hex::format("hex.builtin.provider.process_memory.utils.inject_dll.success"_lang, path.filename().string()));
|
||||
this->reloadProcessModules();
|
||||
CloseHandle(threadHandle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EventManager::post<RequestOpenErrorPopup>(hex::format("hex.builtin.provider.process_memory.utils.inject_dll.failure"_lang, path.filename().string()));
|
||||
});
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ProcessMemoryProvider::reloadProcessModules() {
|
||||
this->m_memoryRegions.clear();
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
DWORD numModules = 0;
|
||||
std::vector<HMODULE> modules;
|
||||
|
||||
do {
|
||||
modules.resize(modules.size() + 1024);
|
||||
if (EnumProcessModules(this->m_processHandle, modules.data(), modules.size() * sizeof(HMODULE), &numModules) == FALSE) {
|
||||
modules.clear();
|
||||
break;
|
||||
}
|
||||
} while (numModules == modules.size() * sizeof(HMODULE));
|
||||
|
||||
modules.resize(numModules / sizeof(HMODULE));
|
||||
|
||||
for (auto &module : modules) {
|
||||
MODULEINFO moduleInfo;
|
||||
if (GetModuleInformation(this->m_processHandle, module, &moduleInfo, sizeof(MODULEINFO)) == FALSE)
|
||||
continue;
|
||||
|
||||
char moduleName[MAX_PATH];
|
||||
if (GetModuleFileNameExA(this->m_processHandle, module, moduleName, MAX_PATH) == FALSE)
|
||||
continue;
|
||||
|
||||
this->m_memoryRegions.insert({ { u64(moduleInfo.lpBaseOfDll), size_t(moduleInfo.SizeOfImage) }, std::fs::path(moduleName).filename().string() });
|
||||
}
|
||||
|
||||
MEMORY_BASIC_INFORMATION memoryInfo;
|
||||
for (u64 address = 0; address < this->getActualSize(); address += memoryInfo.RegionSize) {
|
||||
if (VirtualQueryEx(this->m_processHandle, reinterpret_cast<LPCVOID>(address), &memoryInfo, sizeof(MEMORY_BASIC_INFORMATION)) == 0)
|
||||
break;
|
||||
|
||||
std::string name;
|
||||
if (memoryInfo.State & MEM_IMAGE) continue;
|
||||
if (memoryInfo.State & MEM_FREE) continue;
|
||||
if (memoryInfo.State & MEM_COMMIT) name += hex::format("{} ", "hex.builtin.provider.process_memory.region.commit"_lang);
|
||||
if (memoryInfo.State & MEM_RESERVE) name += hex::format("{} ", "hex.builtin.provider.process_memory.region.reserve"_lang);
|
||||
if (memoryInfo.State & MEM_PRIVATE) name += hex::format("{} ", "hex.builtin.provider.process_memory.region.private"_lang);
|
||||
if (memoryInfo.State & MEM_MAPPED) name += hex::format("{} ", "hex.builtin.provider.process_memory.region.mapped"_lang);
|
||||
|
||||
this->m_memoryRegions.insert({ { reinterpret_cast<u64>(memoryInfo.BaseAddress), reinterpret_cast<u64>(memoryInfo.BaseAddress) + memoryInfo.RegionSize }, name });
|
||||
}
|
||||
|
||||
#elif defined(OS_LINUX)
|
||||
|
||||
wolv::io::File file(std::fs::path("/proc") / std::to_string(this->m_processId) / "maps", wolv::io::File::Mode::Read);
|
||||
|
||||
if (!file.isValid())
|
||||
return;
|
||||
|
||||
for (const auto &line : wolv::util::splitString(file.readString(0xF'FFFF), "\n")) {
|
||||
const auto &split = wolv::util::splitString(line, " ");
|
||||
if (split.size() < 6)
|
||||
continue;
|
||||
|
||||
const u64 start = std::stoull(split[0].substr(0, split[0].find('-')), nullptr, 16);
|
||||
const u64 end = std::stoull(split[0].substr(split[0].find('-') + 1), nullptr, 16);
|
||||
const auto &name = split[5];
|
||||
|
||||
this->m_memoryRegions.insert({ { start, end - start }, name });
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
std::variant<std::string, i128> ProcessMemoryProvider::queryInformation(const std::string &category, const std::string &argument) {
|
||||
auto findRegionByName = [this](const std::string &name) {
|
||||
return std::find_if(this->m_memoryRegions.begin(), this->m_memoryRegions.end(),
|
||||
[name](const auto ®ion) {
|
||||
return region.name == name;
|
||||
});
|
||||
};
|
||||
|
||||
if (category == "region_address") {
|
||||
if (auto iter = findRegionByName(argument); iter != this->m_memoryRegions.end())
|
||||
return iter->region.getStartAddress();
|
||||
else
|
||||
return 0;
|
||||
} else if (category == "region_size") {
|
||||
if (auto iter = findRegionByName(argument); iter != this->m_memoryRegions.end())
|
||||
return iter->region.getSize();
|
||||
else
|
||||
return 0;
|
||||
} else if (category == "process_id") {
|
||||
return this->m_selectedProcess->id;
|
||||
} else if (category == "process_name") {
|
||||
return this->m_selectedProcess->name;
|
||||
} else
|
||||
return Provider::queryInformation(category, argument);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user