Skip to content
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
wants to merge 29 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
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 Feb 23, 2023
b163209
README
ryandmaggio Feb 23, 2023
6c91980
Reapplying changes
ryandmaggio Feb 23, 2023
1a06344
cleaned up stray comments, commented out 64 bit broken code
ryandmaggio Feb 23, 2023
c84ba33
added wrapper around inject_syscall for access specifically
ryandmaggio Mar 1, 2023
f3c454a
cleaned up the readme
ryandmaggio Mar 1, 2023
0e7c6b6
more README cleaning
ryandmaggio Mar 1, 2023
e2d4b08
Update README.md
ryandmaggio Mar 1, 2023
bafc6dd
Update README.md
ryandmaggio Mar 1, 2023
6f6d37e
Update README.md
ryandmaggio Mar 1, 2023
ffae528
added sysinject functionality to panda.py to the user doesn't have to…
ryandmaggio Mar 1, 2023
e9ffa7f
Update README.md
ryandmaggio Mar 1, 2023
a3bc793
Update README.md
ryandmaggio Mar 1, 2023
d7e8966
renamed sysinject_rs to sysinject
ryandmaggio Mar 1, 2023
e64a57d
Update README.md
ryandmaggio Mar 1, 2023
53734e0
Updated README, added mistakenly ommited makefile
ryandmaggio Mar 1, 2023
a067c6f
Cleaned up some bugs in panda.py functions, added example, fleshed ou…
ryandmaggio Mar 3, 2023
e971947
breaking things locally so hopefully PR build works
ryandmaggio Mar 6, 2023
0616da4
fixed references to non-supported x64 so that hopefully the build wor…
ryandmaggio Mar 7, 2023
cd45dba
Maybe fixed it now?
ryandmaggio Mar 8, 2023
1efe9e4
have to make sure x86_64 is defined
ryandmaggio Mar 8, 2023
0f33e42
Fixed return type error
ryandmaggio Mar 13, 2023
9608497
maybe have all the archs covered now
ryandmaggio Mar 13, 2023
1c3faa4
rectifying mipsel misspell
ryandmaggio Mar 13, 2023
43cf710
Realized we do actually support mipsel and mips64
ryandmaggio Mar 13, 2023
cb9732e
actually looked at what archs were expected instead of poking around …
ryandmaggio Mar 13, 2023
cc4c1f5
Maybe now it will build
ryandmaggio Mar 14, 2023
f4c0573
Fix broken bullet in README.md
ryandmaggio Jul 17, 2023
076edda
Update README.md for clarity around sys_access
ryandmaggio Jul 17, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions panda/plugins/sysinject_rs/Cargo.toml
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"]
17 changes: 17 additions & 0 deletions panda/plugins/sysinject_rs/Makefile
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 $@
14 changes: 14 additions & 0 deletions panda/plugins/sysinject_rs/README.md
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check formatting on github.

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`.
211 changes: 211 additions & 0 deletions panda/plugins/sysinject_rs/src/lib.rs
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) {
Copy link
Member

@lacraig2 lacraig2 Feb 24, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ASID//PC pair?```suggestion
if !panda::in_kernel_mode(cpu) {

//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");
}