-
Notifications
You must be signed in to change notification settings - Fork 485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sysinject hacks #1283
Draft
ryandmaggio
wants to merge
29
commits into
dev
Choose a base branch
from
sysinject_hacks
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Sysinject hacks #1283
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
9b9a1ba
prepping for PR. 32 bit systems work, 64 do not.
ryandmaggio b163209
README
ryandmaggio 6c91980
Reapplying changes
ryandmaggio 1a06344
cleaned up stray comments, commented out 64 bit broken code
ryandmaggio c84ba33
added wrapper around inject_syscall for access specifically
ryandmaggio f3c454a
cleaned up the readme
ryandmaggio 0e7c6b6
more README cleaning
ryandmaggio e2d4b08
Update README.md
ryandmaggio bafc6dd
Update README.md
ryandmaggio 6f6d37e
Update README.md
ryandmaggio ffae528
added sysinject functionality to panda.py to the user doesn't have to…
ryandmaggio e9ffa7f
Update README.md
ryandmaggio a3bc793
Update README.md
ryandmaggio d7e8966
renamed sysinject_rs to sysinject
ryandmaggio e64a57d
Update README.md
ryandmaggio 53734e0
Updated README, added mistakenly ommited makefile
ryandmaggio a067c6f
Cleaned up some bugs in panda.py functions, added example, fleshed ou…
ryandmaggio e971947
breaking things locally so hopefully PR build works
ryandmaggio 0616da4
fixed references to non-supported x64 so that hopefully the build wor…
ryandmaggio cd45dba
Maybe fixed it now?
ryandmaggio 1efe9e4
have to make sure x86_64 is defined
ryandmaggio 0f33e42
Fixed return type error
ryandmaggio 9608497
maybe have all the archs covered now
ryandmaggio 1c3faa4
rectifying mipsel misspell
ryandmaggio 43cf710
Realized we do actually support mipsel and mips64
ryandmaggio cb9732e
actually looked at what archs were expected instead of poking around …
ryandmaggio cc4c1f5
Maybe now it will build
ryandmaggio f4c0573
Fix broken bullet in README.md
ryandmaggio 076edda
Update README.md for clarity around sys_access
ryandmaggio File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
[package] | ||
name = "sysinject_rs" | ||
version = "0.1.0" | ||
authors = ["Zak Estrada <Zachary.Estrada@ll.mit.edu", "Luke Craig <Luke.Craig@ll.mit.edu>"] | ||
edition = "2018" | ||
|
||
[lib] | ||
crate-type = ["cdylib"] | ||
|
||
[dependencies] | ||
panda-re = { version = "0.42.1", default-features = false } | ||
#panda-re = { path = "/out/panda-rs/panda-rs", default-features = false } | ||
|
||
[features] | ||
#default = ["mips"] | ||
default = ["arm"] | ||
#default = ["i386"] | ||
#default = ["x86_64"] | ||
|
||
x86_64 = ["panda-re/x86_64"] | ||
i386 = ["panda-re/i386"] | ||
arm = ["panda-re/arm"] | ||
#ppc = ["panda-re/ppc"] | ||
mips = ["panda-re/mips"] | ||
#mipsel = ["panda-re/mipsel"] | ||
#mips64 = ["panda-re/mips64"] | ||
#aarch64 = ["panda-re/aarch64"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Don't forget to add your plugin to config.panda! | ||
|
||
# Build rust plugins with make! | ||
|
||
# The main rule for your plugin. List all object-file dependencies. | ||
|
||
PLUGIN_DIR = $(realpath $(join $(SRC_PATH), /panda/plugins/$(PLUGIN_NAME)/)) | ||
RUST_SOURCE = $(wildcard $(PLUGIN_DIR)/src/*.rs) | ||
PLUGIN_ARTIFACTS_DIR = $(PLUGIN_TARGET_DIR)/$(PLUGIN_NAME)/target | ||
|
||
$(PLUGIN_TARGET_DIR)/panda_$(PLUGIN_NAME).so : $(RUST_SOURCE) $(PLUGIN_DIR)/Cargo.toml | ||
@echo " CARGO $(PLUGIN_DIR)" | ||
@CARGO_TERM_PROGRESS_WHEN=never cargo build --release \ | ||
--no-default-features --features=$(TARGET_NAME) \ | ||
--manifest-path=$(PLUGIN_DIR)/Cargo.toml \ | ||
--target-dir=$(PLUGIN_ARTIFACTS_DIR) | ||
@cp -p $(PLUGIN_ARTIFACTS_DIR)/release/lib$(PLUGIN_NAME).so $@ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
Plugin: sysinject | ||
=========== | ||
|
||
Summary | ||
------ | ||
`sysinject` allows for the injection of syscalls into the guest at arbitrary points. | ||
|
||
The function `inject_syscall` takes 4 arguments | ||
1) `cpu`, the cpu state. This is standard for panda plugins. | ||
2) `callno`, the syscall number. This number will vary for each syscall dependant on the cpu architecture of the guest, so make sure that you have the right one. | ||
3) `nargs`, the number of arguments to your syscall. | ||
4) `raw_args`, the arguments to pass to your syscall, given as type `target_ulong[nargs]`. From python, you'll want to use `panda.ffi.new("target_ulong[]", arglist])`, where `arglist` is a list of each of your arguments. Likewise, each element in `arglist` will need to be converted from its original type to `target_ulong` through `panda.ffi.cast("target_ulong", orig_arg)`. | ||
|
||
To use the plugin, simply put the call to `inject_syscall` where you want it to be triggered. For `mips`, if you are having it triggered at a certain address, you will need to somehow gate the function call to avoid repeated calls, since for that architecture the PC is backed up one instruction at the end of `inject_syscall`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
use panda::mem::{virtual_memory_read, virtual_memory_write}; | ||
use panda::prelude::*; | ||
use panda::sys::cpu_loop_exit_restore; | ||
use panda::{abi, regs, Callback}; | ||
use std::process::abort; | ||
use std::slice; | ||
|
||
/// Inject a system call. Arguments are passed in as a raw array of target_ulong | ||
#[no_mangle] | ||
pub extern "C" fn inject_syscall( | ||
cpu: &mut CPUState, | ||
callno: target_ulong, | ||
nargs: usize, | ||
raw_args: *const target_ulong, | ||
) { | ||
let mut args: Vec<target_ulong> = vec![]; | ||
#[allow(unused_variables)] | ||
let mut instr_len: usize = 0; | ||
|
||
#[allow(unused_variables)] | ||
let mut prev_instr_len: usize = 0; | ||
|
||
#[allow(unused_variables)] | ||
let mut orig_addr: target_ulong = 0; | ||
|
||
#[cfg(not(feature = "x86_64"))] | ||
{ | ||
let targs = unsafe { slice::from_raw_parts(raw_args, nargs) }; | ||
args = targs.to_vec(); | ||
} | ||
|
||
// Separating out x86_64 argument parsing because instructions are not all length 4, and we pass that data in from outside | ||
// (because I didn't want to figure out capstone from the rust side, although that would be preferable) | ||
// We assume the first two arguments passed in are the length of the current instruction, and the length of the previous instruction. | ||
#[cfg(feature = "x86_64")] | ||
{ | ||
let targs = unsafe { slice::from_raw_parts(raw_args, nargs + 2) }; | ||
instr_len = targs[0] as usize; | ||
prev_instr_len = targs[1] as usize; | ||
args = targs[2..].to_vec(); | ||
} | ||
|
||
//Back up all GPRs since we are doing this non-cooperatively | ||
let backed_up_regs: Vec<_> = regs::Reg::iter() | ||
.map({ |reg| (reg, regs::get_reg(cpu, reg)) }) | ||
.collect(); | ||
//Create a vector in case we have stack based arguments | ||
let mut backed_up_stack: Vec<(abi::StorageLocation, target_ulong)> = Vec::new(); | ||
|
||
if nargs > abi::syscall::SYSCALL_ARGS_LEN { | ||
eprintln!( | ||
"Too many syscall arguments: {}, maximum is: {} !", | ||
nargs, | ||
abi::syscall::SYSCALL_ARGS_LEN | ||
); | ||
abort(); | ||
} | ||
|
||
//Setup syscall and state | ||
for i in 0..nargs { | ||
println!("inject_syscall arg {}: {:x}", i, args[i]); | ||
let arg = abi::syscall::SYSCALL_ARGS[i]; | ||
if let abi::StorageLocation::StackOffset(_) = arg { | ||
backed_up_stack.push((arg, arg.read(cpu))); | ||
} | ||
arg.write(cpu, args[i]); | ||
} | ||
|
||
#[allow(unused_variables)] | ||
let orig_inst: Vec<u8>; | ||
|
||
#[cfg(any(feature = "arm", feature = "aarch64"))] | ||
{ | ||
orig_inst = virtual_memory_read(cpu, regs::get_pc(cpu), 4) | ||
.expect("Failed to read original instruction"); | ||
} | ||
|
||
// 64 bit support not ready yet, leaving this in incase we need to work on it later. | ||
//#[cfg(feature = "x86_64")] { | ||
// orig_addr = regs::get_pc(cpu); | ||
// //orig_addr = regs::get_pc(cpu) + (prev_instr_len as target_ulong); | ||
// orig_inst = virtual_memory_read(cpu, orig_addr, instr_len) | ||
// .expect("Failed to read original instruction"); | ||
//} | ||
|
||
regs::set_reg(cpu, abi::syscall::SYSCALL_NUM_REG, callno); | ||
|
||
#[cfg(any(feature = "mips", feature = "mipsel", feature = "mips64"))] | ||
{ | ||
cpu.exception_index = 17; | ||
} | ||
|
||
// Source: qemu/qemu/blob/master/target/arm/cpu.h line 38: #define EXCP_SWI 2 | ||
// Have: exception index == 2 | syscall_nr == [correct] | ||
// Need: immediate value 0 to tell CPU the swi is a syscall | ||
#[cfg(any(feature = "arm", feature = "aarch64"))] | ||
{ | ||
//println!("Setting cpu exception index to 2"); | ||
cpu.exception_index = 2; | ||
virtual_memory_write(cpu, regs::get_pc(cpu) - 4, b"\x00\x00\x00\xef"); | ||
} | ||
|
||
// i386 relies on `int 0x80` for syscalls, emulate that behavior here | ||
#[cfg(feature = "i386")] | ||
{ | ||
println!("Setting exception 0x80\n"); | ||
cpu.exception_index = 0x80; | ||
} | ||
|
||
// 64 bit not supported, leaving in incase we need to work on it in the future. | ||
// #[cfg(feature = "x86_64")] { | ||
// print!("[sysinject_rs] Original bytes:"); | ||
// for i in orig_inst.iter() { | ||
// print!("0x{:x} ", i); | ||
// } | ||
// println!(""); | ||
// let mut new_instr = b"\x0f\x05".to_vec(); | ||
// for i in 0..instr_len-2 { | ||
// new_instr = [new_instr.as_slice(), b"\x90".as_slice()].concat(); | ||
// } | ||
// virtual_memory_write(cpu, orig_addr, new_instr.as_slice()); | ||
// let dummy_read = virtual_memory_read(cpu, orig_addr, instr_len).expect("Failed to read new instruction"); | ||
// print!("[sysinject_rs] Edited bytes:"); | ||
// for i in dummy_read.iter() { | ||
// print!("0x{:x} ", i); | ||
// } | ||
// println!(""); | ||
// } | ||
|
||
let injected_asid = panda::current_asid(cpu); | ||
|
||
//Callback to detect when we return from the syscall so we can cleanup guest | ||
//state | ||
let abe_callback = Callback::new(); | ||
abe_callback.after_block_exec(move |cpu, _, _| { | ||
if panda::current_asid(cpu) == injected_asid { | ||
if !panda::in_kernel_mode(cpu) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ASID//PC pair?```suggestion
|
||
//We are heading back to userland from our syscall, restore | ||
//state | ||
|
||
//Restore registers | ||
for (reg, value) in &backed_up_regs { | ||
regs::set_reg(cpu, *reg, *value); | ||
} | ||
|
||
//Restore stack args if we had them | ||
for (loc, value) in &backed_up_stack { | ||
loc.write(cpu, *value); | ||
} | ||
|
||
//We have to backup one instruction | ||
#[cfg(any(feature = "mips", feature = "mipsel", feature = "mips64"))] | ||
{ | ||
regs::set_pc(cpu, regs::get_pc(cpu) - 4); | ||
} | ||
|
||
// Do not need to back up any instructions in rust, but do need to re-write the original instruction back over the inserted syscall instruction | ||
#[cfg(any(feature = "arm", feature = "aarch64"))] | ||
{ | ||
virtual_memory_write(cpu, regs::get_pc(cpu) - 4, &orig_inst); | ||
} | ||
|
||
// Do not need to back up instructions, or otherwise handle anything for i386 | ||
|
||
// 64 bit still not supported, see previous. | ||
// #[cfg(feature = "x86_64")] { | ||
// println!("resetting pc to {:x}", regs::get_pc(cpu)); | ||
// //let dr = virtual_memory_read(cpu, regs::get_pc(cpu) - (instr_len as target_ulong), instr_len).expect("Failed to read old new instruction"); | ||
// let dr = virtual_memory_read(cpu, regs::get_pc(cpu), instr_len).expect("Failed to read old new instruction"); | ||
// print!("[sysinject_rs] Bytes before re-write:"); | ||
// for i in dr.iter() { | ||
// print!("0x{:x} ", i); | ||
// } | ||
// println!(""); | ||
// virtual_memory_write(cpu, orig_addr, &orig_inst); | ||
// //let dr2 = virtual_memory_read(cpu, regs::get_pc(cpu) - (instr_len as target_ulong), instr_len).expect("Failed to read new old instruction"); | ||
// let dr2 = virtual_memory_read(cpu, orig_addr, instr_len).expect("Failed to read new old instruction"); | ||
// print!("[sysinject_rs] Bytes after re-write:"); | ||
// for i in dr2.iter() { | ||
// print!("0x{:x} ", i); | ||
// } | ||
// //println!("[x86_64] Do not need to reset PC"); | ||
// println!("Resetting PC to {:x}", orig_addr); | ||
// regs::set_pc(cpu, orig_addr); | ||
// | ||
// } | ||
|
||
//Disable callback | ||
abe_callback.disable(); | ||
} | ||
} | ||
}); | ||
|
||
//TODO: we should implement a means of getting back to the caller after the syscall finishes | ||
unsafe { | ||
cpu_loop_exit_restore(cpu, (orig_addr as usize) - instr_len); | ||
} | ||
println!("Got past cpu_loop_exit_restore"); //should be unreachable | ||
abort(); | ||
} | ||
|
||
#[panda::init] | ||
fn init(_: &mut PluginHandle) -> bool { | ||
println!("Loaded sysinject"); | ||
true | ||
} | ||
|
||
#[panda::uninit] | ||
fn exit(_: &mut PluginHandle) { | ||
println!("Unloading sysinject"); | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check formatting on github.