mirror of
https://github.com/HyperDbg/HyperDbg.git
synced 2026-07-18 05:24:47 +00:00
455 lines
No EOL
14 KiB
C++
455 lines
No EOL
14 KiB
C++
|
|
/**
|
|
* @file e.cpp
|
|
* @author Abbas Masoumi Gorji
|
|
* @brief e* command
|
|
* @details
|
|
* @version 0.1
|
|
* @date 2024-07-15
|
|
*
|
|
* @copyright This project is released under the GNU Public License v3.
|
|
*
|
|
*/
|
|
#include "pch.h"
|
|
#include "keystone\keystone.h"
|
|
|
|
extern BOOLEAN g_IsSerialConnectedToRemoteDebuggee;
|
|
extern ACTIVE_DEBUGGING_PROCESS g_ActiveProcessDebuggingState;
|
|
|
|
struct AssembleData
|
|
{
|
|
public:
|
|
std::string AsmRaw {};
|
|
std::string AsmFixed {};
|
|
size_t StatementCount {};
|
|
size_t BytesCount {};
|
|
unsigned char * EncodedBytes {};
|
|
vector<UINT64> EncBytesIntVec {};
|
|
ks_err ks_err {};
|
|
|
|
AssembleData() = default;
|
|
|
|
/**
|
|
* @brief tries to solve the symbol issue with Keystone, which apparently originates from LLVM-MC.
|
|
*
|
|
* @return VOID
|
|
*/
|
|
void
|
|
parseAssemblyData()
|
|
{
|
|
std::string RawAsm = AsmRaw;
|
|
|
|
// remove all "\n" instances
|
|
RawAsm.erase(std::remove(RawAsm.begin(), RawAsm.end(), '\n'), RawAsm.end());
|
|
|
|
// remove multiple spaces
|
|
std::regex multipleSpaces(" +");
|
|
RawAsm = std::regex_replace(RawAsm, multipleSpaces, " ");
|
|
|
|
// split assembly line by ';'
|
|
std::vector<std::string> assemblyInstructions;
|
|
size_t pos = 0;
|
|
std::string delimiter = ";";
|
|
while ((pos = RawAsm.find(delimiter)) != std::string::npos)
|
|
{
|
|
std::string token = RawAsm.substr(0, pos);
|
|
if (!token.empty())
|
|
{
|
|
assemblyInstructions.push_back(token);
|
|
}
|
|
RawAsm.erase(0, pos + delimiter.length());
|
|
}
|
|
if (!RawAsm.empty())
|
|
{
|
|
assemblyInstructions.push_back(RawAsm);
|
|
}
|
|
|
|
// process each assembly instruction
|
|
for (auto & instructionLine : assemblyInstructions)
|
|
{
|
|
std::string expr {};
|
|
UINT64 expr_addr {};
|
|
size_t start {};
|
|
|
|
while ((start = instructionLine.find('<', start)) != std::string::npos)
|
|
{
|
|
size_t end = instructionLine.find('>', start);
|
|
if (end != std::string::npos)
|
|
{
|
|
std::string expr = instructionLine.substr(start + 1, end - start - 1);
|
|
if (!SymbolConvertNameOrExprToAddress(expr, &expr_addr) && expr_addr == 0)
|
|
{
|
|
ShowMessages("err, failed to resolve the symbol [ %s ].\n", expr.c_str());
|
|
start += expr.size() + 2;
|
|
continue;
|
|
}
|
|
|
|
std::ostringstream oss;
|
|
oss << std::hex << std::showbase << expr_addr;
|
|
instructionLine.replace(start, end - start + 1, oss.str());
|
|
}
|
|
else
|
|
{
|
|
// No matching '>' found, break the loop
|
|
break;
|
|
}
|
|
start += expr.size() + 2;
|
|
}
|
|
}
|
|
|
|
// Append ";" between two std::strings
|
|
auto apndSemCln = [](std::string a, std::string b) {
|
|
return std::move(a) + ';' + std::move(b);
|
|
};
|
|
// Concatenate each assembly line
|
|
AsmFixed = std::accumulate(std::next(assemblyInstructions.begin()), assemblyInstructions.end(), assemblyInstructions.at(0), apndSemCln);
|
|
|
|
if (!AsmFixed.empty() && AsmFixed.back() == ';')
|
|
{
|
|
// remove the last ";" for it will be counted as a statement by Keystone and a wrong number would be printed
|
|
AsmFixed.pop_back();
|
|
}
|
|
|
|
while (!AsmFixed.empty() && AsmFixed.back() == ';')
|
|
{
|
|
AsmFixed.pop_back();
|
|
}
|
|
}
|
|
};
|
|
|
|
class _CMD
|
|
{
|
|
public:
|
|
std::string Command_str;
|
|
std::string Address_str;
|
|
std::string AsmSnippet;
|
|
std::string Start_Address_str;
|
|
std::string ProcId_str;
|
|
|
|
_CMD() = default;
|
|
|
|
bool isEmpty() const
|
|
{
|
|
return Command_str.empty() &&
|
|
Address_str.empty() &&
|
|
AsmSnippet.empty() &&
|
|
Start_Address_str.empty() &&
|
|
ProcId_str.empty();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief help of a and !a command
|
|
*
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
CommandAssembleHelp()
|
|
{
|
|
ShowMessages("a !a : assembles snippet at specific address. symbols are supported.\n");
|
|
ShowMessages("\nIf you want to assemble to physical (address) memory then add '!' "
|
|
"at the start of the command\n");
|
|
|
|
ShowMessages("syntax : \ta [Address (hex)] {AsmCmd1; AsmCmd2} [pid ProcessId (hex)]\n");
|
|
|
|
ShowMessages("\n");
|
|
ShowMessages("\t\te.g : a fffff8077356f010 {nop; nop; nop} \n");
|
|
ShowMessages("\t\te.g : a nt!ExAllocatePoolWithTag+10+@rcx {jmp <nt!ExAllocatePoolWithTag+10>} \n");
|
|
ShowMessages("\t\te.g : a nt!ExAllocatePoolWithTag {add DWORD PTR [<nt!ExAllocatePoolWithTag+10+@rax>], 99} \n");
|
|
ShowMessages("\t\t note that within assembly snippet, symbols must be enclosed by \" <> \"\n");
|
|
ShowMessages("\t\t note that type specifier must be capital\n");
|
|
|
|
ShowMessages("\n");
|
|
ShowMessages("\tto merely view the byte code of an assembly snippet:\n");
|
|
ShowMessages("\t\ta {jmp <nt!ExAllocatePoolWithTag+10>} [start_address]\n");
|
|
ShowMessages("\t\t\"start_address\" is useful when dealing with relative instructions like \" jmp \" ");
|
|
}
|
|
|
|
_CMD
|
|
ParseUserCmd(const std::string & command)
|
|
{
|
|
std::vector<std::string> CmdVec;
|
|
_CMD CMD {};
|
|
_CMD CmdEmpty {};
|
|
|
|
auto Cmd_it = command.begin();
|
|
auto Cmd_end = command.end();
|
|
|
|
while (Cmd_it != Cmd_end)
|
|
{
|
|
// skip leading whitespace
|
|
Cmd_it = std::find_if_not(Cmd_it, Cmd_end, ::isspace);
|
|
if (Cmd_it == Cmd_end)
|
|
break;
|
|
if (*Cmd_it == '{')
|
|
{
|
|
// find matching closing brace
|
|
auto closeIt = std::find(Cmd_it + 1, Cmd_end, '}');
|
|
if (closeIt != Cmd_end)
|
|
{
|
|
if (CmdVec.size() < 2)
|
|
{
|
|
// if yes, asm snippet was provided right after "a" command (2nd index)
|
|
// i.e. no address were provided to assembling to
|
|
CmdVec.emplace_back(""); // emtpty addres string
|
|
}
|
|
std::string RawAsm(Cmd_it + 1, closeIt); // remove "{}"
|
|
CmdVec.emplace_back(RawAsm);
|
|
Cmd_it = closeIt + 1;
|
|
}
|
|
else
|
|
{
|
|
// no matching brace
|
|
ShowMessages("err, assembly snippet is not closed.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// normal text, find next space
|
|
auto spaceIt = std::find_if(Cmd_it, Cmd_end, ::isspace);
|
|
CmdVec.emplace_back(Cmd_it, spaceIt);
|
|
Cmd_it = spaceIt;
|
|
}
|
|
}
|
|
|
|
// check if there is any "pid"
|
|
auto Pid_it = std::find_if(CmdVec.begin(), CmdVec.end(), [](const std::string & s) { return s.find("pid") != std::string::npos; });
|
|
if (Pid_it != CmdVec.end())
|
|
{
|
|
bool isLast = (Pid_it == std::prev(CmdVec.end()));
|
|
bool isSecondLast = (Pid_it == std::prev(CmdVec.end(), 2));
|
|
if (!isLast && !isSecondLast)
|
|
{
|
|
ShowMessages("pid must be the last argument.");
|
|
return CmdEmpty;
|
|
}
|
|
if (g_IsSerialConnectedToRemoteDebuggee)
|
|
{
|
|
ShowMessages(ASSERT_MESSAGE_CANNOT_SPECIFY_PID);
|
|
return CmdEmpty;
|
|
}
|
|
|
|
if (std::next(Pid_it) != CmdVec.end()) // is there anything after "pid"?
|
|
{
|
|
CMD.ProcId_str = std::string(*(++Pid_it));
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("no hex number was provided as process id.\n\n");
|
|
return CmdEmpty;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// user didn't provide pid at all. ignoring.
|
|
}
|
|
|
|
// fill the CMD object. will be optimized later.
|
|
if (CmdVec.size() > 0)
|
|
CMD.Command_str = CmdVec.at(0);
|
|
if (CmdVec.size() > 1)
|
|
CMD.Address_str = CmdVec.at(1);
|
|
if (CmdVec.size() > 2)
|
|
CMD.AsmSnippet = CmdVec.at(2);
|
|
if (CmdVec.size() > 3)
|
|
CMD.Start_Address_str = CmdVec.at(3);
|
|
// if (CmdVec.size() > 4) CMD.ProcId_str = CmdVec.at(5); 4 is the "pid" string
|
|
|
|
return CMD;
|
|
}
|
|
|
|
static bool
|
|
sym_resolver(const char * symbol, uint64_t * value)
|
|
{
|
|
std::string sym(symbol);
|
|
|
|
if (!SymbolConvertNameOrExprToAddress(sym, value))
|
|
{
|
|
ShowMessages("err, couldn't resolve Address at '%s'\n\n",
|
|
symbol);
|
|
CommandAssembleHelp();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
Assemble(ks_arch arch, int mode, uint64_t start_addr, int syntax, AssembleData & asmbDat)
|
|
{
|
|
ks_engine * ks;
|
|
|
|
asmbDat.ks_err = ks_open(arch, mode, &ks);
|
|
if (asmbDat.ks_err != KS_ERR_OK)
|
|
{
|
|
ShowMessages("err, failed on ks_open().");
|
|
return -1;
|
|
}
|
|
|
|
if (syntax)
|
|
{
|
|
asmbDat.ks_err = ks_option(ks, KS_OPT_SYNTAX, syntax);
|
|
if (asmbDat.ks_err != KS_ERR_OK)
|
|
{
|
|
ShowMessages("err, failed on ks_option() with error code = %u\n", asmbDat.ks_err);
|
|
}
|
|
}
|
|
|
|
// Setting symbol resolver callback
|
|
asmbDat.ks_err = ks_option(ks, KS_OPT_SYM_RESOLVER, (size_t)sym_resolver);
|
|
if (asmbDat.ks_err != KS_ERR_OK)
|
|
{
|
|
ShowMessages("err, failed on ks_option() with error code = %u\n", asmbDat.ks_err);
|
|
}
|
|
|
|
if (ks_asm(ks, asmbDat.AsmFixed.c_str(), start_addr, &asmbDat.EncodedBytes, &asmbDat.BytesCount, &asmbDat.StatementCount))
|
|
{
|
|
asmbDat.ks_err = ks_errno(ks);
|
|
ShowMessages("err, failed on ks_asm() with count = %lu, error code = %u\n", (int)asmbDat.StatementCount, asmbDat.ks_err);
|
|
}
|
|
else
|
|
{
|
|
if (asmbDat.BytesCount == 0)
|
|
{
|
|
ShowMessages("err, the assemble operation returned no bytes, most likely due to incorrect formatting of assembly snippet.\n");
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("generated assembly: %lu bytes, %lu statements ==>> ", (int)asmbDat.BytesCount, (int)asmbDat.StatementCount);
|
|
|
|
size_t i;
|
|
ShowMessages("%s = ", asmbDat.AsmFixed.c_str());
|
|
for (i = 0; i < asmbDat.BytesCount; i++)
|
|
{
|
|
ShowMessages("%02x ", asmbDat.EncodedBytes[i]);
|
|
asmbDat.EncBytesIntVec.push_back(static_cast<UINT64>(asmbDat.EncodedBytes[i]));
|
|
}
|
|
ShowMessages("\n");
|
|
|
|
ks_close(ks);
|
|
return 0;
|
|
}
|
|
}
|
|
ks_close(ks);
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* @brief a and !a commands handler
|
|
*
|
|
* @param SplitCommand
|
|
* @param Command
|
|
* @return VOID
|
|
*/
|
|
VOID
|
|
CommandAssemble(vector<string> SplitCommand, string Command)
|
|
{
|
|
DEBUGGER_EDIT_MEMORY_TYPE MemoryType {};
|
|
_CMD CMD {};
|
|
UINT64 Address {};
|
|
UINT64 Start_Address {};
|
|
UINT32 ProcId = 0;
|
|
|
|
CMD = ParseUserCmd(Command);
|
|
|
|
if (CMD.isEmpty())
|
|
{
|
|
// error messeges are already printed
|
|
return;
|
|
}
|
|
|
|
if (!CMD.ProcId_str.empty())
|
|
{
|
|
if (!ConvertStringToUInt32(CMD.ProcId_str, &ProcId))
|
|
{
|
|
ShowMessages("please specify a correct hex process id\n\n");
|
|
CommandAssembleHelp();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (g_ActiveProcessDebuggingState.IsActive)
|
|
{
|
|
ProcId = g_ActiveProcessDebuggingState.ProcessId;
|
|
}
|
|
|
|
if (CMD.AsmSnippet.empty())
|
|
{
|
|
ShowMessages("no assembly snippet provided\n");
|
|
CommandAssembleHelp();
|
|
return;
|
|
}
|
|
else if (CMD.Command_str == "a")
|
|
{
|
|
MemoryType = EDIT_VIRTUAL_MEMORY;
|
|
}
|
|
else if (CMD.Command_str == "!a")
|
|
{
|
|
MemoryType = EDIT_PHYSICAL_MEMORY;
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("unknown assemble command.\n\n");
|
|
CommandAssembleHelp();
|
|
return;
|
|
}
|
|
|
|
// fetching start_address i.e. address to assemble to
|
|
if (!CMD.Address_str.empty()) // was any address provided to assemble to?
|
|
{
|
|
if (!SymbolConvertNameOrExprToAddress(CMD.Address_str, &Address))
|
|
{
|
|
ShowMessages("err, couldn't resolve Address at '%s'\n\n",
|
|
CMD.Address_str.c_str());
|
|
CommandAssembleHelp();
|
|
return;
|
|
}
|
|
}
|
|
else if (!CMD.Start_Address_str.empty()) // was a custom start_address provided?
|
|
{
|
|
if (!SymbolConvertNameOrExprToAddress(CMD.Start_Address_str, &Start_Address))
|
|
{
|
|
ShowMessages("err, couldn't resolve Address at '%s'\n\n",
|
|
CMD.Start_Address_str.c_str());
|
|
CommandAssembleHelp();
|
|
return;
|
|
}
|
|
Address = Start_Address;
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("warn, no start_address provided to calculate relative asm commands.\n\n");
|
|
}
|
|
|
|
AssembleData AssembleData;
|
|
AssembleData.AsmRaw = CMD.AsmSnippet; // third element
|
|
AssembleData.parseAssemblyData();
|
|
|
|
if (Assemble(KS_ARCH_X86, KS_MODE_64, Address, KS_OPT_SYNTAX_INTEL, AssembleData))
|
|
{
|
|
ShowMessages("err, code: '%u'\n\n", AssembleData.ks_err);
|
|
CommandAssembleHelp();
|
|
return;
|
|
}
|
|
|
|
if (ProcId == 0)
|
|
{
|
|
ProcId = GetCurrentProcessId();
|
|
}
|
|
|
|
if (Address) // was the user only trying to get the bytes?
|
|
{
|
|
if (HyperDbgWriteMemory(
|
|
(PVOID)Address,
|
|
MemoryType,
|
|
ProcId,
|
|
(PVOID)AssembleData.EncodedBytes,
|
|
(UINT32)AssembleData.BytesCount))
|
|
{
|
|
ShowMessages("successfully assembled at 0x%016llx address.\n", static_cast<uint64_t>(Address));
|
|
}
|
|
else
|
|
{
|
|
ShowMessages("failed to write generated assembly to memory.");
|
|
}
|
|
}
|
|
} |