forked from vmp3/vmprofiler
parent
08d530b85c
commit
70cb770db0
@ -1 +1,12 @@
|
|||||||
#include <vmctx.hpp>
|
#include <vmctx.hpp>
|
||||||
|
|
||||||
|
namespace vm {
|
||||||
|
vmctx_t::vmctx_t(std::uintptr_t module_base,
|
||||||
|
std::uintptr_t image_base,
|
||||||
|
std::uintptr_t vm_entry_rva,
|
||||||
|
std::uintptr_t image_size)
|
||||||
|
: m_module_base(module_base),
|
||||||
|
m_image_base(image_base),
|
||||||
|
m_vm_entry_rva(vm_entry_rva),
|
||||||
|
m_image_size(image_size) {}
|
||||||
|
} // namespace vm
|
@ -0,0 +1,211 @@
|
|||||||
|
#include <vmutils.hpp>
|
||||||
|
|
||||||
|
namespace vm::locate {
|
||||||
|
|
||||||
|
bool is_jmp(const zydis_decoded_instr_t& instr) {
|
||||||
|
return instr.mnemonic >= ZYDIS_MNEMONIC_JB &&
|
||||||
|
instr.mnemonic <= ZYDIS_MNEMONIC_JZ;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool flatten(zydis_routine_t& routine,
|
||||||
|
std::uintptr_t routine_addr,
|
||||||
|
bool keep_jmps,
|
||||||
|
std::uint32_t max_instrs,
|
||||||
|
std::uintptr_t module_base) {
|
||||||
|
zydis_decoded_instr_t instr;
|
||||||
|
std::uint32_t instr_cnt = 0u;
|
||||||
|
|
||||||
|
while (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer(
|
||||||
|
vm::utils::g_decoder.get(), reinterpret_cast<void*>(routine_addr), 0x1000,
|
||||||
|
&instr))) {
|
||||||
|
if (++instr_cnt > max_instrs)
|
||||||
|
return false;
|
||||||
|
// detect if we have already been at this instruction... if so that means
|
||||||
|
// there is a loop and we are going to just return...
|
||||||
|
if (std::find_if(routine.begin(), routine.end(),
|
||||||
|
[&](const zydis_instr_t& zydis_instr) -> bool {
|
||||||
|
return zydis_instr.addr == routine_addr;
|
||||||
|
}) != routine.end())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
std::vector<u8> raw_instr;
|
||||||
|
raw_instr.insert(raw_instr.begin(), (u8*)routine_addr,
|
||||||
|
(u8*)routine_addr + instr.length);
|
||||||
|
|
||||||
|
if (is_jmp(instr) ||
|
||||||
|
instr.mnemonic == ZYDIS_MNEMONIC_CALL &&
|
||||||
|
instr.operands[0].type != ZYDIS_OPERAND_TYPE_REGISTER) {
|
||||||
|
if (instr.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) {
|
||||||
|
routine.push_back({instr, raw_instr, routine_addr});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keep_jmps)
|
||||||
|
routine.push_back({instr, raw_instr, routine_addr});
|
||||||
|
ZydisCalcAbsoluteAddress(&instr, &instr.operands[0], routine_addr,
|
||||||
|
&routine_addr);
|
||||||
|
} else if (instr.mnemonic == ZYDIS_MNEMONIC_RET) {
|
||||||
|
routine.push_back({instr, raw_instr, routine_addr});
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
routine.push_back({instr, raw_instr, routine_addr});
|
||||||
|
routine_addr += instr.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional sanity checking...
|
||||||
|
if (module_base && !vm::utils::scn::executable(module_base, routine_addr))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deobfuscate(zydis_routine_t& routine) {
|
||||||
|
static const auto _uses_reg = [](zydis_decoded_operand_t& op,
|
||||||
|
zydis_register_t reg) -> bool {
|
||||||
|
switch (op.type) {
|
||||||
|
case ZYDIS_OPERAND_TYPE_MEMORY: {
|
||||||
|
return vm::utils::reg::compare(op.mem.base, reg) ||
|
||||||
|
vm::utils::reg::compare(op.mem.index, reg);
|
||||||
|
}
|
||||||
|
case ZYDIS_OPERAND_TYPE_REGISTER: {
|
||||||
|
return vm::utils::reg::compare(op.reg.value, reg);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const auto _reads = [](zydis_decoded_instr_t& instr,
|
||||||
|
zydis_register_t reg) -> bool {
|
||||||
|
if (instr.operands[0].type == ZYDIS_OPERAND_TYPE_MEMORY &&
|
||||||
|
vm::utils::reg::compare(instr.operands[0].mem.base, reg))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx)
|
||||||
|
if (instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_READ &&
|
||||||
|
_uses_reg(instr.operands[op_idx], reg))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const auto _writes = [](zydis_decoded_instr_t& instr,
|
||||||
|
zydis_register_t reg) -> bool {
|
||||||
|
for (auto op_idx = 0u; op_idx < instr.operand_count; ++op_idx)
|
||||||
|
// if instruction writes to the specific register...
|
||||||
|
if (instr.operands[op_idx].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
|
instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_WRITE &&
|
||||||
|
!(instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_READ) &&
|
||||||
|
vm::utils::reg::compare(instr.operands[op_idx].reg.value, reg))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::uint32_t last_size = 0u;
|
||||||
|
static const std::vector<ZydisMnemonic> blacklist = {
|
||||||
|
ZYDIS_MNEMONIC_CLC, ZYDIS_MNEMONIC_BT, ZYDIS_MNEMONIC_TEST,
|
||||||
|
ZYDIS_MNEMONIC_CMP, ZYDIS_MNEMONIC_CMC, ZYDIS_MNEMONIC_STC};
|
||||||
|
|
||||||
|
static const std::vector<ZydisMnemonic> whitelist = {
|
||||||
|
ZYDIS_MNEMONIC_PUSH, ZYDIS_MNEMONIC_POP, ZYDIS_MNEMONIC_CALL,
|
||||||
|
ZYDIS_MNEMONIC_DIV};
|
||||||
|
|
||||||
|
do {
|
||||||
|
last_size = routine.size();
|
||||||
|
for (auto itr = routine.begin(); itr != routine.end(); ++itr) {
|
||||||
|
if (std::find(whitelist.begin(), whitelist.end(), itr->instr.mnemonic) !=
|
||||||
|
whitelist.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (std::find(blacklist.begin(), blacklist.end(), itr->instr.mnemonic) !=
|
||||||
|
blacklist.end()) {
|
||||||
|
routine.erase(itr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
zydis_register_t reg = ZYDIS_REGISTER_NONE;
|
||||||
|
// look for operands with writes to a register...
|
||||||
|
for (auto op_idx = 0u; op_idx < itr->instr.operand_count; ++op_idx)
|
||||||
|
if (itr->instr.operands[op_idx].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
|
itr->instr.operands[op_idx].actions & ZYDIS_OPERAND_ACTION_WRITE)
|
||||||
|
reg = vm::utils::reg::to64(itr->instr.operands[0].reg.value);
|
||||||
|
|
||||||
|
// if this current instruction writes to a register, look ahead in the
|
||||||
|
// instruction stream to see if it gets written too before it gets read...
|
||||||
|
if (reg != ZYDIS_REGISTER_NONE) {
|
||||||
|
// find the next place that this register is written too...
|
||||||
|
auto write_result = std::find_if(itr + 1, routine.end(),
|
||||||
|
[&](zydis_instr_t& instr) -> bool {
|
||||||
|
return _writes(instr.instr, reg);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto read_result = std::find_if(itr + 1, write_result,
|
||||||
|
[&](zydis_instr_t& instr) -> bool {
|
||||||
|
return _reads(instr.instr, reg);
|
||||||
|
});
|
||||||
|
|
||||||
|
// if there is neither a read or a write to this register in the
|
||||||
|
// instruction stream then we are going to be safe and leave the
|
||||||
|
// instruction in the stream...
|
||||||
|
if (read_result == routine.end() && write_result == routine.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// if there is no read of the register before the next write... and
|
||||||
|
// there is a known next write, then remove the instruction from the
|
||||||
|
// stream...
|
||||||
|
if (read_result == write_result && write_result != routine.end()) {
|
||||||
|
// if the instruction reads and writes the same register than skip...
|
||||||
|
if (_reads(read_result->instr, reg) &&
|
||||||
|
_writes(read_result->instr, reg))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
routine.erase(itr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (last_size != routine.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace reg {
|
||||||
|
zydis_register_t to64(zydis_register_t reg) {
|
||||||
|
return ZydisRegisterGetLargestEnclosing(ZYDIS_MACHINE_MODE_LONG_64, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool compare(zydis_register_t a, zydis_register_t b) {
|
||||||
|
return to64(a) == to64(b);
|
||||||
|
}
|
||||||
|
} // namespace reg
|
||||||
|
|
||||||
|
namespace scn {
|
||||||
|
bool read_only(std::uint64_t module_base, std::uint64_t ptr) {
|
||||||
|
auto win_image = reinterpret_cast<win::image_t<>*>(module_base);
|
||||||
|
auto section_count = win_image->get_file_header()->num_sections;
|
||||||
|
auto sections = win_image->get_nt_headers()->get_sections();
|
||||||
|
|
||||||
|
for (auto idx = 0u; idx < section_count; ++idx)
|
||||||
|
if (ptr >= sections[idx].virtual_address + module_base &&
|
||||||
|
ptr < sections[idx].virtual_address + sections[idx].virtual_size +
|
||||||
|
module_base)
|
||||||
|
return !(sections[idx].characteristics.mem_discardable) &&
|
||||||
|
!(sections[idx].characteristics.mem_write);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool executable(std::uint64_t module_base, std::uint64_t ptr) {
|
||||||
|
auto win_image = reinterpret_cast<win::image_t<>*>(module_base);
|
||||||
|
auto section_count = win_image->get_file_header()->num_sections;
|
||||||
|
auto sections = win_image->get_nt_headers()->get_sections();
|
||||||
|
|
||||||
|
for (auto idx = 0u; idx < section_count; ++idx)
|
||||||
|
if (ptr >= sections[idx].virtual_address + module_base &&
|
||||||
|
ptr < sections[idx].virtual_address + sections[idx].virtual_size +
|
||||||
|
module_base)
|
||||||
|
return !(sections[idx].characteristics.mem_discardable) &&
|
||||||
|
sections[idx].characteristics.mem_execute;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace scn
|
||||||
|
} // namespace vm::locate
|
@ -0,0 +1,18 @@
|
|||||||
|
# This file is automatically generated from cmake.toml - DO NOT EDIT
|
||||||
|
# See https://github.com/build-cpp/cmkr for more information
|
||||||
|
|
||||||
|
# Create a configure-time dependency on cmake.toml to improve IDE support
|
||||||
|
if(CMKR_ROOT_PROJECT)
|
||||||
|
configure_file(cmake.toml cmake.toml COPYONLY)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# vm_entry_test
|
||||||
|
set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER})
|
||||||
|
if(CMAKE_FOLDER)
|
||||||
|
set(CMAKE_FOLDER "${CMAKE_FOLDER}/vm_entry_test")
|
||||||
|
else()
|
||||||
|
set(CMAKE_FOLDER vm_entry_test)
|
||||||
|
endif()
|
||||||
|
add_subdirectory(vm_entry_test)
|
||||||
|
set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
[subdir.vm_entry_test]
|
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 1aedaf8bb7f383f54b7cd498767611535526da85
|
@ -0,0 +1,87 @@
|
|||||||
|
# This file is automatically generated from cmake.toml - DO NOT EDIT
|
||||||
|
# See https://github.com/build-cpp/cmkr for more information
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
|
||||||
|
# Regenerate CMakeLists.txt automatically in the root project
|
||||||
|
set(CMKR_ROOT_PROJECT OFF)
|
||||||
|
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||||
|
set(CMKR_ROOT_PROJECT ON)
|
||||||
|
|
||||||
|
# Bootstrap cmkr
|
||||||
|
include(cmkr.cmake OPTIONAL RESULT_VARIABLE CMKR_INCLUDE_RESULT)
|
||||||
|
if(CMKR_INCLUDE_RESULT)
|
||||||
|
cmkr()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Enable folder support
|
||||||
|
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Create a configure-time dependency on cmake.toml to improve IDE support
|
||||||
|
if(CMKR_ROOT_PROJECT)
|
||||||
|
configure_file(cmake.toml cmake.toml COPYONLY)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
project(vm_entry_test)
|
||||||
|
|
||||||
|
# Target cli-parser
|
||||||
|
set(CMKR_TARGET cli-parser)
|
||||||
|
set(cli-parser_SOURCES "")
|
||||||
|
|
||||||
|
set(CMKR_SOURCES ${cli-parser_SOURCES})
|
||||||
|
add_library(cli-parser INTERFACE)
|
||||||
|
|
||||||
|
if(cli-parser_SOURCES)
|
||||||
|
target_sources(cli-parser INTERFACE ${cli-parser_SOURCES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_include_directories(cli-parser INTERFACE
|
||||||
|
"../deps/cli-parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
unset(CMKR_TARGET)
|
||||||
|
unset(CMKR_SOURCES)
|
||||||
|
|
||||||
|
# Target vm_entry_test
|
||||||
|
set(CMKR_TARGET vm_entry_test)
|
||||||
|
set(vm_entry_test_SOURCES "")
|
||||||
|
|
||||||
|
list(APPEND vm_entry_test_SOURCES
|
||||||
|
"src/main.cpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
list(APPEND vm_entry_test_SOURCES
|
||||||
|
cmake.toml
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CMKR_SOURCES ${vm_entry_test_SOURCES})
|
||||||
|
add_executable(vm_entry_test)
|
||||||
|
|
||||||
|
if(vm_entry_test_SOURCES)
|
||||||
|
target_sources(vm_entry_test PRIVATE ${vm_entry_test_SOURCES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT)
|
||||||
|
if(NOT CMKR_VS_STARTUP_PROJECT)
|
||||||
|
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT vm_entry_test)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${vm_entry_test_SOURCES})
|
||||||
|
|
||||||
|
target_compile_definitions(vm_entry_test PRIVATE
|
||||||
|
NOMINMAX
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_features(vm_entry_test PRIVATE
|
||||||
|
cxx_std_20
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(vm_entry_test PRIVATE
|
||||||
|
vmprofiler
|
||||||
|
cli-parser
|
||||||
|
)
|
||||||
|
|
||||||
|
unset(CMKR_TARGET)
|
||||||
|
unset(CMKR_SOURCES)
|
||||||
|
|
@ -0,0 +1,18 @@
|
|||||||
|
[target.cli-parser]
|
||||||
|
type = "interface"
|
||||||
|
include-directories = ["../deps/cli-parser"]
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "vm_entry_test"
|
||||||
|
|
||||||
|
[target.vm_entry_test]
|
||||||
|
type = "executable"
|
||||||
|
compile-features = ["cxx_std_20"]
|
||||||
|
|
||||||
|
sources = [
|
||||||
|
"src/**.cpp",
|
||||||
|
"include/**.hpp"
|
||||||
|
]
|
||||||
|
|
||||||
|
link-libraries = ["vmprofiler", "cli-parser"]
|
||||||
|
compile-definitions = ["NOMINMAX"]
|
@ -0,0 +1,6 @@
|
|||||||
|
#include <vmprofiler.hpp>
|
||||||
|
#include <cli-parser.hpp>
|
||||||
|
|
||||||
|
int __cdecl main(int argc, char* argv[]) {
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in new issue