网站首页 > 技术文章 正文
原理?
正常情况下, Linux 动态加载器ld-linux(见 man 手册 ld-linux(8)) 会搜寻并装载程序所需的共享链接库文件, 而LD_PRELOAD是一个可选的环境变量, 包含一个或多个指向共享链接库文件的路径. 加载器会先于 C 语言运行库之前载入LD_PRELOAD指定的共享链接库,也就是所谓的预装载 (preload)。
预装载意味着会它的函数会比其他库文件中的同名函数先于调用, 也就使得库函数可以被阻截或替换掉. 多个共享链接库文件的路径可以用冒号或空格进行区分. 显然不会受到LD_PRELOAD影响的也就只有那些静态链接的程序了.
当然为避免用于恶意攻击, 在ruid != euid的情况下加载器是不会使用LD_PRELOAD进行预装载的.
更多阅读: https://blog.fpmurphy.com/2012/09/all-about-ld_preload.html#ixzz569cbyze4
例题?
下面以 2014 年Hack In The Box Amsterdam: Bin 100为例. 题目下载链接: hitb_bin100.elf
这是一个 64 位的 ELF 文件. 运行结果如下图所示:
程序似乎在一直打印着一些句子. 并且没有停止下来的迹象. 我们就用 IDA 打开来看一下. 首先按下Shift+F12查找字符串.
显然, 除开一直在打印的句子外, 我们发现了一些有趣的字符串:
.rodata:0000000000400A53 00000006 C KEY:
.rodata:0000000000400A5F 0000001F C OK YOU WIN. HERE'S YOUR FLAG:
我们根据OK YOU WIN. HERE'S YOUR FLAG:的交叉引用来到关键代码处 (我删去了一些不必要的代码).
int __cdecl main(int argc, const char **argv, const char **envp)
{
qmemcpy(v23, &unk_400A7E, sizeof(v23));
v3 = v22;
for ( i = 9LL; i; --i )
{
*(_DWORD *)v3 = 0;
v3 += 4;
}
v20 = 0x31337;
v21 = time(0LL);
do
{
v11 = 0LL;
do
{
v5 = 0LL;
v6 = time(0LL);
srand(233811181 - v21 + v6); // 初始化随机数种子
v7 = v22[v11];
v22[v11] = rand() ^ v7; // 伪随机数
v8 = (&funny)[8 * v11];
while ( v5 < strlen(v8) )
{
v9 = v8[v5];
if ( (_BYTE)v9 == 105 )
{
v24[(signed int)v5] = 105;
}
else
{
if ( (_DWORD)v5 && v8[v5 - 1] != 32 )
v10 = __ctype_toupper_loc(); // 大写
else
v10 = __ctype_tolower_loc(); // 小写
v24[(signed int)v5] = (*v10)[v9];
}
++v5;
}
v24[(signed int)v5] = 0;
++v11;
__printf_chk(1LL, " 鈾%80s 鈾玕n", v24); // 乱码的其实是一个音符
sleep(1u);
}
while ( v11 != 36 );
--v20;
}
while ( v20 );
v13 = v22; // key存储在v22数组内
__printf_chk(1LL, "KEY: ", v12);
do
{
v14 = (unsigned __int8)*v13++;
__printf_chk(1LL, "%02x ", v14); // 输出key
}
while ( v13 != v23 );
v15 = 0LL;
putchar(10);
__printf_chk(1LL, "OK YOU WIN. HERE'S YOUR FLAG: ", v16);
do
{
v17 = v23[v15] ^ v22[v15]; // 跟key的值有异或
++v15;
putchar(v17); // 输出flag
}
while ( v15 != 36 );
putchar(10); // 输出换行
result = 0;
return result;
}
整个的代码流程主要就是在不断地循环输出funny里的句子, 满足循环条件后输出key, 并用key进行异或得到flag的值.
但我们可以发现, 整个循环的次数相对来说是比较少的. 所以我们可以采用一些方法, 让循环进行得更快一些. 比如说我手动 patch 一下, 不让程序输出字符串 (实际上printf的耗时是相当多的), 其次就是使用LD_PRELOAD使得程序的sleep()失效. 可以很明显地节省时间.
手动 patch 的过程比较简单. 我们可以找到代码位置, 然后用一些十六进制编辑器进行修改. 当然我们也可以使用IDA来进行 patch 工作.
.text:00000000004007B7 call ___printf_chk
.text:00000000004007BC xor eax, eax
将光标点在call ___printf_chk上, 然后选择菜单Edit->Patch Program->Assemble(当然你可以使用其他 patch 方式. 效果都一样). 然后将其修改为nop(0x90), 如下图所示
将4007B7到4007BD之间的汇编代码全部修改为nop即可. 然后选择菜单Edit->Patch Program->Apply patches to input file. 当然最好做一个备份 (即勾选Create a backup), 然后点击 OK 即可 (我重命名为了patched.elf, 下载链接: patched.elf).
现在进入LD_PRELOAD部分. 这里我们简单编写一下 c 代码, 下载链接: time.c
static int t = 0x31337;
void sleep(int sec) {
t += sec;
}
int time() {
return t;
}
然后使用命令gcc --shared time.c -o time.so生成动态链接文件. 当然也给出了下载链接: time.so
然后打开 linux 终端, 运行命令: LD_PRELOAD=./time.so ./patched.elf
过一会, 你就能听到 CPU 疯狂运转的声音, 然后很快就出来了 flag.
- 上一篇: 常用网络协议整理笔记(一)
- 下一篇: C语言100题集合023-输入月份号并输出英文月份名
猜你喜欢
- 2025-01-08 嵌入式中,日志调试法的一些规则!
- 2025-01-08 一行代码改进:Logtail的多行日志采集性能提升7倍的奥秘
- 2025-01-08 嵌入式大杂烩周记 第 7 期:zlog
- 2025-01-08 C语言总结:C语言字符串练习题(十二种习题示例)
- 2025-01-08 C语言100题集合027-二维数组的经典案例,非常重要
- 2025-01-08 一个例子让你看清线程调度的随机性
- 2025-01-08 C++17:结构化绑定
- 2025-01-08 C语言中main函数详解
- 2025-01-08 64TB硬盘容量测试程序(C++)
- 2025-01-08 使用CyberRT写第一个代码
- 02-21走进git时代, 你该怎么玩?_gits
- 02-21GitHub是什么?它可不仅仅是云中的Git版本控制器
- 02-21Git常用操作总结_git基本用法
- 02-21为什么互联网巨头使用Git而放弃SVN?(含核心命令与原理)
- 02-21Git 高级用法,喜欢就拿去用_git基本用法
- 02-21Git常用命令和Git团队使用规范指南
- 02-21总结几个常用的Git命令的使用方法
- 02-21Git工作原理和常用指令_git原理详解
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)