diff --git a/src/channel.rs b/src/channel.rs index a58b1ae..0f543ca 100644 --- a/src/channel.rs +++ b/src/channel.rs @@ -2,7 +2,7 @@ use mlua::Lua; use tokio::{sync::{mpsc, broadcast}, net::{TcpListener, TcpStream}, io::{AsyncWriteExt, AsyncReadExt}}; use tracing::{debug, error, warn}; -use crate::runtime::{register_builtin_fn, VERSIONTEXT, repl::LuaRepl}; +use crate::{repl::{LuaRepl, VERSIONTEXT}, tools::register_builtin_fn}; pub struct ControlChannel { addr: String, diff --git a/src/runtime/console.rs b/src/console.rs similarity index 99% rename from src/runtime/console.rs rename to src/console.rs index f403021..867398a 100644 --- a/src/runtime/console.rs +++ b/src/console.rs @@ -1,7 +1,6 @@ use mlua::{UserData, Error}; use tokio::sync::broadcast; - #[derive(Clone)] pub struct Console (broadcast::Sender); diff --git a/src/lib.rs b/src/lib.rs index ea2e337..c411446 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,11 @@ -mod runtime; mod channel; mod helpers; +mod console; +mod repl; +mod tools; use channel::ControlChannel; -use tracing::{error, debug}; +use tracing::error; #[ctor::ctor] fn contructor() { @@ -12,7 +14,6 @@ fn contructor() { .with_max_level(tracing::Level::DEBUG) .with_writer(std::io::stderr) .init(); - debug!("infected process"); tokio::runtime::Builder::new_current_thread() .enable_all() .build()? diff --git a/src/runtime/repl.rs b/src/repl.rs similarity index 97% rename from src/runtime/repl.rs rename to src/repl.rs index 9e165f0..1269246 100644 --- a/src/runtime/repl.rs +++ b/src/repl.rs @@ -9,6 +9,8 @@ const FF : char = '\u{C}'; // line feed, , ^L const CMD: char = '\u{1B}'; // ANSI escape char const CR : char = '\u{A}'; // newline, \n, 10 +pub const VERSIONTEXT : &str = "LuaJit 5.2 via rlua"; + enum CmdStep { Nope, One, diff --git a/src/runtime/builtins.rs b/src/runtime/builtins.rs deleted file mode 100644 index 85de3b6..0000000 --- a/src/runtime/builtins.rs +++ /dev/null @@ -1,345 +0,0 @@ -use std::{ffi::{c_void, c_int}, num::NonZeroUsize, sync::atomic::{AtomicBool, Ordering}}; - -use iced_x86::{Decoder, DecoderOptions, IntelFormatter, Instruction, Formatter}; -use mlua::{Lua, Error, Variadic, Value, ToLua, Table}; -use nix::sys::{mman::{mprotect, ProtFlags, mmap, MapFlags, munmap}, signal::{Signal::SIGSEGV, SigHandler}}; -use procfs::{process::{Process, MemoryMaps, TasksIter, Status, Task, MemoryMap}, ProcError, ProcResult}; -use tracing::warn; - -use crate::helpers::pretty_lua; - -use super::{console::Console, HELPTEXT, GLOBAL_CONSOLE}; - -const SIGSEGV_HOOK : AtomicBool = AtomicBool::new(false); - -pub fn lua_help(lua: &Lua, _args: ()) -> Result<(), Error> { - let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; - console.send(HELPTEXT.into())?; - Ok(()) -} - -pub fn lua_log(lua: &Lua, values: Variadic) -> Result { - let mut out = String::new(); - let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; - for value in values { - out.push_str(&pretty_lua(value)); - out.push(' '); - } - out.push('\n'); - let size = out.len(); - console.send(out)?; - Ok(size) -} - -pub fn lua_hexdump(lua: &Lua, (bytes, ret): (Vec, Option)) -> Result { - if ret.unwrap_or(false) { - return Ok(pretty_hex::simple_hex(&bytes).to_lua(lua)?); - } - let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; - console.send(pretty_hex::pretty_hex(&bytes) + "\n")?; - Ok(Value::Nil) -} - -fn padding(size: i32) -> String { - if size <= 0 { - "".into() - } else { - (0..size as usize).map(|_| " ").collect::() - } -} - -pub fn lua_decomp(lua: &Lua, (bytes, ret): (Vec, Option)) -> Result { - let ret_value = ret.unwrap_or(false); - let bitness = 8 * std::mem::size_of::() as u32; - let mut decoder = Decoder::with_ip(bitness, bytes.as_slice(), 0, DecoderOptions::NONE); - let mut formatter = IntelFormatter::new(); - let mut instr_buffer = String::new(); - let mut raw_buffer = String::new(); - let mut instruction = Instruction::default(); - let mut output = String::new(); - let mut retval = vec![]; - let mut count = 0; - while decoder.can_decode() { - decoder.decode_out(&mut instruction); - instr_buffer.clear(); - formatter.format(&instruction, &mut instr_buffer); - if ret_value { - retval.push(instr_buffer.clone()); - continue; - } - raw_buffer.clear(); - let start_index = instruction.ip() as usize; - let instrs_bytes = &bytes[start_index..start_index+instruction.len()]; - for b in instrs_bytes { - raw_buffer.push_str(&format!("{:02x} ", b)); - } - let padding = padding(30 - raw_buffer.len() as i32); - output.push_str(&format!("{:08X}: {}{}{}\n", instruction.ip(), raw_buffer, padding, instr_buffer)); - count += 1; - } - if ret_value { - Ok(retval.to_lua(lua)?) - } else { - let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; - console.send(output)?; - Ok(count.to_lua(lua)?) - } -} - -pub fn lua_hex(l: &Lua, (value, prefix): (Value, Option)) -> Result { - let pre = if prefix.unwrap_or(true) { "0x" } else { "" }; - match value { - Value::Nil => Ok(format!("{}00", pre)), - Value::Boolean(b) => Ok(format!("{}{:02X}", pre, b as i32)), - Value::Integer(n) => Ok(format!("{}{:02X}", pre, n)), - Value::String(s) => Ok( - s.as_bytes() - .iter() - .map(|x| format!("{:02X}", x)) - .fold(pre.into(), |acc, x| acc + x.as_str()) - ), - Value::Table(t) => Ok( - t.sequence_values::().into_iter() - .filter_map(|x| if let Ok(v) = x { Some(v) } else { None }) - .map(|x| lua_hex(l, (x, Some(false))).unwrap_or("??".into())) // recursive! try stopping me - .fold(pre.into(), |acc, x| acc + x.as_str()) - ), - Value::Number(_) => Err(Error::RuntimeError("float has no hex value".into())), - Value::Function(_) => Err(Error::RuntimeError("function has no hex value".into())), - Value::Thread(_) => Err(Error::RuntimeError("thread has no hex value".into())), - Value::LightUserData(_) => Err(Error::RuntimeError("LightUserData has no hex value".into())), - Value::UserData(_) => Err(Error::RuntimeError("UserData has no hex value".into())), - Value::Error(_) => Err(Error::RuntimeError("Error has no hex value".into())), - } -} - -/// could just use .to_ne_bytes() but lot of trailing zeros -fn i64_to_significant_bytes(n: i64) -> Vec { - let mut out = vec![]; - for i in 0..8 { - let val = (n >> (i*8)) as u8; - let res = n >> ((i+1) * 8); - if val == 0 && res == 0 { break; } - out.push(val); - } - out -} - -pub fn lua_bytes(l: &Lua, value: Value) -> Result, Error> { - match value { - Value::Nil => Ok(vec![]), - Value::Boolean(b) => Ok(if b { vec![1] } else { vec![0] }), - Value::Integer(n) => Ok(i64_to_significant_bytes(n)), - Value::String(s) => Ok(s.as_bytes().to_vec()), - Value::Table(t) => Ok( - t.sequence_values::().into_iter() - .filter_map(|x| if let Ok(v) = x { Some(v) } else { None }) - .map(|x| lua_bytes(l, x).unwrap_or(vec![])) - .fold(vec![], |mut acc, mut x| { acc.append(&mut x); acc }) - ), - Value::Number(_) => Err(Error::RuntimeError("cannot display float bytes value".into())), - Value::Function(_) => Err(Error::RuntimeError("cannot display function bytes value".into())), - Value::Thread(_) => Err(Error::RuntimeError("cannot display thread bytes value".into())), - Value::LightUserData(_) => Err(Error::RuntimeError("cannot display LightUserData bytes value".into())), - Value::UserData(_) => Err(Error::RuntimeError("cannot display UserData bytes value".into())), - Value::Error(_) => Err(Error::RuntimeError("cannot display Error bytes value".into())), - } -} - -pub fn lua_read(_: &Lua, (addr, size): (usize, usize)) -> Result, Error> { - if size == 0 { - return Ok("".into()); - } - let ptr = addr as *const u8; - let slice = unsafe { std::slice::from_raw_parts(ptr, size) }; - Ok(slice.to_vec()) -} - -pub fn lua_write(_: &Lua, (addr, data): (usize, Vec)) -> Result { - for (i, byte) in data.iter().enumerate() { - let off = (addr + i) as *mut u8; - unsafe { *off = *byte } ; - } - Ok(data.len()) -} - -pub fn lua_find( - _: &Lua, (start, size, pattern, first): (usize, usize, Vec, Option) -) -> Result, Error> { - let window = pattern.len(); - let first_only = first.unwrap_or(false); - let mut matches = vec![]; - - for i in 0..(size-window) { - let slice = unsafe { std::slice::from_raw_parts((start + i) as *const u8, window) }; - if slice == pattern { - matches.push(start + i); - if first_only { break; } - } - } - - Ok(matches) -} - -fn proc_table(lua: &Lua, task: Status) -> Result { - let table = lua.create_table()?; - table.set("pid", task.pid)?; - table.set("name", task.name)?; - table.set("state", task.state)?; - table.set("fdsize", task.fdsize)?; - Ok(table) -} - -fn map_table(lua: &Lua, task: MemoryMap) -> Result { - let table = lua.create_table()?; - table.set("perms", task.perms.as_str())?; - table.set("address", task.address.0)?; - table.set("offset", task.offset)?; - table.set("size", task.address.1 - task.address.0)?; - table.set("path", format!("{:?}", task.pathname))?; - Ok(table) -} - -fn proc_maps() -> ProcResult { - Ok(Process::myself()?.maps()?) -} - -pub fn lua_procmaps(lua: &Lua, ret: Option) -> Result { - let maps = proc_maps() - .map_err(|e| Error::RuntimeError( - format!("could not obtain process maps: {}", e) - ))?; - if ret.unwrap_or(false) { - let mut out = vec![]; - for map in maps { - out.push(map_table(lua, map)?); - } - Ok(out.to_lua(lua)?) - } else { - let mut out = String::new(); - let mut count = 0; - for map in maps { - count += 1; - out.push_str( - format!( - " * [{}] 0x{:08X}..0x{:08X} +{:08x} ({}b) \t {:?} {}\n", - map.perms.as_str(), map.address.0, map.address.1, map.offset, map.address.1 - map.address.0, map.pathname, - if map.inode != 0 { format!("({})", map.inode) } else { "".into() }, - ).as_str() - ); - } - let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; - console.send(out)?; - Ok(Value::Integer(count)) - } -} - -fn thread_maps() -> ProcResult { - Ok(Process::myself()?.tasks()?) -} - -fn thread_status(task: Result) -> ProcResult { - Ok(task?.status()?) -} - -pub fn lua_threads(lua: &Lua, ret: Option) -> Result { - let maps = thread_maps() - .map_err(|e| Error::RuntimeError( - format!("could not obtain task maps: {}", e) - ))?; - if ret.unwrap_or(false) { - let mut out = vec![]; - for task in maps { - match thread_status(task) { - Ok(s) => out.push(proc_table(lua, s)?), - Err(e) => warn!("could not parse task metadata: {}", e), - } - } - Ok(out.to_lua(lua)?) - } else { - let mut out = String::new(); - let mut count = 0; - for task in maps { - match thread_status(task) { - Ok(s) => { - count += 1; - out.push_str( - format!(" * [{}] {} {} | {} fd)\n", s.pid, s.state, s.name, s.fdsize).as_str() - ); - }, - Err(e) => warn!("could not parse task metadata: {}", e), - } - } - - let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; - console.send(out)?; - Ok(Value::Integer(count)) - } -} - -pub 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()))), - } -} - -pub fn lua_mmap(_: &Lua, (addr, length, prot, flags, fd, offset): (Option, usize, Option, Option, Option, Option)) -> Result { - 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(), // safe because we manually checked lenght to be > 0 - 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()))), - } -} - -pub 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()))), - } -} - -extern fn handle_sigsegv(_signal: c_int) { - eprintln!("Segmentation fault (ignored)"); -} - -pub fn lua_catch_sigsev(_: &Lua, mode: Option) -> Result { - match mode { - Some(m) => match m { - true => { - let handler = SigHandler::Handler(handle_sigsegv); - match unsafe { nix::sys::signal::signal(SIGSEGV, handler) } { - Ok(_h) => { - SIGSEGV_HOOK.store(true, Ordering::Relaxed); - Ok(true) - }, - Err(e) => Err(Error::RuntimeError(format!("could not set sig handler ({}): {}", e, e.desc()))), - } - }, - false => { - match unsafe { nix::sys::signal::signal(SIGSEGV, SigHandler::SigDfl) } { - Ok(_h) => { - SIGSEGV_HOOK.store(false, Ordering::Relaxed); - Ok(false) - }, - Err(e) => Err(Error::RuntimeError(format!("could not reset sig handler ({}): {}", e, e.desc()))), - } - }, - }, - None => Ok(SIGSEGV_HOOK.load(Ordering::Relaxed)), - } -} - -pub fn lua_exit(_: &Lua, code: Option) -> Result<(), Error> { - #[allow(unreachable_code)] - Ok(std::process::exit(code.unwrap_or(0))) -} diff --git a/src/tools/dumb.rs b/src/tools/dumb.rs new file mode 100644 index 0000000..4f00e90 --- /dev/null +++ b/src/tools/dumb.rs @@ -0,0 +1,38 @@ +use std::{sync::atomic::{AtomicBool, Ordering}, ffi::c_int}; + +use mlua::{Lua, Error}; +use nix::sys::signal::{SigHandler, Signal::SIGSEGV}; + +const SIGSEGV_HOOK : AtomicBool = AtomicBool::new(false); + +extern fn handle_sigsegv(_signal: c_int) { + eprintln!("Segmentation fault (ignored)"); +} + +pub fn lua_catch_sigsev(_: &Lua, mode: Option) -> Result { + match mode { + Some(m) => match m { + true => { + let handler = SigHandler::Handler(handle_sigsegv); + match unsafe { nix::sys::signal::signal(SIGSEGV, handler) } { + Ok(_h) => { + SIGSEGV_HOOK.store(true, Ordering::Relaxed); + Ok(true) + }, + Err(e) => Err(Error::RuntimeError(format!("could not set sig handler ({}): {}", e, e.desc()))), + } + }, + false => { + match unsafe { nix::sys::signal::signal(SIGSEGV, SigHandler::SigDfl) } { + Ok(_h) => { + SIGSEGV_HOOK.store(false, Ordering::Relaxed); + Ok(false) + }, + Err(e) => Err(Error::RuntimeError(format!("could not reset sig handler ({}): {}", e, e.desc()))), + } + }, + }, + None => Ok(SIGSEGV_HOOK.load(Ordering::Relaxed)), + } +} + diff --git a/src/tools/format.rs b/src/tools/format.rs new file mode 100644 index 0000000..f1cb9da --- /dev/null +++ b/src/tools/format.rs @@ -0,0 +1,166 @@ +use iced_x86::{Decoder, DecoderOptions, IntelFormatter, Instruction, Formatter}; +use mlua::{Lua, Error, Variadic, Value, ToLua}; + +use crate::{helpers::pretty_lua, console::Console}; + +pub const GLOBAL_CONSOLE : &str = "GLOBAL_CONSOLE"; + +pub const HELPTEXT : &str = "?> This is a complete lua repl +?> Make scripts or just evaluate expressions +?> print() will go to original process stdout, use log() +?> to send to this console instead +?> Each connection will spawn a fresh repl, but only one +?> concurrent connection is allowed +?> Some ad-hoc functions to work with affected process +?> are already available in this repl globals: + > log([arg...]) print to console rather than stdout + > hexdump(bytes, [ret]) print hexdump of given {bytes} to console + > exit([code]) immediately terminate process + > mmap([a], l, [p], [f], [d], [o]) execute mmap syscall + > munmap(ptr, len) unmap {len} bytes at {ptr} + > mprotect(ptr, len, prot) set {prot} flags from {ptr} to {ptr+len} + > procmaps([ret]) get process memory maps as string + > threads([ret]) get process threads list as string + > read(addr, size) read {size} raw bytes at {addr} + > write(addr, bytes) write given {bytes} at {addr} + > find(ptr, len, match, [first]) search from {ptr} to {ptr+len} for {match} and return addrs + > x(number, [prefix]) show hex representation of given {number} + > b(string) return array of bytes from given {string} + > sigsegv([set]) get or set SIGSEGV handler state + > help() print these messages +"; + + +pub fn lua_help(lua: &Lua, _args: ()) -> Result<(), Error> { + let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; + console.send(HELPTEXT.into())?; + Ok(()) +} + +pub fn lua_log(lua: &Lua, values: Variadic) -> Result { + let mut out = String::new(); + let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; + for value in values { + out.push_str(&pretty_lua(value)); + out.push(' '); + } + out.push('\n'); + let size = out.len(); + console.send(out)?; + Ok(size) +} + +pub fn lua_hexdump(lua: &Lua, (bytes, ret): (Vec, Option)) -> Result { + if ret.unwrap_or(false) { + return Ok(pretty_hex::simple_hex(&bytes).to_lua(lua)?); + } + let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; + console.send(pretty_hex::pretty_hex(&bytes) + "\n")?; + Ok(Value::Nil) +} + +fn padding(size: i32) -> String { + if size <= 0 { + "".into() + } else { + (0..size as usize).map(|_| " ").collect::() + } +} + +pub fn lua_decomp(lua: &Lua, (bytes, ret): (Vec, Option)) -> Result { + let ret_value = ret.unwrap_or(false); + let bitness = 8 * std::mem::size_of::() as u32; + let mut decoder = Decoder::with_ip(bitness, bytes.as_slice(), 0, DecoderOptions::NONE); + let mut formatter = IntelFormatter::new(); + let mut instr_buffer = String::new(); + let mut raw_buffer = String::new(); + let mut instruction = Instruction::default(); + let mut output = String::new(); + let mut retval = vec![]; + let mut count = 0; + while decoder.can_decode() { + decoder.decode_out(&mut instruction); + instr_buffer.clear(); + formatter.format(&instruction, &mut instr_buffer); + if ret_value { + retval.push(instr_buffer.clone()); + continue; + } + raw_buffer.clear(); + let start_index = instruction.ip() as usize; + let instrs_bytes = &bytes[start_index..start_index+instruction.len()]; + for b in instrs_bytes { + raw_buffer.push_str(&format!("{:02x} ", b)); + } + let padding = padding(30 - raw_buffer.len() as i32); + output.push_str(&format!("{:08X}: {}{}{}\n", instruction.ip(), raw_buffer, padding, instr_buffer)); + count += 1; + } + if ret_value { + Ok(retval.to_lua(lua)?) + } else { + let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; + console.send(output)?; + Ok(count.to_lua(lua)?) + } +} + +pub fn lua_hex(l: &Lua, (value, prefix): (Value, Option)) -> Result { + let pre = if prefix.unwrap_or(true) { "0x" } else { "" }; + match value { + Value::Nil => Ok(format!("{}00", pre)), + Value::Boolean(b) => Ok(format!("{}{:02X}", pre, b as i32)), + Value::Integer(n) => Ok(format!("{}{:02X}", pre, n)), + Value::String(s) => Ok( + s.as_bytes() + .iter() + .map(|x| format!("{:02X}", x)) + .fold(pre.into(), |acc, x| acc + x.as_str()) + ), + Value::Table(t) => Ok( + t.sequence_values::().into_iter() + .filter_map(|x| if let Ok(v) = x { Some(v) } else { None }) + .map(|x| lua_hex(l, (x, Some(false))).unwrap_or("??".into())) // recursive! try stopping me + .fold(pre.into(), |acc, x| acc + x.as_str()) + ), + Value::Number(_) => Err(Error::RuntimeError("float has no hex value".into())), + Value::Function(_) => Err(Error::RuntimeError("function has no hex value".into())), + Value::Thread(_) => Err(Error::RuntimeError("thread has no hex value".into())), + Value::LightUserData(_) => Err(Error::RuntimeError("LightUserData has no hex value".into())), + Value::UserData(_) => Err(Error::RuntimeError("UserData has no hex value".into())), + Value::Error(_) => Err(Error::RuntimeError("Error has no hex value".into())), + } +} + +/// could just use .to_ne_bytes() but lot of trailing zeros +fn i64_to_significant_bytes(n: i64) -> Vec { + let mut out = vec![]; + for i in 0..8 { + let val = (n >> (i*8)) as u8; + let res = n >> ((i+1) * 8); + if val == 0 && res == 0 { break; } + out.push(val); + } + out +} + +pub fn lua_bytes(l: &Lua, value: Value) -> Result, Error> { + match value { + Value::Nil => Ok(vec![]), + Value::Boolean(b) => Ok(if b { vec![1] } else { vec![0] }), + Value::Integer(n) => Ok(i64_to_significant_bytes(n)), + Value::String(s) => Ok(s.as_bytes().to_vec()), + Value::Table(t) => Ok( + t.sequence_values::().into_iter() + .filter_map(|x| if let Ok(v) = x { Some(v) } else { None }) + .map(|x| lua_bytes(l, x).unwrap_or(vec![])) + .fold(vec![], |mut acc, mut x| { acc.append(&mut x); acc }) + ), + Value::Number(_) => Err(Error::RuntimeError("cannot display float bytes value".into())), + Value::Function(_) => Err(Error::RuntimeError("cannot display function bytes value".into())), + Value::Thread(_) => Err(Error::RuntimeError("cannot display thread bytes value".into())), + Value::LightUserData(_) => Err(Error::RuntimeError("cannot display LightUserData bytes value".into())), + Value::UserData(_) => Err(Error::RuntimeError("cannot display UserData bytes value".into())), + Value::Error(_) => Err(Error::RuntimeError("cannot display Error bytes value".into())), + } +} diff --git a/src/tools/memory.rs b/src/tools/memory.rs new file mode 100644 index 0000000..91c6072 --- /dev/null +++ b/src/tools/memory.rs @@ -0,0 +1,36 @@ +use mlua::{Lua, Error}; + +pub fn lua_read(_: &Lua, (addr, size): (usize, usize)) -> Result, Error> { + if size == 0 { + return Ok("".into()); + } + let ptr = addr as *const u8; + let slice = unsafe { std::slice::from_raw_parts(ptr, size) }; + Ok(slice.to_vec()) +} + +pub fn lua_write(_: &Lua, (addr, data): (usize, Vec)) -> Result { + for (i, byte) in data.iter().enumerate() { + let off = (addr + i) as *mut u8; + unsafe { *off = *byte } ; + } + Ok(data.len()) +} + +pub fn lua_find( + _: &Lua, (start, size, pattern, first): (usize, usize, Vec, Option) +) -> Result, Error> { + let window = pattern.len(); + let first_only = first.unwrap_or(false); + let mut matches = vec![]; + + for i in 0..(size-window) { + let slice = unsafe { std::slice::from_raw_parts((start + i) as *const u8, window) }; + if slice == pattern { + matches.push(start + i); + if first_only { break; } + } + } + + Ok(matches) +} diff --git a/src/runtime/mod.rs b/src/tools/mod.rs similarity index 52% rename from src/runtime/mod.rs rename to src/tools/mod.rs index 1b36a46..7fafbfa 100644 --- a/src/runtime/mod.rs +++ b/src/tools/mod.rs @@ -1,16 +1,23 @@ -pub mod builtins; -pub mod console; -pub mod repl; - use mlua::{Lua, Error}; use nix::sys::mman::{ProtFlags, MapFlags}; - use tokio::sync::broadcast; -use crate::runtime::console::Console; -use crate::runtime::builtins::*; +use crate::console::Console; -pub const GLOBAL_CONSOLE : &str = "GLOBAL_CONSOLE"; +use self::format::GLOBAL_CONSOLE; + +pub mod format; +pub mod memory; +pub mod syscall; +pub mod proc; + +pub mod dumb; + +use self::dumb::*; +use self::format::*; +use self::memory::*; +use self::proc::*; +use self::syscall::*; pub fn register_builtin_fn(lua: &Lua, console: broadcast::Sender) -> Result<(), Error> { lua.globals().set(GLOBAL_CONSOLE, Console::from(console))?; // TODO passing it this way makes clones @@ -43,29 +50,3 @@ pub fn register_builtin_fn(lua: &Lua, console: broadcast::Sender) -> Res Ok(()) } - -pub const VERSIONTEXT : &str = "LuaJit 5.2 via rlua"; -pub const HELPTEXT : &str = "?> This is a complete lua repl -?> Make scripts or just evaluate expressions -?> print() will go to original process stdout, use log() -?> to send to this console instead -?> Each connection will spawn a fresh repl, but only one -?> concurrent connection is allowed -?> Some ad-hoc functions to work with affected process -?> are already available in this repl globals: - > log([arg...]) print to console rather than stdout - > hexdump(bytes, [ret]) print hexdump of given {bytes} to console - > exit([code]) immediately terminate process - > mmap([a], l, [p], [f], [d], [o]) execute mmap syscall - > munmap(ptr, len) unmap {len} bytes at {ptr} - > mprotect(ptr, len, prot) set {prot} flags from {ptr} to {ptr+len} - > procmaps([ret]) get process memory maps as string - > threads([ret]) get process threads list as string - > read(addr, size) read {size} raw bytes at {addr} - > write(addr, bytes) write given {bytes} at {addr} - > find(ptr, len, match, [first]) search from {ptr} to {ptr+len} for {match} and return addrs - > x(number, [prefix]) show hex representation of given {number} - > b(string) return array of bytes from given {string} - > sigsegv([set]) get or set SIGSEGV handler state - > help() print these messages -"; diff --git a/src/tools/proc.rs b/src/tools/proc.rs new file mode 100644 index 0000000..79d0011 --- /dev/null +++ b/src/tools/proc.rs @@ -0,0 +1,104 @@ +use mlua::{Lua, Error, Table, Value, ToLua}; +use procfs::{process::{Status, MemoryMap, Process, MemoryMaps, Task, TasksIter}, ProcResult, ProcError}; +use tracing::warn; + +use crate::console::Console; + +use super::format::GLOBAL_CONSOLE; + + +fn proc_table(lua: &Lua, task: Status) -> Result { + let table = lua.create_table()?; + table.set("pid", task.pid)?; + table.set("name", task.name)?; + table.set("state", task.state)?; + table.set("fdsize", task.fdsize)?; + Ok(table) +} + +fn map_table(lua: &Lua, task: MemoryMap) -> Result { + let table = lua.create_table()?; + table.set("perms", task.perms.as_str())?; + table.set("address", task.address.0)?; + table.set("offset", task.offset)?; + table.set("size", task.address.1 - task.address.0)?; + table.set("path", format!("{:?}", task.pathname))?; + Ok(table) +} + +fn proc_maps() -> ProcResult { + Ok(Process::myself()?.maps()?) +} + +pub fn lua_procmaps(lua: &Lua, ret: Option) -> Result { + let maps = proc_maps() + .map_err(|e| Error::RuntimeError( + format!("could not obtain process maps: {}", e) + ))?; + if ret.unwrap_or(false) { + let mut out = vec![]; + for map in maps { + out.push(map_table(lua, map)?); + } + Ok(out.to_lua(lua)?) + } else { + let mut out = String::new(); + let mut count = 0; + for map in maps { + count += 1; + out.push_str( + format!( + " * [{}] 0x{:08X}..0x{:08X} +{:08x} ({}b) \t {:?} {}\n", + map.perms.as_str(), map.address.0, map.address.1, map.offset, map.address.1 - map.address.0, map.pathname, + if map.inode != 0 { format!("({})", map.inode) } else { "".into() }, + ).as_str() + ); + } + let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; + console.send(out)?; + Ok(Value::Integer(count)) + } +} + +fn thread_maps() -> ProcResult { + Ok(Process::myself()?.tasks()?) +} + +fn thread_status(task: Result) -> ProcResult { + Ok(task?.status()?) +} + +pub fn lua_threads(lua: &Lua, ret: Option) -> Result { + let maps = thread_maps() + .map_err(|e| Error::RuntimeError( + format!("could not obtain task maps: {}", e) + ))?; + if ret.unwrap_or(false) { + let mut out = vec![]; + for task in maps { + match thread_status(task) { + Ok(s) => out.push(proc_table(lua, s)?), + Err(e) => warn!("could not parse task metadata: {}", e), + } + } + Ok(out.to_lua(lua)?) + } else { + let mut out = String::new(); + let mut count = 0; + for task in maps { + match thread_status(task) { + Ok(s) => { + count += 1; + out.push_str( + format!(" * [{}] {} {} | {} fd)\n", s.pid, s.state, s.name, s.fdsize).as_str() + ); + }, + Err(e) => warn!("could not parse task metadata: {}", e), + } + } + + let console : Console = lua.globals().get(GLOBAL_CONSOLE)?; + console.send(out)?; + Ok(Value::Integer(count)) + } +} diff --git a/src/tools/syscall.rs b/src/tools/syscall.rs new file mode 100644 index 0000000..bb7da1e --- /dev/null +++ b/src/tools/syscall.rs @@ -0,0 +1,43 @@ +use std::{ffi::c_void, num::NonZeroUsize}; + +use mlua::{Lua, Error}; +use nix::sys::mman::{mprotect, ProtFlags, mmap, MapFlags, munmap}; + +use cordy_macro::lua_fn; + +#[lua_fn] +pub 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()))), + } +} + +pub fn lua_mmap(_: &Lua, (addr, length, prot, flags, fd, offset): (Option, usize, Option, Option, Option, Option)) -> Result { + 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(), // safe because we manually checked lenght to be > 0 + 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()))), + } +} + +pub 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()))), + } +} + +pub fn lua_exit(_: &Lua, code: Option) -> Result<(), Error> { + #[allow(unreachable_code)] + Ok(std::process::exit(code.unwrap_or(0))) +}