added test case stuff and added vmutils..

main
John Doe 3 years ago
parent 08d530b85c
commit 70cb770db0

3
.gitmodules vendored

@ -4,3 +4,6 @@
[submodule "deps/zydis"] [submodule "deps/zydis"]
path = deps/zydis path = deps/zydis
url = https://github.com/zyantific/zydis url = https://github.com/zyantific/zydis
[submodule "tests/deps/cli-parser"]
path = tests/deps/cli-parser
url = https://githacks.org/_xeroxz/cli-parser

@ -35,6 +35,16 @@ endif()
add_subdirectory(deps) add_subdirectory(deps)
set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER}) set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
# tests
set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER})
if(CMAKE_FOLDER)
set(CMAKE_FOLDER "${CMAKE_FOLDER}/tests")
else()
set(CMAKE_FOLDER tests)
endif()
add_subdirectory(tests)
set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
# Target vmprofiler # Target vmprofiler
set(CMKR_TARGET vmprofiler) set(CMKR_TARGET vmprofiler)
set(vmprofiler_SOURCES "") set(vmprofiler_SOURCES "")

@ -24,3 +24,4 @@ compile-definitions = [
] ]
[subdir.deps] [subdir.deps]
[subdir.tests]

@ -2,7 +2,14 @@
#include <vmutils.hpp> #include <vmutils.hpp>
namespace vm { namespace vm {
struct ctx_t { class vmctx_t {
const std::uintptr_t module_base, image_base, vm_entry_rva, image_size; public:
explicit vmctx_t(std::uintptr_t module_base,
std::uintptr_t image_base,
std::uintptr_t vm_entry_rva,
std::uintptr_t image_size);
bool init();
const std::uintptr_t m_module_base, m_image_base, m_vm_entry_rva,
m_image_size;
}; };
} // namespace vm } // namespace vm

@ -3,6 +3,7 @@
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <nt/image.hpp>
#include <vector> #include <vector>
using u8 = unsigned char; using u8 = unsigned char;
@ -39,4 +40,79 @@ inline void init() {
ZYDIS_FORMATTER_STYLE_INTEL); ZYDIS_FORMATTER_STYLE_INTEL);
} }
} }
/// <summary>
/// determines if a given decoded native instruction is a JCC...
/// </summary>
/// <param name="instr"></param>
/// <returns></returns>
bool is_jmp(const zydis_decoded_instr_t& instr);
/// <summary>
/// utils pertaining to native registers...
/// </summary>
namespace reg {
/// <summary>
/// converts say... AL to RAX...
/// </summary>
/// <param name="reg">a zydis decoded register value...</param>
/// <returns>returns the largest width register of the given register... AL
/// gives RAX...</returns>
zydis_register_t to64(zydis_register_t reg);
/// <summary>
/// compares to registers with each other... calls to64 and compares...
/// </summary>
/// <param name="a">register a...</param>
/// <param name="b">register b...</param>
/// <returns>returns true if register to64(a) == to64(b)...</returns>
bool compare(zydis_register_t a, zydis_register_t b);
} // namespace reg
/// <summary>
/// flatten native instruction stream, takes every JCC (follows the branch)...
/// </summary>
/// <param name="routine">filled with decoded instructions...</param>
/// <param name="routine_addr">linear virtual address to start flattening
/// from...</param> <param name="keep_jmps">keep JCC's in the flattened
/// instruction stream...</param> <returns>returns true if flattened was
/// successful...</returns>
bool flatten(zydis_routine_t& routine,
std::uintptr_t routine_addr,
bool keep_jmps = false,
std::uint32_t max_instrs = 500,
std::uintptr_t module_base = 0ull);
/// <summary>
/// deadstore deobfuscation of a flattened routine...
/// </summary>
/// <param name="routine">reference to a flattened instruction vector...</param>
void deobfuscate(zydis_routine_t& routine);
/// <summary>
/// small namespace that contains function wrappers to determine the validity of
/// linear virtual addresses...
/// </summary>
namespace scn {
/// <summary>
/// determines if a pointer lands inside of a section that is readonly...
///
/// this also checks to make sure the section is not discardable...
/// </summary>
/// <param name="module_base">linear virtual address of the module....</param>
/// <param name="ptr">linear virtual address</param>
/// <returns>returns true if ptr lands inside of a readonly section of the
/// module</returns>
bool read_only(std::uint64_t module_base, std::uint64_t ptr);
/// <summary>
/// determines if a pointer lands inside of a section that is executable...
///
/// this also checks to make sure the section is not discardable...
/// </summary>
/// <param name="module_base"></param>
/// <param name="ptr"></param>
/// <returns></returns>
bool executable(std::uint64_t module_base, std::uint64_t ptr);
} // namespace scn
} // namespace vm::utils } // namespace vm::utils

@ -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

@ -50,7 +50,7 @@ std::vector<vm_enter_t> get_vm_entries(std::uintptr_t module_base,
PUSH_4B_IMM, PUSH_4B_MASK); PUSH_4B_IMM, PUSH_4B_MASK);
zydis_routine_t rtn; zydis_routine_t rtn;
if (!scn::executable(module_base, result)) if (!vm::utils::scn::executable(module_base, result))
continue; continue;
if (!vm::utils::flatten(rtn, result, false, 500, module_base)) if (!vm::utils::flatten(rtn, result, false, 500, module_base))

@ -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…
Cancel
Save