// // Created by acite on 5/1/24. // #ifndef _H_CORE #define _H_CORE 1 #include #include #include #include #include #include #include #include #include #include "MemoryMap.hpp" #include #include #include #include #include #include #define WORD_SIZE 8 enum Msg{ MSG_PAUSE = 0, MSG_STOP = 1, MSG_CONTINUE = 2, MSG_STARTUP = 3, MSG_REG = 4, MSG_QUIT = 5, MSG_LOG = 6, MSG_ASM = 7, MSG_TITLE = 8, MSG_STEPINTO = 9, MSG_MEMORY = 10, MSG_SELECT = 11, MSG_CURRENTSYMBOL = 12, MSG_RESTART = 13, MSG_BREAK = 14, MSG_STEPOVER = 15, MSG_SYMBOLS = 16, MSG_SWITCHSYMBOL= 17, MSG_NONSYM = 18, MSG_PARSE = 19, MSG_INSYM = 20, MSG_DELBREAK = 21, MSG_SWITCHMODULE= 22, MSG_GETBREAKS = 23, MSG_CALLSTACK = 24, MSG_BREAKONCE = 25 }; struct DebugCommand { char id; void* param; }; struct BreakPoint { uint64_t addr; bool once; uint64_t origin; uint16_t size; bool enable; }; struct Symbol{ char name[255]; uint64_t address; uint64_t size; uint64_t base; }; struct CallstackStruct { uint64_t callPoint; uint64_t stackBase; Symbol symbol; char module[255]; int depth; }; class Core{ private: std::map _breakPoints; int _ep = -1; uint64_t _lastRip = 0; int _counter = 0; uint64_t _needToRestore = 0; public: const uint64_t int3 = 0xCCCCCCCCCCCCCCCC; int pipe = -1; int remotePipe = -1; user_regs_struct regs; std::vector symbols; std::vector staticSymbols; std::unordered_map> symbolMap; Symbol currentSymbol{}; void PeekTexts(uint64_t* data, uint64_t words) const { auto addr = _lastRip; for(uint64_t i=0;i= codeAddr && k.second.addr < codeAddr + codeSize) { uint64_t localOffset = k.second.addr - codeAddr; auto px = reinterpret_cast(codeData + localOffset); uint64_t mask = 0xFFFFFFFFFFFFFFFF << k.second.size * 8; *px = (k.second.origin & (~mask)) | ((*px) & mask); } } } */ uint64_t GetBaseAddress() { if(_map == nullptr) return 0; for(int i=0;i<_map->Size();i++) { auto j = _map->GetItem(i); if(_path == j.path) { return j.startAddress; } } return 0; } uint64_t GetBaseAddress(const std::string& module) { if(_map == nullptr) return 0; for(int i=0;i<_map->Size();i++) { auto j = _map->GetItem(i); if(module == j.path) { return j.startAddress; } } return 0; } uint64_t GetEndAddress(const std::string& module) { if(_map == nullptr) return 0; uint64_t endAddr = 0; for(int i=0;i<_map->Size();i++) { auto j = _map->GetItem(i); if(module == j.path) { endAddr = j.endAddress; } } return endAddr; } uint64_t GetEntryPoint() { int fd = open(_path.c_str(), O_RDONLY); if(fd < 0) return 0; elf::elf ef(elf::create_mmap_loader(fd)); return ef.get_hdr().entry; } std::vector GetSymbols(const std::string& module) { std::vector tr; std::map r; uint64_t lbase = GetBaseAddress(module); uint64_t ebase = GetEndAddress(module); if(symbolMap.contains(module)) { tr = symbolMap[module]; } else { int fd = open(module.c_str(), O_RDONLY); if(fd < 0) return {}; elf::elf ef(elf::create_mmap_loader(fd)); for (const auto& section : ef.sections()) { if (section.get_hdr().type == elf::sht::symtab) { const auto& lsymbols = section.as_symtab(); for (const auto& symbol : lsymbols) { if (symbol.get_data().type() == elf::stt::func && symbol.get_data().value != 0) { // A symbol with zero address maybe an extern symbol in (.so) // We will not place it in local module symbol list // Because we fill find its body in the symbol table of another modules Symbol h{}; strcpy(h.name, symbol.get_name().c_str()); h.address = symbol.get_data().value; h.size = symbol.get_data().size; h.base = lbase; r.emplace(symbol.get_name(), h); } } } } for (const auto& section : ef.sections()) { if (section.get_hdr().type == elf::sht::dynsym) { const auto& lsymbols = section.as_symtab(); for (const auto& symbol : lsymbols) { if (symbol.get_data().type() == elf::stt::func && symbol.get_data().value != 0 && (!r.contains(symbol.get_name()))) { Symbol h{}; strcpy(h.name, symbol.get_name().c_str()); h.address = symbol.get_data().value; h.size = symbol.get_data().size; h.base = lbase; r.emplace(symbol.get_name(), h); } } } } for(const auto& j : r) { tr.emplace_back(j.second); } for(auto &k : tr) { if(k.size == 0) // Unmarked Symbol { // Find a symbol following behind it uint64_t addr = 0xffffffffffffffff; for(auto &j : tr) { if(j.address > k.address && j.address < addr) addr = j.address; } if(addr != 0xffffffffffffffff){ k.size = addr - k.address; continue; } // Found no Symbol // Set its size to the end of its section for (const auto& section : ef.sections()) { if(k.address > section.get_hdr().addr && k.address < section.get_hdr().addr + section.size()) { k.size = section.get_hdr().addr + section.size() - k.address; } } } } symbolMap.emplace(module, tr); } for(const auto &k : staticSymbols) { if(k.address >= lbase && k.address < ebase) { bool within = false; for(const auto &j : tr) { if(k.address >= j.address + j.base && k.address < j.address + j.size + j.base) { within = true; break; } } if(!within) tr.emplace_back(k); } } return tr; } void InsertBreakPoint(uint64_t addr, bool once = true) { if(_breakPoints.contains(addr)) return; auto ins = disassemblyFrom(addr, 1, 0); BreakPoint bp{}; bp.once = once; bp.addr = addr; bp.origin = ptrace(PTRACE_PEEKTEXT, _process, addr, NULL); bp.size = ins[0].size; if(bp.size > 8) bp.size = 8; bp.enable = true; uint64_t mask = 0xFFFFFFFFFFFFFFFF << bp.size * 8; uint64_t int3_data = (bp.origin & mask) | (int3 & (~mask)); ptrace(PTRACE_POKETEXT, _process, addr, int3_data); _breakPoints.emplace(bp.addr, bp); } void DisableBreakPoint(uint64_t addr) { if(!_breakPoints.contains(addr)) return; _breakPoints[addr].enable = false; uint64_t origin = ptrace(PTRACE_PEEKTEXT, _process, addr, NULL); uint64_t mask = 0xFFFFFFFFFFFFFFFF << _breakPoints[addr].size * 8; ptrace(PTRACE_POKETEXT, _process, addr, (_breakPoints[addr].origin & (~mask)) | (origin & mask)); } void RestoreBreakPoint(uint64_t addr) { if(!_breakPoints.contains(addr)) return; _breakPoints[addr].enable = true; uint64_t origin = ptrace(PTRACE_PEEKTEXT, _process, addr, NULL); uint64_t mask = 0xFFFFFFFFFFFFFFFF << _breakPoints[addr].size * 8; uint64_t int3_data = (origin & mask) | (int3 & (~mask)); ptrace(PTRACE_POKETEXT, _process, addr, int3_data); } void DeleteBreakPoint(uint64_t addr) { if(!_breakPoints.contains(addr)) return; DisableBreakPoint(addr); _breakPoints.erase(addr); } bool ContainsBreakPoint(uint64_t addr) { return _breakPoints.contains(addr); } std::vector GetBreakpoints() { std::vector r; for(const auto &k : _breakPoints) { r.emplace_back(k.second); } return r; } void TryContinue() { if(_needToRestore == 0) ptrace(PTRACE_CONT, _process, NULL, NULL); else { ptrace(PTRACE_SINGLESTEP, _process, NULL, NULL); _temporaryStop = true; } } void queueMessage(char id, void* param) const { DebugCommand msg{id, param}; if(pipe != -1) { write(remotePipe, &msg, sizeof(DebugCommand)); } } static bool ContainerContains(const std::vector& c, const std::string& e) { for(const auto &k : c) { if(k == e) return true; } return false; } /// disassembly from an address /// \param address /// \param cc /// \param sz cc and sz cannot be both 0 at the same time /// \return [[nodiscard]] std::vector disassemblyFrom(uint64_t address, int cc, uint32_t sz, const std::vector& end = {}, bool make = false) { if(cc == 0 && sz == 0) return {}; uint64_t originAddress = address; csh handle; cs_insn *insn; size_t count; std::vector r; uint64_t data[4]; if(!cc) cc = INT_MAX; if(!sz) sz = 0xFFFFFFFF; if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) return {}; cs_option(handle, CS_OPT_SYNTAX, CS_OPT_SYNTAX_ATT); for(int i=0, p=0;i= staticSymbols[i].address && originAddress < staticSymbols[i].address + staticSymbols[i].size) { staticSymbols.erase(staticSymbols.begin() + i); i--; } } Symbol sym{}; sym.address = originAddress; sym.base = 0; sprintf(sym.name, "call_%lx", sym.address); for(const auto &k : r) { sym.size += k.size; } staticSymbols.emplace_back(sym); } return std::move(r); } bool GetMapEntire(uint64_t addr, MapEntire& ent) { for(int i=0;i<_map->Size();i++) // Read Text Data { auto item = _map->GetItem(i); if(item.startAddress == addr) { ent = item; return true; } } return false; } private: bool entryBreak = true; int _process = -1; pthread_t _th = 0; std::string _path; std::string _argv; std::string _module; std::shared_ptr _map = nullptr; bool _temporaryStop = false; bool _trapped = false; void sendMessage(char id, void* param) const { DebugCommand msg{id, param}; if (pipe != -1) { write(pipe, &msg, sizeof(DebugCommand)); } } void debugHandler() { char str_buffer[128]; std::vector me; int fk = fork(); if(fk == 0) { if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) == -1) { perror("ptrace"); exit(1); } char env[255]; strcpy(env, _path.c_str()); env[_path.rfind('/') + 1] = 0; chdir(env); execl(_path.c_str(), _argv.c_str(), NULL); perror("execve"); exit(1); } _process = fk; sprintf(str_buffer, "%s - [/proc/%d/]", _argv.c_str(), _process); sendMessage(MSG_TITLE, str_buffer); int status = 0; long err; char buffer[32]; _ep = epoll_create1(0); auto ev1 = epoll_event{.events = EPOLLIN}; ev1.data.fd = pipe; epoll_ctl(_ep, EPOLL_CTL_ADD, pipe, &ev1); sendMessage(MSG_STARTUP, nullptr); // Notify Main Thread while(true) { // Handle User IO Message int n = epoll_wait(_ep, &ev1, 1, 1); if(n > 0) { auto sz = read(pipe, buffer, sizeof(DebugCommand)); if(sz != sizeof(DebugCommand)) continue; auto msg = (DebugCommand*)buffer; switch (msg->id) { case MSG_PAUSE: kill(_process, SIGTRAP); break; case MSG_STOP: kill(_process, SIGKILL); sendMessage(MSG_STOP, nullptr); return; case MSG_RESTART: kill(_process, SIGKILL); sendMessage(MSG_RESTART, nullptr); return; case MSG_CONTINUE: if(_trapped) { TryContinue(); if(err == -1) perror("ptrace"); sendMessage(MSG_CONTINUE, nullptr); _trapped = false; } break; case MSG_STEPINTO: if(_trapped) { err = ptrace(PTRACE_SINGLESTEP, _process, NULL, NULL); sendMessage(MSG_CONTINUE, nullptr); _trapped = false; break; } case MSG_STEPOVER: { auto ins = disassemblyFrom(_lastRip, 2, 0); if(ins.size() < 2 || !std::string(ins[0].mnemonic).contains("call")){ err = ptrace(PTRACE_SINGLESTEP, _process, NULL, NULL); sendMessage(MSG_CONTINUE, nullptr); _trapped = false; break; } if(!_breakPoints.contains(ins[1].address)) InsertBreakPoint(ins[1].address, true); TryContinue(); sendMessage(MSG_CONTINUE, nullptr); _trapped = false; break; } case MSG_SWITCHSYMBOL: { auto b = reinterpret_cast(msg->param); auto ci = disassemblyFrom(b->base + b->address, 0, b->size); auto ch = new std::vector; *ch = ci; strcpy(currentSymbol.name, b->name); currentSymbol.address = b->address; currentSymbol.size = b->size; currentSymbol.base = b->base; // currentSymbol sendMessage(MSG_ASM, ch); sendMessage(MSG_CURRENTSYMBOL, nullptr); sendMessage(MSG_SELECT, nullptr); break; } case MSG_PARSE: { for(int i=0;i<_map->Size();i++) // Read Text Data { auto item = _map->GetItem(i); me.emplace_back(item); if(_lastRip >= item.startAddress && _lastRip < item.endAddress) { auto moduleBase = GetBaseAddress(_module); auto ci = disassemblyFrom(_lastRip, 10000, item.endAddress - _lastRip, {"int3", "endbr64"}, true); if(ci.empty()) break; auto ch = new std::vector; *ch = ci; sendMessage(MSG_ASM, ch); sprintf(str_buffer, "%s - [/proc/%d/] - [%s] at %lx", _argv.c_str(), _process, _module.c_str(), moduleBase); sprintf(currentSymbol.name, "call_%lx", _lastRip); currentSymbol.address = _lastRip; currentSymbol.size = item.endAddress - _lastRip; currentSymbol.base = moduleBase; symbols = GetSymbols(_module); sendMessage(MSG_INSYM, nullptr); sendMessage(MSG_TITLE, str_buffer); sendMessage(MSG_SELECT, nullptr); sendMessage(MSG_CURRENTSYMBOL, nullptr); sendMessage(MSG_SYMBOLS, nullptr); } } break; } case MSG_BREAK: { auto addr = (uint64_t)msg->param; InsertBreakPoint(addr, false); break; } case MSG_BREAKONCE: { auto addr = (uint64_t)msg->param; InsertBreakPoint(addr, true); break; } case MSG_DELBREAK: { auto addr = (uint64_t)msg->param; DeleteBreakPoint(addr); break; } case MSG_SWITCHMODULE: { _module = (char*)msg->param; auto moduleBase = GetBaseAddress(_module); symbols = GetSymbols(_module); if(symbols.empty()) break; currentSymbol = symbols[0]; sprintf(str_buffer, "%s - [/proc/%d/] - [%s] at %lx", _argv.c_str(), _process, _module.c_str(), moduleBase); auto ci = disassemblyFrom(moduleBase + currentSymbol.address, 0, currentSymbol.size); auto ch = new std::vector; *ch = ci; sendMessage(MSG_ASM, ch); sendMessage(MSG_TITLE, str_buffer); sendMessage(MSG_SYMBOLS, nullptr); sendMessage(MSG_CURRENTSYMBOL, nullptr); sendMessage(MSG_SELECT, nullptr); delete[](char*)msg->param; break; } case MSG_GETBREAKS: { auto dst = new cs_insn[_breakPoints.size()]; int p = 0; for(const auto &k : GetBreakpoints()) { auto ins = disassemblyFrom(k.addr, 1, 0); dst[p++] = ins[0]; } sendMessage(MSG_GETBREAKS, dst); break; } case MSG_CALLSTACK: { GetCallStack(); break; } default: break; } } // Handle Debug Process Signal auto r = waitpid(_process, &status, WNOHANG); if(r == 0) continue; if(WIFEXITED(status) || WIFSIGNALED(status)) break; int signal_number = WSTOPSIG(status); switch (signal_number) { case SIGTRAP: { _trapped = true; _map = std::make_shared(_process); regs = {}; me.clear(); ptrace(PTRACE_GETREGS, _process, NULL, ®s); if(entryBreak) // If this is the first time enter trap, insert int3 break point to Entry Point { entryBreak = false; _trapped = false; auto base = GetBaseAddress(); uint64_t entryPoint = base + GetEntryPoint(); InsertBreakPoint(entryPoint); ptrace(PTRACE_CONT, _process, NULL, NULL); break; } bool couldBeBp = true; if(_needToRestore != 0) { RestoreBreakPoint(_needToRestore); _needToRestore = 0; couldBeBp = false; } if(_temporaryStop){ _trapped = false; _temporaryStop = false; ptrace(PTRACE_CONT, _process, NULL, NULL); break; } if(_breakPoints.contains(regs.rip - 1) && couldBeBp) { // Reduce RIP by 1 regs.rip -= 1; ptrace(PTRACE_SETREGS, _process, NULL, ®s); // Restore origin data if(_breakPoints[regs.rip].once) DeleteBreakPoint(regs.rip); else { DisableBreakPoint(regs.rip); _needToRestore = regs.rip; } } _lastRip = regs.rip; bool updateModule = false; bool ridSymbols = true; for(int i=0;i<_map->Size();i++) // Read Text Data { auto item = _map->GetItem(i); me.emplace_back(item); if(regs.rip >= item.startAddress && regs.rip < item.endAddress) { if(_module != item.path) updateModule = true; _module = item.path; auto moduleBase = GetBaseAddress(_module); symbols = GetSymbols(_module); for(auto b : symbols) { // If Rip is between the start and the end of a module if(regs.rip >= b.base + b.address && regs.rip < b.base + b.address + b.size) { ridSymbols = false; if (currentSymbol.address == b.address) break; currentSymbol = b; auto ci = disassemblyFrom(moduleBase + b.address, 0, b.size); auto ch = new std::vector; *ch = ci; sendMessage(MSG_ASM, ch); sendMessage(MSG_CURRENTSYMBOL, nullptr); } } if(ridSymbols) { // ridSymbols keeps true means RIP isn't in any Symbol sprintf(str_buffer, "%s - [/proc/%d/] - [%s] at %lx (!Symbol table Escaping!)", _argv.c_str(), _process, _module.c_str(), moduleBase); sendMessage(MSG_NONSYM, nullptr); strcpy(currentSymbol.name, "[Out of Symbol Table]"); currentSymbol.address = _lastRip; currentSymbol.size = item.endAddress - _lastRip; currentSymbol.base = moduleBase; sendMessage(MSG_CURRENTSYMBOL, nullptr); } else{ sprintf(str_buffer, "%s - [/proc/%d/] - [%s] at %lx", _argv.c_str(), _process, _module.c_str(), moduleBase); sendMessage(MSG_INSYM, nullptr); } sendMessage(MSG_TITLE, str_buffer); } } sendMessage(MSG_PAUSE, nullptr); sendMessage(MSG_REG, ®s); sendMessage(MSG_MEMORY, &me); if(updateModule) sendMessage(MSG_SYMBOLS, nullptr); sendMessage(MSG_SELECT, nullptr); break; } case SIGTERM: kill(_process, SIGKILL); waitpid(_process, &status, 0); _process = -1; sendMessage(MSG_STOP, nullptr); return; default: break; } } sendMessage(MSG_STOP, nullptr); } public: explicit Core(const std::string& f, const std::string& a) { _path = f; _argv = a; auto lambda = [](void *arg) { Core* instance = static_cast(arg); instance->debugHandler(); return (void*)nullptr; }; pthread_create(&_th, nullptr, lambda, this); } ~Core() { if(_process > 0) { pthread_join(_th, nullptr); // delete[] codeData; close(_ep); } } bool Status() const { return _trapped; } std::optional GetSymbolAt(uint64_t address, std::string &mod) { auto _m = _map->GetModules(); for(const auto &k : _m) { if(address >= k.startAddress && address < k.endAddress) { mod = k.path; for(const auto &l : GetSymbols(k.path)) { if(address >= l.address + l.base && address < l.address + l.size + l.base) { return l; } } } } return std::nullopt; } void GetCallStack() { std::vector> callPoints; auto r = new std::stack; uint64_t cbp = regs.rbp; callPoints.emplace_back(regs.rip, cbp); while(cbp) { callPoints.emplace_back(ptrace(PTRACE_PEEKDATA, _process, cbp + 8, NULL), ptrace(PTRACE_PEEKDATA, _process, cbp, NULL)); cbp = ptrace(PTRACE_PEEKDATA, _process, cbp, NULL); } int cc = 0; for(const auto &k : callPoints) { std::string mod; auto s = GetSymbolAt(k.first, mod); Symbol s2{}; strcpy(s2.name, ""); if(s.has_value()) s2 = s.value(); CallstackStruct e{}; e.callPoint = k.first; sprintf(e.module, "<%s>", mod.empty() ? "???" : mod.c_str()); e.symbol = s2; e.stackBase = k.second; e.depth = cc++; r->emplace(e); } CallstackStruct e{}; e.depth = _process; r->emplace(e); sendMessage(MSG_CALLSTACK, r); } }; #endif