diff options
| author | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-13 17:29:05 +1100 |
|---|---|---|
| committer | Nicolas James <Eele1Ephe7uZahRie@tutanota.com> | 2025-02-13 17:29:05 +1100 |
| commit | 048d28b28dcaba3b0773c129d8dd63084420b01e (patch) | |
| tree | 513fc594ef6026fcfd02bc3abf743e01055fcccd /src | |
initial commit
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.cc | 164 | ||||
| -rw-r--r-- | src/main.hh | 16 | ||||
| -rw-r--r-- | src/memory/maps.cc | 60 | ||||
| -rw-r--r-- | src/memory/maps.hh | 62 | ||||
| -rw-r--r-- | src/memory/memory.cc | 54 | ||||
| -rw-r--r-- | src/memory/memory.hh | 43 |
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 |
