lldb 查找应用程序的退出点

lldb finding exit point of app

我正在调试一个可能具有反调试措施的应用程序,设置断点和停止退出应用程序的信号不会阻止应用程序退出,

$ lldb App 
(lldb) target create "App"
error: Invalid fde/cie next entry offset of 0x43029a18 found in cie/fde at 0x1404
Current executable set to 'App' (x86_64).
(lldb) br s -n exit
Breakpoint 1: 3 locations.
(lldb) br s -n _exit
Breakpoint 2: where = libsystem_kernel.dylib`__exit, address = 0x00000000000167a8
(lldb) br s -n _Exit
Breakpoint 3: where = libsystem_c.dylib`_Exit, address = 0x000000000005ed8b
(lldb) process launch -stop-at-entry
Process 17849 stopped
* thread #1: tid = 0xb9ebc, 0x00007fff5fc01000 dyld`_dyld_start, stop reason = signal SIGSTOP
    frame #0: 0x00007fff5fc01000 dyld`_dyld_start
dyld`_dyld_start:
->  0x7fff5fc01000 <+0>: popq   %rdi
    0x7fff5fc01001 <+1>: pushq  [=10=]x0
    0x7fff5fc01003 <+3>: movq   %rsp, %rbp
    0x7fff5fc01006 <+6>: andq   $-0x10, %rsp
Process 17849 launched: '/Users/admin/Downloads/App.app/Contents/MacOS/App' (x86_64)
(lldb) process handle -p false -s true
Do you really want to update all the signals?: [y/N] y
NAME         PASS   STOP   NOTIFY
===========  =====  =====  ======
SIGHUP       false  true   true 
... [removed for brevity]
(lldb) c
Process 17849 resuming
Process 17849 exited with status = 45 (0x0000002d) 
(lldb)

应用程序如何在不触发任何信号、exit、_exit 或 _Exit 的情况下退出?

在 lldb 中有没有办法 运行 进程,然后在退出时 'backtrack' 查看它退出的位置?

有没有办法让 lldb 记录每个汇编指令等(比如它中断时),以便您可以在退出时追溯它?

对于那些感兴趣的人,可以找到对这个答案的不同看法 here


这里发生了什么?

很可能您正在处理像这样的反调试技术:

ptrace(PT_DENY_ATTACH, 0, NULL, 0);

基本思想是只有一个进程可以同时 ptrace 另一个进程,特别是 PT_DENY_ATTACH 选项确保 tracee 退出状态为 ENOTSUP (45)。参见 man ptrace 关于 PT_DENY_ATTACH:

This request is the other operation used by the traced process; it allows a process that is not currently being traced to deny future traces by its parent. All other arguments are ignored. If the process is currently being traced, it will exit with the exit status of ENOTSUP; otherwise, it sets a flag that denies future traces. An attempt by the parent to trace a process which has set this flag will result in a segmentation violation in the parent.

关于45,看一下/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/errno.h:

#define ENOTSUP     45      /* Operation not supported */

如何重现这个?

编写表现出相同行为的程序是微不足道的:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ptrace.h>

int main() {
    printf("--- before ptrace()\n");
    ptrace(PT_DENY_ATTACH, 0, NULL, 0);
    perror("--- ptrace()");
    printf("--- after ptrace()\n");
    return 0;
}

编译:

clang -Wall -pedantic ptrace.c -o ptrace

简单地运行它会成功退出,但尝试调试它会产生以下结果:

(lldb) r
Process 4188 launched: './ptrace' (x86_64)
--- before ptrace()
Process 4188 exited with status = 45 (0x0000002d)

由于这个例子非常小,所以可以步进到 syscall 指令:

(lldb) disassemble
libsystem_kernel.dylib`__ptrace:
    0x7fff6ea1900c <+0>:  xorq   %rax, %rax
    0x7fff6ea1900f <+3>:  leaq   0x394f12f2(%rip), %r11    ; errno
    0x7fff6ea19016 <+10>: movl   %eax, (%r11)
    0x7fff6ea19019 <+13>: movl   [=15=]x200001a, %eax          ; imm = 0x200001A
    0x7fff6ea1901e <+18>: movq   %rcx, %r10
->  0x7fff6ea19021 <+21>: syscall
    0x7fff6ea19023 <+23>: jae    0x7fff6ea1902d            ; <+33>
    0x7fff6ea19025 <+25>: movq   %rax, %rdi
    0x7fff6ea19028 <+28>: jmp    0x7fff6ea10791            ; cerror
    0x7fff6ea1902d <+33>: retq
    0x7fff6ea1902e <+34>: nop
    0x7fff6ea1902f <+35>: nop
(lldb) s
Process 3170 exited with status = 45 (0x0000002d)

因此 杀死 进程的是内核代码,但没有信号或适当的 exit 系统调用。 (直到这个,它仍然让我震惊。)

执行哪个系统调用由 EAX 寄存器的值决定,在本例中 0x200001A 这可能看起来很奇怪,因为 ptrace 系统调用号只是 26 (0x1a), 请参阅 syscalls.master:

26  AUE_PTRACE  ALL { int ptrace(int req, pid_t pid, caddr_t addr, int data); }

经过一番挖掘,我想出了 syscall_sw.h:

#define SYSCALL_CONSTRUCT_UNIX(syscall_number) \
            ((SYSCALL_CLASS_UNIX << SYSCALL_CLASS_SHIFT) | \
             (SYSCALL_NUMBER_MASK & (syscall_number)))

计算结果是 0x200001A

为什么 dtruss 不跟踪 ptrace 系统调用?

使用 dtruss 似乎是个好主意,不幸的是它没有报告 ptrace 系统调用(我的理解是它没有这样做,因为 ptrace 系统调用没有 returns 在这种情况下)。

幸运的是,您可以编写一个 DTrace 脚本来记录一个系统调用,一旦它被输入(即,而不是在它之后 returns)。要触发该行为,程序必须从 lldb:

开始
$ lldb ./ptrace
(lldb) process launch --stop-at-entry

然后记下 PID:

sudo dtrace -q -n 'syscall:::entry /pid == $target/ { printf("syscall> %s\n", probefunc); }' -p $PID

最后在lldbcontinue,结果应该是:

[...]
syscall> sysctl
syscall> csops
syscall> getrlimit
syscall> fstat64
syscall> ioctl
syscall> write_nocancel
syscall> ptrace

可能的解决方案

现在最好在 ptrace 系统调用之前中断并找到调用它的程序代码,或者在当前调试会话中跳过它(LLDB:thread jump -a ADDRESS)。

当然可以尝试中断 ptrace 库调用,但如果这是真的,反调试尝试可能是实际调用是在 asm 块中执行的,因此上面的断点永远不会触发。

一个可能的解决方案是使用 DTrace 在系统调用之前放置一个断点,但这需要禁用系统完整性保护,所以我没有尝试。

或者可以使用 ustack() 函数打印用户态堆栈跟踪:

sudo dtrace -q -n 'syscall:::entry /pid == $target && probefunc == "ptrace"/ { ustack(); }' -p $PID