├── .gitignore ├── include ├── cmds │ ├── DumpCommand.h │ ├── FindCommand.h │ ├── ScanCommand.h │ ├── FreezeCommand.h │ ├── MapCommand.h │ ├── PidCommand.h │ ├── WriteCommand.h │ └── ICommand.h ├── CommandProcessor.h ├── ComparisonType.h ├── DataType.h ├── MemoryStructs.h ├── Process.h ├── MemoryFreezer.h ├── MemoryScanner.h ├── Utils.h └── MemoryFuncs.h ├── Makefile ├── README.md └── src ├── ComparisonType.cpp ├── main.cpp ├── MemoryScanner.cpp ├── cmds ├── PidCommand.cpp ├── MapCommand.cpp ├── DumpCommand.cpp ├── FindCommand.cpp ├── WriteCommand.cpp ├── FreezeCommand.cpp └── ScanCommand.cpp ├── DataType.cpp ├── MemoryFuncs.cpp ├── Utils.cpp ├── CommandProcessor.cpp ├── Process.cpp └── MemoryFreezer.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | compile_flags.txt 3 | rwprocmem 4 | 5 | -------------------------------------------------------------------------------- /include/cmds/DumpCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ICommand.h" 3 | 4 | class DumpCommand : public ICommand 5 | { 6 | public: 7 | static void Main(Process& proc, const std::vector& args); 8 | static std::string Help(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /include/cmds/FindCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ICommand.h" 3 | 4 | class FindCommand : public ICommand 5 | { 6 | public: 7 | static void Main(Process& proc, const std::vector& args); 8 | static std::string Help(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /include/cmds/ScanCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ICommand.h" 3 | 4 | class ScanCommand : public ICommand 5 | { 6 | public: 7 | static void Main(Process& proc, const std::vector& args); 8 | static std::string Help(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /include/cmds/FreezeCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "ICommand.h" 3 | 4 | class FreezeCommand : public ICommand 5 | { 6 | public: 7 | static void Main(Process& proc, const std::vector& args); 8 | static std::string Help(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /include/cmds/MapCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "cmds/ICommand.h" 3 | 4 | class MapCommand : public ICommand 5 | { 6 | public: 7 | static void Main(Process& proc, const std::vector& args); 8 | static std::string Help(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /include/cmds/PidCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "cmds/ICommand.h" 3 | 4 | class PidCommand : public ICommand 5 | { 6 | public: 7 | static void Main(Process& proc, const std::vector& args); 8 | static std::string Help(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /include/cmds/WriteCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "cmds/ICommand.h" 3 | 4 | class WriteCommand : public ICommand 5 | { 6 | public: 7 | static void Main(Process& proc, const std::vector& args); 8 | static std::string Help(); 9 | }; 10 | 11 | -------------------------------------------------------------------------------- /include/CommandProcessor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "Process.h" 5 | 6 | namespace CommandProcessor 7 | { 8 | void ProcessCommand(std::string& input, Process& proc); 9 | void HelpCommand(const std::vector& args); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /include/ComparisonType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | enum class ComparisonType 5 | { 6 | Equal, 7 | NotEqual, 8 | Greater, 9 | Less, 10 | GreaterEqual, 11 | LessEqual, 12 | }; 13 | 14 | ComparisonType ParseComparisonType(const std::string& keywordStr); 15 | 16 | -------------------------------------------------------------------------------- /include/DataType.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | enum class DataType 5 | { 6 | int8, 7 | int16, 8 | int32, 9 | int64, 10 | uint8, 11 | uint16, 12 | uint32, 13 | uint64, 14 | f32, 15 | f64, 16 | string, 17 | }; 18 | 19 | DataType ParseDataType(const std::string& typeStr); 20 | 21 | -------------------------------------------------------------------------------- /include/cmds/ICommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Process.h" 3 | #include 4 | #include 5 | 6 | template 7 | class ICommand 8 | { 9 | public: 10 | static void Main(Process& proc, const std::vector& args) 11 | { 12 | T::Main(proc, args); 13 | } 14 | static std::string Help() 15 | { 16 | return T::Help(); 17 | } 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = g++ 2 | LD = g++ 3 | 4 | CFLAGS = -Wall -Wextra -pedantic -Iinclude -O2 -std=c++20 5 | LDFLAGS = -lfmt 6 | 7 | SRCS = $(wildcard src/*.cpp) $(wildcard src/cmds/*.cpp) 8 | OBJS = $(SRCS:.cpp=.o) 9 | TARGET = rwprocmem 10 | 11 | all: $(OBJS) $(TARGET) 12 | 13 | # Links the object files into an executable 14 | $(TARGET): $(OBJS) 15 | $(LD) $^ $(LDFLAGS) -o $@ 16 | 17 | # Compiles every source file into an object file 18 | %.o: %.cpp 19 | $(CC) $(CFLAGS) -c $< -o $@ 20 | 21 | .PHONY: clean 22 | clean: 23 | rm -f $(OBJS) $(TARGET) 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rwprocmem 2 | A program that can read and modify the memory of other processes. 3 | 4 | # Building 5 | This program is not cross-platform and only works on GNU/Linux systems. A Linux kernel of version 3.2 or higher must be used, and the option `CONFIG_CROSS_MEMORY_ATTACH` must be enabled (required to use to the syscalls `process_vm_readv/process_vm_writev`). 6 | 7 | Any compiler which supports C++20 can be used, `gcc` is used by default in the `Makefile`. 8 | 9 | Dependencies: `libfmt` 10 | 11 | To build the program, run `make` in the root directory of the repository. 12 | 13 | -------------------------------------------------------------------------------- /include/MemoryStructs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct MemRegionPerms 5 | { 6 | bool readFlag; 7 | bool writeFlag; 8 | bool executeFlag; 9 | bool sharedFlag; 10 | }; 11 | 12 | struct MemRegion 13 | { 14 | unsigned long startAddr; 15 | unsigned long endAddr; 16 | unsigned long rangeLength; 17 | 18 | std::string permsStr; 19 | MemRegionPerms perms; 20 | 21 | std::string pathName; 22 | }; 23 | 24 | // Used to store a specific address along with the memory region it belongs to 25 | struct MemAddress 26 | { 27 | unsigned long address; 28 | MemRegion memRegion; 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /src/ComparisonType.cpp: -------------------------------------------------------------------------------- 1 | #include "ComparisonType.h" 2 | #include 3 | 4 | ComparisonType ParseComparisonType(const std::string& keywordStr) 5 | { 6 | if (keywordStr == "==") 7 | { 8 | return ComparisonType::Equal; 9 | } 10 | else if (keywordStr == "!=") 11 | { 12 | return ComparisonType::NotEqual; 13 | } 14 | else if (keywordStr == ">") 15 | { 16 | return ComparisonType::Greater; 17 | } 18 | else if (keywordStr == "<") 19 | { 20 | return ComparisonType::Less; 21 | } 22 | else if (keywordStr == ">=") 23 | { 24 | return ComparisonType::GreaterEqual; 25 | } 26 | else if (keywordStr == "<=") 27 | { 28 | return ComparisonType::LessEqual; 29 | } 30 | else 31 | { 32 | throw std::invalid_argument("Invalid scan type."); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "CommandProcessor.h" 4 | #include "Process.h" 5 | #include 6 | #include 7 | #include 8 | 9 | int main() 10 | { 11 | fmt::print("rwprocmem v1.2.1\n"); 12 | fmt::print("Type 'help' to see a list of commands.\n"); 13 | if (getuid() != 0) 14 | { 15 | fmt::print("WARNING: not running as root, reading and writing to the memory of certain processes may not work.\n"); 16 | } 17 | 18 | std::string input = ""; 19 | 20 | Process proc; 21 | while (input != "exit") 22 | { 23 | proc.PrintMessageQueues(); 24 | 25 | fmt::print("> "); 26 | if (!std::getline(std::cin, input)) 27 | { 28 | break; 29 | } 30 | 31 | CommandProcessor::ProcessCommand(input, proc); 32 | } 33 | 34 | return 0; 35 | } 36 | 37 | -------------------------------------------------------------------------------- /include/Process.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "MemoryStructs.h" 6 | #include "MemoryScanner.h" 7 | #include "MemoryFreezer.h" 8 | 9 | class Process 10 | { 11 | public: 12 | Process(); 13 | Process(pid_t pid); 14 | ~Process(); 15 | 16 | void SetProcessPid(pid_t pid); 17 | 18 | pid_t GetCurrentPid() const; 19 | const std::vector GetMemoryRegions() const; 20 | MemoryScanner& GetMemoryScanner(); 21 | MemoryFreezer& GetMemoryFreezer(); 22 | 23 | void PrintMessageQueues(); 24 | 25 | private: 26 | pid_t m_pid; 27 | MemoryScanner m_MemoryScanner; 28 | MemoryFreezer m_MemoryFreezer; 29 | 30 | void UpdateMemoryRegions(); 31 | 32 | void SetMemoryRangeBoundaries(MemRegion& reg, const std::string& addressRange) const; 33 | void SetMemoryRegionPerms(MemRegion& reg, const std::string& perms) const; 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /src/MemoryScanner.cpp: -------------------------------------------------------------------------------- 1 | #include "MemoryScanner.h" 2 | #include "MemoryStructs.h" 3 | #include 4 | #include 5 | 6 | MemoryScanner::MemoryScanner() 7 | { 8 | this->m_UndoFlag = false; 9 | this->m_ScanStartedFlag = false; 10 | } 11 | 12 | MemoryScanner::~MemoryScanner() {} 13 | 14 | 15 | void MemoryScanner::Clear() 16 | { 17 | this->m_CurrScanVector.clear(); 18 | this->m_PrevScanVector.clear(); 19 | 20 | this->m_UndoFlag = false; 21 | this->m_ScanStartedFlag = false; 22 | } 23 | 24 | void MemoryScanner::Undo() 25 | { 26 | if (this->m_PrevScanVector.size() == 0) 27 | { 28 | throw std::runtime_error("Nothing to undo."); 29 | } 30 | else if (this->m_UndoFlag) 31 | { 32 | throw std::runtime_error("Undo has already been called."); 33 | } 34 | else 35 | { 36 | this->m_CurrScanVector = this->m_PrevScanVector; 37 | this->m_UndoFlag = true; 38 | } 39 | } 40 | 41 | void MemoryScanner::SetPid(pid_t pid) 42 | { 43 | this->m_pid = pid; 44 | this->Clear(); 45 | } 46 | 47 | const std::vector& MemoryScanner::GetCurrScanVector() const 48 | { 49 | return this->m_CurrScanVector; 50 | } 51 | 52 | bool MemoryScanner::GetScanStartedFlag() const 53 | { 54 | return this->m_ScanStartedFlag; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/cmds/PidCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "cmds/PidCommand.h" 2 | #include 3 | #include 4 | #include 5 | #include "Utils.h" 6 | 7 | void PidCommand::Main(Process& proc, const std::vector& args) 8 | { 9 | std::string pidArg = ""; 10 | // Check if an argument was given 11 | try 12 | { 13 | pidArg = args.at(1); 14 | } 15 | catch (const std::out_of_range&) {} 16 | 17 | // If not, print the current pid in use 18 | if (pidArg == "") 19 | { 20 | pid_t currPid = proc.GetCurrentPid(); 21 | 22 | if (currPid == 0) 23 | { 24 | fmt::print("Currently not attached to any process.\n"); 25 | } 26 | else 27 | { 28 | std::string procCmd = Utils::GetProcessCommand(currPid); 29 | fmt::print("Process {}: {}\n", currPid, procCmd); 30 | } 31 | } 32 | else // Check if it's a valid pid and use it 33 | { 34 | pid_t newPid = Utils::StrToNumber(pidArg, "pid"); 35 | std::string procCmd = Utils::GetProcessCommand(newPid); 36 | 37 | proc.SetProcessPid(newPid); 38 | 39 | fmt::print("Process {}: {}\n" 40 | "New pid set.\n", newPid, procCmd); 41 | } 42 | } 43 | 44 | std::string PidCommand::Help() 45 | { 46 | return std::string( 47 | "Usage: pid [pid]\n\n" 48 | 49 | "If [pid] is passed, the program will use specified pid.\n" 50 | "If no argument was passed then the currently used pid will be printed.\n"); 51 | } 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/cmds/MapCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "cmds/MapCommand.h" 2 | #include 3 | #include 4 | 5 | void MapCommand::Main(Process& proc, const std::vector& args) 6 | { 7 | (void)args; // This command doesn't use any args, this silences a warning 8 | 9 | const std::vector memRegions = proc.GetMemoryRegions(); 10 | if (memRegions.empty()) 11 | { 12 | fmt::print("No memory regions were found.\n"); 13 | } 14 | else 15 | { 16 | unsigned long totalMem = 0; // Total amount of bytes used 17 | 18 | // Gets the amount of digits in the number of memory regions 19 | const size_t indexWidth = std::to_string(memRegions.size()).size(); 20 | 21 | for (size_t i = 0; i < memRegions.size(); i++) 22 | { 23 | totalMem += memRegions[i].rangeLength; 24 | 25 | fmt::print("[{:{}}] {:#018x}-{:#018x}\t{} bytes\t[{}]\t{}\n", 26 | i, indexWidth, 27 | memRegions[i].startAddr, memRegions[i].endAddr, 28 | memRegions[i].rangeLength, 29 | memRegions[i].permsStr, 30 | memRegions[i].pathName); 31 | } 32 | fmt::print("\nTotal: {} bytes in {} memory regions.\n", totalMem, memRegions.size()); 33 | } 34 | } 35 | 36 | std::string MapCommand::Help() 37 | { 38 | return std::string( 39 | "Usage: map\n\n" 40 | 41 | "If attached to a process, print the memory regions map (/proc/pid/maps)\n" 42 | "The fields are the following in order:\n" 43 | " \n"); 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/DataType.cpp: -------------------------------------------------------------------------------- 1 | #include "DataType.h" 2 | #include 3 | 4 | DataType ParseDataType(const std::string& typeStr) 5 | { 6 | if (typeStr[0] == 'i') 7 | { 8 | if (typeStr == "int8") 9 | { 10 | return DataType::int8; 11 | } 12 | else if (typeStr == "int16") 13 | { 14 | return DataType::int16; 15 | } 16 | else if (typeStr == "int32") 17 | { 18 | return DataType::int32; 19 | } 20 | else if (typeStr == "int64") 21 | { 22 | return DataType::int64; 23 | } 24 | else 25 | { 26 | throw std::invalid_argument("Invalid signed type."); 27 | } 28 | } 29 | else if (typeStr[0] == 'u') 30 | { 31 | if (typeStr == "uint8") 32 | { 33 | return DataType::uint8; 34 | } 35 | else if (typeStr == "uint16") 36 | { 37 | return DataType::uint16; 38 | } 39 | else if (typeStr == "uint32") 40 | { 41 | return DataType::uint32; 42 | } 43 | else if (typeStr == "uint64") 44 | { 45 | return DataType::uint64; 46 | } 47 | else 48 | { 49 | throw std::invalid_argument("Invalid unsigned type."); 50 | } 51 | } 52 | else if (typeStr == "float") 53 | { 54 | return DataType::f32; 55 | } 56 | else if (typeStr == "double") 57 | { 58 | return DataType::f64; 59 | } 60 | else if (typeStr == "string") 61 | { 62 | return DataType::string; 63 | } 64 | else 65 | { 66 | throw std::invalid_argument("Invalid type."); 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /include/MemoryFreezer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "MemoryStructs.h" 8 | #include 9 | #include 10 | 11 | struct FrozenMemAddress 12 | { 13 | MemAddress memAddress; 14 | bool enabled; // A flag which tells the program whether the address should be frozen 15 | std::string typeStr; // Only used for printing to the user 16 | std::string dataStr; // Also only used for printing to the user (removes the need for a template) 17 | std::vector data; // The data which will be continually written to the address 18 | std::string note; // An optional user note about the saved memory address 19 | }; 20 | 21 | class MemoryFreezer 22 | { 23 | public: 24 | MemoryFreezer(); 25 | ~MemoryFreezer(); 26 | 27 | void AddAddress(MemAddress memAddress, const std::string& typeStr, const std::string& dataStr, 28 | std::vector& data, const std::string& note); 29 | void RemoveAddress(size_t index); 30 | void RemoveAllAddresses(); 31 | 32 | void EnableAddress(size_t index); 33 | void DisableAddress(size_t index); 34 | void EnableAllAddresses(); 35 | void DisableAllAddresses(); 36 | 37 | void ModifyAddress(size_t index, const std::string& typeStr, const std::string& dataStr, 38 | std::vector& data, const std::string& note); 39 | void ModifyAllAddresses(const std::string& typeStr, const std::string& dataStr, 40 | std::vector& data, const std::string& note); 41 | 42 | const std::list& GetFrozenAddresses() const; 43 | int GetEnabledAddressesAmount() const; 44 | 45 | void SetPid(pid_t pid); 46 | 47 | std::string MessageQueuePop(); 48 | size_t GetMessageQueueSize() const; 49 | 50 | private: 51 | void StartThreadLoopIfNeeded(); 52 | void ThreadLoop(); 53 | 54 | int m_EnabledAddressesAmount; 55 | bool m_ThreadRunning; 56 | 57 | pid_t m_pid; 58 | std::list m_FrozenAddresses; 59 | std::list::iterator m_FrozenAddressIter; 60 | std::mutex m_MemoryFreezerMutex; 61 | 62 | std::queue m_MessageQueue; // A queue for messages from the memory freezer thread 63 | }; 64 | 65 | -------------------------------------------------------------------------------- /include/MemoryScanner.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "MemoryStructs.h" 5 | #include "ComparisonType.h" 6 | #include 7 | #include "MemoryFuncs.h" 8 | 9 | class MemoryScanner 10 | { 11 | public: 12 | MemoryScanner(); 13 | ~MemoryScanner(); 14 | 15 | void Clear(); 16 | void Undo(); 17 | 18 | template 19 | size_t NewScan(const std::vector& memRegions, size_t dataSize, const void* data, 20 | ComparisonType cmpType); 21 | 22 | template 23 | size_t NextScan(size_t dataSize, const void* data, ComparisonType cmpType); 24 | 25 | void SetPid(pid_t pid); 26 | 27 | const std::vector& GetCurrScanVector() const; 28 | bool GetScanStartedFlag() const; 29 | 30 | private: 31 | bool m_UndoFlag; 32 | bool m_ScanStartedFlag; 33 | 34 | pid_t m_pid; 35 | std::vector m_CurrScanVector; 36 | std::vector m_PrevScanVector; 37 | }; 38 | 39 | 40 | // Returns the amount of addresses where the data was found 41 | template 42 | size_t MemoryScanner::NewScan(const std::vector& memRegions, size_t dataSize, const void* data, 43 | ComparisonType cmpType) 44 | { 45 | // This should never happen 46 | if (this->m_ScanStartedFlag) 47 | { 48 | throw std::runtime_error("Incorrect call to NewScan after a scan has already begun."); 49 | } 50 | 51 | this->m_CurrScanVector = MemoryFuncs::FindDataInMemory(this->m_pid, memRegions, dataSize, 52 | data, cmpType); 53 | this->m_UndoFlag = false; // Reset the undo flag 54 | this->m_ScanStartedFlag = true; 55 | 56 | return this->m_CurrScanVector.size(); 57 | } 58 | 59 | // Also returns the amount of addresses where the data was found 60 | template 61 | size_t MemoryScanner::NextScan(size_t dataSize, const void* data, ComparisonType cmpType) 62 | { 63 | auto temporary = this->m_CurrScanVector; 64 | 65 | this->m_CurrScanVector = MemoryFuncs::FindDataInMemory(this->m_pid, this->m_CurrScanVector, 66 | dataSize, data, cmpType); 67 | 68 | // Replace the previous scan vector only if the scan succeeded 69 | this->m_PrevScanVector = temporary; 70 | this->m_UndoFlag = false; // Reset the undo flag 71 | 72 | return this->m_CurrScanVector.size(); 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/cmds/DumpCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "cmds/DumpCommand.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "MemoryFuncs.h" 8 | #include "Utils.h" 9 | 10 | void DumpCommand::Main(Process& proc, const std::vector& args) 11 | { 12 | if (args.size() < 3) 13 | { 14 | throw std::runtime_error("Missing arguments."); 15 | } 16 | 17 | unsigned long baseAddr = Utils::StrToNumber(args[1], "address"); 18 | unsigned long length = Utils::StrToNumber(args[2], "length"); 19 | 20 | const std::vector dataVec = MemoryFuncs::ReadProcessMemory(proc.GetCurrentPid(), baseAddr, length); 21 | const size_t dataLen = dataVec.size(); 22 | constexpr int BYTES_PER_LINE = 16; 23 | 24 | for (size_t i = 0; i < dataLen; i += BYTES_PER_LINE) 25 | { 26 | std::string printableData = ""; // Holds printable ASCII characters 27 | // Output the memory address offset (memory address is 16 characters long) 28 | fmt::print("{:#018x}: ", baseAddr + i); 29 | 30 | for (int j = 0; j < BYTES_PER_LINE; j++) 31 | { 32 | // Avoid going out of bounds 33 | if (i + j >= dataLen) 34 | { 35 | // Add padding if the last line is shorter 36 | for (int k = 0; k < BYTES_PER_LINE - j; k++) 37 | { 38 | // Pad with 3 spaces because there is a space inbetween every hex byte 39 | // and every hex byte takes 2 characters 40 | fmt::print(" "); 41 | } 42 | break; 43 | } 44 | int byte = dataVec[i + j]; // Read a byte from the vector 45 | fmt::print("{:02x} ", byte); 46 | 47 | // Save ASCII character, if it's printable 48 | printableData += std::isprint(byte) ? byte : '.'; 49 | } 50 | fmt::print("|{}|\n", printableData); 51 | } 52 | // Notify the user if the dump is partial 53 | if (dataLen != length) 54 | { 55 | fmt::print("WARNING: Partial read of {}/{} bytes at address {:#018x}.\n", dataLen, length, baseAddr); 56 | } 57 | } 58 | 59 | std::string DumpCommand::Help() 60 | { 61 | return std::string( 62 | "Usage: dump
\n\n" 63 | 64 | "Ouputs a hex dump with the given length of the data in the given address.\n"); 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/MemoryFuncs.cpp: -------------------------------------------------------------------------------- 1 | #include "MemoryFuncs.h" 2 | #include 3 | #include 4 | #include 5 | 6 | // Returns an error message when process_vm_readv/process_vm_writev fail 7 | // Parameter expects errno 8 | static std::string GetErrorMessage(int err) 9 | { 10 | std::string errMsg; 11 | switch (err) 12 | { 13 | case EFAULT: 14 | errMsg = "Memory address is outside the accessible space of the process."; 15 | break; 16 | 17 | case EINVAL: 18 | errMsg = "Invalid arguments."; 19 | break; 20 | 21 | case ENOMEM: 22 | errMsg = "Failed to allocate memory for iovec structures."; 23 | break; 24 | 25 | case EPERM: 26 | errMsg = "Permission denied."; 27 | break; 28 | 29 | case ESRCH: 30 | errMsg = "Invalid PID (process doesn't exist)."; 31 | break; 32 | 33 | default: 34 | errMsg = "Unknown error."; 35 | break; 36 | } 37 | return errMsg; 38 | } 39 | 40 | // This function should be used when the requested memory region is readable (r permission is set) 41 | // process_vm_readv will fail if the region is not readable and ptrace should be used instead 42 | // TODO: create a ptrace alternative for both read and write functions 43 | std::vector MemoryFuncs::ReadProcessMemory(pid_t pid, unsigned long baseAddr, long length) 44 | { 45 | // Creates a vector of the size given in `length` 46 | std::vector buffer(length); 47 | 48 | iovec local[1]; 49 | local[0].iov_base = &buffer[0]; // Pass a pointer to the beginning of the buffer 50 | local[0].iov_len = length; 51 | 52 | iovec remote[1]; 53 | remote[0].iov_base = (void*)baseAddr; 54 | remote[0].iov_len = length; 55 | 56 | ssize_t nread = process_vm_readv(pid, local, 1, remote, 1, 0); 57 | if (nread < 0) 58 | { 59 | throw std::runtime_error(GetErrorMessage(errno)); 60 | } 61 | 62 | return buffer; 63 | } 64 | 65 | ssize_t MemoryFuncs::WriteToProcessMemory(pid_t pid, unsigned long baseAddr, long dataSize, void* data) 66 | { 67 | iovec local[1]; 68 | local[0].iov_base = data; 69 | local[0].iov_len = dataSize; 70 | 71 | iovec remote[1]; 72 | remote[0].iov_base = (void*)baseAddr; 73 | remote[0].iov_len = dataSize; 74 | 75 | ssize_t nread = process_vm_writev(pid, local, 1, remote, 1, 0); 76 | if (nread < 0) 77 | { 78 | throw std::runtime_error(GetErrorMessage(errno)); 79 | } 80 | return nread; 81 | } 82 | 83 | template <> 84 | bool MemoryFuncs::CompareData(const void* lhs, const void* rhs, 85 | size_t dataSize, ComparisonType cmpType) 86 | { 87 | if (cmpType != ComparisonType::Equal) 88 | { 89 | throw std::runtime_error("Comparing strings for equality is the only supported comparison type."); 90 | } 91 | return std::memcmp(lhs, rhs, dataSize) == 0; 92 | } 93 | 94 | -------------------------------------------------------------------------------- /src/cmds/FindCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "cmds/FindCommand.h" 2 | #include "ComparisonType.h" 3 | #include "Process.h" 4 | #include "Utils.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "DataType.h" 11 | #include "MemoryFuncs.h" 12 | 13 | template 14 | std::vector FindData(const Process& proc, const std::string& dataStr) 15 | { 16 | constexpr unsigned long dataTypeSize = sizeof(T); 17 | T dataValue = Utils::StrToNumber(dataStr); 18 | 19 | return MemoryFuncs::FindDataInMemory(proc.GetCurrentPid(), proc.GetMemoryRegions(), 20 | dataTypeSize, &dataValue, ComparisonType::Equal); 21 | } 22 | 23 | template <> 24 | std::vector FindData(const Process& proc, const std::string& dataStr) 25 | { 26 | return MemoryFuncs::FindDataInMemory(proc.GetCurrentPid(), proc.GetMemoryRegions(), 27 | dataStr.size(), dataStr.c_str(), ComparisonType::Equal); 28 | } 29 | 30 | void FindCommand::Main(Process& proc, const std::vector& args) 31 | { 32 | if (args.size() < 3) 33 | { 34 | throw std::runtime_error("Missing arguments."); 35 | } 36 | 37 | std::vector foundAddrs; 38 | const std::string& typeStr = args[1]; 39 | const std::string& dataStr = args[2]; 40 | 41 | switch (ParseDataType(typeStr)) 42 | { 43 | case DataType::int8: foundAddrs = FindData(proc, dataStr); break; 44 | case DataType::int16: foundAddrs = FindData(proc, dataStr); break; 45 | case DataType::int32: foundAddrs = FindData(proc, dataStr); break; 46 | case DataType::int64: foundAddrs = FindData(proc, dataStr); break; 47 | case DataType::uint8: foundAddrs = FindData(proc, dataStr); break; 48 | case DataType::uint16: foundAddrs = FindData(proc, dataStr); break; 49 | case DataType::uint32: foundAddrs = FindData(proc, dataStr); break; 50 | case DataType::uint64: foundAddrs = FindData(proc, dataStr); break; 51 | case DataType::f32: foundAddrs = FindData(proc, dataStr); break; 52 | case DataType::f64: foundAddrs = FindData(proc, dataStr); break; 53 | case DataType::string: foundAddrs = FindData(proc, dataStr); break; 54 | // No default: so that the compiler can generate a warning for us in case we forget something. 55 | } 56 | 57 | Utils::PrintMemoryAddresses(foundAddrs); 58 | } 59 | std::string FindCommand::Help() 60 | { 61 | return std::string( 62 | "Usage: find \n\n" 63 | 64 | "Lists the memory addresses where the given data was found.\n\n" 65 | 66 | "The argument can be one of the following:\n" 67 | "[u]int8, [u]int16, [u]int32, [u]int64, float, double, string\n" 68 | "The 'u' prefix tells the program to use the unsigned type.\n\n" 69 | 70 | "Data for [u]int8, [u]int16, [u]int32, [u]int64 can be written as decimal numbers or hexadecimal numbers.\n" 71 | "Data for float and double can be written as floating point numbers or hexadecimal numbers.\n" 72 | "Data for string can only be a string.\n"); 73 | } 74 | 75 | -------------------------------------------------------------------------------- /include/Utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "MemoryStructs.h" 9 | #include 10 | 11 | namespace Utils 12 | { 13 | std::vector SplitString(const std::string& str, char delim); 14 | 15 | std::string GetProcessCommand(pid_t pid); 16 | 17 | std::string JoinVectorOfStrings(const std::vector& vec, int startIndex, 18 | char joinChar); 19 | 20 | void PrintMemoryAddresses(const std::vector& memAddrs); 21 | 22 | MemRegion FindRegionOfAddress(const std::vector& memRegions, unsigned long address); 23 | 24 | // These function templates are very thin wrappers around std::from_chars 25 | // std::from_chars is actually unable to detect whether a number is in hex 26 | // so we have to explicitly call the function with a base 16/hex argument 27 | template 28 | inline std::from_chars_result from_chars_hex(const char* start, const char* end, T& value); 29 | 30 | template 31 | inline std::from_chars_result from_chars_hex(const char* start, const char* end, T& value); 32 | 33 | template 34 | T StrToNumber(const std::string& dataString, std::string varName = "data"); 35 | } 36 | 37 | 38 | template 39 | inline std::from_chars_result Utils::from_chars_hex(const char* start, const char* end, T& value) 40 | { 41 | return std::from_chars(start, end, value, 16); 42 | } 43 | 44 | template 45 | inline std::from_chars_result Utils::from_chars_hex(const char* start, const char* end, T& value) 46 | { 47 | return std::from_chars(start, end, value, std::chars_format::hex); 48 | } 49 | 50 | // Set varName to make the errors easier to understand 51 | template 52 | T Utils::StrToNumber(const std::string& dataString, std::string varName) 53 | { 54 | T dataValue = 0; 55 | std::from_chars_result res; 56 | 57 | const char* dataStringStart = dataString.c_str(); 58 | const char* dataStringEnd = dataStringStart + dataString.size(); 59 | 60 | // Use hex if the input is in hex 61 | if (dataStringStart[0] == '0' && (dataStringStart[1] == 'x' || dataStringStart[1] == 'X')) 62 | { 63 | dataStringStart += 2; // Move past the '0x' 64 | res = Utils::from_chars_hex(dataStringStart, dataStringEnd, dataValue); 65 | } 66 | else // Use generic conversion 67 | { 68 | res = std::from_chars(dataStringStart, dataStringEnd, dataValue); 69 | } 70 | 71 | // Error checking 72 | if (res.ec == std::errc::invalid_argument) 73 | { 74 | const std::string err = fmt::format("Invalid {}.", varName); 75 | throw std::invalid_argument(err); 76 | } 77 | else if (res.ec == std::errc::result_out_of_range) 78 | { 79 | const std::string err = fmt::format( 80 | "Result of {} string conversion to a number is out of range for the given type.", varName); 81 | throw std::runtime_error(err); 82 | } 83 | else if (res.ptr != dataStringEnd) 84 | { 85 | const std::string err = fmt::format( 86 | "Failed to fully convert the given {} string to a number.", varName); 87 | throw std::runtime_error(err); 88 | } 89 | return dataValue; 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/cmds/WriteCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "cmds/WriteCommand.h" 2 | #include "Utils.h" 3 | #include 4 | #include 5 | #include 6 | #include "DataType.h" 7 | #include "MemoryFuncs.h" 8 | 9 | template 10 | void WriteData(pid_t pid, unsigned long baseAddr, const std::string& dataStr) 11 | { 12 | // The data is in index 3, according to the syntax 13 | constexpr long dataTypeSize = sizeof(T); 14 | T dataValue = Utils::StrToNumber(dataStr); 15 | 16 | ssize_t nread = MemoryFuncs::WriteToProcessMemory(pid, baseAddr, dataTypeSize, &dataValue); 17 | if (nread != dataTypeSize) 18 | { 19 | fmt::print("WARNING: Partial write of {}/{} bytes at address {:#018x}.\n", 20 | nread, dataTypeSize, baseAddr); 21 | } 22 | } 23 | 24 | // Accepts string 25 | template <> 26 | void WriteData(pid_t pid, unsigned long baseAddr, const std::string& dataStr) 27 | { 28 | const long dataStrSize = dataStr.size(); 29 | 30 | ssize_t nread = MemoryFuncs::WriteToProcessMemory(pid, baseAddr, dataStrSize, (void*)dataStr.c_str()); 31 | if (nread != dataStrSize) 32 | { 33 | fmt::print("WARNING: Partial write of {}/{} bytes at address {:#018x}.\n", 34 | nread, dataStrSize, baseAddr); 35 | } 36 | } 37 | 38 | void WriteCommand::Main(Process& proc, const std::vector& args) 39 | { 40 | if (args.size() < 4) 41 | { 42 | throw std::runtime_error("Missing arguments."); 43 | } 44 | 45 | // The address is in index 1 46 | unsigned long baseAddr = Utils::StrToNumber(args[1], "address"); 47 | 48 | const pid_t pid = proc.GetCurrentPid(); 49 | const std::string& typeStr = args[2]; 50 | const std::string& dataStr = args[3]; 51 | switch (ParseDataType(typeStr)) 52 | { 53 | case DataType::int8: WriteData(pid, baseAddr, dataStr); break; 54 | case DataType::int16: WriteData(pid, baseAddr, dataStr); break; 55 | case DataType::int32: WriteData(pid, baseAddr, dataStr); break; 56 | case DataType::int64: WriteData(pid, baseAddr, dataStr); break; 57 | case DataType::uint8: WriteData(pid, baseAddr, dataStr); break; 58 | case DataType::uint16: WriteData(pid, baseAddr, dataStr); break; 59 | case DataType::uint32: WriteData(pid, baseAddr, dataStr); break; 60 | case DataType::uint64: WriteData(pid, baseAddr, dataStr); break; 61 | case DataType::f32: WriteData(pid, baseAddr, dataStr); break; 62 | case DataType::f64: WriteData(pid, baseAddr, dataStr); break; 63 | case DataType::string: WriteData(pid, baseAddr, dataStr); break; 64 | // No default: so that the compiler can generate a warning for us in case we forget something. 65 | } 66 | } 67 | 68 | std::string WriteCommand::Help() 69 | { 70 | return std::string( 71 | "Usage: write
\n\n" 72 | 73 | "Writes data to a given memory address.\n\n" 74 | 75 | "The
must be in hexadecimal.\n\n" 76 | 77 | "The argument can be one of the following:\n" 78 | "[u]int8, [u]int16, [u]int32, [u]int64, float, double, string\n" 79 | "The 'u' prefix tells the program to use the unsigned type.\n\n" 80 | 81 | "Data for [u]int8, [u]int16, [u]int32, [u]int64 can be written as decimal numbers or hexadecimal numbers.\n" 82 | "Data for float and double can be written as floating point numbers or hexadecimal numbers.\n" 83 | "Data for string can only be a string.\n"); 84 | } 85 | -------------------------------------------------------------------------------- /src/Utils.cpp: -------------------------------------------------------------------------------- 1 | #include "Utils.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Splits a string into a vector of strings 8 | std::vector Utils::SplitString(const std::string& str, char delim) 9 | { 10 | std::vector tokens; 11 | if (str.empty()) // Return an empty vector if the string is empty 12 | { 13 | return tokens; 14 | } 15 | 16 | std::stringstream stream(str); 17 | std::string intermediate; 18 | 19 | while (std::getline(stream, intermediate, delim)) 20 | { 21 | if (intermediate.size() != 0) 22 | { 23 | tokens.push_back(intermediate); 24 | } 25 | } 26 | 27 | return tokens; 28 | } 29 | 30 | // Returns the contents of /proc/.../cmdline if it's not empty 31 | // Otherwise returns the contents of /proc/.../comm 32 | std::string Utils::GetProcessCommand(pid_t pid) 33 | { 34 | const std::string basePath = fmt::format("/proc/{}", pid); 35 | const std::string cmdLinePath = fmt::format("{}/cmdline", basePath); 36 | 37 | std::ifstream cmdLineFile(cmdLinePath); 38 | if (!cmdLineFile.is_open()) 39 | { 40 | const std::string err = fmt::format("Failed to open file '{}': {}.", cmdLinePath, std::strerror(errno)); 41 | throw std::runtime_error(err); 42 | } 43 | 44 | std::stringstream cmdLine; 45 | cmdLine << cmdLineFile.rdbuf(); 46 | 47 | // Return the command line string if it's not empty 48 | // if it is empty then the "comm" will be returned 49 | if (cmdLine.str().size() != 0) 50 | { 51 | return cmdLine.str(); 52 | } 53 | cmdLineFile.close(); 54 | 55 | const std::string commPath = fmt::format("{}/comm", basePath); 56 | 57 | std::ifstream commFile(commPath); 58 | if (!commFile.is_open()) 59 | { 60 | const std::string err = fmt::format("Failed to open file '{}': {}.", commPath, std::strerror(errno)); 61 | throw std::runtime_error(err); 62 | } 63 | 64 | std::stringstream comm; 65 | comm << commFile.rdbuf(); 66 | 67 | std::string commStr = "[]"; 68 | if (!comm.str().empty()) 69 | { 70 | commStr = '[' + comm.str(); 71 | commStr.pop_back(); // Remove the 0x0A character (newline) 72 | commStr += ']'; 73 | } 74 | return commStr; 75 | } 76 | 77 | std::string Utils::JoinVectorOfStrings(const std::vector& vec, int startIndex, char joinChar) 78 | { 79 | // Merge all the strings into 1 string 80 | std::string fullString = ""; 81 | for (auto it = vec.cbegin() + startIndex; it != vec.cend(); it++) 82 | { 83 | fullString += *it; 84 | // Add the given character in between the strings when joining them 85 | if (it + 1 != vec.cend()) 86 | { 87 | fullString += joinChar; 88 | } 89 | } 90 | return fullString; 91 | } 92 | 93 | void Utils::PrintMemoryAddresses(const std::vector& memAddrs) 94 | { 95 | // Gets the amount of digits in the number of found addresses 96 | const size_t indexWidth = std::to_string(memAddrs.size()).size(); 97 | 98 | int index = 0; 99 | for (auto it = memAddrs.cbegin(); it != memAddrs.cend(); it++) 100 | { 101 | fmt::print("[{:{}}] {:#018x} [{}] (in {})\n", 102 | index, indexWidth, it->address, it->memRegion.permsStr, it->memRegion.pathName); 103 | index++; 104 | } 105 | } 106 | 107 | MemRegion Utils::FindRegionOfAddress(const std::vector &memRegions, unsigned long address) 108 | { 109 | for (auto it = memRegions.cbegin(); it != memRegions.cend(); it++) 110 | { 111 | if (address >= it->startAddr && address <= it->endAddr) 112 | { 113 | return *it; 114 | } 115 | } 116 | 117 | const std::string err = fmt::format("Couldn't find the memory region of the address {:#018x}.", address); 118 | throw std::runtime_error(err); 119 | } 120 | 121 | -------------------------------------------------------------------------------- /src/CommandProcessor.cpp: -------------------------------------------------------------------------------- 1 | #include "CommandProcessor.h" 2 | #include "Process.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include "cmds/ICommand.h" 10 | #include "cmds/PidCommand.h" 11 | #include "cmds/MapCommand.h" 12 | #include "cmds/DumpCommand.h" 13 | #include "cmds/WriteCommand.h" 14 | #include "cmds/FindCommand.h" 15 | #include "cmds/ScanCommand.h" 16 | #include "cmds/FreezeCommand.h" 17 | 18 | using CommandMainFunc = void (*)(Process&, const std::vector&); 19 | using CommandHelpFunc = std::string (*)(); 20 | 21 | struct CmdFuncs 22 | { 23 | CommandMainFunc MainFunc; 24 | CommandHelpFunc HelpFunc; 25 | }; 26 | 27 | // All the commands are stored in this map 28 | static const std::unordered_map cmdMap = 29 | { 30 | { "pid", { &ICommand::Main, &ICommand::Help } }, 31 | { "map", { &ICommand::Main, &ICommand::Help } }, 32 | { "dump", { &ICommand::Main, &ICommand::Help } }, 33 | { "write", { &ICommand::Main, &ICommand::Help } }, 34 | { "find", { &ICommand::Main, &ICommand::Help } }, 35 | { "scan", { &ICommand::Main, &ICommand::Help } }, 36 | { "freeze", { &ICommand::Main, &ICommand::Help } } 37 | }; 38 | 39 | 40 | // Source: https://stackoverflow.com/a/58844407 41 | // Temporary solution, doesn't support quote escaping 42 | static std::vector TokenizeCommand(const std::string& input) 43 | { 44 | std::vector tokens; 45 | 46 | unsigned counter = 0; 47 | std::string segment; 48 | std::stringstream stream_input(input); 49 | 50 | while(std::getline(stream_input, segment, '\"')) 51 | { 52 | ++counter; 53 | if (counter % 2 == 0) 54 | { 55 | if (!segment.empty()) 56 | { 57 | tokens.push_back(segment); 58 | } 59 | } 60 | else 61 | { 62 | std::stringstream stream_segment(segment); 63 | while(std::getline(stream_segment, segment, ' ')) 64 | { 65 | if (!segment.empty()) 66 | { 67 | tokens.push_back(segment); 68 | } 69 | } 70 | } 71 | } 72 | return tokens; 73 | } 74 | 75 | void CommandProcessor::ProcessCommand(std::string &input, Process& proc) 76 | { 77 | // Split the input of the user into tokens 78 | std::vector tokens = TokenizeCommand(input); 79 | 80 | // Leave if the input is empty 81 | if (tokens.empty()) 82 | { 83 | return; 84 | } 85 | 86 | // tokens[0] is always the command, while the rest of the tokens are arguments 87 | std::string command = tokens[0]; 88 | 89 | if (command == "help") 90 | { 91 | CommandProcessor::HelpCommand(tokens); 92 | } 93 | else if (command != "exit") 94 | { 95 | // Gets an iterator to the command the user wants 96 | auto commandFuncIt = cmdMap.find(command); 97 | if (commandFuncIt != cmdMap.end()) 98 | { 99 | try 100 | { 101 | commandFuncIt->second.MainFunc(proc, tokens); 102 | } 103 | catch (const std::exception& e) 104 | { 105 | fmt::print(stderr, "{}: {}\n", command, e.what()); 106 | } 107 | } 108 | else 109 | { 110 | fmt::print(stderr, "Command {} not found.\n", command); 111 | } 112 | } 113 | } 114 | 115 | void CommandProcessor::HelpCommand(const std::vector& args) 116 | { 117 | // The requested command to get help for 118 | std::string command = ""; 119 | // Get the the argument if it was given 120 | try 121 | { 122 | command = args.at(1); 123 | } 124 | catch(const std::out_of_range&) {} // Do nothing, this just means the argument wasn't passed 125 | 126 | if (command == "") 127 | { 128 | // Print all available commands if no argument was given 129 | fmt::print("Available commands:\n"); 130 | for (auto i = cmdMap.cbegin(); i != cmdMap.cend(); i++) 131 | { 132 | fmt::print("- {}\n", i->first); 133 | } 134 | } 135 | else // If an argument was given, find the command in the command map and execute the help function 136 | { 137 | auto cmdFunctions = cmdMap.find(command); 138 | if (cmdFunctions == cmdMap.end()) 139 | { 140 | fmt::print(stderr, "{}: Command '{}' not found or no help available.\n", args[0], command); 141 | } 142 | else 143 | { 144 | fmt::print("\n{}\n", cmdFunctions->second.HelpFunc()); 145 | } 146 | } 147 | } 148 | 149 | -------------------------------------------------------------------------------- /src/Process.cpp: -------------------------------------------------------------------------------- 1 | #include "Process.h" 2 | #include "Utils.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | Process::Process() 11 | : m_MemoryFreezer(MemoryFreezer()) 12 | { 13 | this->m_pid = 0; 14 | this->m_MemoryScanner = MemoryScanner(); 15 | } 16 | 17 | Process::Process(pid_t pid) 18 | { 19 | this->SetProcessPid(pid); 20 | } 21 | 22 | Process::~Process() {} 23 | 24 | 25 | void Process::SetProcessPid(pid_t pid) 26 | { 27 | // Ensure the given pid is valid 28 | // This doesn't check if the pid actually exists 29 | if (pid > 0 && pid != getpid()) 30 | { 31 | this->m_pid = pid; 32 | this->m_MemoryScanner.SetPid(pid); 33 | this->m_MemoryFreezer.SetPid(pid); 34 | } 35 | else 36 | { 37 | throw std::invalid_argument("Invalid pid."); 38 | } 39 | } 40 | 41 | void Process::SetMemoryRangeBoundaries(MemRegion& reg, const std::string& addressRange) const 42 | { 43 | // The delimiter in the address range is '-', as seen in any maps file 44 | std::vector addressTokens = Utils::SplitString(addressRange, '-'); 45 | 46 | // The start address is in the 1st index, the end address is in the 2nd index 47 | // The addresses are in hexadecimal 48 | reg.startAddr = std::stoul(addressTokens[0], nullptr, 16); 49 | reg.endAddr = std::stoul(addressTokens[1], nullptr, 16); 50 | } 51 | 52 | void Process::SetMemoryRegionPerms(MemRegion& reg, const std::string& perms) const 53 | { 54 | reg.permsStr = perms; 55 | reg.perms = { false, false, false, false }; // Default initialization 56 | 57 | // The structure of the permissions string is constant and when all 58 | // permissions are set looks like this: rwxp 59 | // The meaning of each permission from left to right is 60 | // Readable, writable, executable, private (this can be either private(p) or shared (s)) 61 | // In this case, we check if the region is shared memory 62 | if (perms[0] == 'r') 63 | { 64 | reg.perms.readFlag = true; 65 | } 66 | if (perms[1] == 'w') 67 | { 68 | reg.perms.writeFlag = true; 69 | } 70 | if (perms[2] == 'x') 71 | { 72 | reg.perms.executeFlag = true; 73 | } 74 | if (perms[3] == 's') 75 | { 76 | reg.perms.sharedFlag = true; 77 | } 78 | } 79 | 80 | pid_t Process::GetCurrentPid() const 81 | { 82 | return this->m_pid; 83 | } 84 | 85 | const std::vector Process::GetMemoryRegions() const 86 | { 87 | if (this->m_pid == 0) 88 | { 89 | throw std::runtime_error("A pid has not been set. (see command `pid`)"); 90 | } 91 | 92 | // Make sure we are returning the most up to date memory region structs 93 | std::string processMapPath = fmt::format("/proc/{}/maps", this->m_pid); 94 | std::ifstream processMap(processMapPath); 95 | 96 | if (!processMap.is_open()) 97 | { 98 | const std::string errMsg = fmt::format("Failed to open the maps for pid {}: {}.", 99 | this->m_pid, std::strerror(errno)); 100 | throw std::runtime_error(errMsg); 101 | } 102 | 103 | std::vector memRegions; 104 | std::string line; 105 | while (std::getline(processMap, line)) 106 | { 107 | MemRegion reg; 108 | std::vector tokens = Utils::SplitString(line, ' '); 109 | 110 | // The /proc/pid/maps file always has the same pattern, therefore: 111 | // tokens[0] has the memory address range 112 | // tokens[1] holds the permissions 113 | // tokens[2] holds the offset (unused) 114 | // tokens[3] holds the device (unused) 115 | // tokens[4] holds the inode (unused) 116 | // tokens[5] holds the pathname (optional) 117 | 118 | // Sets the start and end addresses of the region 119 | this->SetMemoryRangeBoundaries(reg, tokens[0]); 120 | 121 | // Calculate the address range length 122 | reg.rangeLength = reg.endAddr - reg.startAddr; 123 | 124 | // Set the actual permissions 125 | this->SetMemoryRegionPerms(reg, tokens[1]); 126 | 127 | // This is the minimum amount of fields in the maps file 128 | // when there is no "pathname" field 129 | constexpr int MIN_AMOUNT_OF_FIELDS = 5; 130 | 131 | // We have to make sure that the pathname token exists (sometimes it doesn't) 132 | if (tokens.size() > MIN_AMOUNT_OF_FIELDS) 133 | { 134 | reg.pathName = Utils::JoinVectorOfStrings(tokens, MIN_AMOUNT_OF_FIELDS, ' '); 135 | } 136 | else 137 | { 138 | reg.pathName = "unknown"; 139 | } 140 | 141 | // This is here in order to prevent errors when running commands that scan all memory regions 142 | // The [vvar] region, even though marked as readable in the maps file, in reality 143 | // is not readable, so it is safe to disable this flag here 144 | if (reg.pathName == "[vvar]") 145 | { 146 | reg.perms.readFlag = false; 147 | } 148 | memRegions.push_back(reg); 149 | } 150 | return memRegions; 151 | } 152 | 153 | MemoryScanner& Process::GetMemoryScanner() 154 | { 155 | return this->m_MemoryScanner; 156 | } 157 | 158 | MemoryFreezer& Process::GetMemoryFreezer() 159 | { 160 | return this->m_MemoryFreezer; 161 | } 162 | 163 | void Process::PrintMessageQueues() 164 | { 165 | size_t memFreezerQueueSize = this->m_MemoryFreezer.GetMessageQueueSize(); 166 | if (memFreezerQueueSize == 0) 167 | { 168 | return; 169 | } 170 | 171 | fmt::print("\nMessages from MemoryFreezer:\n"); 172 | while (memFreezerQueueSize--) 173 | { 174 | fmt::print("{}\n", this->m_MemoryFreezer.MessageQueuePop()); 175 | } 176 | } 177 | 178 | -------------------------------------------------------------------------------- /include/MemoryFuncs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "MemoryStructs.h" 10 | #include "ComparisonType.h" 11 | 12 | namespace MemoryFuncs 13 | { 14 | // Wrappers for process_vm_readv/process_vm_writev respectively 15 | std::vector ReadProcessMemory(pid_t pid, unsigned long baseAddr, long length); 16 | ssize_t WriteToProcessMemory(pid_t pid, unsigned long baseAddr, long dataSize, void* data); 17 | 18 | // Compares two values based on the given comparison type 19 | template 20 | bool CompareData(const void* lhs, const void* rhs, size_t dataSize, ComparisonType cmpType); 21 | 22 | template <> 23 | bool CompareData(const void* lhs, const void* rhs, size_t dataSize, 24 | ComparisonType cmpType); 25 | 26 | // Returns a vector of the memory addresses where the given data was found 27 | // dataToFind can be of any type 28 | // dataSize is the size of the type / length of string (if string type is used) 29 | // This overload checks a region of addresses 30 | template 31 | std::vector FindDataInMemory(pid_t pid, const std::vector& memRegions, 32 | size_t dataSize, const void* dataToFind, ComparisonType cmpType); 33 | 34 | // This overload checks a vector of addresses 35 | template 36 | std::vector FindDataInMemory(pid_t pid, const std::vector& memAddrs, 37 | size_t dataSize, const void* dataToFind, ComparisonType cmpType); 38 | } 39 | 40 | 41 | template 42 | bool MemoryFuncs::CompareData(const void* lhs, const void* rhs, size_t dataSize, 43 | ComparisonType cmpType) 44 | { 45 | (void)dataSize; // Used only in the std::string specialization 46 | 47 | // Cast the void pointers into values 48 | T lhsVal = *(T*)lhs; 49 | T rhsVal = *(T*)rhs; 50 | switch (cmpType) 51 | { 52 | case ComparisonType::Equal: 53 | return lhsVal == rhsVal; 54 | 55 | case ComparisonType::NotEqual: 56 | return lhsVal != rhsVal; 57 | 58 | case ComparisonType::Greater: 59 | return lhsVal > rhsVal; 60 | 61 | case ComparisonType::Less: 62 | return lhsVal < rhsVal; 63 | 64 | case ComparisonType::GreaterEqual: 65 | return lhsVal >= rhsVal; 66 | 67 | case ComparisonType::LessEqual: 68 | return lhsVal <= rhsVal; 69 | 70 | default: 71 | throw std::runtime_error("Invalid comparison type in CompareMemoryValue()"); 72 | } 73 | } 74 | 75 | template 76 | std::vector MemoryFuncs::FindDataInMemory(pid_t pid, const std::vector& memRegions, 77 | size_t dataSize, const void* dataToFind, ComparisonType cmpType) 78 | { 79 | // Vector of the memory addresses with the found data 80 | std::vector addrs; 81 | 82 | for (auto it = memRegions.cbegin(); it != memRegions.cend(); it++) 83 | { 84 | // Skip unreadable memory regions 85 | if (!it->perms.readFlag) 86 | { 87 | continue; 88 | } 89 | 90 | // TODO: implement a limit on how much memory can be read at a time 91 | std::vector regMemory; 92 | try 93 | { 94 | regMemory = MemoryFuncs::ReadProcessMemory(pid, it->startAddr, it->rangeLength); 95 | } 96 | catch (const std::exception& e) 97 | { 98 | fmt::print(stderr, "WARNING: Error reading memory region {:#018x} ({}): {}\n", 99 | it->startAddr, it->pathName, e.what()); 100 | continue; 101 | } 102 | 103 | // Check if there was a partial read 104 | if (regMemory.size() != it->rangeLength) 105 | { 106 | fmt::print("WARNING: Partial read of {}/{} bytes at memory address {:#018x}.\n", 107 | regMemory.size(), it->rangeLength, it->startAddr); 108 | } 109 | 110 | // The vector is a contiguous array in memory so we can do this 111 | const unsigned char* dataPtr = ®Memory[0]; 112 | for (unsigned long i = 0; i < regMemory.size(); i++) 113 | { 114 | // We always want to have at least dataTypeSize bytes 115 | if (regMemory.size() - i < dataSize) 116 | { 117 | break; 118 | } 119 | 120 | const unsigned char* offsetDataPtr = dataPtr + i; 121 | // offsetDataPtr should be the lhs, dataToFind should be rhs 122 | if (MemoryFuncs::CompareData((void*)offsetDataPtr, dataToFind, dataSize, cmpType)) 123 | { 124 | // Store the memory address where the data was found 125 | MemAddress addrStruct = { it->startAddr + i, *it }; 126 | addrs.push_back(addrStruct); 127 | } 128 | } 129 | } 130 | return addrs; 131 | } 132 | 133 | // This overload checks a vector of addresses 134 | template 135 | std::vector MemoryFuncs::FindDataInMemory(pid_t pid, const std::vector& memAddrs, 136 | size_t dataSize, const void* dataToFind, ComparisonType cmpType) 137 | { 138 | // Vector of memory addresses with the found data 139 | std::vector addrs; 140 | 141 | for (auto it = memAddrs.cbegin(); it != memAddrs.cend(); it++) 142 | { 143 | // Skip unreadable addresses 144 | if (!it->memRegion.perms.readFlag) 145 | { 146 | continue; 147 | } 148 | 149 | // When trying to read from individual addresses, some addresses may no longer be used by 150 | // the process we read them from. 151 | std::vector addrMemory; 152 | try 153 | { 154 | addrMemory = MemoryFuncs::ReadProcessMemory(pid, it->address, dataSize); 155 | } 156 | catch (const std::exception& e) 157 | { 158 | fmt::print(stderr, "WARNING: Error reading memory address {:#018x}: {}\n", 159 | it->address, e.what()); 160 | continue; 161 | } 162 | 163 | // Check if there was a partial read 164 | if (addrMemory.size() != dataSize) 165 | { 166 | fmt::print("WARNING: Partial read of {}/{} at memory address {:#018x}.\n", 167 | addrMemory.size(), dataSize, it->address); 168 | } 169 | 170 | // The target data should be in the rhs 171 | if (MemoryFuncs::CompareData((void*)&addrMemory[0], dataToFind, dataSize, cmpType)) 172 | { 173 | addrs.push_back(*it); 174 | } 175 | } 176 | return addrs; 177 | } 178 | 179 | -------------------------------------------------------------------------------- /src/cmds/FreezeCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "cmds/FreezeCommand.h" 2 | #include "MemoryFreezer.h" 3 | #include 4 | #include 5 | #include "Utils.h" 6 | #include "DataType.h" 7 | 8 | template 9 | std::vector DataToByteVector(const std::string& data) 10 | { 11 | constexpr size_t dataSize = sizeof(T); 12 | // Convert the data to the correct type (for the correct binary representation) 13 | T dataValue = Utils::StrToNumber(data); 14 | 15 | // Get a pointer to the dataValue in memory 16 | uint8_t* dataValueBytePtr = (uint8_t*)&dataValue; 17 | 18 | std::vector byteVector; 19 | // Construct the vector of bytes 20 | byteVector.insert(byteVector.end(), &dataValueBytePtr[0], &dataValueBytePtr[dataSize]); 21 | 22 | return byteVector; 23 | } 24 | 25 | template <> 26 | std::vector DataToByteVector(const std::string& data) 27 | { 28 | return std::vector(data.begin(), data.end()); 29 | } 30 | 31 | static void ListFrozenMemoryAddresses(const std::list& frozenAddrs) 32 | { 33 | if (frozenAddrs.size() == 0) 34 | { 35 | fmt::print("No memory addresses to list.\n"); 36 | return; 37 | } 38 | 39 | const size_t indexWidth = std::to_string(frozenAddrs.size()).size(); 40 | int index = 0; 41 | for (auto it = frozenAddrs.cbegin(); it != frozenAddrs.cend(); it++) 42 | { 43 | fmt::print("[{:{}}][{}] {:#018x} (in {}) [{}: {}]", 44 | index, indexWidth, 45 | it->enabled ? 'X' : ' ', // If the address is enabled, mark it with an X 46 | it->memAddress.address, 47 | it->memAddress.memRegion.pathName, 48 | it->typeStr, 49 | it->dataStr); 50 | 51 | if (!it->note.empty()) 52 | { 53 | fmt::print(" Note: \"{}\"", it->note); 54 | } 55 | 56 | fmt::print("\n"); 57 | index++; 58 | } 59 | } 60 | 61 | void FreezeCommand::Main(Process& proc, const std::vector& args) 62 | { 63 | if (args.size() < 2) 64 | { 65 | throw std::runtime_error("Missing keyword argument."); 66 | } 67 | 68 | MemoryFreezer& memFreezer = proc.GetMemoryFreezer(); 69 | 70 | const std::string& keywordStr = args[1]; 71 | if (keywordStr == "list") 72 | { 73 | const std::list& frozenAddrs = memFreezer.GetFrozenAddresses(); 74 | ListFrozenMemoryAddresses(frozenAddrs); 75 | } 76 | // Keywords that require 1 arg 77 | else if (keywordStr == "remove" || keywordStr == "enable" || keywordStr == "disable") 78 | { 79 | if (args.size() < 3) 80 | { 81 | throw std::runtime_error("Missing arguments."); 82 | } 83 | 84 | bool allFlag = false; 85 | size_t index = -1; // Sets the maximum value 86 | const std::string& indexStr = args[2]; 87 | if (indexStr == "all") 88 | { 89 | allFlag = true; 90 | } 91 | else 92 | { 93 | index = Utils::StrToNumber(indexStr, "index"); 94 | } 95 | 96 | if (keywordStr == "remove") 97 | { 98 | if (allFlag) 99 | { 100 | memFreezer.RemoveAllAddresses(); 101 | } 102 | else 103 | { 104 | memFreezer.RemoveAddress(index); 105 | } 106 | } 107 | else if (keywordStr == "enable") 108 | { 109 | if (allFlag) 110 | { 111 | memFreezer.EnableAllAddresses(); 112 | } 113 | else 114 | { 115 | memFreezer.EnableAddress(index); 116 | } 117 | } 118 | else if (keywordStr == "disable") 119 | { 120 | if (allFlag) 121 | { 122 | memFreezer.DisableAllAddresses(); 123 | } 124 | else 125 | { 126 | memFreezer.DisableAddress(index); 127 | } 128 | } 129 | } 130 | // This keyword requires 3 args 131 | else if (keywordStr == "add" || keywordStr == "modify") 132 | { 133 | if (args.size() < 5) 134 | { 135 | throw std::runtime_error("Missing arguments."); 136 | } 137 | 138 | std::string note = ""; 139 | try 140 | { 141 | note = args.at(5); 142 | } 143 | // The error can be ignored since the note argument is optional 144 | catch (const std::out_of_range&) {} 145 | 146 | const std::string& typeStr = args[3]; 147 | const std::string& dataStr = args[4]; 148 | std::vector byteVector; 149 | 150 | switch (ParseDataType(typeStr)) 151 | { 152 | case DataType::int8: byteVector = DataToByteVector(dataStr); break; 153 | case DataType::int16: byteVector = DataToByteVector(dataStr); break; 154 | case DataType::int32: byteVector = DataToByteVector(dataStr); break; 155 | case DataType::int64: byteVector = DataToByteVector(dataStr); break; 156 | case DataType::uint8: byteVector = DataToByteVector(dataStr); break; 157 | case DataType::uint16: byteVector = DataToByteVector(dataStr); break; 158 | case DataType::uint32: byteVector = DataToByteVector(dataStr); break; 159 | case DataType::uint64: byteVector = DataToByteVector(dataStr); break; 160 | case DataType::f32: byteVector = DataToByteVector(dataStr); break; 161 | case DataType::f64: byteVector = DataToByteVector(dataStr); break; 162 | case DataType::string: byteVector = DataToByteVector(dataStr); break; 163 | } 164 | 165 | if (keywordStr == "add") 166 | { 167 | unsigned long address = Utils::StrToNumber(args[2], "address"); 168 | MemRegion memRegion = Utils::FindRegionOfAddress(proc.GetMemoryRegions(), address); 169 | MemAddress memAddress = { address, memRegion }; 170 | 171 | memFreezer.AddAddress(memAddress, typeStr, dataStr, byteVector, note); 172 | } 173 | else if (keywordStr == "modify") 174 | { 175 | if (args[2] == "all") // Modify all addresses 176 | { 177 | memFreezer.ModifyAllAddresses(typeStr, dataStr, byteVector, note); 178 | } 179 | else // Modify an address in a specific index 180 | { 181 | size_t index = Utils::StrToNumber(args[2], "index"); 182 | memFreezer.ModifyAddress(index, typeStr, dataStr, byteVector, note); 183 | } 184 | } 185 | } 186 | else 187 | { 188 | throw std::runtime_error("Invalid keyword."); 189 | } 190 | } 191 | 192 | std::string FreezeCommand::Help() 193 | { 194 | return std::string( 195 | "Usage: freeze [args...]\n\n" 196 | 197 | "Continuously overwrites values in memory addresses, effectively \"freezing\" them.\n\n" 198 | 199 | "Keywords with no args required:\n" 200 | "list -- Lists the frozen memory addresses in the following format:\n" 201 | "\t[index][enabled/disabled] [address] [pathname] [type] [data]\n\n" 202 | 203 | "Keywords that require 1 argument:\n" 204 | "remove -- Removes the address in the given index, or removes all addresses.\n" 205 | "enable -- Enables the program to freeze the address in the given index, or all addresses.\n" 206 | "disable -- Disables the program from freezing the address in the given index, or all addresses.\n\n" 207 | 208 | "Keywords that require 3 arguments:\n" 209 | "add
[note] -- Adds the address to the freezing list which will write to
continuously.\n" 210 | "\tWhen addresses are added, they are disabled and have to be enabled manually.\n" 211 | "\tA note is an optional string that will appear next to the address in the freeze list.\n" 212 | "modify [note] -- Modifies an existing address by changing the and .\n" 213 | "\tPassing a new note will overwrite the old note, otherwise the old note will stay.\n"); 214 | } 215 | 216 | -------------------------------------------------------------------------------- /src/cmds/ScanCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "cmds/ScanCommand.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "Utils.h" 7 | #include "DataType.h" 8 | #include "ComparisonType.h" 9 | #include "cmds/WriteCommand.h" 10 | #include "cmds/FreezeCommand.h" 11 | 12 | template 13 | size_t CallScanner(Process& proc, size_t dataSize, const void* data, ComparisonType cmpType) 14 | { 15 | MemoryScanner& memScanner = proc.GetMemoryScanner(); 16 | 17 | // Calls the correct scan depending on if a new scan was started or not 18 | if (memScanner.GetScanStartedFlag()) 19 | { 20 | return memScanner.NextScan(dataSize, data, cmpType); 21 | } 22 | else 23 | { 24 | return memScanner.NewScan(proc.GetMemoryRegions(), dataSize, data, cmpType); 25 | } 26 | } 27 | 28 | template 29 | size_t ScanForData(Process& proc, const std::string& dataStr, ComparisonType cmpType) 30 | { 31 | constexpr size_t dataSize = sizeof(T); 32 | T dataValue = Utils::StrToNumber(dataStr); 33 | 34 | return CallScanner(proc, dataSize, (void*)&dataValue, cmpType); 35 | } 36 | 37 | template <> 38 | size_t ScanForData(Process& proc, const std::string& dataStr, ComparisonType cmpType) 39 | { 40 | return CallScanner(proc, dataStr.size(), (void*)dataStr.c_str(), cmpType); 41 | } 42 | 43 | static void ListSavedAddresses(const std::vector& memAddrs) 44 | { 45 | if (memAddrs.empty()) 46 | { 47 | throw std::runtime_error("No memory addresses to list."); 48 | } 49 | else 50 | { 51 | Utils::PrintMemoryAddresses(memAddrs); 52 | } 53 | } 54 | 55 | static void WriteToSavedAddresses(Process& proc, const std::vector& args) 56 | { 57 | // This is the vector of arguments which will be passed to the write command 58 | // the first argument is always the command name 59 | // the second argument is the address (it will be updated inside the loop) 60 | // the third argument is the type of the data 61 | // the fourth argument is the data itself 62 | // Scan command syntax: scan write 63 | if (args.size() < 4) 64 | { 65 | throw std::runtime_error("Missing arguments."); 66 | } 67 | std::vector writeCmdArgs = { "write", "", args[2], args[3] }; 68 | 69 | auto memAddrs = proc.GetMemoryScanner().GetCurrScanVector(); 70 | int writeSuccess = 0; // Tracks how many addresses were written to 71 | 72 | for (auto it = memAddrs.cbegin(); it != memAddrs.cend(); it++) 73 | { 74 | // Skip addresses to which data cannot be written 75 | if (!it->memRegion.perms.writeFlag) 76 | { 77 | continue; 78 | } 79 | 80 | writeCmdArgs[1] = std::to_string(it->address); 81 | 82 | try 83 | { 84 | WriteCommand::Main(proc, writeCmdArgs); 85 | writeSuccess++; 86 | } 87 | catch (const std::exception& e) 88 | { 89 | fmt::print(stderr, "Error writing to address {:#018x}: {}\n", it->address, e.what()); 90 | } 91 | } 92 | fmt::print("Written to {}/{} memory addresses.\n", writeSuccess, memAddrs.size()); 93 | } 94 | 95 | static void AddScanListToFreezeList(Process& proc, const std::vector& args) 96 | { 97 | // The syntax of the freeze add command is: 98 | // freeze add
[note] 99 | // The synax of the scan freeze command is: 100 | // scan freeze [note] 101 | if (args.size() < 4) 102 | { 103 | throw std::runtime_error("Missing arguments."); 104 | } 105 | 106 | std::string note = ""; 107 | try 108 | { 109 | note = args.at(4); 110 | } 111 | // No error handling since the argument is optional 112 | catch (const std::out_of_range&) {} 113 | 114 | // The third element is the address which will be modified inside the loop 115 | std::vector freezeCmdArgs = { "freeze", "add", "", args[2], args[3], note }; 116 | 117 | auto memAddrs = proc.GetMemoryScanner().GetCurrScanVector(); 118 | int success = 0; 119 | 120 | for (auto it = memAddrs.cbegin(); it != memAddrs.cend(); it++) 121 | { 122 | // Skip addresses without the write flag enabled 123 | if (!it->memRegion.perms.writeFlag) 124 | { 125 | continue; 126 | } 127 | 128 | freezeCmdArgs[2] = std::to_string(it->address); 129 | 130 | try 131 | { 132 | FreezeCommand::Main(proc, freezeCmdArgs); 133 | success++; 134 | } 135 | catch (const std::exception& e) 136 | { 137 | fmt::print(stderr, "Error adding address {:#018x}: {}\n", it->address, e.what()); 138 | } 139 | } 140 | fmt::print("Added {}/{} addresses to the freeze list.\n", success, memAddrs.size()); 141 | } 142 | 143 | void ScanCommand::Main(Process& proc, const std::vector& args) 144 | { 145 | if (args.size() < 2) 146 | { 147 | throw std::runtime_error("Missing keyword argument."); 148 | } 149 | 150 | const std::string& keywordStr = args[1]; 151 | if (keywordStr == "clear") 152 | { 153 | proc.GetMemoryScanner().Clear(); 154 | } 155 | else if (keywordStr == "undo") 156 | { 157 | proc.GetMemoryScanner().Undo(); 158 | } 159 | else if (keywordStr == "list") 160 | { 161 | ListSavedAddresses(proc.GetMemoryScanner().GetCurrScanVector()); 162 | } 163 | else if (keywordStr == "write") 164 | { 165 | WriteToSavedAddresses(proc, args); 166 | } 167 | else if (keywordStr == "freeze") 168 | { 169 | AddScanListToFreezeList(proc, args); 170 | } 171 | else 172 | { 173 | // ParseComparisonType will throw if the keyword is incorrect or doesn't exist 174 | ComparisonType cmpType = ParseComparisonType(keywordStr); 175 | 176 | // Check if enough arguments were given 177 | // scan 178 | if (args.size() < 4) 179 | { 180 | throw std::runtime_error("Missing arguments for scanning."); 181 | } 182 | 183 | size_t resAmount = 0; 184 | const std::string& typeStr = args[2]; 185 | const std::string& dataStr = args[3]; 186 | switch (ParseDataType(typeStr)) 187 | { 188 | case DataType::int8: resAmount = ScanForData(proc, dataStr, cmpType); break; 189 | case DataType::int16: resAmount = ScanForData(proc, dataStr, cmpType); break; 190 | case DataType::int32: resAmount = ScanForData(proc, dataStr, cmpType); break; 191 | case DataType::int64: resAmount = ScanForData(proc, dataStr, cmpType); break; 192 | case DataType::uint8: resAmount = ScanForData(proc, dataStr, cmpType); break; 193 | case DataType::uint16: resAmount = ScanForData(proc, dataStr, cmpType); break; 194 | case DataType::uint32: resAmount = ScanForData(proc, dataStr, cmpType); break; 195 | case DataType::uint64: resAmount = ScanForData(proc, dataStr, cmpType); break; 196 | case DataType::f32: resAmount = ScanForData(proc, dataStr, cmpType); break; 197 | case DataType::f64: resAmount = ScanForData(proc, dataStr, cmpType); break; 198 | case DataType::string: resAmount = ScanForData(proc, dataStr, cmpType); break; 199 | } 200 | fmt::print("{} addresses found.\n", resAmount); 201 | } 202 | } 203 | 204 | std::string ScanCommand::Help() 205 | { 206 | return std::string( 207 | "Usage: scan [type] [value]\n\n" 208 | 209 | "Scans the memory of a process and keeps track of the addresses where the value was found.\n" 210 | "Subsequent scans check the values in the saved memory addresses.\n\n" 211 | 212 | "Keywords with no args required:\n" 213 | "clear -- Clears the saved addresses.\n" 214 | "undo -- Undo the last scan. (depth of 1 scan)\n" 215 | "list -- List the saved memory addresses.\n\n" 216 | 217 | "Keywords that require type and value:\n" 218 | "== -- Scans for addresses with a value equal to in the given .\n" 219 | "!= -- Scans for addresses which do not have .\n" 220 | "> -- Scans for addresses where the value is greater than the given .\n" 221 | "< -- Scans for addresses where the value is less than the given .\n" 222 | ">= -- Scans for addresses where the value is greater or equal to \n" 223 | "<= -- Scans for addresses where the value is less or equal to .\n" 224 | "write -- Writes the with the given to all the saved memory addresses.\n" 225 | "freeze -- Adds all the writable addressses in the scan list to the freeze list.\n" 226 | "\tAn optional note can be added as well as another argument after .\n"); 227 | } 228 | 229 | -------------------------------------------------------------------------------- /src/MemoryFreezer.cpp: -------------------------------------------------------------------------------- 1 | #include "MemoryFreezer.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "MemoryFuncs.h" 8 | #include 9 | 10 | MemoryFreezer::MemoryFreezer() 11 | { 12 | this->m_EnabledAddressesAmount = 0; 13 | this->m_ThreadRunning = false; 14 | this->m_pid = 0; 15 | } 16 | 17 | MemoryFreezer::~MemoryFreezer() {} 18 | 19 | void MemoryFreezer::AddAddress(MemAddress memAddress, const std::string& typeStr, const std::string& dataStr, 20 | std::vector& data, const std::string& note) 21 | { 22 | // Prevent adding read-only addresses 23 | if (!memAddress.memRegion.perms.writeFlag) 24 | { 25 | throw std::runtime_error("Cannot add read-only address."); 26 | } 27 | 28 | // Check if the address already exists 29 | for (auto it = this->m_FrozenAddresses.cbegin(); it != this->m_FrozenAddresses.cend(); it++) 30 | { 31 | if (it->memAddress.address == memAddress.address) 32 | { 33 | throw std::runtime_error("The address is already in the list."); 34 | } 35 | } 36 | 37 | // Add the address but have it disabled 38 | FrozenMemAddress frozenAddr = { memAddress, false, typeStr, dataStr, data, note }; 39 | 40 | this->m_MemoryFreezerMutex.lock(); 41 | 42 | this->m_FrozenAddresses.push_back(frozenAddr); 43 | 44 | this->m_MemoryFreezerMutex.unlock(); 45 | } 46 | 47 | void MemoryFreezer::RemoveAddress(size_t index) 48 | { 49 | if (index >= this->m_FrozenAddresses.size()) 50 | { 51 | throw std::runtime_error("Index out of bounds."); 52 | } 53 | else 54 | { 55 | auto iter = this->m_FrozenAddresses.cbegin(); 56 | std::advance(iter, index); 57 | 58 | this->m_MemoryFreezerMutex.lock(); 59 | 60 | // Make sure to decrement the enabled addresses, if the erased address was enabled 61 | if (iter->enabled) 62 | { 63 | this->m_EnabledAddressesAmount -= 1; 64 | } 65 | this->m_FrozenAddresses.erase(iter); 66 | 67 | // Prevents a possible crash where the thread might attempt to use a deleted iterator 68 | this->m_FrozenAddressIter = this->m_FrozenAddresses.begin(); 69 | 70 | this->m_MemoryFreezerMutex.unlock(); 71 | } 72 | } 73 | 74 | void MemoryFreezer::RemoveAllAddresses() 75 | { 76 | this->m_MemoryFreezerMutex.lock(); 77 | 78 | this->m_FrozenAddresses.clear(); 79 | this->m_EnabledAddressesAmount = 0; 80 | 81 | this->m_MemoryFreezerMutex.unlock(); 82 | } 83 | 84 | void MemoryFreezer::EnableAddress(size_t index) 85 | { 86 | if (index >= this->m_FrozenAddresses.size()) 87 | { 88 | throw std::runtime_error("Index out of bounds."); 89 | } 90 | else 91 | { 92 | auto iter = this->m_FrozenAddresses.begin(); 93 | std::advance(iter, index); 94 | 95 | if (!iter->enabled) 96 | { 97 | this->m_MemoryFreezerMutex.lock(); 98 | 99 | iter->enabled = true; 100 | this->m_EnabledAddressesAmount += 1; 101 | 102 | this->m_MemoryFreezerMutex.unlock(); 103 | 104 | // We may need to start/restart the thread if there were no addresses before 105 | // and a new address was added now 106 | this->StartThreadLoopIfNeeded(); 107 | } 108 | } 109 | } 110 | 111 | void MemoryFreezer::DisableAddress(size_t index) 112 | { 113 | if (index >= this->m_FrozenAddresses.size()) 114 | { 115 | throw std::runtime_error("Index out of bounds."); 116 | } 117 | else 118 | { 119 | auto iter = this->m_FrozenAddresses.begin(); 120 | std::advance(iter, index); 121 | 122 | if (iter->enabled) 123 | { 124 | this->m_MemoryFreezerMutex.lock(); 125 | 126 | this->m_EnabledAddressesAmount -= 1; 127 | iter->enabled = false; 128 | 129 | this->m_MemoryFreezerMutex.unlock(); 130 | } 131 | } 132 | } 133 | 134 | void MemoryFreezer::EnableAllAddresses() 135 | { 136 | for (auto it = this->m_FrozenAddresses.begin(); it != this->m_FrozenAddresses.end(); it++) 137 | { 138 | this->m_MemoryFreezerMutex.lock(); 139 | 140 | // Only count (and enable) addresses which are currently disabled 141 | if (!it->enabled) 142 | { 143 | it->enabled = true; 144 | this->m_EnabledAddressesAmount += 1; 145 | } 146 | 147 | this->m_MemoryFreezerMutex.unlock(); 148 | } 149 | this->StartThreadLoopIfNeeded(); 150 | } 151 | 152 | void MemoryFreezer::DisableAllAddresses() 153 | { 154 | this->m_MemoryFreezerMutex.lock(); 155 | 156 | for (auto it = this->m_FrozenAddresses.begin(); it != this->m_FrozenAddresses.end(); it++) 157 | { 158 | it->enabled = false; 159 | } 160 | this->m_EnabledAddressesAmount = 0; 161 | 162 | this->m_MemoryFreezerMutex.unlock(); 163 | } 164 | 165 | void MemoryFreezer::ModifyAddress(size_t index, const std::string& typeStr, const std::string& dataStr, 166 | std::vector& data, const std::string& note) 167 | { 168 | if (index >= this->m_FrozenAddresses.size()) 169 | { 170 | throw std::runtime_error("Index out of bounds."); 171 | } 172 | else 173 | { 174 | auto iter = this->m_FrozenAddresses.begin(); 175 | std::advance(iter, index); 176 | 177 | this->m_MemoryFreezerMutex.lock(); 178 | 179 | iter->typeStr = typeStr; 180 | iter->dataStr = dataStr; 181 | iter->data = data; 182 | if (!note.empty()) // Only replace the note if it is not empty 183 | { 184 | iter->note = note; 185 | } 186 | 187 | this->m_MemoryFreezerMutex.unlock(); 188 | } 189 | } 190 | 191 | void MemoryFreezer::ModifyAllAddresses(const std::string& typeStr, const std::string& dataStr, 192 | std::vector& data, const std::string& note) 193 | { 194 | for (size_t i = 0; i < this->m_FrozenAddresses.size(); i++) 195 | { 196 | this->ModifyAddress(i, typeStr, dataStr, data, note); 197 | } 198 | } 199 | 200 | const std::list& MemoryFreezer::GetFrozenAddresses() const 201 | { 202 | return this->m_FrozenAddresses; 203 | } 204 | 205 | int MemoryFreezer::GetEnabledAddressesAmount() const 206 | { 207 | return this->m_EnabledAddressesAmount; 208 | } 209 | 210 | void MemoryFreezer::SetPid(pid_t pid) 211 | { 212 | this->m_pid = pid; 213 | 214 | this->m_MemoryFreezerMutex.lock(); 215 | 216 | this->m_FrozenAddresses.clear(); 217 | this->m_EnabledAddressesAmount = 0; 218 | 219 | this->m_MemoryFreezerMutex.unlock(); 220 | } 221 | 222 | void MemoryFreezer::StartThreadLoopIfNeeded() 223 | { 224 | // Start a thread only if there is no thread running already and if there are enabled addresses 225 | if (!this->m_ThreadRunning && this->m_EnabledAddressesAmount > 0) 226 | { 227 | this->m_ThreadRunning = true; 228 | 229 | std::thread th(&MemoryFreezer::ThreadLoop, this); 230 | th.detach(); 231 | } 232 | } 233 | 234 | void MemoryFreezer::ThreadLoop() 235 | { 236 | this->m_FrozenAddressIter = this->m_FrozenAddresses.begin(); 237 | while (true) 238 | { 239 | this->m_MemoryFreezerMutex.lock(); 240 | 241 | // Stopping condition 242 | if (this->m_EnabledAddressesAmount <= 0) 243 | { 244 | this->m_MemoryFreezerMutex.unlock(); 245 | break; 246 | } 247 | 248 | auto& it = this->m_FrozenAddressIter; 249 | // Restart the iterator if it reached the end 250 | if (it == this->m_FrozenAddresses.end()) 251 | { 252 | it = this->m_FrozenAddresses.begin(); 253 | } 254 | 255 | // Only freeze enabled addresses 256 | if (it->enabled) 257 | { 258 | const std::vector& data = it->data; 259 | const long dataSize = data.size(); 260 | try 261 | { 262 | ssize_t nread = MemoryFuncs::WriteToProcessMemory(this->m_pid, it->memAddress.address, dataSize, (void*)&data[0]); 263 | // Check for partial write 264 | if (nread != dataSize) 265 | { 266 | const std::string msg = fmt::format( 267 | "WARNING: Disabling address {:#018x} due to a partial write of {}/{}.", 268 | it->memAddress.address, nread, data.size()); 269 | this->m_MessageQueue.push(msg); 270 | it->enabled = false; 271 | this->m_EnabledAddressesAmount -= 1; 272 | } 273 | } 274 | catch (const std::exception& e) 275 | { 276 | const std::string msg = fmt::format("Error writing to memory location {:#018x}: {}", 277 | it->memAddress.address, e.what()); 278 | this->m_MessageQueue.push(msg); 279 | it->enabled = false; // Disable the address which caused an exception 280 | this->m_EnabledAddressesAmount -= 1; 281 | } 282 | } 283 | this->m_MemoryFreezerMutex.unlock(); 284 | // Move forward 285 | std::advance(it, 1); 286 | } 287 | this->m_ThreadRunning = false; 288 | } 289 | 290 | size_t MemoryFreezer::GetMessageQueueSize() const 291 | { 292 | return this->m_MessageQueue.size(); 293 | } 294 | 295 | std::string MemoryFreezer::MessageQueuePop() 296 | { 297 | std::string msg = this->m_MessageQueue.front(); 298 | this->m_MessageQueue.pop(); 299 | return msg; 300 | } 301 | 302 | --------------------------------------------------------------------------------