关于反调试&反反调试那些事

前言

在逆向和保护的过程中,总会涉及到反调试和反反调试的问题,这篇文章主要是总结一下几种常见的反调试手段以及反反调试的方法。

反调试

ptrace

为了方便应用软件的开发和调试,从Unix的早期版本开始就提供了一种对运行中的进程进行跟踪和控制的手段,那就是系统调用ptrace()
通过ptrace可以对另一个进程实现调试跟踪,同时ptrace还提供了一个非常有用的参数那就是PT_DENY_ATTACH,这个参数用来告诉系统,阻止调试器依附。

所以最常用的反调试方案就是通过调用ptrace来实现反调试。

1
2
3
4
5
6
7
8
9
10
11
#ifndef PT_DENY_ATTACH
#define PT_DENY_ATTACH 31
#endif

typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);

ptrace(PT_DENY_ATTACH, 0, 0, 0);

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);

sysctl

当一个进程被调试的时候,该进程会有一个标记来标记自己正在被调试,所以可以通过sysctl去查看当前进程的信息,看有没有这个标记位即可检查当前调试状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BOOL isDebuggerPresent(){
int name[4]; //指定查询信息的数组

struct kinfo_proc info; //查询的返回结果
size_t info_size = sizeof(info);

info.kp_proc.p_flag = 0;

name[0] = CTL_KERN;
name[1] = KERN_PROC;
name[2] = KERN_PROC_PID;
name[3] = getpid();

if(sysctl(name, 4, &info, &info_size, NULL, 0) == -1){
NSLog(@"sysctl error ...");
return NO;
}

return ((info.kp_proc.p_flag & P_TRACED) != 0);
}

检测到调试器就退出,或者制造崩溃,或者隐藏工程啥的,当然也可以定时去查看有没有这个标记。

syscall

为从实现从用户态切换到内核态,系统提供了一个系统调用函数syscall,上面讲到的ptrace也是通过系统调用去实现的。

Kernel Syscalls这里可以找到ptrace对应的编号。

1
26. ptrace               801e812c T

所以如下的调用等同于调用ptrace:

1
syscall(26,31,0,0,0);

arm

syscall是通过软中断来实现从用户态到内核态,也可以通过汇编svc调用来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifdef __arm__
asm volatile(
"mov r0,#31\n"
"mov r1,#0\n"
"mov r2,#0\n"
"mov r12,#26\n"
"svc #80\n"

);
#endif
#ifdef __arm64__
asm volatile(
"mov x0,#26\n"
"mov x1,#31\n"
"mov x2,#0\n"
"mov x3,#0\n"
"mov x16,#0\n"
"svc #128\n"
);
#endif

下面几种可能在实际中用到的比较少,不过也可以尝试一下。

SIGSTOP

通过捕获系统SIGSTOP信号来判断。

1
2
3
4
5
6
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGSTOP, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(source, ^{
NSLog(@"SIGSTOP!!!");
exit(0);
});
dispatch_resume(source);

task_get_exception_ports

获取异常端口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct macosx_exception_info{
exception_mask_t masks[EXC_TYPES_COUNT];
mach_port_t ports[EXC_TYPES_COUNT];
exception_behavior_t behaviors[EXC_TYPES_COUNT];
thread_state_flavor_t flavors[EXC_TYPES_COUNT];
mach_msg_type_number_t cout;
};

struct macosx_exception_info *info = malloc(sizeof(struct macosx_exception_info));
task_get_exception_ports(mach_task_self(),
EXC_MASK_ALL,
info->masks,
&info->cout,
info->ports,
info->behaviors,
info->flavors);

for(uint32_t i = 0; i < info->cout; i ++){
if(info->ports[i] != 0 || info->flavors[i] == THREAD_STATE_NONE){
NSLog(@"debugger detected via exception ports (null port)!\n");
}
}

isatty

1
2
3
if (isatty(1)) {
NSLog(@"Being Debugged isatty");
}

ioctl

1
2
3
if (!ioctl(1, TIOCGWINSZ)) {
NSLog(@"Being Debugged ioctl");
}

反反调试(Tweak)

了解了几种不同的反调试的方法,那么就可以根据几种常用的反调试方法来反反调试。

这里主要针对ptracesysctlsyscall来反反调试,做法就很简单了,hook函数,判断参数,返回结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#import <substrate.h>
#import <sys/sysctl.h>

static int (*orig_ptrace) (int request, pid_t pid, caddr_t addr, int data);
static int my_ptrace (int request, pid_t pid, caddr_t addr, int data){
if(request == 31){
NSLog(@"[AntiAntiDebug] - ptrace request is PT_DENY_ATTACH");
return 0;
}
return orig_ptrace(request,pid,addr,data);
}

static void* (*orig_dlsym)(void* handle, const char* symbol);
static void* my_dlsym(void* handle, const char* symbol){
if(strcmp(symbol, "ptrace") == 0){
NSLog(@"[AntiAntiDebug] - dlsym get ptrace symbol");
return (void*)my_ptrace;
}
return orig_dlsym(handle, symbol);
}

static int (*orig_sysctl)(int * name, u_int namelen, void * info, size_t * infosize, void * newinfo, size_t newinfosize);
static int my_sysctl(int * name, u_int namelen, void * info, size_t * infosize, void * newinfo, size_t newinfosize){
int ret = orig_sysctl(name,namelen,info,infosize,newinfo,newinfosize);
if(namelen == 4 && name[0] == 1 && name[1] == 14 && name[2] == 1){
struct kinfo_proc *info_ptr = (struct kinfo_proc *)info;
if(info_ptr && (info_ptr->kp_proc.p_flag & P_TRACED) != 0){
NSLog(@"[AntiAntiDebug] - sysctl query trace status.");
info_ptr->kp_proc.p_flag ^= P_TRACED;
if((info_ptr->kp_proc.p_flag & P_TRACED) == 0){
NSLog(@"[AntiAntiDebug] trace status reomve success!");
}
}
}
return ret;
}

static void* (*orig_syscall)(int code, va_list args);
static void* my_syscall(int code, va_list args){
int request;
va_list newArgs;
va_copy(newArgs, args);
if(code == 26){
request = (long)args;
if(request == 31){
NSLog(@"[AntiAntiDebug] - syscall call ptrace, and request is PT_DENY_ATTACH");
return nil;
}
}
return (void*)orig_syscall(code, newArgs);
}

%ctor{
MSHookFunction((void *)MSFindSymbol(NULL,"_ptrace"),(void*)my_ptrace,(void**)&orig_ptrace);
MSHookFunction((void *)dlsym,(void*)my_dlsym,(void**)&orig_dlsym);
MSHookFunction((void *)sysctl,(void*)my_sysctl,(void**)&orig_sysctl);
MSHookFunction((void *)syscall,(void*)my_syscall,(void**)&orig_syscall);

NSLog(@"[AntiAntiDebug] Module loaded!!!");
}

反反调试(lldb)

通过lldb下断点,然后修改参数,或者直接返回也可以达到反反调试的效果。不过这里讲的是通过python脚本把过程自动化的一种方法。

为了方便直接使用facebookchisel来增加脚本。

新建一个文件夹,然后使用

1
2
commandsDirectory = os.path.join(lldbHelperDir, '文件名')
loadCommandsInDirectory(commandsDirectory)

加载即可。

下面是python代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
反反调试脚本,过了反调试后记得:
aadebug -d
否则会很卡,如果有定时器定时检测,建议写tweak
"""

import lldb
import fblldbbase as fb
import fblldbobjcruntimehelpers as objc

def lldbcommands():
return [
AMAntiAntiDebug()
]

class AMAntiAntiDebug(fb.FBCommand):
def name(self):
return 'aadebug'

def description(self):
return "anti anti debug ptrace syscall sysctl"

def options(self):
return [
fb.FBCommandArgument(short='-d', long='--disable', arg='disable', boolean=True, default=False, help='disable anti anti debug.')
]

def run(self, arguments, options):
if options.disable:
target = lldb.debugger.GetSelectedTarget()
target.BreakpointDelete(self.ptrace.id)
target.BreakpointDelete(self.syscall.id)
target.BreakpointDelete(self.sysctl.id)
print "anti anti debug is disabled!!!"
else:
self.antiPtrace()
self.antiSyscall()
self.antiSysctl()
print "anti anti debug finished!!!"

def antiPtrace(self):
ptrace = lldb.debugger.GetSelectedTarget().BreakpointCreateByName("ptrace")
if is64Bit():
ptrace.SetCondition('$x0==31')
else:
ptrace.SetCondition('$r0==31')
ptrace.SetScriptCallbackFunction('sys.modules[\'' + __name__ + '\'].ptrace_callback')
self.ptrace = ptrace

def antiSyscall(self):
syscall = lldb.debugger.GetSelectedTarget().BreakpointCreateByName("syscall")
if is64Bit():
syscall.SetCondition('$x0==26 && *(int *)$sp==31')
else:
syscall.SetCondition('$r0==26 && $r1==31')
syscall.SetScriptCallbackFunction('sys.modules[\'' + __name__ + '\'].syscall_callback')
self.syscall = syscall

def antiSysctl(self):
sysctl = lldb.debugger.GetSelectedTarget().BreakpointCreateByName("sysctl")
if is64Bit():
sysctl.SetCondition('$x1==4 && *(int *)$x0==1 && *(int *)($x0+4)==14 && *(int *)($x0+8)==1')
else:
sysctl.SetCondition('$r1==4 && *(int *)$r0==1 && *(int *)($r0+4)==14 && *(int *)($r0+8)==1')
sysctl.SetScriptCallbackFunction('sys.modules[\'' + __name__ + '\'].sysctl_callback')
self.sysctl = sysctl

def antiExit(self):
self.exit = lldb.debugger.GetSelectedTarget().BreakpointCreateByName("exit")
exit.SetScriptCallbackFunction('sys.modules[\'' + __name__ + '\'].exit_callback')

#暂时只考虑armv7和arm64
def is64Bit():
arch = objc.currentArch()
if arch == "arm64":
return True
return False

def ptrace_callback(frame, bp_loc, internal_dict):
print "find ptrace"
register = "x0"
if not is64Bit():
register = "r0"
frame.FindRegister(register).value = "0"
lldb.debugger.HandleCommand('continue')

def syscall_callback(frame, bp_loc, internal_dict):
print "find syscall"
#不知道怎么用api修改sp指向的内容QAQ
lldb.debugger.GetSelectedTarget().GetProcess().SetSelectedThread(frame.GetThread())
if is64Bit():
lldb.debugger.HandleCommand('memory write "$sp" 0')
else:
lldb.debugger.HandleCommand('register write $r1 0')
lldb.debugger.HandleCommand('continue')

def sysctl_callback(frame, bp_loc, internal_dict):
module = frame.GetThread().GetFrameAtIndex(1).GetModule()
currentModule = lldb.debugger.GetSelectedTarget().GetModuleAtIndex(0)
if module == currentModule:
print "find sysctl"
register = "x2"
if not is64Bit():
register = "r2"
frame.FindRegister(register).value = "0"
lldb.debugger.HandleCommand('continue')

def exit_callback(frame, bp_loc, internal_dict):
print "find exit"
lldb.debugger.GetSelectedTarget().GetProcess().SetSelectedThread(frame.GetThread())
lldb.debugger.HandleCommand('thread return')
lldb.debugger.HandleCommand('continue')

总结

反调试的方式有多种,可以通过:

1
ebugserver *:1234 -x auto /path/to/executable

启动附加,然后断点测试。

本文代码地址:

AntiAntiDebug

有问题或者有更好的方法欢迎一起探讨。

AloneMonkey wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!