Linux下可信环境绕过思路
本文提到的可信是指操作系统验证执行程序是否合法的安全机制。
一般的可信执行实现方式是hook execve函数,在加载时判断程序是否带有签名信息并验证签名,只有签名正确才继续执行。
以下几点是常规绕过思路:
- 系统自带程序替换
- 程序安装包伪造
- ld-linux.so动态链接器绕过
- 动态库劫持绕过
- 加载内核ko模块绕过
系统自带程序替换
系统自带程序(如ls,ps等)可能是基于直接判断文件名和路径,可以尝试用同名程序替换绕过。
程序安装包伪造
发行版Linux(如ubuntu/CentOS等)都有自带的包安装机制。针对这种安装包做签名验证很不方便,有可能做成一个特殊的流程来处理。
绕过思路就是自己构造deb或者rpm包,通过命令行安装。甚至伪造apt/yum源,通过apt/yum安装,绕过可信机制。
ld-linux.so动态链接器绕过
ld动态库是一个很特殊的存在,可以通过ldd查看系统/程序使用的动态链接器。
┌──(root㉿kali)-[~]
└─# ldd /bin/ls
linux-vdso.so.1 (0x00007ffec15f8000)
libselinux.so.1 => /lib/x86_64-linux-gnu/libselinux.so.1 (0x00007f8937a64000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f893788b000)
libpcre2-8.so.0 => /lib/x86_64-linux-gnu/libpcre2-8.so.0 (0x00007f89377ef000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f89377e9000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8937ad2000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f89377c8000)
可以看到动态链接器是/lib64/ld-linux-x86-64.so.2
PS:libdl.so和ld-linux.so有点像,但是这个动态库的作用是让程序在执行过程中可以动态的把so加载到进程空间。 支持的函数有dlopen(),dlsym()等
ld-linux.so这个动态库很有意思,这个动态库是可以直接执行的,也可以用它执行其他程序。
常规的动态库没有程序入口地址(入口地址为0,执行就报段错误)
┌──(root㉿kali)-[~]
└─# readelf -h /root/hook_read.so
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 64 (bytes into file)
Start of section headers: 13664 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 28
Section header string table index: 27
而dl-linux.so不一样,有正确的入口地址,可以直接执行
┌──(root㉿kali)-[~]
└─# readelf -h /lib64/ld-linux-x86-64.so.2
ELF Header:
Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - GNU
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1050
Start of program headers: 64 (bytes into file)
Start of section headers: 201048 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 23
Section header string table index: 22
也可以通过ld-linux.so 执行其他程序,如下:
┌──(root㉿kali)-[~]
└─# /lib64/ld-linux-x86-64.so.2 /usr/bin/pwd
/root
一般程序开启另外一个进程的操作是fork + execve。 但是通过ld-linux.so执行程序的过程是没有调用fork和execve函数的,ld-linux.so启动后把程序加载到自己的进程内存空间并执行。通过这种方式可以绕过execve函数,进而绕过可信执行校验。
动态库劫持
一般程序编译的时候,如果加上’--static’选项,则编译过程中程序依赖的函数会一起编译进ELF文件中,缺点是文件较大,优点是不依赖环境中的库。而默认是编译成依赖动态库的ELF文件,优点是文件小,so复用内存消耗小。缺点是如果跨OS可能会出现动态库版本不一致的兼容性问题。
以cat程序为例:
┌──(root㉿kali)-[~]
└─# ldd /usr/bin/cat
linux-vdso.so.1 (0x00007fffcabaa000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007febd90cd000)
/lib64/ld-linux-x86-64.so.2 (0x00007febd92cc000)
cat程序依赖libc.so.6,在Linux上基本上所有程序都依赖libc.so.6,也即是俗称的c库。c库是对系统调用的封装。 编程时include
比如Linux上有open系统调用,在c库上实现了fopen的函数,是对open的封装,这有一个好处是代码可以跨平台编译。 如果直接用系统调用,无法跨Linux/Windows编译。
执行cat的时候会打开文件,最终会调用open函数,这个open函数是在c库中实现的。
Linux下动态库加载顺序
- rpath (编译时gcc -rpath把动态库搜索路径加入ELF中,ELF头部中会有DT_RPATH动态库搜索路径)
- LD_LIBRARY_PATH
- DT_RUNPATH dynamic section attribute of the binary if present.
- Lookup based on the ldconfig cache file /etc/ld.so.cache –> /etc/ld.so.conf –>
- In the trusted default path /lib, and then /usr/lib.
另外要注意的是LD_PRELOAD环境变量,这个变量不在动态库搜索顺序中讨论,是因为无论程序需不需要,LD_PRELOAD指定的动态库都会优先加载到进程内存空间中。
使用LD_PRELOAD劫持有一个好处,比如劫持c库,如果是通过上述加载顺序进行劫持,那么我们提供的c库就得是完整的c库,程序依赖的函数都得实现。
而LD_PRELOAD可以劫持函数,而不关心所有的库函数。如下所示,劫持close函数来实现绕过可信环境检测。
┌──(root㉿kali)-[~]
└─# cat hook_close.c
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
typedef int (*selfdef_fun)(int);
int close(int i)
{
printf("hock close function \n");
void *handle = dlopen("/lib/x86_64-linux-gnu/libc.so.6", RTLD_LAZY);
selfdef_fun libc_close = dlsym(handle, "close");
return libc_close(i);
}
┌──(root㉿kali)-[~]
└─# gcc hook_close.c -fPIC -Wall --shared -ldl -o hook_close.so
┌──(root㉿kali)-[~]
└─# export LD_PRELOAD="/root/hook_close.so"
┌──(root㉿kali)-[~]
└─# cat hook_close.c
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>
typedef int (*selfdef_fun)(int);
int close(int i)
{
printf("hock close function \n");
void *handle = dlopen("/lib/x86_64-linux-gnu/libc.so.6", RTLD_LAZY);
selfdef_fun libc_close = dlsym(handle, "close");
return libc_close(i);
}
hock close function
┌──(root㉿kali)-[~]
└─#
参考文献
[1] https://en.wikipedia.org/wiki/Rpath