From b291587c9f146d3f174ecb59533bce895f04f88a Mon Sep 17 00:00:00 2001 From: Daniel Trugman Date: Mon, 22 Feb 2021 21:14:01 +0000 Subject: [PATCH] Add task memory reading Add API for direct access to a tasks' memory through the mem file --- README.md | 31 +++++++++++++++----- include/pfs/mem.hpp | 53 +++++++++++++++++++++++++++++++++++ include/pfs/task.hpp | 3 ++ sample/format.hpp | 22 +++++++++++++++ sample/sample.cpp | 10 +++++++ src/mem.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++++ src/task.cpp | 8 ++++++ test/mem.cpp | 15 ++++++++++ 8 files changed, 202 insertions(+), 7 deletions(-) create mode 100644 include/pfs/mem.hpp create mode 100644 src/mem.cpp create mode 100644 test/mem.cpp diff --git a/README.md b/README.md index d71e383..969a8c0 100644 --- a/README.md +++ b/README.md @@ -51,25 +51,42 @@ That's it, you are good to go. The directory `sample` contains a full blown application that calls all(!) the supported APIs and prints all the information gathered. When compiling the library, the sample applications is compiled as well. -Anyway, here are some minimal examples: +Anyway, here are some cool examples: -*Example 1:* Get all the loaded kernel modules +**Example 1:** Iterater over all the loaded unsigned or out-of-tree kernel modules ``` auto pfs = pfs::procfs(); auto modules = pfs.get_modules(); +for (const auto& module : modules) +{ + if (module.is_out_of_tree || module.is_unsigned) + { + ... do your work ... + } +} ``` -*Example 2:* Iterate over the memory maps for task 1234 (This can be both a process or a thread) +**Example 2:** Find all the memory maps for task 1234 (This can be both a process or a thread) that start with an ELFs header ``` auto task = pfs::procfs().get_task(1234); +auto mem = task.get_mem(); for (auto& map : task.get_maps()) { - ... do your work ... + if (!map.perm.can_read) + { + continue; + } + + static const std::vector ELF_HEADER = { 0x7F, 0x45, 0x4C, 0x46 }; + if (mem.read(map.start_address, ELF_HEADER.size()) == ELF_HEADER) + { + ... do your work ... + } } ``` _(You can either create `pfs` every time or once and keep it, the overhead is really small)_ -*Example 3:* Iterate over all the IPv4 TCP socket currently in listening state (in my current network namespace): +**Example 3:** Iterate over all the IPv4 TCP sockets currently in listening state (in my current network namespace): ``` // Same as pfs::procfs().get_task().get_net().get_tcp() for (auto& socket : pfs::procfs().get_net().get_tcp()) @@ -82,7 +99,7 @@ for (auto& socket : pfs::procfs().get_net().get_tcp()) ``` _(API behaves similar to the `procfs`, where `/proc/net` is a soft link to `/proc/self/net`)_ -*Example 4:* Get all the IPv6 UDP sockets in the root network namespace belonging to a specific user ID: +**Example 4:** Get all the IPv6 UDP sockets in the root network namespace belonging to a specific user ID: ``` for (auto& socket : pfs::procfs().get_task(1).get_net().get_udp6()) { @@ -93,7 +110,7 @@ for (auto& socket : pfs::procfs().get_task(1).get_net().get_udp6()) } ``` -*Example 5:* Check if the process catches SIGSTOP signals +**Example 5:** Check if the process catches SIGSTOP signals ``` auto status = pfs::procfs().get_task(1234).get_status(); bool handles_sigstop = status.sig_cgt.is_set(pfs::signal::sigstop); diff --git a/include/pfs/mem.hpp b/include/pfs/mem.hpp new file mode 100644 index 0000000..57106e5 --- /dev/null +++ b/include/pfs/mem.hpp @@ -0,0 +1,53 @@ +/* + * Copyright 2020-present Daniel Trugman + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PFS_MEM_HPP +#define PFS_MEM_HPP + +#include +#include + +#include "types.hpp" + +namespace pfs { + +class mem final +{ +public: + mem(const mem&) = default; + mem(mem&&) = default; + + mem& operator=(const mem&) = delete; + mem& operator=(mem&&) = delete; + + ~mem(); + +public: // API + std::vector read(const mem_region& region); + std::vector read(off_t offset, size_t len); + +private: + friend class task; + mem(const std::string& path); + +private: + const std::string _path; + int _fd; +}; + +} // namespace pfs + +#endif // PFS_MEM_HPP diff --git a/include/pfs/task.hpp b/include/pfs/task.hpp index c592fea..9a45869 100644 --- a/include/pfs/task.hpp +++ b/include/pfs/task.hpp @@ -20,6 +20,7 @@ #include #include +#include "mem.hpp" #include "net.hpp" #include "types.hpp" @@ -59,6 +60,8 @@ class task final std::set get_maps() const; + mem get_mem() const; + std::set get_mountinfo() const; net get_net() const; diff --git a/sample/format.hpp b/sample/format.hpp index d36977c..0abfd8c 100644 --- a/sample/format.hpp +++ b/sample/format.hpp @@ -37,6 +37,28 @@ std::string join(const T& container) return out_str; } +bool is_printable(uint8_t byte) +{ + static const uint8_t PRINTABLE_MIN = 0x21; + static const uint8_t PRINTABLE_MAX = 0x7e; + + return PRINTABLE_MIN <= byte && byte <= PRINTABLE_MAX; +} + +std::string hexlify(const std::vector& buffer) +{ + std::ostringstream out; + out << std::hex; + + for (const auto& v : buffer) + { + char c = is_printable(v) ? static_cast(v) : '.'; + out << c; + } + + return out.str(); +} + std::ostream& operator<<(std::ostream& out, const pfs::socket::timer timer) { switch (timer) diff --git a/sample/sample.cpp b/sample/sample.cpp index d837be2..79b00ca 100644 --- a/sample/sample.cpp +++ b/sample/sample.cpp @@ -58,6 +58,16 @@ void print_task(const pfs::task& task) auto maps = task.get_maps(); print(maps); + if (!maps.empty()) + { + static const size_t BYTES = 8; + auto mem = task.get_mem(); + auto first_map = *maps.begin(); + auto header_bytes = mem.read(first_map.start_address, BYTES); + auto header = hexlify(header_bytes); + print(header); + } + auto fds = task.get_fds(); print(fds); diff --git a/src/mem.cpp b/src/mem.cpp new file mode 100644 index 0000000..a8aab3b --- /dev/null +++ b/src/mem.cpp @@ -0,0 +1,67 @@ +/* + * Copyright 2020-present Daniel Trugman + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include + +#include "pfs/mem.hpp" + +namespace pfs { + +mem::mem(const std::string& path) + : _path(path), _fd(open(path.c_str(), O_RDONLY)) +{ + if (_fd < 0) + { + throw std::system_error(errno, std::system_category(), + "Couldn't open file"); + } +} + +mem::~mem() +{ + close(_fd); +} + +std::vector mem::read(const mem_region& region) +{ + return read(region.start_address, + region.end_address - region.start_address); +} + +std::vector mem::read(off_t offset, size_t bytes) +{ + std::vector buffer(bytes); + + struct iovec iov = {&buffer[0], buffer.size()}; + + ssize_t bytes_read = preadv(_fd, &iov, 1, offset); + if (bytes_read == -1) + { + throw std::system_error(errno, std::system_category(), + "Couldn't read from memory"); + } + buffer.resize(bytes_read); + + return buffer; +} + +} // namespace pfs diff --git a/src/task.cpp b/src/task.cpp index 05a6535..47833a3 100644 --- a/src/task.cpp +++ b/src/task.cpp @@ -321,6 +321,14 @@ std::set task::get_maps() const return parsers::parse_lines(path, parsers::parse_maps_line); } +mem task::get_mem() const +{ + static const std::string MEM_FILE("mem"); + auto path = _task_root + MEM_FILE; + + return mem(path); +} + std::set task::get_mountinfo() const { static const std::string MOUNTINFO_FILE("mountinfo"); diff --git a/test/mem.cpp b/test/mem.cpp new file mode 100644 index 0000000..ba2cf74 --- /dev/null +++ b/test/mem.cpp @@ -0,0 +1,15 @@ +#include "catch.hpp" + +#include "pfs/procfs.hpp" + +TEST_CASE("Read mem", "[mem][read]") +{ + std::vector secret = {'s', 'e', 'c', 'r', 'e', 't'}; + off_t secret_offset = reinterpret_cast(&secret[0]); + + auto self = pfs::procfs().get_task(); + auto mem = self.get_mem(); + auto extracted = mem.read(secret_offset, secret.size()); + + REQUIRE(secret == extracted); +}