适用于Linux的CreateRemoteThread

Mitsuha带来了了新的潮流 WinAPI具有CreateRemoteThread函数,使您可以在另一个进程的地址空间中启动一个新线程。 它可以用于各种DLL注入,既可以用于不良目的(游戏作弊,密码盗窃等),也可以用于即时修复正在运行的程序中的错误,也可以将插件添加到非DLL注入的地方。提供。


通常,此功能具有可疑的应用程序实用程序,因此Linux没有现成的CreateRemoteThread类似物也就不足为奇了。 但是,我想知道如何实现它。 研究主题变成了一次很好的冒险。


我将详细讨论如何在ELF规范的帮助下,了解一些x86_64体系结构和Linux系统调用的知识,以及编写自己的小调试器,该调试器可以在已经运行且正在运行的过程中加载和执行任意代码。


理解本文需要具备有关Linux系统编程的基础知识:C语言,基于Java语言编写和调试程序,了解计算机中计算机代码和内存的作用,系统调用的概念,熟悉主要库以及阅读文档。



结果,我能够在Gnome控制中心“添加”预览密码的功能:


Gnome控制中心的注射演示


主要思想


如果要求中没有关于将代码加载到已经运行的进程中的子句,则解决方案将非常简单:LD_PRELOAD。 此环境变量允许在应用程序中加载任意库。 在共享库中,您可以定义在加载库时执行的构造函数


LD_PRELOAD和构造函数一起允许使用动态加载程序在任何进程中执行任意代码。 这是经常用于调试的相对知名的功能。 例如,您可以使用该应用程序下载自己的库,该库定义了函数malloc()和free(),这可以帮助捕获内存泄漏。


不幸的是,LD_PRELOAD仅在进程启动时起作用。 它不能用于将库加载到已经运行的进程中。 有一个dlopen()函数可以在进程运行时加载库,但是显然,进程本身必须调用它才能加载插件。


关于静态可执行文件

LD_PRELOAD仅适用于使用动态加载程序的程序。 如果程序是使用-static开关构建的,则它将包含所有必需的库。 在这种情况下,库中依赖项的解析是在构建时执行的,程序通常尚未准备好,并且在运行时无法在汇编后动态加载库。

在静态组装的程序中,您可以在运行时嵌入代码,但这应该以稍微不同的方式完成。 而且这也不是完全安全的,因为程序可能还没有准备好迎接这种转变。

通常,没有现成的便捷解决方案,您必须编写自行车。 否则,您将不会阅读此文本:)


从概念上讲,要强制其他人的进程执行某种代码,您需要执行以下操作:


  1. 在目标过程中获得控制权。
  2. 将代码加载到目标进程的内存中。
  3. 准备下载的代码以在目标进程中执行。
  4. 通过目标进程组织执行下载的代码。

走吧...


在过程中获得控制


首先,我们需要使目标流程服从我们的意愿。 毕竟,通常进程仅执行自己的代码,已加载的库的代码或JIT编译的结果。 但是肯定不是我们的代码。


一种选择是在过程中使用某种漏洞,使您可以控制自己。 教程中的一个经典示例:缓冲区溢出,允许重写堆栈上的返回地址。 这很有趣,有时甚至可以,但是不适合一般情况。


我们将使用另一种诚实的方式来获得控制权: 调试系统调用 。 交互式调试器可以完美地停止第三方进程,评估表达式以及许多其他事情。 他们可以-我们可以。


在Linux上,主要的调试系统调用是ptrace() 。 它使您可以连接到流程,检查其状态并控制其执行进度。 ptrace()本身已被很好地记录了下来,但是其用法的详细信息只有在实践中才清楚。


将代码加载到进程存储器中


在缓冲区溢出的情况下,有效载荷( shell代码 )通常包含在使同一缓冲区溢出的内容中。 使用调试器时,可以将必要的代码直接写入过程存储器。 在WinAPI中,有一个特殊的功能WriteProcessMemory。 为此,Linux遵循UNIX方式:对于系统中的每个进程,都有一个/ proc / $ pid / mem文件显示该进程的内存。 可以使用通常的输入输出将某些内容写入过程存储器。


准备执行代码


仅将代码写入内存是不够的。 仍然需要将其写入可执行内存 。 在通过漏洞进行记录的情况下,这样做并不容易,但是由于我们可以完全控制目标进程,因此为我们自己找到或分配“正确的”内存将不是问题。


准备的另一个重要点是外壳代码本身。 在其中,我们可能希望使用库中的某些功能,例如输入输出,图形基元等。 但是,我们必须写下光秃秃的机器代码,它本身不知道库中所有这些很酷的函数的地址。 你从哪里得到的?


为了简化操作系统的寿命并使恶意代码的寿命复杂化,库通常不使用固定地址(并且包含所谓的位置无关代码 )。 因此,无法猜测地址。


当进程正常启动时,执行重定位加载器负责确定库的确切地址。 但是,他一开始只完成一次。 如果该进程允许动态加载库,则其中有一个动态加载器 ,可以在进程运行时执行相同的操作。 但是,动态加载程序的地址也不固定。


通常,对于库,有四个选项:


  • 完全不使用库,在干净的系统调用中执行所有操作
  • 将所有必要库的副本放入外壳代码中
  • 自己完成动态加载程序的工作
  • 找到一个动态的引导程序并使其加载我们的库

我们将选择后者,因为我们需要这些库,并编写了很长时间的完整Bootloader。 这不是最秘密的方法,也不是最有趣的方法,而是最简单,最强大和最可靠的方法。


控制权转移到代码


ptrace()允许您更改处理器寄存器,因此将控制转移到已加载并准备好的代码应该没有问题:只需将我们的代码地址写到%rip寄存器中-瞧! 但是,实际上,一切并非如此简单。 困难与以下事实有关:调试过的进程实际上并没有消失,并且它还具有某种已经执行并且将继续执行的代码。


解决方案草图


总计,我们将在第三方流程中实现我们的流程,如下所示:


  1. 我们已连接到目标进程以进行调试。
  2. 我们在内存中找到了必要的库:
    • libdl-加载新库
    • libpthread-启动新线程
  3. 我们在库中找到必要的功能:
    • libdl:dlopen(),dlsym()
    • libpthread:pthread_create(),pthread_detach()
  4. 我们将shell代码引入目标进程的内存中:


     void shellcode(void) { void *payload = dlopen("/path/to/payload.so", RTLD_LAZY); void *entry = dlsym(payload, "entry_point"); pthread_t thread; pthread_create(&thread, NULL, entry, NULL); pthread_detach(thread); } 

  5. 我们给出要实现的shell代码。

结果,库将为我们做正确的事情:它们将向我们的库加载内存中所需的代码,并启动执行该代码的新线程。


局限性


上述方法施加了某些限制:


  • 引导加载程序必须具有足够的特权才能调试目标进程。
  • 该过程应使用libdl(已准备好动态加载模块)。
  • 该进程应使用libpthread(可用于多线程)。
  • 不支持静态应用程序。

此外,我个人也懒得为所有架构提供支持,因此我们将限于x86_64。 (即使是32位x86也会更复杂。)


如您所见,所有这些都结束了对恶意目标的秘密使用。 但是,该任务仍然保留了研究兴趣,甚至为工业应用留下了微弱的机会。


题外话:关于使用libdl和libpthread


经验丰富的读者可能会想:如果__libc_dlopen_mode()和__libc_dlsym()内部函数已经内置在glibc中,为什么libdl只是它们的包装,为什么需要libdl? 同样,如果可以使用clone()系统调用轻松创建新线程,为什么需要libpthread?


确实,在互联网上,如何使用它们的例子远不止一个:



在流行的黑客文献中甚至提到了它们:


  • 学习Linux二进制分析
  • 内存取证的艺术

那为什么不呢? 好吧,至少因为我们不是在编写适合解决方案的恶意代码,而该解决方案忽略了90%的检查,占用的空间减少了20倍,但在80%的情况下仍然有效。 另外,我想用自己的双手尝试一切。


实际上,对于glibc, 不需要 libdl来加载库。 进程使用它表明它已经准备好进行动态代码加载。 尽管如此,从原则上讲,您可以拒绝使用libdl(因为以后我们仍然需要寻找glibc)。


为什么要在glibc中使用dlopen()?

这本身就是一个有趣的问题。 简短答案:实施细节。

重点是名称服务开关 (NSS)-glibc的一部分,它提供各种名称的转换:机器,协议,用户,邮件服务器等的名称。由她负责诸如getaddrinfo()之类的功能,以通过以下方式获取IP地址:域名和getpwuid()即可通过其数字标识符获取有关用户的信息。

NSS具有模块化架构,并且模块可以动态加载。 实际上,为此,glibc还需要用于动态加载库的机制。 这就是为什么当您尝试在静态组装的应用程序中使用getaddrinfo()时,链接器会显示“难以理解”的警告:
 /tmp/build/socket.o:在函数Socket :: bind中:
 socket.o :(。text + 0x374):警告:在静态链接中使用'getaddrinfo'
应用程序在运行时需要glibc版本的共享库
用于链接

对于线程,线程通常不仅是堆栈和可执行代码,而且还是存储在线程本地存储 (TLS)中的全局数据。 正确初始化新线程需要OS内核,二进制代码加载器和编程语言运行时的协同操作。 因此,只需简单地调用clone()就可以创建一个流,该流可以写入“ Hello world!”文件,但是对于需要访问TLS和应用程序程序员所看不到的其他有趣内容的更复杂的代码,这可能不起作用。


与多线程有关的另一点是单线程进程。 如果我们在一个不被认为是多线程的进程中创建一个新线程会怎样? 对,含糊的行为。 实际上,在此过程中,线程之间没有工作同步,这迟早会导致数据损坏。 如果我们要求应用程序使用libpthread,则可以确保它已准备好在多线程环境中工作(至少应该已经准备好)。


步骤1.连接到流程


首先,我们需要连接到目标进程进行调试,然后再与它断开连接。 这是ptrace ()系统调用进入的地方。


首次接触ptrace()


在ptrace()的文档中,您可以找到几乎所有必要的信息:


  装卸
       可以使用调用将线程附加到跟踪器

            ptrace(PTRACE_ATTACH,pid,0,0);


            ptrace(PTRACE_SEIZE,PID,0,PTRACE_O_flags);

        PTRACE_ATTACH将SIGSTOP发送到此线程。 如果追踪者想要这个
        SIGSTOP无效,需要抑制它。 请注意,如果
       其他信号在附加期间同时发送到该线程,
       示踪剂可能会看到示踪剂与其他信号一起进入信号传递停止
        nal首先! 通常的做法是重新注入这些信号,直到
       看到SIGSTOP,然后抑制SIGSTOP注入。 设计错误
       这是ptrace附加和并发传递的SIGSTOP可能
       竞赛和并发的SIGSTOP可能会丢失。

因此,第一步是使用PTRACE_ATTACH:


 int ptrace_attach(pid_t pid) { /*     */ if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) return -1; /*    */ if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; return 0; } 

在ptrace()之后,目标进程还没有准备好进行调试。 我们已经连接到它,但是为了交互式研究过程的状态,必须停止它。 ptrace()向进程发送SIGSTOP信号,但是我们仍然需要等待直到进程真正停止。


要等待,请使用waitpid ()系统调用。 同时,值得注意的是几种有趣的边界情况。 首先,该过程可能简单地结束或死掉而没有收到SIGSTOP。 在这种情况下,我们无能为力。 其次,某些其他信号可能会事先发送到该过程。 在这种情况下,我们应该让进程处理它(使用PTRACE_CONT),而我们自己,继续等待我们的SIGSTOP:


 static int wait_for_process_stop(pid_t pid, int expected_signal) { for (;;) { int status = 0; /* ,    -  */ if (waitpid(pid, &status, 0) < 0) return -1; /*      —   */ if (WIFSIGNALED(status) || WIFEXITED(status)) return -1; /*   ,     */ if (WIFSTOPPED(status)) { /* *  WSTOPSIG()   , *   ptrace()   *     . */ int stop_signal = status >> 8; /*    ,    */ if (stop_signal == expected_signal) break; /*        */ if (ptrace(PTRACE_CONT, pid, 0, stop_signal) < 0) return -1; continue; } /*   —   */ return -1; } return 0; } 

流程断开


停止调试过程非常简单:只需使用PTRACE_DETACH:


 int ptrace_detach(pid_t pid) { if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) return -1; return 0; } 

严格来说,并非总是必须明确禁用调试器。 调试器进程结束时,它将自动与所有调试的进程断开连接,并且如果进程被ptrace()停止,则该进程将恢复。 但是,如果调试器使用SIGSTOP信号而不使用ptrace()明确停止了调试的进程,则没有相应的SIGCONT或PTRACE_DETACH信号也不会唤醒它。 因此,最好从文化上脱离流程。


Ptrace_scope设置


调试器可以完全控制要调试的进程。 如果任何人都可以调试任何东西,那么恶意代码将是什么呢! 显然,交互式调试是一项相当具体的活动,通常仅对开发人员才必要。 在系统正常运行期间,大多数情况下无需调试过程。


由于这些原因,出于安全原因,系统通常默认情况下禁用调试任何进程的功能。 Yama安全模块对此负责,通过文件/ proc / sys / sys / kernel / yama / ptrace_scope进行管理。 它提供了四种行为:


  • 0-用户可以调试他启动的任何进程
  • 1-默认模式,仅调试器启动的进程可以被调试
  • 2-只有root系统管理员才能调试进程
  • 3-所有人都禁止调试,在系统重新启动之前,该模式不会关闭

显然,出于我们的目的,必须能够调试在调试器之前启动的进程,因此对于实验,您需要通过将0写入特殊的ptrace_scope文件(需要管理员权限)来将系统切换到开发模式:


 $ sudo sh -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope' 

或以管理员身份运行调试器:


 $ sudo ./inject-thread ... 

第一步结果


结果,在第一步中,我们能够作为调试器连接到目标进程,然后与它断开连接。


目标进程将停止,我们可以确保操作系统确实将我们视为调试器:


 $ sudo ./inject-thread --target $(pgrep docker) $ cat /proc/$(pgrep docker)/status | head Name: docker State: t (tracing stop) <---    Tgid: 31330 Ngid: 0 Pid: 31330 PPid: 1 TracerPid: 2789 <--- PID   Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 64 $ ps a | grep [2]789 2789 pts/5 S+ 0:00 ./inject-thread --target 31330 

步骤2.在内存中搜索库


下一步更简单:您需要在目标进程的内存中找到具有我们所需功能的库。 但是有很多内存,从哪里开始寻找以及究竟是什么?


文件/ proc / $ pid / maps


一个特殊的文件将帮助我们解决这个问题,内核通过该文件告知进程在内存中的位置和位置。 如您所知 ,在每个进程的/ proc目录中都有一个子目录。 其中有一个描述过程存储卡的文件


 $ cat / proc / self / maps
 00400000-0040c000 r-xp 00000000 fe:01 1044592 / bin / cat
 0060b000-0060c000 r-p 0000b000 fe:01 1044592 / bin / cat
 0060c000-0060d000 rw-p 0000c000 fe:01 1044592 / bin / cat
 013d5000-013f6000 rw-p 00000000 00:00 0 [堆]
 7f9920bd1000-7f9920d72000 r-xp 00000000 fe:01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7f9920d72000-7f9920f72000 --- p 001a1000 fe:01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7f9920f72000-7f9920f76000 r-p 001a1000 fe:01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7f9920f76000-7f9920f78000 rw-p 001a5000 fe:01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7fc3f8381000-7fc3f8385000 rw-p 00000000 00:00 0
 7fc3f8385000-7fc3f83a6000 r-xp 00000000 fe:01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f83ec000-7fc3f840e000 rw-p 00000000 00:00 0
 7fc3f840e000-7fc3f8597000 r-p 00000000 fe:01 657286 / usr / lib / locale / locale-archive
 7fc3f8597000-7fc3f859a000 rw-p 00000000 00:00 0
 7fc3f85a3000-7fc3f85a5000 rw-p 00000000 00:00 0
 7fc3f85a5000-7fc3f85a6000 r-p 00020000 fe:01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f85a6000-7fc3f85a7000 rw-p 00021000 fe:01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f85a7000-7fc3f85a8000 rw-p 00000000 00:00 0
 7ffdb6f0e000-7ffdb6f2f000 rw-p 00000000 00:00 0 [堆栈]
 7ffdb6f7f000-7ffdb6f81000 r-xp 00000000 00:00 0 [vdso]
 7ffdb6f81000-7ffdb6f83000 r-p 00000000 00:00 0 [vvar]
 ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

该文件的内容由操作系统内核根据内部结构动态生成,这些内部结构描述了我们感兴趣的进程的内存区域,并包含以下信息:


  • 分配给该区域的地址范围
  • 对该区域的访问权
    • r/- :读
    • w/- :写
    • x/- :执行
    • p/s :与其他进程共享内存
  • 文件偏移量(如果有)
  • 显示文件所在设备的代码
  • 文件索引号(如果有)
  • 显示文件的路径(如果有)

某些内存区域被映射到文件上:当进程读取此类内存时,实际上是从相应文件中以特定偏移量读取数据。 如果您可以写入区域,则内存更改仅对进程本身可见( 写时复制机制, p模式为私有),或与磁盘同步( s模式为共享)。


其他区域是匿名的 -此内存不对应任何文件。 操作系统只是为进程提供了它使用的一块物理内存。 例如,此类区域用于“常规”进程内存:堆栈和堆。 匿名区域可以是一个进程专用的,也可以在多个进程之间共享共享内存机制)。


此外,内存中还有几个特殊区域,它们用伪名[vdso]和[vsyscall]标记。 它们用于优化某些系统调用。


我们对显示库文件内容的区域感兴趣。 如果我们读取存储卡并按显示文件的名称过滤掉其中的条目,那么我们将找到所需的库占用的所有地址。 特别简化了存储卡的格式,以便于程序处理,并且使用scanf()系列的功能可以轻松理解存储卡的格式:


 static bool read_proc_line(const char *line, const char *library, struct memory_region *region) { unsigned long vaddr_low = 0; unsigned long vaddr_high = 0; char read = 0; char write = 0; char execute = 0; int path_offset = 0; /*    /proc/$pid/maps */ sscanf(line, "%lx-%lx %c%c%c%*c %*lx %*x:%*x %*d %n", &vaddr_low, &vaddr_high, &read, &write, &execute, &path_offset); /* ,       */ if (!strstr(line + path_offset, library)) return false; /*           */ if (region) { region->vaddr_low = vaddr_low; region->vaddr_high = vaddr_high; region->readable = (read == 'r'); region->writeable = (write == 'w'); region->executable = (execute == 'x'); region->content = NULL; } return true; } 


, libc-2.19.so, :


libc-2.19.so中的孔


2 - ? 51? ? ?


, , .


, , . , , , (, , ).


, ( 4 ). , .


, . — — . 2 — , ( x86_64 4 , 2 , 1 ). .



, :


  • libdl: dlopen() dlsym()
  • libpthread: pthread_create() pthread_detach()

, , . Linux ( address space layout randomization , ASLR). (- , ), — - .


, , , /proc/$pid/maps. , .


3. ELF-


, , , .


:


 $ nm -D /lib/x86_64-linux-gnu/libdl-2.19.so | grep dlopen 0000000000001090 T dlopen 

nm . .


- , nm , . , dlsym().



— ELF-, . procfs. UNIX way, /proc/$pid/mem , — ( /proc/$pid/maps).


Linux mmap(), ( , ). :


 static int map_region(pid_t pid, struct memory_region *region) { size_t length = region->vaddr_high - region->vaddr_low; off_t offset = region->vaddr_low; char path[32] = {0}; snprintf(path, sizeof(path), "/proc/%d/mem", pid); /*     */ int fd = open(path, O_RDONLY); if (fd < 0) goto error; /*      */ void *buffer = malloc(length); if (!buffer) goto error_close_file; /*   */ if (read_region(fd, offset, buffer, length) < 0) goto error_free_buffer; region->content = buffer; close(fd); return 0; error_free_buffer: free(buffer); error_close_file: close(fd); error: return -1; } static int read_region(int fd, off_t offset, void *buffer, size_t length) { /*      */ if (lseek(fd, offset, SEEK_SET) < 0) return -1; size_t remaining = length; char *ptr = buffer; /* *     .   , *      ,  . */ while (remaining > 0) { ssize_t count = read(fd, ptr, remaining); if (count < 0) return -1; remaining -= count; ptr += count; } return 0; } 

ELF- . , -, , -, .


ELF


ELF — Linux. , , .


ELF . ELF . — , . , — . ELF-.


, libdl-2.19.so :


节和段libdl-2.19.so


( readelf --headers .)


, , (29 9). — , , . ELF — , . Linux, , LOAD, ( ).


ELF- , . , .


, . «» . .bss, , ( ).


, ELF — , . ...


?


() . , dlsym(), . - .


ELF (. 2-10). , .dynamic , DYNAMIC . .dynamic , :


  • .dynsym — ;
  • .dynstr — ;
  • .hash — -, .

, , ELF:


动态分段搜索


ELF, (1), (2), (3), (4) ,


ELF →


() ELF <elf.h>, , , . , ELF — . 32- 64- , , , . x86_64, ELF .


ELF- ( Elf64_Ehdr ). ( program headers ), e_phoff e_phnum :


ELF接头


— , , ELF- — , , , , .


e_phoff, , . e_phnum e_phentsize .


( ), ELF — 64 .


→ DYNAMIC


. — Elf64_Phdr ( 64- ELF-), . PT_DYNAMIC p_type :


ELF段表


:


  • p_vaddr — , ;
  • p_memsz — .

.dynamic 0x2D88 ( ). DYNAMIC — 0x202D88. 0x210 (8448) . .


DYNAMIC → .dynsym, .dynstr, .hash


.dynamic, DYNAMIC, . Elf64_Dyn , :


动态部分标签


8 d_val d_ptr , 8- d_tag , , . :


  • DT_HASH (4) — .hash ( d_ptr)
  • DT_STRTAB (5) — .dynstr ( d_ptr)
  • DT_SYMTAB (6) — .dynsym ( d_ptr)
  • DT_STRSZ (10) — .dynstr ( d_val)
  • DT_NULL (0) —

. .dynamic : , , , .


, DYNAMIC , . , , - , .


.dynamic , . -, .dynstr , ? .



. , .dynsym , . ( «» .symtab, , , . .)



Elf64_Sym , ELF — , , , . dlopen :


ELF


:


  • st_name — ,
  • st_info — ( )
  • st_value

( , nm , dlopen() .text, 0x1090 .)


, .



— - , . ( ). .dynstr , libdl-2.19.so :


ELF


, ( «dlopen», 0xA5) , . .


-


.hash - , . - — — ELF-, . , .dynsym, , . ( ) - .


- <elf.h>, (. 2-19). - , :


- ELF


在哪里


  • nbuckets — buckets
  • nchains — chains ( )
  • buckets —
  • chains —

- :


  1. h .
  2. i buckets[h % nbuckets] , .
  3. ( ) , .
  4. chains[i % nchains] .
  5. 3—4 , .

-, ELF:


 static uint32_t elf_hash(const char *name) { uint32_t h = 0; uint32_t g; while (*name) { h = (h << 4) + *name++; g = h & 0xF0000000; if (g) h ^= g >> 24; h &= ~g; } return h; } 

, "dlopen" - 112420542 :



libdl — , 39 , . - .



, :


  • dlopen() dlsym() libdl
  • pthread_create() pthread_detach() libpthread

, .


. . , .


ELF- . , ( ). , . , , . .


4. -


, , - , : , . - .


-


, -:


 void shellcode(void) { void *payload = dlopen("/path/to/payload.so", RTLD_LAZY); void (*entry)(void) = dlsym(payload, "entry_point"); pthread_t thread; pthread_create(&thread, NULL, entry, NULL); pthread_detach(thread); } 

?


, — . , , , - — - ! .


— - . , , : .


 /* *      .rodata:   * .         , *        . */ .section .rodata /* *   .       . *      -:    ,  *  ,       . */ .global shellcode_start .global shellcode_address_dlopen .global shellcode_address_dlsym .global shellcode_address_pthread_create .global shellcode_address_pthread_detach .global shellcode_address_payload .global shellcode_address_entry .global shellcode_end /* *   dlopen().     #include <dlfcn.h>, *       . */ .set RTLD_LAZY, 1 .align 8 shellcode_start: /* * void *payload = dlopen(shellcode_address_payload, RTLD_LAZY); * *        x86_64: * * -     %rdi, %rsi, %rdx, %rcx * -     %rax * -      * *         . * *       %rax,    *     . */ lea shellcode_address_payload(%rip),%rdi mov $RTLD_LAZY,%rsi mov shellcode_address_dlopen(%rip),%rax callq *%rax /* * void (*entry)(void) = dlsym(payload, shellcode_address_entry); */ mov %rax,%rdi lea shellcode_address_entry(%rip),%rsi mov shellcode_address_dlsym(%rip),%rax callq *%rax /* * pthread_t thread; * pthread_create(&thread, NULL, entry, NULL); * *            * ,     pthread_create(). */ sub $8,%rsp mov %rsp,%rdi xor %rsi,%rsi mov %rax,%rdx xor %rcx,%rcx mov shellcode_address_pthread_create(%rip),%rax callq *%rax /* * pthread_detach(thread); * *    ,   ,  *     . */ mov (%rsp),%rdi add $8,%rsp mov shellcode_address_pthread_detach(%rip),%rax callq *%rax /* *   - —    ,     *      ret.    *     ,  *      . */ int $3 /* *       ,   *   ,    - *     .   “  *  ” (global offset table, GOT),   *           . */ .align 8 shellcode_address_dlopen: .space 8 shellcode_address_dlsym: .space 8 shellcode_address_pthread_create: .space 8 shellcode_address_pthread_detach: .space 8 shellcode_address_payload: .space 256 shellcode_address_entry: .space 256 /* *  - . */ shellcode_end: .end 

, . :


 $ as -o shellcode.o shellcode.S 

, , , . : (procedure linkage table, PLT), .


- , (, ) . - .


-


- . , , , . ?


-


, . , . , . , .


(- ), : , , . , , JIT- , . ?



:


  • - ,
  • - ,

, . -, - , . -, . -, , - -, .


, . . x86_64 int $3 — 0xCC — . ptrace() PTRACE_POKETEXT — , 8 , . , , .


, , , : . - , .


?


, ! malloc()!


. , -, . . , mmap():


 void inject_shellcode(const void *shellcode_src, size_t shellcode_size) { void *shellcode_dst = mmap(NULL, shellcode_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); copy_shellcode(shellcode_dst, shellcode_src, shellcode_size); } 

, ptrace() , .



, ? , . Linux x86_64 :


  • %rax
  • — — %rsi, %rdi, %rdx, %r10, %r8, %r9
  • SYSCALL,
  • %rax

ptrace() PTRACE_GETREGS PTRACE_SETREGS. , . - SYSCALL.


: , %rip. , , SYSCALL.


SYSCALL


SYSCALL? , . - , - . — libc. , , , :


 unsigned long find_syscall_instruction(struct library *library) { for (size_t i = 0; i < library->region_count; i++) { struct memory_region *region = &library->regions[i]; if (!(region->readable && region->executable)) continue; const uint8_t *region_data = region->content; size_t region_size = region->vaddr_high - region->vaddr_low; if (region_size < 2) continue; /* * 0F 05 syscall */ for (size_t offset = 0; offset < region_size - 1; offset++) { if (region_data[offset + 0] == 0x0F && region_data[offset + 1] == 0x05) { return region->vaddr_low + offset; } } } return 0; } 

, /proc/$pid/maps . x86_64 , - . , 0x0F 0x05. , , ARM, 0xDF 0x00 ( SVC #0), .


PTRACE_{GET,SET}REGS


:


 int get_registers(pid_t pid, struct user_regs_struct *registers) { int err = 0; if (ptrace(PTRACE_GETREGS, pid, registers, registers) < 0) err = -errno; return err; } 

struct user_regs_struct , <sys/user.h>. . . , varargs :


 static int set_regs_for_syscall(struct user_regs_struct *registers, unsigned long syscall_insn_vaddr, long syscall_number, int args_count, va_list args) { registers->rip = syscall_insn_vaddr; registers->rax = syscall_number; for (int i = 0; i < args_count; i++) { switch (i) { case 0: registers->rdi = va_arg(args, long); break; case 1: registers->rsi = va_arg(args, long); break; case 2: registers->rdx = va_arg(args, long); break; case 3: registers->r10 = va_arg(args, long); break; case 4: registers->r8 = va_arg(args, long); break; case 5: registers->r9 = va_arg(args, long); break; default: return -E2BIG; } } return 0; } static long perform_syscall(pid_t pid, unsigned long syscall_insn_vaddr, long syscall_number, int args_count, ...) { struct user_regs_struct old_registers; struct user_regs_struct new_registers; /* *    ,   *      . */ get_registers(pid, &old_registers); /* *      ,   * ,     . */ new_registers = old_registers; va_list args; va_start(args, args_count); set_regs_for_syscall(&new_registers, syscall_insn_vaddr, syscall_number, args_count, args); va_end(args); set_registers(pid, &new_registers); /* *    ,    *   ,    * (  ),    . *     . */ wait_for_syscall_completion(pid); /* *       *    . *        . */ get_registers(pid, &new_registers); long result = new_registers.rax; set_registers(pid, &old_registers); return result; } 

PTRACE_SYSCALL


: , ?


PTRACE_SYSCALL. PTRACE_CONT, . , - : , .


PTRACE_SYSCALL SIGTRAP : ( ) ( ). , ptrace() , , .


, SIGTRAP:


 static int wait_for_syscall_enter_exit_stop(pid_t pid) { if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) return -1; if (wait_for_process_stop(pid, SIGTRAP) < 0) return -1; return 0; } void wait_for_syscall_completion(pid_t pid) { wait_for_syscall_enter_exit_stop(pid); wait_for_syscall_enter_exit_stop(pid); } 

— , — (wait_for_process_stop() ). . , .


PTRACE_O_TRACESYSGOOD


, PTRACE_SYSCALL : , , - . , SIGTRAP ( ).


SIGTRAP . PTRACE_O_TRACESYSGOOD, :


  • SIGTRAP — -
  • SIGTRAP | 0x80 —

  int ptrace_attach(pid_t pid) { if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) return -1; if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; + /*     */ + unsigned long options = PTRACE_O_TRACESYSGOOD; + if (ptrace(PTRACE_SETOPTIONS, pid, 0, options) < 0) + return -1; return 0; } static int wait_for_syscall_enter_exit_stop(pid_t pid) { if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) return -1; - if (wait_for_process_stop(pid, SIGTRAP) < 0) + if (wait_for_process_stop(pid, SIGTRAP | 0x80) < 0) return -1; return 0; } 

-


- :


 void write_shellcode(void) { char shellcode_text[SHELLCODE_TEXT_SIZE]; size_t shellcode_size = shellcode_end - shellcode_start; /*   ,  ,  . . */ prepare_shellcode(shellcode_text, shellcode_size); /*   -   */ write_remote_memory(target, shellcode_text_vaddr, shellcode_text, shellcode_size); } 

- : dlopen(), .


 static inline void copy_shellcode(char *shellcode_text, const char *shellcode_addr, const void *data, size_t length) { ptrdiff_t offset = shellcode_addr - shellcode_start; memcpy(shellcode_text + offset, data, length); } static void prepare_shellcode(char *shellcode_text, size_t shellcode_size) { copy_shellcode(shellcode_text, shellcode_start, shellcode_start, shellcode_size); copy_shellcode(shellcode_text, shellcode_address_dlopen, &dlopen_vaddr, sizeof(dlopen_vaddr)); copy_shellcode(shellcode_text, shellcode_address_dlsym, &dlsym_vaddr, sizeof(dlsym_vaddr)); copy_shellcode(shellcode_text, shellcode_address_pthread_create, &pthread_create_vaddr, sizeof(pthread_create_vaddr)); copy_shellcode(shellcode_text, shellcode_address_pthread_detach, &pthread_detach_vaddr, sizeof(pthread_detach_vaddr)); copy_shellcode(shellcode_text, shellcode_address_payload, payload, sizeof(payload)); copy_shellcode(shellcode_text, shellcode_address_entry, entry, sizeof(entry)); } 

, , -:


 extern const char shellcode_start[]; extern const char shellcode_address_dlopen[]; extern const char shellcode_address_dlsym[]; extern const char shellcode_address_pthread_create[]; extern const char shellcode_address_pthread_detach[]; extern const char shellcode_address_payload[]; extern const char shellcode_address_entry[]; extern const char shellcode_end[]; 

, .


- . /proc/$pid/mem, :


 int write_remote_memory(pid_t pid, unsigned long vaddr, const void *data, size_t size) { char path[32] = {0}; snprintf(path, sizeof(path), "/proc/%d/mem", pid); /*       */ int fd = open(path, O_WRONLY); if (fd < 0) return -1; /*     */ if (lseek(fd, vaddr, SEEK_SET) < 0) { close(fd); return -1; } /*    */ int err = do_write_remote_memory(fd, data, size); close(fd); return err; } static int do_write_remote_memory(int fd, const void *data, size_t size) { size_t left = size; /* *    ,  ,     *   ,       *      . */ while (left > 0) { ssize_t wrote = write(fd, data, left); if (wrote < 0) return -1; data += wrote; left -= wrote; } return 0; } 


, - — « » . . - , .


5.


- . , : %rip -, PTRACE_SETREGS, PTRACE_CONT . .


, , . -? ?



, . , « » . , . :


  • (async-signal-safe)

— . dlopen() pthread_create() . - dlopen(), dlopen() ?


-, , , . , pthread_create() . , ( ). clone().


pthread_create()?

, - , ?

: clone().

, (libc) (pthread). clone() (thread control block, TCB) (thread-local storage, TLS), , . . pthread_create() , .

«», clone() libc pthread. , .


clone() :


  • ?
  • ?
  • -?


: -?


, - : , , , .


. , , . 怎么了 : exit(). , .


. exit() -:


 +.set __NR_exit, 60 .set RTLD_LAZY, 1 @@ - /* - *  . - */ - int $3 + /* + * exit(0); + */ + xor %rdi,%rdi + mov $__NR_exit,%rax + syscall 

: exit() — exit() . exit() , exit() — . Linux exit_group().



. . , , PROT_EXEC:


 shellcode_stack_vaddr = remote_mmap(target, syscall_vaddr, 0, SHELLCODE_STACK_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN, -1, 0); 

, Linux x86_64 — «» , . mmap() , clone() . , mmap() MAP_GROWSDOWN, , .


PTRACE_O_TRACECLONE


. , - . waitpid(), : , .


— PTRACE_O_TRACECLONE. . , . , , , . , PTRACE_ATTACH , .


-, :


 - unsigned long options = PTRACE_O_TRACESYSGOOD; + unsigned long options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE; if (ptrace(PTRACE_SETOPTIONS, pid, 0, options) < 0) return -1; 

-, clone(), PTRACE_EVENT_CLONE, , PTRACE_SYSCALL. :


 -void wait_for_syscall_completion(pid_t pid) +void wait_for_syscall_completion(pid_t pid, long syscall) { wait_for_syscall_enter_exit_stop(pid); + + /*  clone()   PTRACE_EVENT_CLONE */ + if (syscall == __NR_clone) + wait_for_clone_event(pid); wait_for_syscall_enter_exit_stop(pid); } 

:


 static int wait_for_clone_event(pid_t pid) { if (ptrace(PTRACE_CONT, pid, 0, 0) < 0) return -1; int event = SIGTRAP | (PTRACE_EVENT_CLONE << 8); if (wait_for_process_stop(pid, event) < 0) return -1; return 0; } 

clone() PID , . :


 void clear_ptrace_options(pid_t pid) { ptrace(PTRACE_SETOPTIONS, pid, 0, 0); } 

, clone() ptrace(), PTRACE_O_TRACECLONE. , , - .



, - . clone() :


 static int spawn_shell_thread() { shell_tid = remote_clone(target, syscall_ret_vaddr, CLONE_FILES | CLONE_FS | CLONE_IO | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD | CLONE_VM, /*   **  */ shellcode_stack_vaddr + SHELLCODE_STACK_SIZE); if (!shell_tid) return -1; return 0; } 

clone() : , , , . , .


CLONE_FILES, CLONE_FS, CLONE_IO, CLONE_SIGHAND, CLONE_SYSVSEM, CLONE_VM — . , CLONE_FILES , ( fork()). — — , . . , CLONE_VM , , .


CLONE_THREAD : Linux — « », . , , getpid() , kill() — - , execve() — , .


, clone() fork(): , . clone() : , — . . ( , , .)


, pthread_create() , , . ?



fork() :


 pid_t child = fork(); if (child < 0) { /* fork() ,    */ } if (child == 0) { /*     execve() */ } /*     */ 

, . clone() . .


. , clone() , . syscall ret, , . .


SYSCALL + RET


, . , syscall ret:


 -if (region_size < 2) +if (region_size < 3) continue; /* * 0F 05 syscall + * C3 retq */ -for (size_t offset = 0; offset < region_size - 1; offset++) { +for (size_t offset = 0; offset < region_size - 2; offset++) { if (region_data[offset + 0] == 0x0F && - region_data[offset + 1] == 0x05) + region_data[offset + 1] == 0x05 && + region_data[offset + 2] == 0xC3) { return region->vaddr_low + offset; } } 

, .



. prepare_shellcode() , , :


  void write_shellcode(void) { char shellcode_text[SHELLCODE_TEXT_SIZE]; size_t shellcode_size = shellcode_end - shellcode_start; /*   ,  ,  . . */ prepare_shellcode(shellcode_text, shellcode_size); /*   -   */ write_remote_memory(target, shellcode_text_vaddr, shellcode_text, shellcode_size); + /*    «»   */ + unsigned long retaddr_vaddr = + shellcode_stack_vaddr + SHELLCODE_STACK_SIZE - 8; + write_remote_memory(target, retaddr_vaddr, + &shellcode_text_vaddr, sizeof(shellcode_text_vaddr)); } 

, , .


, , . System V ABI , ( %rsp) 16 . shellcode_stack_vaddr + SHELLCODE_STACK_SIZE : ( 4096 ), 1 . 8 , , retq, - . - :


 - sub $8,%rsp + sub $16,%rsp /*   */ mov %rsp,%rdi xor %rsi,%rsi mov %rax,%rdx xor %rcx,%rcx mov shellcode_address_pthread_create(%rip),%rax callq *%rax 

, %rsp 16 pthread_create(). SIGSEGV, — pthread_create() , .



, - , clone():


  static int spawn_shell_thread() { shell_tid = remote_clone(target, syscall_ret_vaddr, CLONE_FILES | CLONE_FS | CLONE_IO | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD | CLONE_VM, /*   **  */ - shellcode_stack_vaddr + SHELLCODE_STACK_SIZE); + shellcode_stack_vaddr + SHELLCODE_STACK_SIZE - 8); if (!shell_tid) return -1; return 0; } 

ptrace() SIGSTOP, :


 int ignore_thread_stop(pid_t pid) { return wait_for_process_stop(pid, SIGSTOP); } 

. ptrace():


 void resume_thread(pid_t pid) { ptrace(PTRACE_CONT, pid, 0, 0); } 


, , , exit(). waitpid(). — CLONE_THREAD wait() ,— PTRACE_O_TRACECLONE, :


 int wait_for_process_exit(pid_t pid) { int status = 0; if (waitpid(pid, &status, 0) < 0) return -1; if (!WIFEXITED(status)) return -1; return WEXITSTATUS(status); } 

pthread , , pthread_join() pthread , . , — . , , .


可用内存


, - . , - , munmap():


 void remote_munmap(pid_t pid, unsigned long syscall_insn_vaddr, unsigned long addr, size_t len) { perform_syscall(pid, syscall_insn_vaddr, __NR_munmap, 2, (long) addr, (long) len); } static void unmap_shellcode() { remote_munmap(target, syscall_ret_vaddr, shellcode_text_vaddr, SHELLCODE_TEXT_SIZE); remote_munmap(target, syscall_ret_vaddr, shellcode_stack_vaddr, SHELLCODE_STACK_SIZE); } 

, , , — ptrace() . (, SIGSTOP), , ( ):


 int stop_thread(pid_t pid) { if (kill(pid, SIGSTOP) < 0) return -1; if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; return 0; } 


, , . PTRACE_DETACH:


 int ptrace_detach(pid_t pid) { if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) return -1; return 0; } 


, . , . , .


结论


? . , , .


Gnome Control Center


Linux . GTK+ . , make:


 libpayload.so: payload.c $(CC) $(CFLAGS) $(shell pkg-config --cflags --libs gtk+-3.0) -shared -o $@ $< 

entry() GTK- — GTK UI , :


 #include <glib.h> #include <gtk/gtk.h> static gboolean actual_entry(gpointer _arg) { /*       : */ hook_gtk_entry_constructor(); /*   FALSE,       */ return FALSE; } void entry(void) { /*    -,   */ g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, actual_entry, NULL, NULL); } 

, GTK , GtkEntry . "input-purpose" . «», , .


GTK glib — — GtkEntry . constructed(), . :


 static void (*old_gtk_entry_constructed)(GObject *object); static void new_gtk_entry_constructed(GObject *object) { GtkEntry *entry = GTK_ENTRY(object); /*    */ old_gtk_entry_constructed(object); /*    ,  ,   entry */ } static void hook_gtk_entry_constructor(void) { /*     GtkEntry */ GTypeClass *entry_type_class = g_type_class_peek(GTK_TYPE_ENTRY); GObjectClass *entry_object_class = G_OBJECT_CLASS(entry_type_class); /* *     "constructed"     . */ old_gtk_entry_constructed = entry_object_class->constructed; entry_object_class->constructed = new_gtk_entry_constructed; } 

GtkEntry :


  • , ,

, GtkEntry , , . , :


 static void new_gtk_entry_constructed(GObject *object) { GtkEntry *entry = GTK_ENTRY(object); old_gtk_entry_constructed(object); /*       */ g_signal_connect(entry, "notify::input-purpose", G_CALLBACK(input_purpose_changed), NULL); /*      */ g_signal_connect(entry, "icon-press", G_CALLBACK(icon_pressed), NULL); /*      */ g_signal_connect(entry, "icon-release", G_CALLBACK(icon_released), NULL); } 

. , . .


 static void input_purpose_changed(GtkEntry *entry) { GtkInputPurpose purpose = gtk_entry_get_input_purpose(entry); if (purpose == GTK_INPUT_PURPOSE_PASSWORD) { gtk_entry_set_icon_activatable(entry, GTK_ENTRY_ICON_PRIMARY, TRUE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-remove"); } else { gtk_entry_set_icon_activatable(entry, GTK_ENTRY_ICON_PRIMARY, FALSE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, NULL); } } 

: , , , - , :


 static void icon_pressed(GtkEntry *entry, GtkEntryIconPosition position) { if (position != GTK_ENTRY_ICON_PRIMARY) return; gtk_entry_set_visibility(entry, TRUE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-add"); } static void icon_released(GtkEntry *entry, GtkEntryIconPosition position) { if (position != GTK_ENTRY_ICON_PRIMARY) return; gtk_entry_set_visibility(entry, FALSE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-remove"); } 

仅此而已。


GitHub (GPLv2).


, . gdb :


 $ gdb --pid $(pgrep target) \ --batch \ -ex 'compile file -raw shell-code.c' 

Source: https://habr.com/ru/post/zh-CN473740/


All Articles