Skip to content

Commit

Permalink
Add task memory reading
Browse files Browse the repository at this point in the history
Add API for direct access to a tasks' memory through the mem file
  • Loading branch information
dtrugman committed Feb 22, 2021
1 parent 069189f commit b291587
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 7 deletions.
31 changes: 24 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t> 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())
Expand All @@ -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())
{
Expand All @@ -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);
Expand Down
53 changes: 53 additions & 0 deletions include/pfs/mem.hpp
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <vector>

#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<uint8_t> read(const mem_region& region);
std::vector<uint8_t> 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
3 changes: 3 additions & 0 deletions include/pfs/task.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <set>
#include <string>

#include "mem.hpp"
#include "net.hpp"
#include "types.hpp"

Expand Down Expand Up @@ -59,6 +60,8 @@ class task final

std::set<mem_region> get_maps() const;

mem get_mem() const;

std::set<mount> get_mountinfo() const;

net get_net() const;
Expand Down
22 changes: 22 additions & 0 deletions sample/format.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t>& buffer)
{
std::ostringstream out;
out << std::hex;

for (const auto& v : buffer)
{
char c = is_printable(v) ? static_cast<char>(v) : '.';
out << c;
}

return out.str();
}

std::ostream& operator<<(std::ostream& out, const pfs::socket::timer timer)
{
switch (timer)
Expand Down
10 changes: 10 additions & 0 deletions sample/sample.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
67 changes: 67 additions & 0 deletions src/mem.cpp
Original file line number Diff line number Diff line change
@@ -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 <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>

#include <system_error>

#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<uint8_t> mem::read(const mem_region& region)
{
return read(region.start_address,
region.end_address - region.start_address);
}

std::vector<uint8_t> mem::read(off_t offset, size_t bytes)
{
std::vector<uint8_t> 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
8 changes: 8 additions & 0 deletions src/task.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,14 @@ std::set<mem_region> task::get_maps() const
return parsers::parse_lines<ret_type>(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<mount> task::get_mountinfo() const
{
static const std::string MOUNTINFO_FILE("mountinfo");
Expand Down
15 changes: 15 additions & 0 deletions test/mem.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#include "catch.hpp"

#include "pfs/procfs.hpp"

TEST_CASE("Read mem", "[mem][read]")
{
std::vector<uint8_t> secret = {'s', 'e', 'c', 'r', 'e', 't'};
off_t secret_offset = reinterpret_cast<off_t>(&secret[0]);

auto self = pfs::procfs().get_task();
auto mem = self.get_mem();
auto extracted = mem.read(secret_offset, secret.size());

REQUIRE(secret == extracted);
}

0 comments on commit b291587

Please sign in to comment.