0,前言
本文假设你对Linux运行应用程序有一定了解或者已经阅读过上一篇文章关于这方面的讲述。
1,背景
由于工作原因,需要了解LD_PRELOAD相关知识,在网络上搜索到一篇关于这方面知识较为全面的文章GDB调试LD_PRELOAD动态链接库(自己也对这篇文章做了些补充并被作者收纳,见该文章ID为gzhuflyer的评论),但总感觉作者讲解这方面知识跳动性有点大,很难实践理解,因此写下这篇文章。
2,示例程序运行
1,使用 rust 调用 C库例子进行分析。在linux系统中先准备rust环境,执行下面命令即可。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
2,安装编译 syscall_intercept 库
sudo apt install cmake libcapstone-dev
git clone https://github.com/pmem/syscall_intercept.git
cd syscall_intercept
mkdir build
cd build && make && sudo make install
3,创建示例程序
cargo new ld-test --lib
ld-test目录结构如下
将Cargo.toml 内容修改为:
[package]
name = "ld-test"
version = "0.1.0"
edition = "2021"
[dependencies]
ctor = "0.2.0"
libc = "0.2"
libsyscall-intercept = "0.1.1"
[lib]
name = "ld_test"
crate-type = ["dylib"]
将lib.rs内容修改为:
use std::cell::Cell;
#[macro_use]
extern crate ctor;
use libsyscall_intercept::{set_hook_fn,InterceptResult};
#[ctor]
fn init_preload() {
unsafe { set_hook_fn(hook) };
}
extern "C" fn hook(
num: i64,
_a0: i64,
_a1: i64,
_a2: i64,
_a3: i64,
_a4: i64,
_a5: i64,
result: *mut i64,
) -> i32 {
// detect and avoid recursive interception
let _guard = match InterceptGuard::try_lock() {
Some(g) => g,
None => return InterceptResult::Forward as i32,
};
if num == libc::SYS_getdents64 || num == libc::SYS_getdents {
unsafe {
*result = -libc::ENOTSUP as i64;
}
return InterceptResult::Hook as i32;
}
InterceptResult::Forward as i32
}
thread_local! {
static INTERCEPTED: Cell<bool> = Cell::new(false);
}
struct InterceptGuard;
impl InterceptGuard {
fn try_lock() -> Option<Self> {
INTERCEPTED.with(|x| {
if x.get() {
None
} else {
x.set(true);
Some(InterceptGuard)
}
})
}
}
impl Drop for InterceptGuard {
fn drop(&mut self) {
INTERCEPTED.with(|x| x.set(false));
}
}
运行cargo b 命令编译生成动态库,并执行如下命令可以看到ls命令被拦截
gzhuflyer@frank-virtual-machine:~/workspace/ld-test$ LD_PRELOAD=./target/debug/libld_test.so ls
ls: 正在读取目录 '.': 不支持的操作
3, gdb调试 LD_PRELOAD动态库
1,下面展示 gdb 动态库调试截图
gdb 运行程序之前,使用 set exec-wrapper env ‘LD_PRELOAD=/home/gzhuflyer/workspace/ld-test/target/debug/libld_test.so’ 设置环境变量,可以看到在执行main函数之前,会先调用执行动态库里面的hook函数,在这个示例中,使用rust ctor库中的宏,其作用和C语言 constructor 关键字类似。
4,写在最后
LD_PRELOAD在安全领域中也很常见,常用户函数和命令的执行,以及在操作系统中设置后门等操作,使用gdb调试时,在程序main函数打端点,并使用 info sharedlibrary,也可以查看到在执行main函数前已经加载了那些库,从而发现系统是否存在被恶意设置拦截等行为。至此,希望读者对LD_PRELOAD有更直观的了解。