一、iOS调试
iOS调试里面非常常见的就是LLDB调试,LLDB是Xcode自带的调试工具,既可以本地调试Mac应用程序,也可以远程调试iPhone应用程序。当我们使用Xcode调试手机应用程序时,Xcode会将debugsever文件复制到手机中,以便在手机上启动一个服务,等待Xcode进行远程调试,接着通过LLDB把调试指令发给手机的debugServer,进行一系列调试。所谓反调试,就是防止第三方动态查看调试自己的App。
debugserver在Mac上的路径(以iOS12.1系统为例):
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/12.1\ \(16B91\)/DeveloperDiskImage.dmg
/usr/bin/debugserver
(DeveloperDiskImage.dmg映像内部路径)
debugserver在iOS设备上的路径:
/Developer/usr/bin
二、认识ptrace
为了方便应用软件的开发和调试,Unix(iOS是类Uinx)的早期版本就提供了一种对运行中的进程进行跟踪和控制的手段,那就是系统调用ptrace。通过ptrace,可以对另一个进程实现调试跟踪,并且可以检测被控制进程的内存和寄存器里面的数据,它是通过调试器附加的形式进行调试的。
三、关于系统调用
系统调用指的是运行在用户空间的程序向操作系统内核请求需要更高权限运行的服务。系统调用提供用户程序与操作系统之间的接口。大多数系统交互式操作需求在内核态运行。如设备IO操作或者进程间通信。
它是一个系统提供的很强大的底层服务,我们用户层的框架是构建在system call之上的。
可以通过命令查看系统提供给用户的系统调用数:
sudo dtrace -ln 'syscall:::entry' | wc -l
命令中有个叫dtrace的东西,我们wiki之简单先了解一下Dtrace:
DTrace is a comprehensive dynamic tracing framework created by Sun Microsystems for troubleshooting kernel and application problems on production systems in real time. Originally developed for Solaris, it has since been released under the free Common Development and Distribution License (CDDL) and has been ported to several other Unix-like systems.
DTrace是Sun Microsystems创建的一个全面的动态跟踪框架,用于实时诊断生产系统上的内核和应用程序问题。它最初是为Solaris开发的,后来在免费的公共开发和发行许可(CDDL)下发布,并被移植到其他几个类unix系统。
DTrace can be used to get a global overview of a running system, such as the amount of memory, CPU time, filesystem and network resources used by the active processes. It can also provide much more fine-grained information, such as a log of the arguments with which a specific function is being called, or a list of the processes accessing a specific file.
DTrace可以用于获得一个正在运行的系统的全局概览,例如活动进程使用的内存量、CPU时间、文件系统和网络资源。它还可以提供更细粒度的信息,例如调用特定函数的参数的日志,或者访问特定文件的进程的列表。
三、梳理关系
前文提到了LLDB、debugserver、ptrace、dtrace,我们来通过一个小试验理清它们之间的关系,如下:
- 先通过命令创建出一个dtrace引用,并且让其对系统调用ptrace进行监控(从dtrace上面的第二段简介可知)
sudo dtrace -qn 'syscall::ptrace:entry { printf("%s(%d, %d, %d, %d) from %s\n", probefunc, arg0, arg1, arg2, arg3, execname); }'
此时dtrace会进入等待状态,等待系统调用ptrace执行。
-
新建终端窗口尝试对某进程进行LLDB附加调试
我这里对本机运行的QQ进程LLDB调试附加:lldb -p 971
-
查看dtrace端输出:
可以看到系统调用了ptrace,来自debugserver,从打印信息上看我们可以这样理解,LLDB发送调试指令给debugserver,debugserver调起系统调用ptrace并附加到指定进程上。
- 进一步查看debugserver,利用命令
pgrep debugserver
获取debugserver进程号(PID),查看进程信息:
可以看到确确实实是lldb调起了debugserver进程,总结一下整个附加调试器的流程:lldb -> debugserver ->ptrace
四、重点研究系统调用ptrace
- 新建工程查看ptrace定义
在iOS App工程下我试过#import <sys/ptrace.h>
报错,需要新建一个命令行工程,然后#import
,主要目的是为了定位到头文件查看信息,为了不让读者操作麻烦,我直接给出该文件路径:/usr/include/sys/ptrace.h
-
解析ptrace函数
函数原型:int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
参数1:表明ptrace要做的事情 (可以往回查看一下dtrace窗口输出,ptrace第一个参数是14
,对应宏定义PT_ATTACHEXC
,看到注释,就是我们附加调试到正在运行的进程的意思)。
参数2:进程号PID
参数3:地址
参数4:数据
参数3、4根据参数1决定,具体含义没有深究。
ptrace提供了一个非常有用的参数,那就是PT_DENY_ATTACH
,如果我们可以人为地传入这个值给第一个参数,那么就不是可以中断lldb调试了吗?没错,ptrace反调试的原理就是如此!
五、编写ptrace反调试函数
ptrace反调试的实现形式有多种,可以看一下反调试与绕过的奇淫技巧,这里我做了两次反调试,详细看注释:
//
// main.m
// Test
//
// Created by Kinken_Yuen on 2018/12/9.
// Copyright © 2018年 Kinken_Yuen. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "MyPtrace.h"
#import <dlfcn.h>
int main(int argc, char * argv[]) {
/**
反调试
*/
#ifndef PT_DENY_ATTACH
#define PT_DENY_ATTACH 31
#endif
//第一次,通过符号直接调用ptrace
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
ptrace(PT_DENY_ATTACH, 0, 0, 0);
//第二次,通过dlopen,dlsym调用
void *handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
ptrace_ptr_t ptrace_ptr = (ptrace_ptr_t)dlsym(handle, "ptrace");
ptrace_ptr(PT_DENY_ATTACH,0,0,0);
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
Xcode运行工程之后手机上的App直接闪退,Xcode显示了停止运行,反调试成功,当然这是比较初级的反调试,很容易反反调试,日后再深入学习。
Demo
六、总结
理解lldb调试原理,了解debugserver
理解系统调用ptrace,反调试原理
思考如何反反调试,如何增强自己的ptrace反调试?