Apple高级调试与逆向工程应用安全

(三) Ptrace与反调试

2020-03-06  本文已影响0人  收纳箱

1. 你好Ptrace

1.1 系统调用

系统调用是由系统核心提供的强大底层服务,是用户系统框架(比如C的stdlib、Cocoa、UIKit和你自己的框架)的建立基础。

1.2 附着的基础:ptrace

我们利用dtrace来监视ptrace的调用,并把参数打印出来。

sudo dtrace -qn 'syscall::ptrace:entry { printf("%s(%d, %d, %d, %d) from %s\n", probefunc, arg0, arg1, arg2, arg3, execname); }'

之后我们新建一个tab用LLDB附着Finder

lldb -n Finder
(lldb) process attach --name "Finder"
//之前的tab输出
ptrace(14, 358, 0, 0) from debugserver

我们看到有一个叫debugserver的进程调用了ptrace,并且附着到了Finder的进程上。debugserver是如何被调用的?我们用的是LLDB而不是debugserver,现在debugserver进程还是活跃的么?

要回答这些问题,我们新开一个tab,再输入

 ~> pgrep debugserver
2917

LLDB如果成功附着且还在运行,我们看到了一个输出2917,代表了debugserver的进程ID,即PID,说明debugserver还在运行。我们来看看debugserver是如何启动起来的。

 ~> ps -fp `pgrep -x debugserver`
  UID   PID  PPID   C STIME   TTY           TIME CMD
  502  2917  2916   0 10:28AM ttys001    0:00.21 /Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/debugserver --fd=5 --native-regs --setsid

这个命令打印了debugserver的完整路径和启动这个进程的所有参数。那哪个进程启动了它呢?

 ~> ps -o ppid= $(pgrep -x debugserver)
 2916
 ~> ps -a 2916
  PID TTY           TIME CMD
 2916 ttys001    0:08.60 /Applications/Xcode.app/Contents/Developer/usr/bin/lldb -n Finder

正如你所看到的,LLDB启动了debugserver这个进程,然后用ptrace系统调用把它自己附着到了Finder上。

1.3 ptrace的参数

虽然我们可以看到ptrace的执行,但这些都是些数字,我们需要在<sys/ptrace.h>中查看它们的具体含义。

//mac项目中运行下面的代码
while true {
  sleep(2)
  print("helloptrace")
}

ptrace监听tab窗我们可以看到

ptrace(14, 3365, 0, 0) from debugserver
ptrace(13, 3365, 7939, 0) from debugserver

暂停工程,我们来看看<sys/ptrace.h>头文件,ptrace的方法定义。

//$1: 是你想ptrace做什么
//$2: 你想应用到哪个pid上
//$3、$4取决于$1是什么
int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);

那么

// #define PT_ATTACHEXC    14      /* attach to running process with signal exception */
ptrace(14, 3365, 0, 0) from debugserver

关于PT_ATTACHEXC的详细信息可以通过man ptrace命令在终端中查看。这个请求可以让一个进程附着到另一个不相关的进程,它只需要要跟踪的pid,其他参数不需要。

//#define PT_THUPDATE     13      /* signal for thread# */
ptrace(13, 3365, 7939, 0) from debugserver

至于13的PT_THUPDATE是和怎么控制进程相关的,在我们的例子里面就是debugserver;处理传递给被控制进程的UNIX信号和Mach消息,我们的例子里面就是,helloptrace。这个特殊的ptrace行为涉及到Mach核心怎么在内部实现ptrace处理的实现细节有关。

1.4 创建一个附着问题

进程实际上可以使用PT_DENY_ATTACH请求来拒绝被附着。这通常被用来作为一种反调试机制来保护我们的程序被逆向。

我们加上ptrace(PT_DENY_ATTACH, 0, nil, 0)来试一试。

ptrace(PT_DENY_ATTACH, 0, nil, 0)
while true {
  sleep(2)
  print("helloptrace")
}

//运行结果
Program ended with exit code: 45

这是因为Xcode启动时helloptrace会自动启动LLDB附着。如果你拒绝附着,那么LLDB就会提前退出,程序结束运行。

1.5 玩一玩PT_DENY_ATTACH

通常,开发者会在程序中加入ptrace(PT_DENY_ATTACH, 0, 0, 0),通常就在main函数里面。
LLDB有一个参数-w来等待一个进程启动。那么就可以在程序启动还没有执行到ptrace(PT_DENY_ATTACH, 0, 0, 0)之前进行附着,修改或忽略ptrace

~> sudo lldb -n "helloptrace" -w
Password:
(lldb) process attach --name "helloptrace" --waitfor

然后我们在终端的新tab中运行helloptrace,就会发现LLDB成功附着了。

Process 3575 stopped
* thread #1, stop reason = signal SIGSTOP
    frame #0: 0x00000001142cc34e dyld`getattrlist + 10
dyld`getattrlist:
->  0x1142cc34e <+10>: jae    0x1142cc358               ; <+20>
    0x1142cc350 <+12>: mov    rdi, rax
    0x1142cc353 <+15>: jmp    0x1142ca769               ; cerror_nocancel
    0x1142cc358 <+20>: ret
Target 0: (helloptrace) stopped.

我们在libsystem_kernel.dylib中下一个ptrace的断点,然后继续运行。

(lldb) rb ptrace -s libsystem_kernel.dylib
Breakpoint 1: no locations (pending).
WARNING:  Unable to resolve breakpoint to any actual locations.
(lldb) continue
Process 3575 resuming
1 location added to breakpoint 1
Process 3575 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00007fff662caa44 libsystem_kernel.dylib`__ptrace
libsystem_kernel.dylib`__ptrace:
->  0x7fff662caa44 <+0>:  xor    rax, rax
    0x7fff662caa47 <+3>:  lea    r11, [rip + 0x29cd7eda]   ; errno
    0x7fff662caa4e <+10>: mov    dword ptr [r11], eax
    0x7fff662caa51 <+13>: mov    eax, 0x200001a
Target 0: (helloptrace) stopped.

我们断在了ptrace将要执行的时候,我们可以直接用LLDB提前返回,不执行这个函数。

(lldb) thread return 0
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x000000010e0d2a35 helloptrace`main at main.swift:31:1
   28
   29   import Foundation
   30
-> 31   ptrace(PT_DENY_ATTACH, 0, nil, 0)
   32
   33   while true {
   34     sleep(2)
(lldb) continue
Process 3575 resuming

我们可以看到,helloptrace正常打印了。

helloptrace

我们也可以修改寄存器的值,下面是前四个参数的值,可以看到RDI中是31(PT_DENY_ATTACH)。再把RDI中的值替换为0,再继续运行,可以达到同样的效果。

(lldb) p $rdi
(unsigned long) $1 = 31
(lldb) p $rsi
(unsigned long) $2 = 0
(lldb) p $rdx
(unsigned long) $3 = 0
(lldb) p $rcx
(unsigned long) $4 = 0
(lldb) p $rdi = 0
(unsigned long) $6 = 0
(lldb) register read rdi
     rdi = 0xffffffffffffffff
(lldb) continue
Process 3635 resuming

当然,如果你还会其他的方法也可以,比如利用fishhook替换ptrace的行为。

1.6 其他反调试技术

长久以来,iTunes都是用PT_DENY_ATTACH的方式来反调试的。iTunes v12.7.0的时候,开始用另一种方式来反调试了。

它使用sysctl函数来检测它是否在被反调试,如果是的话就关闭程序。sysctlptrace一样是另一个系统调用。iTunes在运行时利用一个NSTimer来反复调用sysctl执行检测。

下面是一个简单的Swift代码来展示iTunes做了什么:

let mib = UnsafeMutablePointer<Int32>.allocate(capacity: 4)
mib[0] = CTL_KERN
mib[1] = KERN_PROC
mib[2] = KERN_PROC_PID
mib[3] = getpid()
var size: Int = MemoryLayout<kinfo_proc>.size
var info: kinfo_proc? = nil
sysctl(mib, 4, &info, &size, nil, 0)
if (info.unsafelyUnwrapped.kp_proc.p_flag & P_TRACED) > 0 {
    exit(1)
}
上一篇下一篇

猜你喜欢

热点阅读