附件链接:https://pan.baidu.com/s/1gdMPHj1KBKyZqmhDBg2j6w 提取码: zab8
LD_PRELOAD
完整源代码 (hook.c)
/* * 文件名: hook.c
* * 编译命令 (Compile Command):
* gcc -g -shared -fPIC hook.c -o hook.so
* * 运行命令 (Run Command):
* LD_PRELOAD=./hook.so ./目标程序名
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* * 重定义 strncmp 函数
* 原型:int strncmp(const char *s1, const char *s2, size_t n);
* 作用:劫持系统原本的字符串比较函数
*/
int strncmp(const char *s1, const char *s2, size_t n) {
// 移除 LD_PRELOAD 环境变量
// 防止后续可能调用的子进程或其他系统调用陷入死循环或重复 Hook
// 这一步在某些简单的 CTF 或 demo 中不是必须的,但属于一种“清理环境”的操作
unsetenv("LD_PRELOAD");
// 打印提示信息,证明 Hook 成功
// \033[32m ... \033[0m 是 ANSI 转义码,用于在终端输出绿色文字
printf("\033[32mSuccess hook strncmp function! \n\033[0m");
// 【关键逻辑】
// 无论 s1 和 s2 的内容是什么,强制返回 0。
// 在 C 语言标准库中,strncmp 返回 0 表示两个字符串“相等”。
// 这意味着无论输入什么密码,程序都会认为验证通过。
return 0;
}
代码原理分析与复现指南
1. 代码核心逻辑分析
这段代码利用了 Linux 动态链接机制中的 LD_PRELOAD 特性,实现了一种典型的 Hook(挂钩)攻击。
核心目的
绕过程序的密码验证或字符串比对逻辑。
关键点解析
函数签名一致性
代码中定义了
int strncmp(const char *s1, const char *s2, size_t n)。这个签名必须与 C 标准库(glibc)中的
strncmp完全一致,这样程序在运行时才会误以为这是真正的系统函数。
强制返回 0 (
return 0;)这是攻击的灵魂所在。
strncmp的正常行为是:若字符串相同返回 0,不同返回非 0。通常程序的密码验证逻辑是:
if (strncmp(input, password, len) == 0) { // 登录成功 }。通过强制返回 0,无论你输入的密码是什么,程序都会认为“密码正确”。
unsetenv("LD_PRELOAD")这行代码的作用是移除环境变量。
如果不移除,假如被 Hook 的程序内部通过
system()或exec()启动了其他子进程,LD_PRELOAD会遗传给子进程,导致子进程也被 Hook,可能引发意想不到的崩溃或死循环。移除它可以让环境更“干净”。
2. 如何复现(Step-by-Step)
第一步:编译 Hook 库
使用 gcc 将上方的 hook.c 编译为动态链接库 (.so)。
# -shared: 生成共享库
# -fPIC: 生成位置无关代码 (Position Independent Code),动态库必须选项
gcc -g -shared -fPIC hook.c -o hook.so
注意位数匹配: 如果你的目标程序
pwn_test是 32 位的,你需要编译 32 位的 hook.so:gcc -m32 -g -shared -fPIC hook.c -o hook.so(如果报错缺少头文件,可能需要安装gcc-multilib)
第二步:实施注入(运行)
在运行目标程序之前,设置环境变量 LD_PRELOAD 指向刚才生成的 hook.so。

gdb下断点查看内存和gdb


hitb_bin100

前言
上一题通过 LD_PRELOAD 技术绕过了程序验证。但在某些情况下,Hook 技术可能会失效:
静态链接程序:程序没有调用外部动态库,
LD_PRELOAD无效。环境限制:无法设置环境变量。
永久性需求:需要修改后的程序在任何地方都能直接运行,不需要依赖外部
.so文件。
这时,就需要采用更底层的手段——Patch(打补丁)。即直接修改二进制文件中的机器码,改变程序的执行流程。
0x01 原理分析:寻找“关键跳”
任何密码验证程序,底层逻辑基本都是:
比对密码(
cmp/test)。根据比对结果跳转(
je/jne/jz/jnz)。结果一样 -> 跳到“成功”代码块。
结果不一样 -> 跳到“失败”代码块。
目标非常明确:找到这个控制跳转的指令并进行修改。
1. 静态分析 (IDA Pro / Objdump)
假设使用 IDA Pro 打开 hitb_bin100.elf(或者使用 Linux 自带的 objdump -d)。
可以看到类似这样的汇编逻辑(伪代码):
.text:0804854B call _strncmp ; 调用比较函数
.text:08048550 add esp, 0Ch
.text:08048553 test eax, eax ; 检查 strncmp 的返回值 (eax)
.text:08048555 jne short loc_Fail ; [关键跳] 如果 eax != 0 (密码错误),跳转到失败处
.text:08048557 ; --- 这里是成功的分支 ---
.text:08048557 mov eax, offset aGoodJob ; "Good Job!"
.text:0804855C call _printf
...
.text:0804856A loc_Fail: ; --- 这里是失败的分支 ---
.text:0804856A mov eax, offset aPasswordError ; "Password Error!"
分析:
0x08048555处的jne(Jump if Not Equal) 是决定程序执行流程的关键指令。如果密码错误(
strncmp返回非 0),jne会生效,跳过“Good Job”,直接去打印“Password Error”。攻击思路:修改这个
jne指令,使程序无论比较结果如何都不跳转,直接顺序执行,从而打印 "Good Job"!
0x02 实施 Patch (手术开始)
需要修改二进制文件中的机器码。
方案 A:Nop 掉跳转 (最常用)
可以将 jne 指令替换为 nop (No Operation,空指令)。nop 在 x86 架构下的机器码是 0x90。 这意味着程序执行到这里时,不进行任何操作,直接继续执行下一条指令。
原始指令:
75 13(假设jne的机器码是 75,偏移量 13)修改目标:
90 90(两个字节都换成 Nop)
方案 B:修改跳转逻辑
如果逻辑是“相等则跳转到成功”,可以把 je (74) 改成 jmp (EB, 无条件跳转) 或者 jne (75, 不相等则跳转)。
0x03 Ubuntu 环境下的实战操作
在 Ubuntu 终端中,我们可以只用命令行工具完成全套流程。
第一步:定位特征指令 (Objdump)
首先,我们需要找到 strncmp 调用附近的跳转指令地址。
# -d 反汇编, -M intel 使用 Intel 语法, grep 搜索 strncmp 上下文
$ objdump -d -M intel hitb_bin100.elf | grep -A 5 "call.*strncmp"
输出示例:
804854b: e8 b0 fe ff ff call 8048400 <strncmp@plt>
8048550: 83 c4 0c add esp,0xc
8048553: 85 c0 test eax,eax
8048555: 75 13 jne 804856a <main+0x6d> <-- 目标在这里
关键信息:
虚拟内存地址:
8048555机器码:
75 13(其中75是jne操作码)
第二步:计算文件偏移 (File Offset)
我们在 objdump 中看到的是内存地址(Virtual Address),修改文件需要知道它在磁盘文件中的位置(File Offset)。
使用 readelf 查看段表信息:
$ readelf -S hitb_bin100.elf | grep .text
[13] .text PROGBITS 080483d0 0003d0 0001bc 00 AX 0 0 16
计算公式: 文件偏移 = 目标内存地址 - 段内存起始地址 + 段文件起始偏移
假设:
目标地址 (Target Addr) =
0x08048555.text 起始地址 (Addr) =
0x080483d0.text 文件偏移 (Off) =
0x0003d0
计算:0x8048555 - 0x80483d0 + 0x3d0 = 0x555
技巧:对于简单的 ELF 文件,代码段通常连续加载,有时文件偏移和内存地址的后三位是一样的(如果对齐得当)。这里
0x555正好对应内存地址的后三位。
第三步:实施修改 (Patch)
方法 1:使用 vim 和 xxd (最通用)
以二进制模式打开文件:
vim -b hitb_bin100.elf转换为十六进制显示: 输入
:%!xxd回车。搜索地址偏移(例如
0000550行): 输入/0000550回车查找。 找到对应位置的75 13。修改: 将
75改为90,13改为90。转回二进制: 输入
:%!xxd -r回车。保存退出: 输入
:wq。
方法 2:使用 hexedit (CLI 专用工具)
如果觉得 vim 操作繁琐,可以安装轻量级十六进制编辑器:
sudo apt install hexedit
hexedit hitb_bin100.elf
按
Enter跳转地址,输入555(即刚才计算的偏移)。直接输入
90 90覆盖原有内容。按
Ctrl+X保存退出。
方法 3:使用 printf + dd (脚本化/自动化)
这是最“黑客”的方式,一条命令搞定,适合批量处理。
# 将 0x9090 (NOP NOP) 写入到文件偏移 0x555 处
# conv=notrunc 保证不截断文件,只是覆盖
printf '\x90\x90' | dd of=hitb_bin100.elf bs=1 seek=$((0x555)) count=2 conv=notrunc
0x04 效果验证
修改保存为 hitb_bin100_patched.elf。 赋予执行权限并运行:
$chmod +x hitb_bin100_patched.elf$ ./hitb_bin100_patched.elf
Input password: (随便输入,比如 123)
Good Job! The flag is HITB{D0_y0u_kn0w_th1s_key}
执行流程分析:
程序比较 "123" 和正确密码,发现不同。
strncmp返回 1。程序执行到原来的
jne处。由于该处已被修改为
nop,CPU 不会执行跳转操作。程序继续向下执行,进入了原本只有密码正确才能到达的“Good Job”区域。