aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.cc164
-rw-r--r--src/main.hh16
-rw-r--r--src/memory/maps.cc60
-rw-r--r--src/memory/maps.hh62
-rw-r--r--src/memory/memory.cc54
-rw-r--r--src/memory/memory.hh43
6 files changed, 399 insertions, 0 deletions
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 0000000..6c049b5
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,164 @@
+#include "main.hh"
+
+using pointer_t = std::byte* const;
+struct pointer {
+ const pointer_t address;
+ const std::byte* const deref;
+ std::shared_ptr<struct pointer> parent;
+ const size_t start_offset;
+ const size_t total_offset;
+};
+using pointers_t = std::vector<std::shared_ptr<struct pointer>>;
+
+static pointers_t make_pointers(const memory::buffer_t& memory,
+ const pointers_t& targets,
+ const void* const start, const int tolerance,
+ const size_t total_offset) {
+
+ pointers_t ret{};
+ for (auto i = 0ul; i < std::size(memory) / sizeof(pointer_t); ++i) {
+ const auto byte_offset = i * sizeof(pointer_t);
+
+ const std::byte* const deref = [&]() {
+ std::byte* ret;
+ std::memcpy(&ret, &memory[byte_offset], sizeof(void*));
+ return ret;
+ }();
+
+ // -1 is 0xff...etc
+ if (deref == nullptr || deref > (pointer_t)(-1) - tolerance) {
+ continue;
+ }
+
+ const auto lower = std::lower_bound(
+ std::begin(targets), std::end(targets), deref,
+ [](const auto& target, const std::byte* const& deref) {
+ return target->address < deref;
+ });
+ const auto upper = std::upper_bound(
+ lower, std::end(targets), deref + tolerance,
+ [](const std::byte* const& deref, const auto& target) {
+ return deref < target->address;
+ });
+
+ std::for_each(lower, upper, [&](const auto& target) {
+ const auto address = (std::byte*)start + byte_offset;
+ if (address == target->address || deref == target->deref) {
+ return;
+ }
+ ret.emplace_back(std::make_shared<struct pointer>(
+ pointer{.address = address,
+ .deref = deref,
+ .parent = target,
+ .start_offset = byte_offset,
+ .total_offset = total_offset + byte_offset}));
+ });
+ }
+ return ret;
+}
+
+static pointers_t make_pointer_chain(const char* const binary_name,
+ const pointer_t target, const int depth,
+ const int tolerance) {
+ const memory::maps maps{binary_name};
+ const pid_t pid = std::stoi(maps.get_pid());
+
+ pointers_t ret{std::make_shared<struct pointer>(
+ pointer{.address = target, .parent = nullptr})};
+ for (int i = 0; i < depth; ++i) {
+
+ size_t total_offset = 0;
+ pointers_t pointers{};
+ for (const auto& map : maps) {
+ const auto size = static_cast<std::size_t>((std::byte*)map.end -
+ (std::byte*)map.start);
+ total_offset += size;
+ if (!map.is_read() || !map.is_write() || !map.is_protected()) {
+ continue;
+ }
+
+ const memory::buffer_t memory =
+ memory::read_buffer(size, pid, map.start);
+
+ std::ranges::move(make_pointers(memory, ret, map.start, tolerance,
+ total_offset - size),
+ std::back_inserter(pointers));
+ }
+
+ std::cout << "depth: " << i + 1 << " = " << std::size(pointers)
+ << " chains...\n";
+
+ if (std::size(pointers) == 0) {
+ std::cout << "warning: only discovered chains of depth " << i
+ << '\n';
+ break;
+ }
+
+ ret = std::move(pointers);
+ }
+ return ret;
+}
+
+static void do_pointer_scanner(const char* const binary_name,
+ const pointer_t target, const int depth,
+ const int tolerance) {
+
+ const auto pointer_chains =
+ make_pointer_chain(binary_name, target, depth, tolerance);
+
+ for (const auto& chain : pointer_chains) {
+ std::cout << std::hex << chain->total_offset << " = " << chain->address
+ << std::dec;
+ const std::byte* prev = chain->deref;
+ for (const decltype(chain->parent)* p = &chain->parent; p != nullptr;
+ p = &(*p)->parent) {
+ if (*p == nullptr) {
+ break;
+ }
+
+ if ((*p)->address != nullptr) {
+ std::cout << " ] ";
+ }
+ if (prev != nullptr) {
+ std::cout << ((*p)->address - prev);
+ }
+ prev = (*p)->deref;
+
+ if ((*p)->address == nullptr) {
+ break;
+ }
+ }
+ std::cout << '\n';
+ }
+}
+
+int main(const int argc, const char* const argv[]) {
+ if (argc < 3 || argc > 5) {
+ std::cout << "usage: " << argv[0]
+ << " BINARY_NAME 0xADDRESS DEPTH=10 "
+ "TOLERANCE=4096bytes\n";
+ return EXIT_SUCCESS;
+ }
+
+ std::cout.sync_with_stdio(false);
+
+ try {
+ const char* const binary_name = argv[1];
+ const pointer_t address = [&]() {
+ std::stringstream ss{argv[2]};
+ void* ptr;
+ ss >> std::hex >> ptr;
+ return static_cast<pointer_t>(ptr);
+ }();
+ const int depth = (argc >= 4 ? std::stoi(argv[3]) : 10);
+ const int tolerance = (argc >= 5 ? std::stoi(argv[4]) : 4096);
+
+ do_pointer_scanner(binary_name, address, depth, tolerance);
+ } catch (const std::exception& e) {
+ std::cerr << "unhandled exception!\n what(): " << e.what() << '\n';
+ } catch (...) {
+ std::cerr << "unhandled unknown exception!\n";
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/src/main.hh b/src/main.hh
new file mode 100644
index 0000000..119bcf6
--- /dev/null
+++ b/src/main.hh
@@ -0,0 +1,16 @@
+#ifndef MAIN_HH_
+#define MAIN_HH_
+
+#include <algorithm>
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <ranges>
+#include <string>
+#include <vector>
+
+#include "memory/maps.hh"
+#include "memory/memory.hh"
+
+#endif
diff --git a/src/memory/maps.cc b/src/memory/maps.cc
new file mode 100644
index 0000000..290675a
--- /dev/null
+++ b/src/memory/maps.cc
@@ -0,0 +1,60 @@
+#include "memory/maps.hh"
+
+namespace memory {
+
+static std::string get_pid_from_name(const std::string& name) {
+ for (const auto& pid : std::filesystem::directory_iterator("/proc")) {
+ if (!pid.is_directory()) {
+ continue;
+ }
+
+ std::ifstream file{std::string{pid.path()} + "/comm"};
+ if (!file.is_open()) {
+ continue;
+ }
+ const std::string contents{std::istreambuf_iterator<char>{file},
+ std::istreambuf_iterator<char>{}};
+ if (!contents.starts_with(name)) {
+ continue;
+ }
+
+ const std::string pathname = pid.path();
+ const auto pos = static_cast<long>(pathname.find_last_of('/'));
+ return {std::next(std::begin(pathname) + pos), std::end(pathname)};
+ }
+
+ throw std::runtime_error{"failed to get pid from name \"" + name +
+ "\", is it open?"};
+}
+
+maps::maps(const char* const name) : pid(get_pid_from_name(name)) {
+ std::ifstream file{"/proc/" + this->pid + "/maps"};
+ if (!file.is_open()) {
+ throw std::runtime_error{"failed to open maps for pid \"" + this->pid +
+ '\"'};
+ }
+ std::stringstream file_ss;
+ file_ss << file.rdbuf();
+ for (std::string line; std::getline(file_ss, line);) {
+ if (line.empty()) {
+ continue;
+ }
+
+ std::stringstream line_ss{line};
+
+ // address perms offset dev inode pathname
+ // 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/dbus-daemon
+ maps::region region;
+ line_ss >> region.start;
+ line_ss.ignore(); // ignore dash
+ line_ss >> region.end;
+ line_ss >> region.perms;
+ line_ss >> region.offset;
+ line_ss >> region.dev;
+ line_ss >> region.inode;
+ line_ss >> region.pathname;
+ this->regions.push_back(std::move(region));
+ }
+}
+
+} // namespace memory
diff --git a/src/memory/maps.hh b/src/memory/maps.hh
new file mode 100644
index 0000000..67914b6
--- /dev/null
+++ b/src/memory/maps.hh
@@ -0,0 +1,62 @@
+#ifndef MEMORY_HH_
+#define MEMORY_HH_
+
+#include <filesystem>
+#include <fstream>
+#include <sstream>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+namespace memory {
+
+class maps {
+public:
+ struct region {
+ public:
+ void* start;
+ void* end;
+ char perms[5];
+ std::string offset; // TODO who cares
+ std::string dev;
+ int inode;
+ std::string pathname;
+
+ public:
+ region() noexcept = default;
+ region(const region&) = default;
+ region(region&&) = default;
+
+ public:
+ bool is_read() const noexcept { return this->perms[0] == 'r'; }
+ bool is_write() const noexcept { return this->perms[1] == 'w'; }
+ bool is_execute() const noexcept { return this->perms[2] == 'x'; }
+ bool is_protected() const noexcept { return this->perms[3] == 'p'; }
+ };
+ using regions_t = std::vector<region>;
+
+private:
+ std::string pid;
+ regions_t regions;
+
+public:
+ auto begin() const noexcept {
+ return std::begin(this->regions);
+ }
+ auto end() const noexcept {
+ return std::end(this->regions);
+ }
+
+public:
+ maps(const char* const name);
+ maps(const maps& m) = default;
+ maps(maps&&) = default;
+
+public:
+ const std::string& get_pid() const noexcept { return this->pid; }
+ const regions_t& get_regions() const noexcept { return this->regions; }
+};
+
+} // namespace memory
+
+#endif
diff --git a/src/memory/memory.cc b/src/memory/memory.cc
new file mode 100644
index 0000000..59fb50f
--- /dev/null
+++ b/src/memory/memory.cc
@@ -0,0 +1,54 @@
+#include "memory/memory.hh"
+
+namespace memory {
+
+buffer_t read_buffer(const std::size_t num_bytes, const pid_t& pid,
+ const void* const address) {
+ buffer_t buffer{num_bytes};
+ const struct iovec local {
+ .iov_base = std::data(buffer), .iov_len = std::size(buffer)
+ };
+ const struct iovec remote {
+ .iov_base = const_cast<void* const>(address), .iov_len = num_bytes
+ };
+
+ if (const auto read = process_vm_readv(pid, &local, 1, &remote, 1, 0);
+ static_cast<unsigned long>(read) != num_bytes) {
+
+ if (read == -1) {
+ throw std::runtime_error{"read fail - " +
+ std::string{strerror(errno)}};
+ }
+ throw std::runtime_error("read fail - not enough bytes read (" +
+ std::to_string(num_bytes) + " wanted, " +
+ std::to_string(read) + " read)");
+ }
+
+ return buffer;
+}
+
+void write_buffer(const buffer_t& buffer, const pid_t& pid,
+ const void* const address) {
+ const struct iovec local {
+ .iov_base = (void*)std::data(buffer), .iov_len = std::size(buffer)
+ };
+ const struct iovec remote {
+ .iov_base = const_cast<void* const>(address),
+ .iov_len = std::size(buffer)
+ };
+
+ if (const auto written = process_vm_writev(pid, &local, 1, &remote, 1, 0);
+ static_cast<unsigned long>(written) != std::size(buffer)) {
+
+ if (written == -1) {
+ throw std::runtime_error{"write fail - " +
+ std::string{strerror(errno)}};
+ }
+ throw std::runtime_error("read fail - not enough bytes written (" +
+ std::to_string(std::size(buffer)) +
+ " wanted, " + std::to_string(written) +
+ " written)");
+ }
+}
+
+} // namespace memory
diff --git a/src/memory/memory.hh b/src/memory/memory.hh
new file mode 100644
index 0000000..c35feea
--- /dev/null
+++ b/src/memory/memory.hh
@@ -0,0 +1,43 @@
+#ifndef MEMORY_MEMORY_HH_
+#define MEMORY_MEMORY_HH_
+
+#include <cstddef>
+#include <cstring>
+#include <sys/uio.h>
+#include <vector>
+#include <stdexcept>
+
+namespace memory {
+
+using buffer_t = std::vector<std::byte>;
+
+buffer_t read_buffer(const std::size_t num_bytes, const pid_t& pid,
+ const void* const address);
+void write_buffer(const buffer_t& buffer, const pid_t& pid,
+ const void* const address);
+
+template <typename T>
+T read(const pid_t& pid, const void* const address) {
+ static_assert(std::is_trivially_copyable<T>::value);
+
+ const buffer_t data = read_buffer(sizeof(T), pid, address);
+ T ret;
+ std::memcpy(&ret, std::data(data), sizeof(T));
+ return std::move(ret);
+}
+
+template <typename T>
+void write(const T& data, const pid_t& pid, const void* const address) {
+ static_assert(std::is_trivially_copyable<T>::value);
+
+ const buffer_t buffer = [&]() {
+ buffer_t buffer{sizeof(T)};
+ std::memcpy(std::data(buffer), &data, sizeof(T));
+ return buffer;
+ }();
+ write_buffer(buffer, pid, address);
+}
+
+} // namespace memory
+
+#endif