iOS反调试初步探索

为了防止项目被逆向调试,需要对代码增加反调试相关功能。反调试的策略主要分为两种,一种是禁止调试,一种是监测是否被调试

1、禁止调试

ptrace简介

要想知道怎么禁止调试,需要先知道什么是ptrace

ptrace是C标准库中的一个函数,该函数是一个系统调用方法 , 可以监视进程执行 , 查看 / 更改 被监视进程的 内存 和 寄存器 情况 , 常用于断点调试。

ptrace方法如下

 #include <sys/ptrace.h>
 long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

可以看到ptrace有四个参数:
enum __ptrace_request request:指定ptrace要执行的命令。
pid_t pid: 跟踪进程id。
void *addr: 进程的某个内存地址。
void *data: 存放读取出的或者要写入的数据。

第一个参数可以传入以下值

PTRACE_TRACEME,   本进程被其父进程所跟踪。其父进程应该希望跟踪子进程
PTRACE_PEEKTEXT,  从内存地址中读取一个字节,内存地址由addr给出
PTRACE_PEEKDATA,  同上
PTRACE_PEEKUSER,  可以检查用户态内存区域(USER area),从USER区域中读取一个字节,偏移量为addr
PTRACE_POKETEXT,  往内存地址中写入一个字节。内存地址由addr给出
PTRACE_POKEDATA,  往内存地址中写入一个字节。内存地址由addr给出
PTRACE_POKEUSER,  往USER区域中写入一个字节,偏移量为addr
PTRACE_GETREGS,    读取寄存器
PTRACE_GETFPREGS,  读取浮点寄存器
PTRACE_SETREGS,  设置寄存器
PTRACE_SETFPREGS,  设置浮点寄存器
PTRACE_CONT,    重新运行
PTRACE_SYSCALL,  重新运行
PTRACE_SINGLESTEP,  设置单步执行标志
PTRACE_ATTACH,追踪指定pid的进程
PTRACE_DETACH,  结束追踪

具体ptrace用法和原理可以参考下文
https://zhuanlan.zhihu.com/p/438534744

那么怎么用ptrace来实现反调试呢
其实除了上述参数,苹果增加了一个PT_DENY_ATTACH选项,这个参数用来告诉系统,阻止调试器依附。

OC中使用ptrace阻止调试器依附

在OC中,我们可以在main方法中这样写

int main(int argc, char * argv[]) {
    @autoreleasepool {
        ptrace(PT_DENY_ATTACH, 0, 0, 0);
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

但是遗憾的是iPhone运行环境sys/ptrace.h是没有暴露的,
所以我们需要自定义ptrace方法

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import </usr/include/sys/ptrace.h>
#import <dlfcn.h>
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);

int main(int argc, char * argv[]) {
    @autoreleasepool { 
        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);
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

dlopen是一个计算机函数,功能是以指定模式打开指定的动态链接库文件,并返回一个句柄给dlsym的调用进程。
dlsym功能是根据动态链接库操作句柄与符号,返回符号对应的地址。
这样,拿到了ptrace,第一个参数传入PT_DENY_ATTACH,实现阻止调试器依附。

这是OC是实现方案。那么在swift中该如何使用ptrace呢?

Swift中使用ptrace阻止调试器依附

在OC中main函数如下

int main(int argc, char * argv[]) {
  @autoreleasepool {
      return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  }
}

并且由于OC是完全兼容C的,所以可以直接在main方法中调用ptrace函数,但是Swift该怎么做呢?

首先,在Swift中,main函数不见了,换成了@UIApplicationMain。
@UIApplicationMain可以理解成声明了一个main函数,编译器收到这个指令会自动创建main函数

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
}

由于要在main函数中执行ptrace方法(其实不需要一定在main方法中执行ptrace,在main方法或者main方法执行之前调用ptrace都可以,比如在load中执行),我们需要自定义main函数。

新建main.swift文件。
main.swift文件内容如下

import Foundation
import UIKit

autoreleasepool {
    UIApplicationMain(
        CommandLine.argc,
        UnsafeMutableRawPointer(CommandLine.unsafeArgv)
            .bindMemory(
                to: UnsafeMutablePointer<Int8>.self,
                capacity: Int(CommandLine.argc)),
        nil,
        NSStringFromClass(AppDelegate.self)
    )
}

新建C文件myPtrace

#include "myPtrace.h"
#import <sys/types.h>

typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
#if !defined(PT_DENY_ATTACH)
#define PT_DENY_ATTACH 31
#endif

void disable_gdb() {
    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);
    dlclose(handle);
}

在main.swift中调用

disable_gdb()
autoreleasepool {
    UIApplicationMain(
       ...
    )
}

ptrace攻防战

由于ptrace可以被hook,所以我们可以通过提前hook来防止别人hook,新建一个framework,使这个framework在Link Binary Libraries中靠前,在framework中hook住ptrace,并调用,这样可以防止ptrace被hook。

其实,就算这样,ptrace也可能通过修改二进制导致ptrace失效。

更多相关内容可以参考下文
https://www.jianshu.com/p/9ed2de5e7497

2、监测是否被调试

实现原理

当一个进程被调试时,该进程会有一个标识(P_TRACED)来标记自己正在被调试,这时,可以使用sysctl函数获取到当前进程的相关信息,分析这些信息了解进程是否正在被调试,从而阻止调试。

sysctl监听调试

func checkTracing(){
        let mib = UnsafeMutablePointer<Int32>.allocate(capacity: 4)
        mib[0] = CTL_KERN//内核查看
        mib[1] = KERN_PROC//进程查看
        mib[2] = KERN_PROC_PID//进程ID
        mib[3] = getpid()//获取pid
        var size: Int = MemoryLayout<kinfo_proc>.size
        var info: kinfo_proc? = nil
        /* 函数的返回值若为0时,证明没有错误,其他数字为错误码。 arg1 传入一个数组,该数组中的第一个元素指定本请求定向到内核的哪个子系统。第二个及其后元素依次细化指定该系统的某个部分。 arg2 数组中的元素数目 arg3 一个结构体,指向一个供内核存放该值的缓冲区,存放进程查询结果 arg4 缓冲区的大小 arg5/arg6 为了设置某个新值,arg5参数指向一个大小为arg6参数值的缓冲区。如果不准备指定一个新值,那么arg5应为一个空指针,arg6因为0. */
        sysctl(mib, 4, &info, &size, nil, 0)
        //info.kp_proc.p_flag中存放的是标志位(二进制),在proc.h文件中有p_flag的宏定义,通过&运算可知对应标志位的值是否为0。(若结果值为0则对应标志位为0)。其中P_TRACED为正在跟踪调试过程。
        if (info.unsafelyUnwrapped.kp_proc.p_flag & P_TRACED) > 0 {
            //监听到被调试,退出程序
            exit(1)
        }
    }

由于sysctl是系统函数,也是可以被hook的,为了防止被hook,同ptrace一样,可以在自己写的framework中定时调用该方法来防止被fishhook。

更多

但是加了这两层防护也是不安全的,因为在逆向工程中,可以通过修改macho的二进制来破解ptrace or sysctl,所以通过汇编来调用sysctl/ptrace/exit等函数可能是相对安全的方法。由于笔者水平有限,汇编相关内容在这里就不再讲解了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 在逆向和保护的过程中,总会涉及到反调试和反反调试的问题,这篇文章主要是总结一下几种常见的反调试手段。 当我们上线一...
    含笑州阅读 2,683评论 1 4
  • 一: ptrace 作用 ptrace系统调从名字上看是用于进程跟踪的,它提供了父进程可以观察和控制其子进程执行的...
    ldzSpace阅读 12,271评论 1 26
  • ptrace是一个系统调用。它是一个系统提供的很强大的底层服务。用户层的框架是构建在system call之上的。...
    HF_K阅读 1,761评论 1 2
  • 1. 反调试与反-反调试 1.1 常用反调试 1.1.1 ptrace 为了方便应用软件的开发和调试,从Unix的...
    收纳箱阅读 1,204评论 0 0
  • 当静态分析无法获取足够的信息时,就需要进行动态分析,在 app 运行时,追踪方法调用、查看内存信息。最后找到想要分...
    黑超熊猫zuik阅读 4,400评论 0 54