Skip to content

Commit

Permalink
Tried to fix the inline hooks:
Browse files Browse the repository at this point in the history
- Replaced jump with a long jump that doesn't use `mov rax`
- Updated trampoline setup
- Fixed `iced-x86` compilation. See: icedland/iced#247
  • Loading branch information
not-matthias committed Jan 8, 2022
1 parent ec275a7 commit 8787ed9
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 79 deletions.
8 changes: 6 additions & 2 deletions driver/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@ x86_64 = { version = "0.14.7", features = ["nightly", "const_fn", "abi_x86_inter
x86 = { version = "0.44.0" }
winapi = { git = "https://github.com/Trantect/winapi-rs.git", branch = "feature/km", features = ["wdm", "ntstatus"] }
once_cell = { version = "1.9.0", default-features = false, features = ["unstable", "alloc"] }
iced-x86 = { version = "1.15.0", default-features = false, features = ["no_std", "decoder", "block_encoder", "instr_info", "no_d3now", "no_evex", "no_vex", "no_xop"] }
#iced-x86 = { version = "1.15.0", default-features = false, features = ["no_std", "decoder", "block_encoder", "encoder", "instr_info", "no_d3now", "no_evex", "no_vex", "no_xop"] }
iced-x86 = { path = "C:/Users/not-matthias/Documents/iced/src/rust/iced-x86", default-features = false, features = ["no_std", "decoder", "block_encoder", "instr_info", "no_d3now", "no_evex", "no_vex", "no_xop"] }

[profile.release]
lto = "thin" # IMPORTANT: "fat" doesn't work and will result in an access violation
#lto = "fat"
debug = true
panic = "abort"
overflow-checks = false
debug-assertions = false
debug-assertions = false

[patch.crates-io]
ahash = { git = "https://github.com/tkaitchuck/aHash", branch = "simplification" }
34 changes: 20 additions & 14 deletions driver/src/hook/handlers.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use crate::nt::inline_hook::InlineHook;
use crate::nt::ptr::Pointer;

use nt::include::{ZwQuerySystemInformation, SYSTEM_INFORMATION_CLASS};
use nt::include::MmIsAddressValid;
use winapi::km::wdm::POOL_TYPE::NonPagedPool;

use crate::dbg_break;
use crate::nt::include::{ExAllocatePoolWithTag, ExFreePool};
use winapi::shared::ntdef::NTSTATUS;

pub static mut ZWQSI_ORIGINAL: Option<Pointer<InlineHook>> = None;
pub fn zw_query_system_information(
pub extern "system" fn zw_query_system_information(
system_information_class: u32,
system_information: u64,
system_information_length: u32,
Expand Down Expand Up @@ -84,16 +86,16 @@ pub fn test_hooks() {

// Test zw_query_system_information
//
log::info!("Testing zw_query_system_information.");
let status = unsafe {
ZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS::SystemProcessInformation,
0x0 as _,
0x0,
0x0 as _,
)
};
log::info!("zw_query_system_information returned {:x}.", status);
// log::info!("Testing zw_query_system_information.");
// let status = unsafe {
// ZwQuerySystemInformation(
// SYSTEM_INFORMATION_CLASS::SystemProcessInformation,
// 0x0 as _,
// 0x0,
// 0x0 as _,
// )
// };
// log::info!("zw_query_system_information returned {:x}.", status);

// Test ex_allocate_pool_with_tag
//
Expand All @@ -107,9 +109,13 @@ pub fn test_hooks() {
//
// unsafe { ExFreePool(ptr as _) };

// Test MmIsAddressValid
// // Test MmIsAddressValid
// //
// dbg_break!();
//
// unsafe { MmIsAddressValid(0 as _) };
// log::info!("Is address valid: {:?}", unsafe {
// MmIsAddressValid(0 as _)
// });

dbg_break!();
}
2 changes: 1 addition & 1 deletion driver/src/hook/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl Hook {

// Install inline hook on the **copied** page (not the original one).
//
let inline_hook = InlineHook::new(hook_va, handler)?;
let inline_hook = InlineHook::new(address, hook_va, handler)?;

Some(Self {
original_va: address,
Expand Down
44 changes: 22 additions & 22 deletions driver/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#![feature(const_ptr_as_ref)]
#![feature(const_trait_impl)]
#![allow(clippy::new_ret_no_self)]
#![feature(int_abs_diff)]

extern crate alloc;

Expand Down Expand Up @@ -47,35 +48,34 @@ static LOGGER: KernelLogger = KernelLogger;
static mut PROCESSORS: Option<Processors> = None;

fn init_hooks() -> Option<Vec<Hook>> {
// ZwQuerySystemInformation
// // ZwQuerySystemInformation
// //
// let zwqsi_hook = Hook::hook_function(
// "ZwQuerySystemInformation",
// handlers::zw_query_system_information as *const (),
// )?;
// unsafe {
// handlers::ZWQSI_ORIGINAL = match zwqsi_hook.hook_type {
// HookType::Function { ref inline_hook } => Pointer::new(inline_hook.as_ptr()),
// HookType::Page => None,
// };
// }

// ExAllocatePoolWithTag
//
let zwqsi_hook = Hook::hook_function(
"ZwQuerySystemInformation",
handlers::zw_query_system_information as *const (),
let eapwt_hook = Hook::hook_function(
"ExAllocatePoolWithTag",
handlers::ex_allocate_pool_with_tag as *const (),
)?;
unsafe {
handlers::ZWQSI_ORIGINAL = match zwqsi_hook.hook_type {
handlers::EAPWT_ORIGINAL = match eapwt_hook.hook_type {
HookType::Function { ref inline_hook } => Pointer::new(inline_hook.as_ptr()),
HookType::Page => None,
HookType::Page => unreachable!(),
};
}

// ExAllocatePool
// MmIsAddressValid
//
// let eapwt_hook = Hook::hook_function(
// "ExAllocatePoolWithTag",
// handlers::ex_allocate_pool_with_tag as *const (),
// )?;
// unsafe {
// handlers::EAPWT_ORIGINAL = match eapwt_hook.hook_type {
// HookType::Function { ref inline_hook } => Pointer::new(inline_hook.as_ptr()),
// HookType::Page => unreachable!(),
// };
// }

// Currently not supported because of the minimalistic InlineHook implementation.
// // MmIsAddressValid
// //
// let mmiav_hook = Hook::hook_function(
// "MmIsAddressValid",
// handlers::mm_is_address_valid as *const (),
Expand All @@ -87,7 +87,7 @@ fn init_hooks() -> Option<Vec<Hook>> {
// };
// }

Some(vec![zwqsi_hook])
Some(vec![eapwt_hook])
}

fn virtualize_system() -> Option<()> {
Expand Down
142 changes: 102 additions & 40 deletions driver/src/nt/inline_hook.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use crate::dbg_break;
use crate::nt::include::RtlCopyMemory;
use crate::nt::memory::AllocatedMemory;
use alloc::vec::Vec;
use core::arch::x86_64::_mm_clflush;
use iced_x86::{
BlockEncoder, BlockEncoderOptions, Code, Decoder, DecoderOptions, FlowControl, Instruction,
InstructionBlock, Register,
};
use snafu::prelude::*;

pub const JMP_SHELLCODE_LEN: usize = 12;
pub const JMP_SHELLCODE_LEN: usize = 14;

#[derive(Debug, Snafu)]
pub enum InlineHookError {
Expand Down Expand Up @@ -38,17 +38,28 @@ pub struct InlineHook {
}

impl InlineHook {
// Note: We have to allocate a new instance here, so that it's valid after the virtualization. Otherwise,
// all the addresses would be 0x0.
pub fn new(address: u64, handler: *const ()) -> Option<AllocatedMemory<Self>> {
/// Creates a new inline hook (not yet enabled) for the specified function.
///
///
/// ## Note
///
/// Note: We have to allocate a new instance here, so that it's valid after the virtualization. Otherwise,
/// all the addresses would be 0x0.
///
/// TODO: Can we somehow get rid of the original address?
pub fn new(
original_address: u64,
address: u64,
handler: *const (),
) -> Option<AllocatedMemory<Self>> {
log::info!(
"Creating a new inline hook. Address: {:x}, handler: {:x}",
address,
handler as u64
);

let mut hook = AllocatedMemory::<Self>::alloc(core::mem::size_of::<Self>())?;
hook.trampoline = match Self::trampoline_shellcode(address as u64) {
hook.trampoline = match Self::trampoline_shellcode(original_address, address as u64) {
Ok(trampoline) => trampoline,
Err(e) => {
log::error!("Failed to create trampoline: {:?}", e);
Expand Down Expand Up @@ -88,35 +99,61 @@ impl InlineHook {
self.trampoline.as_ptr() as _
}

fn jmp_shellcode(target_address: u64) -> Vec<u8> {
/// Creates the jmp shellcode.
///
/// ## How it works.
///
/// We are using the following assembly shellcode:
/// ```asm
/// jmp [rip+00h]
/// 0xDEADBEEF
/// ```
///
/// Or in a different format:
///
/// ```asm
/// jmp qword ptr cs:jmp_add
/// jmp_addr: dq 0xDEADBEEF
/// ```
///
/// The core premise behind it is, that we jump to the address that is right after the current
/// instruction.
///
/// ## Why use this instead of `mov rax, jmp rax`?
///
/// This shellcode has one very important feature: **It doesn't require any registers to store the
/// jmp address**. And because of that, we don't have to fear overwriting some register values.
///
fn jmp_shellcode(target_address: u64) -> [u8; 14] {
log::info!(
"Creating the jmp shellcode for address: {:#x}",
target_address
);

// Create the instructions:
// Create the shellcode. See function documentation for more information.
//
// nop
// mov rax, target_address
// jmp rax
//
let instructions = [
Instruction::with(Code::Nopq),
Instruction::with2(Code::Mov_r64_imm64, Register::RAX, target_address).unwrap(),
Instruction::with1(Code::Jmp_rm64, Register::RAX).unwrap(),
let mut shellcode = [
0xff, 0x25, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC,
];

// Encode the instructions. It's a absolute jump, so we don't have to care about the rip.
//
let block = InstructionBlock::new(&instructions, 0x0);
let shellcode = BlockEncoder::encode(64, block, BlockEncoderOptions::NONE)
.map(|b| b.code_buffer)
.unwrap();
unsafe {
(shellcode.as_mut_ptr().add(6) as *mut u64).write_volatile(target_address as u64)
};

shellcode
}

fn trampoline_shellcode(function_address: u64) -> Result<AllocatedMemory<u8>, InlineHookError> {
/// Creates a trampoline shellcode that jumps to the original function.
///
/// ## Parameters
///
/// - `original_address`: The address of the original function. We need this so that we can relocate potential jumps that have been overwritten by the hook.
/// - `function_address`: The address of same function in the copied page.
///
/// TODO: Replace one of these parameters
fn trampoline_shellcode(
original_address: u64,
function_address: u64,
) -> Result<AllocatedMemory<u8>, InlineHookError> {
log::info!(
"Creating the trampoline shellcode for function: {:#x}",
function_address
Expand All @@ -128,7 +165,7 @@ impl InlineHook {
let bytes = unsafe {
core::slice::from_raw_parts(function_address as *mut u8, JMP_SHELLCODE_LEN * 2)
};
let mut decoder = Decoder::with_ip(64, &bytes, function_address, DecoderOptions::NONE);
let mut decoder = Decoder::with_ip(64, &bytes, original_address, DecoderOptions::NONE);

let mut total_bytes = 0;
let mut trampoline = Vec::new();
Expand All @@ -139,28 +176,45 @@ impl InlineHook {

// Create the new trampoline instruction
//
let instr = match instr.flow_control() {
FlowControl::Next => instr,
match instr.flow_control() {
FlowControl::Next | FlowControl::Return => {
total_bytes += instr.len();
trampoline.push(instr);
}
FlowControl::Call => {
// TODO: Relocate call
instr
if instr.is_call_near() {
total_bytes += instr.len();

let branch_target = instr.near_branch_target();

// TODO: Just relocate the relative jump

// mov rax, branch_target
// jmp rax
//
// let mov_rax =
// Instruction::with2(Code::Mov_r64_imm64, Register::RAX, branch_target)
// .unwrap();
// let jmp_rax = Instruction::with1(Code::Jmp_rm64, Register::RAX).unwrap();
//
// trampoline.push(mov_rax);
// trampoline.push(jmp_rax);
trampoline.push(
Instruction::with_branch(Code::Jmp_rel32_64, branch_target).unwrap(),
);
} else {
log::warn!("Found call far");
}
}
FlowControl::Return => instr,
FlowControl::IndirectBranch
| FlowControl::ConditionalBranch
| FlowControl::UnconditionalBranch
| FlowControl::IndirectCall
| FlowControl::Interrupt
| FlowControl::XbeginXabortXend
| FlowControl::Exception => {
log::warn!("Using unsupported instruction: {:?}", instr);
instr
}
| FlowControl::Exception => log::warn!("Unsupported instruction"),
};

total_bytes += instr.len();
trampoline.push(instr);

if total_bytes >= JMP_SHELLCODE_LEN {
break;
}
Expand All @@ -180,6 +234,10 @@ impl InlineHook {
let last_instr = trampoline.last().unwrap();
let jmp_back_address = last_instr.next_ip();
if last_instr.flow_control() != FlowControl::Return {
log::info!(
"Creating jmp back to original instructions at {:#x}",
jmp_back_address
);
trampoline
.push(Instruction::with_branch(Code::Jmp_rel32_64, jmp_back_address).unwrap());
}
Expand All @@ -190,17 +248,21 @@ impl InlineHook {
.ok_or_else(|| InlineHookError::AllocationFailed)?;

log::info!("Allocated trampoline memory at {:p}", memory.as_ptr());
log::info!(
"Offset between original and trampoline: {:#x}",
function_address.abs_diff(memory.as_ptr() as u64)
);

let block = InstructionBlock::new(&trampoline, memory.as_ptr() as _);
let encoded = BlockEncoder::encode(decoder.bitness(), block, BlockEncoderOptions::NONE)
.map(|b| b.code_buffer)
.map_err(|_| InlineHookError::EncodingFailed)?;

log::info!("Encoded trampoline: {:x?}", encoded);

// Copy the encoded bytes and return the allocated memory.
//
unsafe { RtlCopyMemory(memory.as_ptr() as _, encoded.as_ptr() as _, encoded.len()) };

dbg_break!();
unsafe { core::ptr::copy_nonoverlapping(encoded.as_ptr(), memory.as_ptr(), encoded.len()) };

Ok(memory)
}
Expand Down

0 comments on commit 8787ed9

Please sign in to comment.