预加载(狸猫换太子技术)

最后更新于:2023年2月24日 下午

动态链接预加载,可以用于偷梁换柱,英文名为 DLL injection

注意以下都是运行时替换,有时并不是我们想要的方式

首先创建一个 test 目录,在下面创建 Add.h, Add.cpp, fakeAdd.cpp, main.cpp,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Add.h
#pragma once
int Add(int x, int y);
int Sub(int x, int y);

// Add.cpp
#include "Add.h"
int Add(int x, int y) {
return x + y;
}
int Sub(int x, int y) {
return x - y;
}

// fakeAdd.cpp
#include <iostream>
struct Goodbye {
Goodbye() {std::cout << "Fake Add start!\n";}
~Goodbye() {std::cout << "Goodbye!\n";}
} goodbye;
int Add(int x, int y) {
return x * y;
}

// main.cpp
#include <bits/stdc++.h>
#define cerr(x) std::cerr << (#x) << " is " << (x) << '\n'
using LL = long long;
#include "Add.hpp"

int main() {
//freopen("in", "r", stdin);
std::cin.tie(nullptr)->sync_with_stdio(false);
std::cout << "Please input two integers:" << std::endl;
int a, b;
std::cin >> a >> b;
std::cout << "a = " << a << ", b = " << b << std::endl;
cerr(Add(a, b));
cerr(Sub(a, b));
return 0;
}

For Mac

使用模板

1
2
g++-11 preload.cpp -shared -fPIC -o preload.dylib
DYLD_INSERT_LIBRARIES=./preload.dylib DYLD_FORCE_FLAT_NAMESPACE=1 <command>

示例

  • g++-11 Add.cpp -shared -fPIC -o Add.dylib 生成正常的 add 函数
  • g++-11 main.cpp -L. Add.dylib -o main 生成 main(可以通过 otools -L main 来查看是否真的链接上了)
  • ./main 直接运行正常的加法处理程序
  • g++-11 fakeAdd.cpp -fPIC -shared -o fakeAdd.dylib 生成 fake 版本 add 函数
  • DYLD_INSERT_LIBRARIES=./fakeAdd.dylib DYLD_FORCE_FLAT_NAMESPACE=1 ./main 看效果

从这个例子可以看出,fakeAdd.dylib 有更高的优先级

For Linux

使用模板

1
2
g++ preload.cpp -shared -fPIC -o preload.so
LD_PRELOAD=./preload.so <command>

Linux 若链接进可执行程序,必须以 lib 开头以 .so 为后缀。若动态加载,可以不用以 lib 开头以 .so 为后缀

还可以使用 LD_LIBRARY_PATH 环境变量。或者在 VScode 中配置一下,参考 VScode-lldb 文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "a",
"program": "${workspaceFolder}/a.out",
"args": [],
"cwd": "${workspaceFolder}",
"env": {
"LD_LIBRARY_PATH": "${workspaceFolder}/lib/",
"sourceMap": { "/build/time/source/path" : "/current/source/path" },
},
},
]
}

示例

  • g++ Add.cpp -shared -fPIC -o libAdd.so 生成正常的 Add 函数
  • g++ main.cpp -L ./ -lAdd -o main 生成 main(可通过 ldd main 查看链接情况,发现 Add.so 未找到)
  • ./main 直接运行正常的加法处理程序(会报错)
  • g++ fakeAdd.cpp -shared -fPIC -o libFakeAdd.so
  • LD_PRELOAD=./libFakeAdd.so ./main(和上面一样会报错)

有四种处理报错的方式

  • export LD_LIBRARY_PATH=.,什么都不用改,用完后 unset 一下即可(注意 fish 下 unset 无效)
  • ./main 前加 LD_LIBRARY_PATH=.
  • libAdd.so 放到 /usr/lib
  • /etc/ld.so.config 中加入当前路径,再用 ldconfig reload 一下

For windows

挖坑待补

参考资料