iOS反調(diào)試初步探索

為了防止項(xiàng)目被逆向調(diào)試,需要對代碼增加反調(diào)試相關(guān)功能。反調(diào)試的策略主要分為兩種,一種是禁止調(diào)試,一種是監(jiān)測是否被調(diào)試

1、禁止調(diào)試

ptrace簡介

要想知道怎么禁止調(diào)試,需要先知道什么是ptrace

ptrace是C標(biāo)準(zhǔn)庫中的一個(gè)函數(shù),該函數(shù)是一個(gè)系統(tǒng)調(diào)用方法 , 可以監(jiān)視進(jìn)程執(zhí)行 , 查看 / 更改 被監(jiān)視進(jìn)程的 內(nèi)存 和 寄存器 情況 , 常用于斷點(diǎn)調(diào)試。

ptrace方法如下

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

可以看到ptrace有四個(gè)參數(shù):
enum __ptrace_request request:指定ptrace要執(zhí)行的命令。
pid_t pid: 跟蹤進(jìn)程id。
void *addr: 進(jìn)程的某個(gè)內(nèi)存地址。
void *data: 存放讀取出的或者要寫入的數(shù)據(jù)。

第一個(gè)參數(shù)可以傳入以下值

PTRACE_TRACEME,   本進(jìn)程被其父進(jìn)程所跟蹤。其父進(jìn)程應(yīng)該希望跟蹤子進(jìn)程
PTRACE_PEEKTEXT,  從內(nèi)存地址中讀取一個(gè)字節(jié),內(nèi)存地址由addr給出
PTRACE_PEEKDATA,  同上
PTRACE_PEEKUSER,  可以檢查用戶態(tài)內(nèi)存區(qū)域(USER area),從USER區(qū)域中讀取一個(gè)字節(jié),偏移量為addr
PTRACE_POKETEXT,  往內(nèi)存地址中寫入一個(gè)字節(jié)。內(nèi)存地址由addr給出
PTRACE_POKEDATA,  往內(nèi)存地址中寫入一個(gè)字節(jié)。內(nèi)存地址由addr給出
PTRACE_POKEUSER,  往USER區(qū)域中寫入一個(gè)字節(jié),偏移量為addr
PTRACE_GETREGS,    讀取寄存器
PTRACE_GETFPREGS,  讀取浮點(diǎn)寄存器
PTRACE_SETREGS,  設(shè)置寄存器
PTRACE_SETFPREGS,  設(shè)置浮點(diǎn)寄存器
PTRACE_CONT,    重新運(yùn)行
PTRACE_SYSCALL,  重新運(yùn)行
PTRACE_SINGLESTEP,  設(shè)置單步執(zhí)行標(biāo)志
PTRACE_ATTACH,追蹤指定pid的進(jìn)程
PTRACE_DETACH,  結(jié)束追蹤

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

那么怎么用ptrace來實(shí)現(xiàn)反調(diào)試呢
其實(shí)除了上述參數(shù),蘋果增加了一個(gè)PT_DENY_ATTACH選項(xiàng),這個(gè)參數(shù)用來告訴系統(tǒng),阻止調(diào)試器依附。

OC中使用ptrace阻止調(diào)試器依附

在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運(yùn)行環(huán)境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是一個(gè)計(jì)算機(jī)函數(shù),功能是以指定模式打開指定的動(dòng)態(tài)鏈接庫文件,并返回一個(gè)句柄給dlsym的調(diào)用進(jìn)程。
dlsym功能是根據(jù)動(dòng)態(tài)鏈接庫操作句柄與符號,返回符號對應(yīng)的地址。
這樣,拿到了ptrace,第一個(gè)參數(shù)傳入PT_DENY_ATTACH,實(shí)現(xiàn)阻止調(diào)試器依附。

這是OC是實(shí)現(xiàn)方案。那么在swift中該如何使用ptrace呢?

Swift中使用ptrace阻止調(diào)試器依附

在OC中main函數(shù)如下

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

并且由于OC是完全兼容C的,所以可以直接在main方法中調(diào)用ptrace函數(shù),但是Swift該怎么做呢?

首先,在Swift中,main函數(shù)不見了,換成了@UIApplicationMain。
@UIApplicationMain可以理解成聲明了一個(gè)main函數(shù),編譯器收到這個(gè)指令會(huì)自動(dòng)創(chuàng)建main函數(shù)

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
}

由于要在main函數(shù)中執(zhí)行ptrace方法(其實(shí)不需要一定在main方法中執(zhí)行ptrace,在main方法或者main方法執(zhí)行之前調(diào)用ptrace都可以,比如在load中執(zhí)行),我們需要自定義main函數(shù)。

新建main.swift文件。
main.swift文件內(nèi)容如下

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中調(diào)用

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

ptrace攻防戰(zhàn)

由于ptrace可以被hook,所以我們可以通過提前hook來防止別人hook,新建一個(gè)framework,使這個(gè)framework在Link Binary Libraries中靠前,在framework中hook住ptrace,并調(diào)用,這樣可以防止ptrace被hook。

其實(shí),就算這樣,ptrace也可能通過修改二進(jìn)制導(dǎo)致ptrace失效。

更多相關(guān)內(nèi)容可以參考下文
http://www.itdecent.cn/p/9ed2de5e7497

2、監(jiān)測是否被調(diào)試

實(shí)現(xiàn)原理

當(dāng)一個(gè)進(jìn)程被調(diào)試時(shí),該進(jìn)程會(huì)有一個(gè)標(biāo)識(P_TRACED)來標(biāo)記自己正在被調(diào)試,這時(shí),可以使用sysctl函數(shù)獲取到當(dāng)前進(jìn)程的相關(guān)信息,分析這些信息了解進(jìn)程是否正在被調(diào)試,從而阻止調(diào)試。

sysctl監(jiān)聽調(diào)試

func checkTracing(){
        let mib = UnsafeMutablePointer<Int32>.allocate(capacity: 4)
        mib[0] = CTL_KERN//內(nèi)核查看
        mib[1] = KERN_PROC//進(jìn)程查看
        mib[2] = KERN_PROC_PID//進(jìn)程ID
        mib[3] = getpid()//獲取pid
        var size: Int = MemoryLayout<kinfo_proc>.size
        var info: kinfo_proc? = nil
        /* 函數(shù)的返回值若為0時(shí),證明沒有錯(cuò)誤,其他數(shù)字為錯(cuò)誤碼。 arg1 傳入一個(gè)數(shù)組,該數(shù)組中的第一個(gè)元素指定本請求定向到內(nèi)核的哪個(gè)子系統(tǒng)。第二個(gè)及其后元素依次細(xì)化指定該系統(tǒng)的某個(gè)部分。 arg2 數(shù)組中的元素?cái)?shù)目 arg3 一個(gè)結(jié)構(gòu)體,指向一個(gè)供內(nèi)核存放該值的緩沖區(qū),存放進(jìn)程查詢結(jié)果 arg4 緩沖區(qū)的大小 arg5/arg6 為了設(shè)置某個(gè)新值,arg5參數(shù)指向一個(gè)大小為arg6參數(shù)值的緩沖區(qū)。如果不準(zhǔn)備指定一個(gè)新值,那么arg5應(yīng)為一個(gè)空指針,arg6因?yàn)?. */
        sysctl(mib, 4, &info, &size, nil, 0)
        //info.kp_proc.p_flag中存放的是標(biāo)志位(二進(jìn)制),在proc.h文件中有p_flag的宏定義,通過&運(yùn)算可知對應(yīng)標(biāo)志位的值是否為0。(若結(jié)果值為0則對應(yīng)標(biāo)志位為0)。其中P_TRACED為正在跟蹤調(diào)試過程。
        if (info.unsafelyUnwrapped.kp_proc.p_flag & P_TRACED) > 0 {
            //監(jiān)聽到被調(diào)試,退出程序
            exit(1)
        }
    }

由于sysctl是系統(tǒng)函數(shù),也是可以被hook的,為了防止被hook,同ptrace一樣,可以在自己寫的framework中定時(shí)調(diào)用該方法來防止被fishhook。

更多

但是加了這兩層防護(hù)也是不安全的,因?yàn)樵谀嫦蚬こ讨?,可以通過修改macho的二進(jìn)制來破解ptrace or sysctl,所以通過匯編來調(diào)用sysctl/ptrace/exit等函數(shù)可能是相對安全的方法。由于筆者水平有限,匯編相關(guān)內(nèi)容在這里就不再講解了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在逆向和保護(hù)的過程中,總會(huì)涉及到反調(diào)試和反反調(diào)試的問題,這篇文章主要是總結(jié)一下幾種常見的反調(diào)試手段。 當(dāng)我們上線一...
    含笑州閱讀 2,943評論 1 4
  • 一: ptrace 作用 ptrace系統(tǒng)調(diào)從名字上看是用于進(jìn)程跟蹤的,它提供了父進(jìn)程可以觀察和控制其子進(jìn)程執(zhí)行的...
    ldzSpace閱讀 13,134評論 1 26
  • ptrace是一個(gè)系統(tǒng)調(diào)用。它是一個(gè)系統(tǒng)提供的很強(qiáng)大的底層服務(wù)。用戶層的框架是構(gòu)建在system call之上的。...
    HF_K閱讀 1,975評論 1 2
  • 1. 反調(diào)試與反-反調(diào)試 1.1 常用反調(diào)試 1.1.1 ptrace 為了方便應(yīng)用軟件的開發(fā)和調(diào)試,從Unix的...
    收納箱閱讀 1,407評論 0 0
  • 當(dāng)靜態(tài)分析無法獲取足夠的信息時(shí),就需要進(jìn)行動(dòng)態(tài)分析,在 app 運(yùn)行時(shí),追蹤方法調(diào)用、查看內(nèi)存信息。最后找到想要分...
    黑超熊貓zuik閱讀 4,594評論 0 54

友情鏈接更多精彩內(nèi)容