模拟固件下的patch和hook(复现)
网络安全 iot 6

附件链接: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(挂钩)攻击

核心目的

绕过程序的密码验证或字符串比对逻辑。

关键点解析

  1. 函数签名一致性

    • 代码中定义了 int strncmp(const char *s1, const char *s2, size_t n)

    • 这个签名必须与 C 标准库(glibc)中的 strncmp 完全一致,这样程序在运行时才会误以为这是真正的系统函数。

  2. 强制返回 0 (return 0;)

    • 这是攻击的灵魂所在。

    • strncmp 的正常行为是:若字符串相同返回 0,不同返回非 0。

    • 通常程序的密码验证逻辑是:if (strncmp(input, password, len) == 0) { // 登录成功 }

    • 通过强制返回 0,无论你输入的密码是什么,程序都会认为“密码正确”。

  3. 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 技术可能会失效:

  1. 静态链接程序:程序没有调用外部动态库,LD_PRELOAD 无效。

  2. 环境限制:无法设置环境变量。

  3. 永久性需求:需要修改后的程序在任何地方都能直接运行,不需要依赖外部 .so 文件。

这时,就需要采用更底层的手段——Patch(打补丁)。即直接修改二进制文件中的机器码,改变程序的执行流程。

0x01 原理分析:寻找“关键跳”

任何密码验证程序,底层逻辑基本都是:

  1. 比对密码(cmp / test)。

  2. 根据比对结果跳转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 (其中 75jne 操作码)

第二步:计算文件偏移 (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:使用 vimxxd (最通用)

  1. 以二进制模式打开文件:

    vim -b hitb_bin100.elf
    
  2. 转换为十六进制显示: 输入 :%!xxd 回车。

  3. 搜索地址偏移(例如 0000550 行): 输入 /0000550 回车查找。 找到对应位置的 75 13

  4. 修改: 将 75 改为 9013 改为 90

  5. 转回二进制: 输入 :%!xxd -r 回车。

  6. 保存退出: 输入 :wq

方法 2:使用 hexedit (CLI 专用工具)

如果觉得 vim 操作繁琐,可以安装轻量级十六进制编辑器:

sudo apt install hexedit
hexedit hitb_bin100.elf
  1. Enter 跳转地址,输入 555(即刚才计算的偏移)。

  2. 直接输入 90 90 覆盖原有内容。

  3. 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}

执行流程分析

  1. 程序比较 "123" 和正确密码,发现不同。

  2. strncmp 返回 1。

  3. 程序执行到原来的 jne 处。

  4. 由于该处已被修改为 nop,CPU 不会执行跳转操作。

  5. 程序继续向下执行,进入了原本只有密码正确才能到达的“Good Job”区域。

0x05 总结:Hook vs Patch

特性

Hook (劫持)

Patch (补丁)

修改对象

内存中的函数指向

磁盘上的二进制文件

文件完整性

保持原文件不变 (Hash 值不变)

破坏原文件 (Hash 值改变)

适用场景

动态链接、不想破坏文件

静态链接、永久破解、去除反调试

隐蔽性

高 (重启失效)

低 (容易被校验发现

模拟固件下的patch和hook(复现)
https://www.kiki1e.top/archives/mo-ni-gu-jian-xia-de-patchhe-hook-fu-xian
作者
kiki1e
发布于
更新于
许可