feat: improved lua functions, added mem syscalls

refactored the 'runtime.rs' file a lot, improved usage of procmaps and
hexdump functions, separated hexdump from read, added mmap, munmap,
mprotect syscalls
This commit is contained in:
əlemi 2023-04-03 19:20:25 +02:00
parent 97c66aeb3b
commit 8aa26253fe
Signed by: alemi
GPG key ID: A4895B84D311642C
2 changed files with 149 additions and 85 deletions

View file

@ -19,4 +19,5 @@ tracing-subscriber = "0.3"
mlua = { version = "0.8", features = ["luajit52", "vendored", "async", "send", "serialize"] }
serde = "1.0.159"
serde_json = "1.0.95"
hexdump = "0.1.1"
nix = "0.26.2"
pretty-hex = "0.3"

View file

@ -1,100 +1,163 @@
use mlua::{Lua, Variadic, Value, Table};
use std::{ffi::c_void, num::NonZeroUsize};
use mlua::{Lua, Variadic, Value, Error, UserData, ToLua};
use nix::sys::mman::{mmap, mprotect, ProtFlags, MapFlags, munmap};
use pox::{proc_maps::get_process_maps, tricks::fmt_path};
use pretty_hex::pretty_hex;
use tokio::sync::broadcast;
use crate::helpers::pretty_lua;
pub fn prepare_lua_runtime(lua: &Lua, console: broadcast::Sender<String>) {
let c = console.clone();
let log = lua.create_function(move |_lua, values: Variadic<Value>| {
#[derive(Clone)]
struct Console (broadcast::Sender<String>);
impl UserData for Console {}
pub fn register_builtin_fn(lua: &Lua, console: broadcast::Sender<String>) -> Result<(), Error> {
lua.globals().set("console", Console(console))?; // TODO passing it this way makes clones
lua.globals().set("PROT_NONE", ProtFlags::PROT_NONE.bits())?;
lua.globals().set("PROT_READ", ProtFlags::PROT_READ.bits())?;
lua.globals().set("PROT_WRITE", ProtFlags::PROT_WRITE.bits())?;
lua.globals().set("PROT_EXEC", ProtFlags::PROT_EXEC.bits())?;
lua.globals().set("MAP_ANON", MapFlags::MAP_ANON.bits())?;
lua.globals().set("MAP_PRIVATE",MapFlags::MAP_PRIVATE.bits())?;
lua.globals().set("log", lua.create_function(lua_log)?)?;
lua.globals().set("hexdump", lua.create_function(lua_hexdump)?)?;
lua.globals().set("read", lua.create_function(lua_read)?)?;
lua.globals().set("write", lua.create_function(lua_write)?)?;
lua.globals().set("procmaps", lua.create_function(lua_procmaps)?)?;
lua.globals().set("exit", lua.create_function(lua_exit)?)?;
lua.globals().set("mmap", lua.create_function(lua_mmap)?)?;
lua.globals().set("munmap", lua.create_function(lua_munmap)?)?;
lua.globals().set("mprotect", lua.create_function(lua_mprotect)?)?;
lua.globals().set("help", lua.create_function(lua_help)?)?;
lua.globals().set("x", lua.create_function(lua_hex)?)?;
Ok(())
}
fn lua_help(lua: &Lua, _args: ()) -> Result<(), Error> {
let console : Console = lua.globals().get("console")?;
console.0.send(" > log([arg...]) print to console rather than stdout\n".into()).unwrap();
console.0.send(" > hexdump(bytes, [ret]) print hexdump of given bytes to console\n".into()).unwrap();
console.0.send(" > exit([code]) immediately terminate process\n".into()).unwrap();
console.0.send(" > mmap([a], l, [p], [f], [d], [o]) execute mmap syscall\n".into()).unwrap();
console.0.send(" > munmap(addr, len) unmap {size} bytes at {addr}\n".into()).unwrap();
console.0.send(" > mprotect(addr, len, prot) set permission flags on target memory area\n".into()).unwrap();
console.0.send(" > procmaps([ret]) returns process memory maps as string\n".into()).unwrap();
console.0.send(" > read(addr, size) read raw bytes at given address\n".into()).unwrap();
console.0.send(" > write(addr, bytes) write raw bytes at given address\n".into()).unwrap();
console.0.send(" > x(n) show hex representation of given number\n".into()).unwrap();
console.0.send(" > help() print these messages".into()).unwrap();
Ok(())
}
fn lua_log(lua: &Lua, values: Variadic<Value>) -> Result<usize, Error> {
let mut out = String::new();
let console : Console = lua.globals().get("console")?;
for value in values {
out.push_str(&pretty_lua(value));
out.push(' ');
}
out.push('\n');
let size = out.len();
c.send(out).unwrap();
console.0.send(out).unwrap();
Ok(size)
}).unwrap();
lua.globals().set("log", log).unwrap();
}
let procmaps = lua.create_function(move |_lua, ()| {
let mut out = String::new();
for map in get_process_maps(std::process::id() as i32).unwrap() {
out.push_str(
format!(
"[{}] 0x{:08X}..0x{:08X} +{:08x} \t {} {}\n",
map.flags, map.start(), map.start() + map.size(), map.offset, fmt_path(map.filename()),
if map.inode != 0 { format!("({})", map.inode) } else { "".into() },
).as_str()
);
fn lua_hexdump(lua: &Lua, (bytes, ret): (Vec<u8>, Option<bool>)) -> Result<Value, Error> {
let txt = pretty_hex(&bytes) + "\n";
if ret.is_some() && ret.unwrap() {
return Ok(txt.to_lua(lua)?);
}
Ok(out)
}).unwrap();
lua.globals().set("procmaps", procmaps).unwrap();
let console : Console = lua.globals().get("console")?;
match console.0.send(txt) {
Ok(n) => Ok(n.to_lua(lua)?),
Err(e) => Err(Error::RuntimeError(format!("could not convert bytes to hexdump: {}", e))),
}
}
let hexdump = lua.create_function(move |_lua, (addr, size): (usize, usize)| {
fn lua_hex(_: &Lua, n: usize) -> Result<String, Error> {
Ok(format!("0x{:X}", n))
}
fn lua_read(_: &Lua, (addr, size): (usize, usize)) -> Result<Vec<u8>, Error> {
if size == 0 {
return Ok("".into());
}
let ptr = addr as *mut u8;
let slice = unsafe { std::slice::from_raw_parts(ptr, size) };
let mut out = String::new();
for line in hexdump::hexdump_iter(slice) {
out.push_str(&line);
out.push('\n');
}
Ok(out)
}).unwrap();
lua.globals().set("hexdump", hexdump).unwrap();
Ok(slice.to_vec())
}
let write = lua.create_function(move |_lua, (addr, data): (usize, Vec<u8>)| {
fn lua_write(_: &Lua, (addr, data): (usize, Vec<u8>)) -> Result<usize, Error> {
for (i, byte) in data.iter().enumerate() {
let off = (addr + i) as *mut u8;
unsafe { *off = *byte } ;
}
Ok(data.len())
}).unwrap();
lua.globals().set("write", write).unwrap();
}
let exit = lua.create_function(move |_lua, code: Option<i32>| {
fn lua_procmaps(lua: &Lua, ret: Option<bool>) -> Result<Value, Error> {
let mut out = String::new();
for map in get_process_maps(std::process::id() as i32).unwrap() {
out.push_str(
format!(
"[{}] 0x{:08X}..0x{:08X} +{:08x} ({}b) \t {} {}\n",
map.flags, map.start(), map.start() + map.size(), map.offset, map.size(), fmt_path(map.filename()),
if map.inode != 0 { format!("({})", map.inode) } else { "".into() },
).as_str()
);
}
if ret.is_some() && ret.unwrap() {
return Ok(out.to_lua(lua)?);
}
let console : Console = lua.globals().get("console")?;
let written = print(console, out)?;
Ok(written.to_lua(lua)?)
}
fn lua_mprotect(_: &Lua, (addr, size, prot): (usize, usize, i32)) -> Result<(), Error> {
match unsafe { mprotect(addr as *mut c_void, size, ProtFlags::from_bits_truncate(prot)) } {
Ok(()) => Ok(()),
Err(e) => Err(Error::RuntimeError(format!("could not run mprotect ({}): {}", e, e.desc()))),
}
}
fn lua_mmap(_: &Lua, (addr, length, prot, flags, fd, offset): (Option<usize>, usize, Option<i32>, Option<i32>, Option<i32>, Option<i64>)) -> Result<usize, Error> {
if length <= 0 {
return Ok(0); // TODO make this an Err
}
match unsafe { mmap(
if let Some(a) = addr { NonZeroUsize::new(a) } else { None },
NonZeroUsize::new(length).unwrap(),
if let Some(p) = prot { ProtFlags::from_bits_truncate(p) } else { ProtFlags::PROT_READ | ProtFlags::PROT_WRITE },
if let Some(f) = flags { MapFlags::from_bits_truncate(f) } else { MapFlags::MAP_PRIVATE | MapFlags::MAP_ANON },
fd.unwrap_or(-1),
offset.unwrap_or(0),
) } {
Ok(x) => Ok(x as usize),
Err(e) => Err(Error::RuntimeError(format!("could not run mmap ({}): {}", e, e.desc()))),
}
}
fn lua_munmap(_: &Lua, (addr, len): (usize, usize)) -> Result<(), Error> {
match unsafe { munmap(addr as *mut c_void, len) } {
Ok(()) => Ok(()),
Err(e) => Err(Error::RuntimeError(format!("could not run munmap ({}): {}", e, e.desc()))),
}
}
fn lua_exit(_: &Lua, code: Option<i32>) -> Result<(), Error> {
#[allow(unreachable_code)]
Ok(std::process::exit(code.unwrap_or(0)))
}).unwrap();
lua.globals().set("exit", exit).unwrap();
let help = lua.create_function(move |_lua, ()| {
console.send(" > log(...) print to (this) remote shell\n".into()).unwrap();
console.send(" > exit(code) immediately terminate process\n".into()).unwrap();
console.send(" > procmaps() returns process memory maps as string\n".into()).unwrap();
console.send(" > write(addr, bytes) write raw bytes at given address\n".into()).unwrap();
console.send(" > hexdump(addr, size) dump bytes at addr in hexdump format\n".into()).unwrap();
console.send(" > help() print these messages".into()).unwrap();
Ok(())
}).unwrap();
lua.globals().set("help", help).unwrap();
}
pub fn pretty_lua(val: Value) -> String {
// TODO there must be some builtin to do this, right???
match val {
Value::Nil => "nil".into(),
Value::Boolean(b) => if b { "true".into() } else { "false".into() },
Value::LightUserData(x) => format!("LightUserData({:?})", x),
Value::Integer(n) => format!("{}", n),
Value::Number(n) => format!("{:.3}", n),
Value::String(s) => s.to_str().expect("string is not str").into(),
Value::Table(t) => try_serialize_table(&t),
Value::Function(f) => format!("Function({:?}", f),
Value::Thread(t) => format!("Thread({:?})", t),
Value::UserData(x) => format!("UserData({:?})", x),
Value::Error(e) => format!("Error({:?}) : {}", e, e.to_string()),
}
}
fn try_serialize_table(t: &Table) -> String {
match serde_json::to_string(t) {
Ok(txt) => txt,
Err(_e) => format!("{:?}", t),
fn print(console: Console, txt: String) -> Result<usize, Error> {
match console.0.send(txt) {
Ok(n) => Ok(n),
Err(e) => Err(Error::RuntimeError(format!("could not print message to console: {}", e))),
}
}