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
最后在lldb
中continue
,结果应该是:
[...]
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
我正在调试一个可能具有反调试措施的应用程序,设置断点和停止退出应用程序的信号不会阻止应用程序退出,
$ 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
最后在lldb
中continue
,结果应该是:
[...]
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