chore: split builtins in multiple files
Probably also other stuff but I left this uncommitted on my PC for too long and now I don't remember what I did (:
This commit is contained in:
parent
536612f356
commit
888b3279a2
11 changed files with 409 additions and 384 deletions
|
@ -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,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use mlua::{UserData, Error};
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Console (broadcast::Sender<String>);
|
||||
|
|
@ -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()?
|
||||
|
|
|
@ -9,6 +9,8 @@ const FF : char = '\u{C}'; // line feed, <C-L>, ^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,
|
|
@ -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<Value>) -> Result<usize, Error> {
|
||||
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<u8>, Option<bool>)) -> Result<Value, Error> {
|
||||
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::<String>()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lua_decomp(lua: &Lua, (bytes, ret): (Vec<u8>, Option<bool>)) -> Result<Value, Error> {
|
||||
let ret_value = ret.unwrap_or(false);
|
||||
let bitness = 8 * std::mem::size_of::<usize>() 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<bool>)) -> Result<String, Error> {
|
||||
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::<Value>().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<u8> {
|
||||
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<Vec<u8>, 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::<Value>().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<Vec<u8>, 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<u8>)) -> Result<usize, Error> {
|
||||
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<u8>, Option<bool>)
|
||||
) -> Result<Vec<usize>, 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<Table, Error> {
|
||||
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<Table, Error> {
|
||||
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<MemoryMaps> {
|
||||
Ok(Process::myself()?.maps()?)
|
||||
}
|
||||
|
||||
pub fn lua_procmaps(lua: &Lua, ret: Option<bool>) -> Result<Value, Error> {
|
||||
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<TasksIter> {
|
||||
Ok(Process::myself()?.tasks()?)
|
||||
}
|
||||
|
||||
fn thread_status(task: Result<Task, ProcError>) -> ProcResult<Status> {
|
||||
Ok(task?.status()?)
|
||||
}
|
||||
|
||||
pub fn lua_threads(lua: &Lua, ret: Option<bool>) -> Result<Value, Error> {
|
||||
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>, 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(), // 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<bool>) -> Result<bool, Error> {
|
||||
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<i32>) -> Result<(), Error> {
|
||||
#[allow(unreachable_code)]
|
||||
Ok(std::process::exit(code.unwrap_or(0)))
|
||||
}
|
38
src/tools/dumb.rs
Normal file
38
src/tools/dumb.rs
Normal file
|
@ -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<bool>) -> Result<bool, Error> {
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
166
src/tools/format.rs
Normal file
166
src/tools/format.rs
Normal file
|
@ -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<Value>) -> Result<usize, Error> {
|
||||
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<u8>, Option<bool>)) -> Result<Value, Error> {
|
||||
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::<String>()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lua_decomp(lua: &Lua, (bytes, ret): (Vec<u8>, Option<bool>)) -> Result<Value, Error> {
|
||||
let ret_value = ret.unwrap_or(false);
|
||||
let bitness = 8 * std::mem::size_of::<usize>() 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<bool>)) -> Result<String, Error> {
|
||||
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::<Value>().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<u8> {
|
||||
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<Vec<u8>, 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::<Value>().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())),
|
||||
}
|
||||
}
|
36
src/tools/memory.rs
Normal file
36
src/tools/memory.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
use mlua::{Lua, Error};
|
||||
|
||||
pub fn lua_read(_: &Lua, (addr, size): (usize, usize)) -> Result<Vec<u8>, 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<u8>)) -> Result<usize, Error> {
|
||||
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<u8>, Option<bool>)
|
||||
) -> Result<Vec<usize>, 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)
|
||||
}
|
|
@ -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<String>) -> 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<String>) -> 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
|
||||
";
|
104
src/tools/proc.rs
Normal file
104
src/tools/proc.rs
Normal file
|
@ -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<Table, Error> {
|
||||
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<Table, Error> {
|
||||
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<MemoryMaps> {
|
||||
Ok(Process::myself()?.maps()?)
|
||||
}
|
||||
|
||||
pub fn lua_procmaps(lua: &Lua, ret: Option<bool>) -> Result<Value, Error> {
|
||||
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<TasksIter> {
|
||||
Ok(Process::myself()?.tasks()?)
|
||||
}
|
||||
|
||||
fn thread_status(task: Result<Task, ProcError>) -> ProcResult<Status> {
|
||||
Ok(task?.status()?)
|
||||
}
|
||||
|
||||
pub fn lua_threads(lua: &Lua, ret: Option<bool>) -> Result<Value, Error> {
|
||||
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))
|
||||
}
|
||||
}
|
43
src/tools/syscall.rs
Normal file
43
src/tools/syscall.rs
Normal file
|
@ -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>, 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(), // 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<i32>) -> Result<(), Error> {
|
||||
#[allow(unreachable_code)]
|
||||
Ok(std::process::exit(code.unwrap_or(0)))
|
||||
}
|
Loading…
Reference in a new issue