profilers will now have access to unicorn-engine traces so generators

can grab decrypted values using uc_reg_read... powerful!
master
John Doe 2 years ago
parent 20ad0b5950
commit 79c3695828

3
.gitmodules vendored

@ -7,3 +7,6 @@
[submodule "tests/deps/cli-parser"] [submodule "tests/deps/cli-parser"]
path = tests/deps/cli-parser path = tests/deps/cli-parser
url = https://githacks.org/_xeroxz/cli-parser url = https://githacks.org/_xeroxz/cli-parser
[submodule "deps/unicorn"]
path = deps/unicorn
url = https://github.com/unicorn-engine/unicorn.git

@ -90,6 +90,7 @@ target_include_directories(vmprofiler PUBLIC
target_link_libraries(vmprofiler PUBLIC target_link_libraries(vmprofiler PUBLIC
Zydis Zydis
linux-pe linux-pe
unicorn
) )
unset(CMKR_TARGET) unset(CMKR_TARGET)

@ -17,6 +17,7 @@ include-directories = [
link-libraries = [ link-libraries = [
"Zydis", "Zydis",
"linux-pe", "linux-pe",
"unicorn"
] ]
compile-definitions = [ compile-definitions = [

@ -1,6 +1,23 @@
# This file is automatically generated from cmake.toml - DO NOT EDIT # This file is automatically generated from cmake.toml - DO NOT EDIT
# See https://github.com/build-cpp/cmkr for more information # 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 # Create a configure-time dependency on cmake.toml to improve IDE support
if(CMKR_ROOT_PROJECT) if(CMKR_ROOT_PROJECT)
configure_file(cmake.toml cmake.toml COPYONLY) configure_file(cmake.toml cmake.toml COPYONLY)
@ -23,6 +40,16 @@ endif()
add_subdirectory(zydis) add_subdirectory(zydis)
set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER}) set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
# unicorn
set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER})
if(CMAKE_FOLDER)
set(CMAKE_FOLDER "${CMAKE_FOLDER}/unicorn")
else()
set(CMAKE_FOLDER unicorn)
endif()
add_subdirectory(unicorn)
set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
# Target linux-pe # Target linux-pe
set(CMKR_TARGET linux-pe) set(CMKR_TARGET linux-pe)
set(linux-pe_SOURCES "") set(linux-pe_SOURCES "")

1
deps/cmake.toml vendored

@ -6,6 +6,7 @@ ZYDIS_FUZZ_AFL_FAST = false
ZYDIS_LIBFUZZER = false ZYDIS_LIBFUZZER = false
[subdir.zydis] [subdir.zydis]
[subdir.unicorn]
[target.linux-pe] [target.linux-pe]
type = "interface" type = "interface"

1
deps/unicorn vendored

@ -0,0 +1 @@
Subproject commit 63a445cbba18bf1313ac3699b5d25462b5d529f4

@ -1,4 +1,5 @@
#pragma once #pragma once
#include <vminstrs.hpp>
#include <vmutils.hpp> #include <vmutils.hpp>
namespace vm { namespace vm {

@ -1,4 +1,5 @@
#pragma once #pragma once
#include <unicorn/unicorn.h>
#include <vmutils.hpp> #include <vmutils.hpp>
namespace vm::instrs { namespace vm::instrs {
@ -68,6 +69,24 @@ struct vinstr_t {
} imm; } imm;
}; };
/// <summary>
/// emu instruction containing current cpu register values and such...
/// </summary>
struct emu_instr_t {
zydis_decoded_instr_t m_instr;
uc_context* m_cpu;
};
/// <summary>
/// handler trace containing information about a stream of instructions... also
/// contains some information about the virtual machine such as vip and vsp...
/// </summary>
struct hndlr_trace_t {
std::uintptr_t m_hndlr_addr;
zydis_reg_t m_vip, m_vsp;
std::vector<emu_instr_t> m_instrs;
};
/// <summary> /// <summary>
/// matcher function which returns true if an instruction matches a desired /// matcher function which returns true if an instruction matches a desired
/// one... /// one...
@ -80,9 +99,10 @@ using matcher_t = std::function<bool(const zydis_reg_t vip,
/// virtual instruction structure generator... this can update the vip and vsp /// virtual instruction structure generator... this can update the vip and vsp
/// argument... it cannot update the instruction stream (hndlr)... /// argument... it cannot update the instruction stream (hndlr)...
/// </summary> /// </summary>
using vinstr_gen_t = std::function<std::optional<vinstr_t>(zydis_reg_t& vip, using vinstr_gen_t =
zydis_reg_t& vsp, std::function<std::optional<vinstr_t>(zydis_reg_t& vip,
zydis_rtn_t& hndlr)>; zydis_reg_t& vsp,
hndlr_trace_t& hndlr)>;
/// <summary> /// <summary>
/// each virtual instruction has its own profiler_t structure which can generate /// each virtual instruction has its own profiler_t structure which can generate
@ -111,21 +131,52 @@ struct profiler_t {
vinstr_gen_t generate; vinstr_gen_t generate;
}; };
/// <summary>
/// list of all profiles here...
/// </summary>
extern profiler_t jmp; extern profiler_t jmp;
extern profiler_t sreg;
/// <summary>
/// unsorted vector of profiles... they get sorted once at runtime...
/// </summary>
inline std::vector<profiler_t*> profiles = {&jmp}; inline std::vector<profiler_t*> profiles = {&jmp};
/// <summary>
/// sorts the profiles by descending order of matchers... this will prevent a
/// smaller profiler with less matchers from being used when it should not be...
///
/// this function can be called multiple times...
/// </summary>
inline void init() {
if (static std::atomic_bool once = true; once.exchange(false))
std::sort(profiles.begin(), profiles.end(),
[&](profiler_t* a, profiler_t* b) -> bool {
return a->matchers.size() > b->matchers.size();
});
}
/// <summary>
/// determines the virtual instruction for the vm handler given vsp and vip...
/// </summary>
/// <param name="vip">vip native register...</param>
/// <param name="vsp">vsp native register...</param>
/// <param name="hndlr"></param>
/// <returns>returns vinstr_t structure...</returns>
inline vinstr_t determine(zydis_reg_t& vip, inline vinstr_t determine(zydis_reg_t& vip,
zydis_reg_t& vsp, zydis_reg_t& vsp,
zydis_rtn_t& hndlr) { hndlr_trace_t& hndlr) {
const auto& instrs = hndlr.m_instrs;
const auto profile = std::find_if( const auto profile = std::find_if(
profiles.begin(), profiles.end(), [&](profiler_t* profile) -> bool { profiles.begin(), profiles.end(), [&](profiler_t* profile) -> bool {
for (auto& matcher : profile->matchers) { for (auto& matcher : profile->matchers) {
const auto matched = std::find_if(hndlr.begin(), hndlr.end(), const auto matched =
[&](zydis_instr_t& instr) -> bool { std::find_if(instrs.begin(), instrs.end(),
const auto& i = instr.instr; [&](const emu_instr_t& instr) -> bool {
return matcher(vip, vsp, i); const auto& i = instr.m_instr;
}); return matcher(vip, vsp, i);
if (matched == hndlr.end()) });
if (matched == instrs.end())
return false; return false;
} }
return true; return true;
@ -137,4 +188,21 @@ inline vinstr_t determine(zydis_reg_t& vip,
auto result = (*profile)->generate(vip, vsp, hndlr); auto result = (*profile)->generate(vip, vsp, hndlr);
return result.has_value() ? result.value() : vinstr_t{mnemonic_t::unknown}; return result.has_value() ? result.value() : vinstr_t{mnemonic_t::unknown};
} }
/// <summary>
/// get profile from mnemonic...
/// </summary>
/// <param name="mnemonic">mnemonic of the profile to get...</param>
/// <returns>pointer to the profile...</returns>
inline profiler_t* get_profile(mnemonic_t mnemonic) {
if (mnemonic == mnemonic_t::unknown)
return nullptr;
const auto res = std::find_if(profiles.begin(), profiles.end(),
[&](profiler_t* profile) -> bool {
return profile->mnemonic == mnemonic;
});
return res == profiles.end() ? nullptr : *res;
}
} // namespace vm::instrs } // namespace vm::instrs

@ -11,6 +11,9 @@ vmctx_t::vmctx_t(std::uintptr_t module_base,
m_image_size(image_size) {} m_image_size(image_size) {}
bool vmctx_t::init() { bool vmctx_t::init() {
vm::utils::init();
vm::instrs::init();
// flatten and deobfuscate the vm entry... // flatten and deobfuscate the vm entry...
if (!vm::utils::flatten(m_vm_entry, m_module_base + m_vm_entry_rva)) if (!vm::utils::flatten(m_vm_entry, m_module_base + m_vm_entry_rva))
return false; return false;

@ -4,8 +4,8 @@ namespace vm::instrs {
profiler_t jmp = { profiler_t jmp = {
"JMP", "JMP",
mnemonic_t::jmp, mnemonic_t::jmp,
// MOV REG, [VSP] {{// MOV REG, [VSP]
{{[&](const zydis_reg_t vip, [&](const zydis_reg_t vip,
const zydis_reg_t vsp, const zydis_reg_t vsp,
const zydis_decoded_instr_t& instr) -> bool { const zydis_decoded_instr_t& instr) -> bool {
return instr.mnemonic == ZYDIS_MNEMONIC_MOV && return instr.mnemonic == ZYDIS_MNEMONIC_MOV &&
@ -34,10 +34,11 @@ profiler_t jmp = {
}}}, }}},
[&](zydis_reg_t& vip, [&](zydis_reg_t& vip,
zydis_reg_t& vsp, zydis_reg_t& vsp,
zydis_rtn_t& hndlr) -> std::optional<vinstr_t> { hndlr_trace_t& hndlr) -> std::optional<vinstr_t> {
const auto& instrs = hndlr.m_instrs;
const auto xchg = std::find_if( const auto xchg = std::find_if(
hndlr.begin(), hndlr.end(), [&](const zydis_instr_t& instr) -> bool { instrs.begin(), instrs.end(), [&](const emu_instr_t& instr) -> bool {
const auto& i = instr.instr; const auto& i = instr.m_instr;
return i.mnemonic == ZYDIS_MNEMONIC_XCHG && return i.mnemonic == ZYDIS_MNEMONIC_XCHG &&
i.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && i.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
i.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER && i.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER &&
@ -50,77 +51,77 @@ profiler_t jmp = {
}); });
// this JMP virtual instruction changes VSP as well as VIP... // this JMP virtual instruction changes VSP as well as VIP...
if (xchg != hndlr.end()) { if (xchg != instrs.end()) {
// grab the register that isnt VSP in the XCHG... // grab the register that isnt VSP in the XCHG...
// xchg reg, vsp or xchg vsp, reg... // xchg reg, vsp or xchg vsp, reg...
zydis_reg_t write_dep = xchg->instr.operands[0].reg.value != vsp zydis_reg_t write_dep = xchg->m_instr.operands[0].reg.value != vsp
? xchg->instr.operands[0].reg.value ? xchg->m_instr.operands[0].reg.value
: xchg->instr.operands[1].reg.value; : xchg->m_instr.operands[1].reg.value;
// update VIP... VSP becomes VIP... with the XCHG... // update VIP... VSP becomes VIP... with the XCHG...
vip = xchg->instr.operands[0].reg.value != vsp vip = xchg->m_instr.operands[0].reg.value != vsp
? xchg->instr.operands[1].reg.value ? xchg->m_instr.operands[1].reg.value
: xchg->instr.operands[0].reg.value; : xchg->m_instr.operands[0].reg.value;
// find the next MOV REG, write_dep... this REG will be VSP... // find the next MOV REG, write_dep... this REG will be VSP...
const auto mov_reg_write_dep = std::find_if( const auto mov_reg_write_dep = std::find_if(
xchg, hndlr.end(), [&](const zydis_instr_t& instr) -> bool { xchg, instrs.end(), [&](const emu_instr_t& instr) -> bool {
const auto& i = instr.instr; const auto& i = instr.m_instr;
return i.mnemonic == ZYDIS_MNEMONIC_MOV && return i.mnemonic == ZYDIS_MNEMONIC_MOV &&
i.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && i.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
i.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER && i.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER &&
i.operands[1].reg.value == write_dep; i.operands[1].reg.value == write_dep;
}); });
if (mov_reg_write_dep == hndlr.end()) if (mov_reg_write_dep == instrs.end())
return {}; return {};
vsp = mov_reg_write_dep->instr.operands[0].reg.value; vsp = mov_reg_write_dep->m_instr.operands[0].reg.value;
} else { } else {
// find the MOV REG, [VSP] instruction... // find the MOV REG, [VSP] instruction...
const auto mov_reg_deref_vsp = std::find_if( const auto mov_reg_deref_vsp = std::find_if(
hndlr.begin(), hndlr.end(), instrs.begin(), instrs.end(),
[&](const zydis_instr_t& instr) -> bool { [&](const emu_instr_t& instr) -> bool {
const auto& i = instr.instr; const auto& i = instr.m_instr;
return i.mnemonic == ZYDIS_MNEMONIC_MOV && return i.mnemonic == ZYDIS_MNEMONIC_MOV &&
i.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && i.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
i.operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY && i.operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY &&
i.operands[1].mem.base == vsp; i.operands[1].mem.base == vsp;
}); });
if (mov_reg_deref_vsp == hndlr.end()) if (mov_reg_deref_vsp == instrs.end())
return {}; return {};
// find the MOV REG, mov_reg_deref_vsp->operands[0].reg.value // find the MOV REG, mov_reg_deref_vsp->operands[0].reg.value
const auto mov_vip_reg = std::find_if( const auto mov_vip_reg = std::find_if(
mov_reg_deref_vsp, hndlr.end(), mov_reg_deref_vsp, instrs.end(),
[&](const zydis_instr_t& instr) -> bool { [&](const emu_instr_t& instr) -> bool {
const auto& i = instr.instr; const auto& i = instr.m_instr;
return i.mnemonic == ZYDIS_MNEMONIC_MOV && return i.mnemonic == ZYDIS_MNEMONIC_MOV &&
i.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && i.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
i.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER && i.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER &&
i.operands[1].reg.value == i.operands[1].reg.value ==
mov_reg_deref_vsp->instr.operands[0].reg.value; mov_reg_deref_vsp->m_instr.operands[0].reg.value;
}); });
if (mov_vip_reg == hndlr.end()) if (mov_vip_reg == instrs.end())
return {}; return {};
vip = mov_vip_reg->instr.operands[0].reg.value; vip = mov_vip_reg->m_instr.operands[0].reg.value;
// see if VSP gets updated as well... // see if VSP gets updated as well...
const auto mov_reg_vsp = std::find_if( const auto mov_reg_vsp = std::find_if(
mov_reg_deref_vsp, hndlr.end(), mov_reg_deref_vsp, instrs.end(),
[&](const zydis_instr_t& instr) -> bool { [&](const emu_instr_t& instr) -> bool {
const auto& i = instr.instr; const auto& i = instr.m_instr;
return i.mnemonic == ZYDIS_MNEMONIC_MOV && return i.mnemonic == ZYDIS_MNEMONIC_MOV &&
i.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && i.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
i.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER && i.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER &&
i.operands[1].reg.value == vsp; i.operands[1].reg.value == vsp;
}); });
if (mov_reg_vsp != hndlr.end()) if (mov_reg_vsp != instrs.end())
vsp = mov_reg_vsp->instr.operands[0].reg.value; vsp = mov_reg_vsp->m_instr.operands[0].reg.value;
} }
return vinstr_t{mnemonic_t::jmp}; return vinstr_t{mnemonic_t::jmp};
}}; }};

@ -0,0 +1,48 @@
#include <vminstrs.hpp>
namespace vm::instrs {
profiler_t sreg = {
"SREG",
mnemonic_t::sreg,
{{// MOV REG, [VSP]
[&](const zydis_reg_t vip,
const zydis_reg_t vsp,
const zydis_decoded_instr_t& instr) -> bool {
return instr.mnemonic == ZYDIS_MNEMONIC_MOV &&
instr.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
instr.operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY &&
instr.operands[1].mem.base == vsp;
},
// ADD VSP, 8
[&](const zydis_reg_t vip,
const zydis_reg_t vsp,
const zydis_decoded_instr_t& instr) -> bool {
return instr.mnemonic == ZYDIS_MNEMONIC_ADD &&
instr.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
instr.operands[0].reg.value == vsp &&
instr.operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
instr.operands[1].imm.value.u == 8;
},
// MOV REG, [VIP]
[&](const zydis_reg_t vip,
const zydis_reg_t vsp,
const zydis_decoded_instr_t& instr) -> bool {
return instr.mnemonic == ZYDIS_MNEMONIC_MOV &&
instr.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
instr.operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY &&
instr.operands[1].mem.base == vip;
},
// MOV [RSP+REG], REG
[&](const zydis_reg_t vip,
const zydis_reg_t vsp,
const zydis_decoded_instr_t& instr) -> bool {
return instr.mnemonic == ZYDIS_MNEMONIC_MOV &&
instr.operands[0].type == ZYDIS_OPERAND_TYPE_MEMORY &&
instr.operands[0].mem.base == ZYDIS_REGISTER_RSP &&
instr.operands[0].mem.index != ZYDIS_REGISTER_NONE &&
instr.operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER;
}}},
[&](zydis_reg_t& vip,
zydis_reg_t& vsp,
hndlr_trace_t& hndlr) -> std::optional<vinstr_t> { return {}; }};
}

@ -119,8 +119,7 @@ void deobfuscate(zydis_rtn_t& routine) {
std::uint32_t last_size = 0u; std::uint32_t last_size = 0u;
static const std::vector<ZydisMnemonic> blacklist = { static const std::vector<ZydisMnemonic> blacklist = {
ZYDIS_MNEMONIC_CLC, ZYDIS_MNEMONIC_BT, ZYDIS_MNEMONIC_TEST, ZYDIS_MNEMONIC_CLC, ZYDIS_MNEMONIC_BT, ZYDIS_MNEMONIC_TEST,
ZYDIS_MNEMONIC_CMP, ZYDIS_MNEMONIC_CMC, ZYDIS_MNEMONIC_STC, ZYDIS_MNEMONIC_CMP, ZYDIS_MNEMONIC_CMC, ZYDIS_MNEMONIC_STC};
ZYDIS_MNEMONIC_JMP};
static const std::vector<ZydisMnemonic> whitelist = { static const std::vector<ZydisMnemonic> whitelist = {
ZYDIS_MNEMONIC_PUSH, ZYDIS_MNEMONIC_POP, ZYDIS_MNEMONIC_CALL, ZYDIS_MNEMONIC_PUSH, ZYDIS_MNEMONIC_POP, ZYDIS_MNEMONIC_CALL,
@ -139,6 +138,11 @@ void deobfuscate(zydis_rtn_t& routine) {
break; break;
} }
if (is_jmp(itr->instr)) {
routine.erase(itr);
break;
}
zydis_reg_t reg = ZYDIS_REGISTER_NONE; zydis_reg_t reg = ZYDIS_REGISTER_NONE;
// look for operands with writes to a register... // look for operands with writes to a register...
for (auto op_idx = 0u; op_idx < itr->instr.operand_count; ++op_idx) for (auto op_idx = 0u; op_idx < itr->instr.operand_count; ++op_idx)

Loading…
Cancel
Save