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:
əlemi 2023-07-08 12:56:33 +02:00
parent 536612f356
commit 888b3279a2
Signed by: alemi
GPG key ID: A4895B84D311642C
11 changed files with 409 additions and 384 deletions

View file

@ -2,7 +2,7 @@ use mlua::Lua;
use tokio::{sync::{mpsc, broadcast}, net::{TcpListener, TcpStream}, io::{AsyncWriteExt, AsyncReadExt}}; use tokio::{sync::{mpsc, broadcast}, net::{TcpListener, TcpStream}, io::{AsyncWriteExt, AsyncReadExt}};
use tracing::{debug, error, warn}; 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 { pub struct ControlChannel {
addr: String, addr: String,

View file

@ -1,7 +1,6 @@
use mlua::{UserData, Error}; use mlua::{UserData, Error};
use tokio::sync::broadcast; use tokio::sync::broadcast;
#[derive(Clone)] #[derive(Clone)]
pub struct Console (broadcast::Sender<String>); pub struct Console (broadcast::Sender<String>);

View file

@ -1,9 +1,11 @@
mod runtime;
mod channel; mod channel;
mod helpers; mod helpers;
mod console;
mod repl;
mod tools;
use channel::ControlChannel; use channel::ControlChannel;
use tracing::{error, debug}; use tracing::error;
#[ctor::ctor] #[ctor::ctor]
fn contructor() { fn contructor() {
@ -12,7 +14,6 @@ fn contructor() {
.with_max_level(tracing::Level::DEBUG) .with_max_level(tracing::Level::DEBUG)
.with_writer(std::io::stderr) .with_writer(std::io::stderr)
.init(); .init();
debug!("infected process");
tokio::runtime::Builder::new_current_thread() tokio::runtime::Builder::new_current_thread()
.enable_all() .enable_all()
.build()? .build()?

View file

@ -9,6 +9,8 @@ const FF : char = '\u{C}'; // line feed, <C-L>, ^L
const CMD: char = '\u{1B}'; // ANSI escape char const CMD: char = '\u{1B}'; // ANSI escape char
const CR : char = '\u{A}'; // newline, \n, 10 const CR : char = '\u{A}'; // newline, \n, 10
pub const VERSIONTEXT : &str = "LuaJit 5.2 via rlua";
enum CmdStep { enum CmdStep {
Nope, Nope,
One, One,

View file

@ -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
View 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
View 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
View 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)
}

View file

@ -1,16 +1,23 @@
pub mod builtins;
pub mod console;
pub mod repl;
use mlua::{Lua, Error}; use mlua::{Lua, Error};
use nix::sys::mman::{ProtFlags, MapFlags}; use nix::sys::mman::{ProtFlags, MapFlags};
use tokio::sync::broadcast; use tokio::sync::broadcast;
use crate::runtime::console::Console; use crate::console::Console;
use crate::runtime::builtins::*;
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> { 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 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(()) 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
View 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
View 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)))
}