[转]iOS Anti-Debugging Protections
source-1: http://www.coredump.gr/articles/ios-anti-debugging-protections-part-1/
source-2: http://www.coredump.gr/articles/ios-anti-debugging-protections-part-2/
Many iOS applications use anti-debugging techniques to prevent malicious users from using a debugger to analyze or modify their behavior. In this first part of the iOS anti-debugging series I will describe one of the most commonly used anti-debugging techniques in iOS nowadays and provide ways to bypass it.
Using ptrace with PT_DENY_ATTACH
Ptrace is a system call that is primarily used to trace and debug applications. The ptrace call is defined as:
int ptrace(int request, pid_t pid, caddr_t addr, int data); |
The first argument (request) specifies the operation to perform. All valid operations are defined in /usr/include/sys/ptrace.h. One of the operations is called PT_DENY_ATTACH and has the value 31. When the request is set to that value the application informs the operating system that it doesn’t want to be traced or debugged. Any attempts to trace the process will be denied and the application will receive a segmentation violation.
The following block of code contains an example C program that uses the ptrace call to prevent GDB from debugging it. Currently, GDB is the only debugger that works on iOS devices. The following paragraphs contain an analysis of the protection as well as ways to bypass it.
|
1
2
3
4
5
6
7
8
9
10
11
12
|
int main(int argc, char **argv){ ptrace(PT_DENY_ATTACH, 0, 0, 0); printf("Try to attach to me!"); while (1) { sleep(1); printf("."); fflush(stdout); } return 0;} |
The call to activate the protection is on line 3:
ptrace(PT_DENY_ATTACH, 0, 0, 0); |
When the request is set to PT_DENY_ATTACH all other arguments aren’t used and set to zero.
First, let’s examine the effects of this protection. We will run the application in one terminal and try to attach using GDB in another:
tl0gic:~ mobile$ ./ptraceTry to attach to me!........ |
Now we try to attach with GDB:
tl0gic:~ mobile$ ps ax | grep ptrace2761 s000 S+ 0:00.05 ./ptrace2774 s001 R+ 0:00.01 grep ptracetl0gic:~ mobile$ gdb -p 2761/private/var/mobile/2761: No such file or directoryAttaching to process 2761.Segmentation fault: 11tl0gic:~ mobile$ |
As you can see GDB terminated with a segmentation fault.
Next, let’s try to start the application from GDB:
tl0gic:~ mobile$ gdb ./ptraceReading symbols for shared libraries . done(gdb) runStarting program: /private/var/mobile/ptraceReading symbols for shared libraries ...................... done Program exited with code 055.(gdb) |
The application was terminated with exit code 055.
Bypassing ptrace
In the following paragraphs we will describe two different ways to bypass the ptrace protection. In the first, we will modify the arguments of ptrace to invalidate the call, and in the second we will do a memory patch to replace the ptrace call with NOP instructions.
Method 1 – modifying the arguments to ptrace
First, start GDB with the process we want to debug:
$ gdb ./ptraceThen, setup a breakpoint on ptrace:(gdb) break ptraceFunction "ptrace" not defined.Make breakpoint pending on future shared library load? (y or [n]) y Breakpoint 1 (ptrace) pending. |
Note that GDB complains that ptrace isn’t defined. This is normal just select“”y” as the answer. The next step is to start the process. It will take some time for GDB to load all the symbols. At the end it will notify us that it resolved the ptrace symbol and was able to setup the breakpoint. Once the process is started the breakpoint is hit and we are back at the GDB prompt.
Starting program: /private/var/mobile/ptraceReading symbols for shared libraries ...................... doneBreakpoint 1 at 0x30e6f3a8Pending breakpoint 1 - "ptrace" resolved Breakpoint 1, 0x30e6f3a8 in ptrace ()(gdb) |
Let’s examine the registers. On ARM CPUs the first four registers (r0 to r3) contain the first four arguments to a function call. Since ptrace accepts exactly four arguments we can just print the first four registers to examine the contents of the arguments.
(gdb) info registers r0 r1 r2 r3r0 0x1f 31r1 0x0 0r2 0x0 0r3 0x0 0 |
As you can see, r0 contains the number 31, which is the value of PT_DENY_ATTACH. The other registers are all set to zero. As we discussed above when ptrace is invoked with the request set to PT_DENY_ATTACH all other arguments aren’t used so they are set to zero.
At this point we will replace the first argument with an invalid value. Ptrace will try to execute the invalid request and return an error instead. Most applications don’t really check the return value of ptrace for errors and therefore we can get away with it.
(gdb) set $r0=-1(gdb) continueContinuing.Try to attach to me!..... |
As you can see the application is running with GDB attached ☺
Method 2 - memory patch
The second way is to do a memory patch when the application is running and remove the call to ptrace completely. We will use otool to disassemble the binary and find the address we need to patch. Then, we will load the application in GDB and patch it.
Lets start by disassembling the application and locating the call to ptrace:
$ otool -tV ./ptrace 00002f20 4610 cpy r0, r200002f22 4619 cpy r1, r300002f24 461a cpy r2, r300002f26 e868f000 blx 0x2ff8 ; symbol stub for: _ptrace00002f2a 019ef240 blx 0x24326800002f2e 0100f2c0 blx 0x2c313000002f32 4479 add r1, pc |
From the disassembly above we can see that the call to ptrace in this binary happens at address 0x2f26 (instruction “blx 0x2ff8”). Also, the opcode is 4 bytes long. Therefore, to completely remove the call we need to replace 4 bytes at address 0x2f26 with one or more instructions that don’t do anything (NOP). There are several opcodes for NOP instructions in ARM, in this patch we will use 0xbf00.
First, we will load the application in GDB and examine the disassembly of address 0x2f26 (where the call to ptrace is):
tl0gic:~ mobile$ gdb ./ptraceReading symbols for shared libraries . done(gdb) x/5i 0x2f260x2f26 : blx 0x2ff80x2f2a : movw r1, #158 ; 0x9e0x2f2e : movt r1, #0 ; 0x00x2f32 : add r1, pc0x2f34 : str r0, [sp, #16] |
Then, we will setup a breakpoint in main() and start our application. We need to do that because GDB doesn’t have write access to the process’ memory unless the application is running.
(gdb) b mainBreakpoint 1 at 0x2f0e(gdb) runStarting program: /private/var/mobile/ptraceReading symbols for shared libraries ...................... done Breakpoint 1, 0x00002f0e in main () |
Now that the breakpoint is hit we are back in GDB and we can perform the memory patch:
(gdb) set *(long *)0x2f26 = 0xbf00bf00 |
Note that we are casting the address 0x2f26 to a type of long so that GDB knows how many bytes to write at the address. In this case we know that the call to ptrace is 4 bytes long so we are using a long type which is also 4 bytes. Note that the value we are writing is 0xbf00bf00 and contains two NOPs. We need to use two NOPs because each NOP is two bytes. After we execute the command we will examine the disassembly one more time to verify that we patched the application properly:
(gdb) x/5i 0x2f260x2f26 : nop0x2f28 : nop0x2f2a : movw r1, #158 ; 0x9e0x2f2e : movt r1, #0 ; 0x00x2f32 : add r1, pc(gdb) continueContinuing.Try to attach to me!......... |
As you can see the instruction at address 0x2f26 is a NOP instruction and is followed by another NOP instruction. The call to ptrace is completely gone. We can now use the GDB command “continue” to continue execution.
=============================================================================
In the previous part (iOS Anti-Debugging Protections: Part 1) we discussed about ptrace and how it can be used to prevent a debugger from attaching to a process. This post describes a technique that is commonly used to detect the presence of a debugger. Note that unlike the ptrace technique this method doesn’t prevent a debugger from attaching to a process. Instead, it uses the sysctl function to retrieve information about the process and determine whether it is being debugged. Apple has an article in their Mac Technical Q&As with sample code that uses this method: Detecting the Debugger
The sysctl call is defined as:
int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen); |
The first argument name is an array of integers that describe the type of information we are requesting. Apple describes this name as a “Management Information Base” (MIB) style name in the sysctl man page. The second argument contains the number of integers in the name array. The third and fourth arguments hold the output buffer and the output buffer size respectively. These arguments will be populated with the requested information when the function returns. Arguments five and six are only used when setting information.
The following block of code contains an example C program that uses a sysctl call to determine whether it is being debugged. The next paragraphs contain an analysis of the protection as well as information on how to bypass it.
|
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
|
#include <stdio.h>#include <sys/types.h>#include <unistd.h>#include <sys/sysctl.h>#include <stdlib.h> static int is_debugger_present(void){ 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) { perror("sysctl"); exit(-1); } return ((info.kp_proc.p_flag & P_TRACED) != 0);} int main (int argc, const char * argv[]){ printf("Looping forever"); fflush(stdout); while (1) { sleep(1); if (is_debugger_present()) { printf("Debugger detected! Terminating...\n"); return -1; } printf("."); fflush(stdout); } return 0;} |
The call to sysctl is on line 20:
sysctl(name, 4, &info, &info_size, NULL, 0) |
First, lets analyze the arguments of the sysctl call. The first argument name is initialized as:
name[0] = CTL_KERN;name[1] = KERN_PROC;name[2] = KERN_PROC_PID;name[3] = getpid(); |
The item at index 0 is set to CTL_KERN. This is the top-level name for kernel-specific information. All the available top-level names have a prefix of “CTL_” and are defined in the header file /usr/include/sys/sysctl.h. The item at index 1 is set to KERN_PROC. This indicates that sysctl will return a struct with process entries. The next item KERN_PROC_PID specifies that the target process will be selected based on a process ID (PID). Finally, the last item is the PID of that process.
The second argument of sysctl (size) is set to 4 since this is the total number of items in the name. Arguments three and four are set to the output buffer and its size. The output buffer is a struct of type kinfo_proc which is defined in /usr/include/sys/sysctl.h. The struct contains another struct (kp_proc) of type extern_proc that is defined in /usr/include/sys/proc.h. The kp_proc struct contains information about the process including a flag (p_flag) that describes the process state. All the valid values for p_flag can be found in /usr/include/sys/proc.h. The following block contains some sample values from that file:
#define P_TIMEOUT 0x00000400 /* Timing out during sleep */#define P_TRACED 0x00000800 /* Debugged process being traced */#define P_DISABLE_ASLR 0x00001000 /* Disable ASLR */ |
The P_TRACED value is set when the process is being debugged. The following line of code in the sample program checks if the value is set:
return ((info.kp_proc.p_flag & P_TRACED) != 0); |
Bypassing the sysctl check
This type of check can be bypassed by clearing the contents of the p_flag variable after the call returns. The following paragraphs contain step-by-step instructions on how to accomplish that with the help of GDB.
First, load the application in GDB:
tl0gic:~ mobile$ gdb ./sysctlReading symbols for shared libraries . done(gdb) |
Setup a conditional breakpoint on sysctl:
(gdb) break sysctl if $r1==4 && *(int *)$r0==1 && *(int *)($r0+4)==14 && *(int *)($r0+8)==1 |
This breakpoint will be triggered only if the size argument of sysctl (in $r1) has a value of 4 and the first three items in the name array (at addresses $r0, $r0+4, and $r0+8) are equal to CTL_KERN (1), KERN_PROC (14) and KERN_PROC_PID (1).
Run the process until the breakpoint is hit:
(gdb) runStarting program: /private/var/mobile/sysctlReading symbols for shared libraries ...................... doneLooping foreverBreakpoint 1, 0x35b60672 in sysctl ()(gdb) |
Save the value of $r2, this is the address of output buffer where sysctl will store the process information: (gdb) set $pinfo=$r2
Continue executing until the sysctl call is complete:(gdb) finishRun till exit from #0 0x35b60672 in sysctl ()0x00002ed6 in is_debugger_present ()(gdb) |
Before we continue to the next step we need to setup a breakpoint at the end of sysctl. We will use that breakpoint later to automate this process (don’t worry about the breakpoint condition for now):
(gdb) break *$pc if $pinfo!=-1 |
Now we need to find the exact offset of the p_flag value inside the output buffer. There are two ways to accomplish that:
- Sum the bytes for each of the struct elements that precede the p_flag
- Disassemble the sample application and find how the compiler calculates it.
We will go with the second option. The following block contains the disassembly for the is_debugger_present function:
_is_debugger_present:00002e68 b580 push {r7, lr}00002e6a 466f mov r7, sp00002e6c f5ad7d05 sub.w sp, sp, #532 @ 0x21400002e70 f24010c0 movw r0, 0x1c000002e74 f2c00000 movt r0, 0x000002e78 4478 add r0, pc00002e7a 6800 ldr r0, [r0, #0]00002e7c 6800 ldr r0, [r0, #0]00002e7e 9084 str r0, [sp, #528]00002e80 2001 movs r0, #100002e82 f2c00000 movt r0, 0x000002e86 210e movs r1, #1400002e88 f2c00100 movt r1, 0x000002e8c 2200 movs r2, #000002e8e f2c00200 movt r2, 0x000002e92 f24013ec movw r3, 0x1ec00002e96 f2c00300 movt r3, 0x000002e9a 9304 str r3, [sp, #16]00002e9c 9209 str r2, [sp, #36]00002e9e 9080 str r0, [sp, #512]00002ea0 9181 str r1, [sp, #516]00002ea2 9082 str r0, [sp, #520]00002ea4 f000e8a2 blx 0x2fec @ symbol stub for: _getpid00002ea8 2104 movs r1, #400002eaa f2c00100 movt r1, 0x000002eae ab04 add r3, sp, #1600002eb0 2200 movs r2, #000002eb2 f2c00200 movt r2, 0x000002eb6 f10d0914 add.w r9, sp, #20 @ 0x1400002eba f50d7c00 add.w ip, sp, #512 @ 0x20000002ebe 9083 str r0, [sp, #524]00002ec0 4660 mov r0, ip00002ec2 9203 str r2, [sp, #12]00002ec4 464a mov r2, r900002ec6 f8dd900c ldr.w r9, [sp, #12]00002eca f8cd9000 str.w r9, [sp]00002ece f8cd9004 str.w r9, [sp, #4]00002ed2 f000e894 blx 0x2ffc @ symbol stub for: _sysctl00002ed6 f1100f01 cmn.w r0, #1 @ 0x100002eda d10c bne.n 0x2ef600002edc f24000f1 movw r0, 0xf100002ee0 f2c00000 movt r0, 0x000002ee4 4478 add r0, pc00002ee6 f000e884 blx 0x2ff0 @ symbol stub for: _perror00002eea f64f70ff movw r0, 0xffff00002eee f6cf70ff movt r0, 0xffff00002ef2 f000e878 blx 0x2fe4 @ symbol stub for: _exit00002ef6 f240103a movw r0, 0x13a00002efa f2c00000 movt r0, 0x000002efe 4478 add r0, pc00002f00 6800 ldr r0, [r0, #0]00002f02 9909 ldr r1, [sp, #36]00002f04 f4016100 and.w r1, r1, #2048 @ 0x80000002f08 6800 ldr r0, [r0, #0]00002f0a 9a84 ldr r2, [sp, #528]00002f0c 4290 cmp r0, r200002f0e 9102 str r1, [sp, #8]00002f10 d103 bne.n 0x2f1a00002f12 9802 ldr r0, [sp, #8]00002f14 f50d7d05 add.w sp, sp, #532 @ 0x21400002f18 bd80 pop {r7, pc} |
At 0x2eb6 the base address of the kinfo_proc struct is calculated as $sp+20 and loaded in $r9. Then, at 0x2ec4 the address is copied into $r2 (the third argument of sysctl). Once the sysctl call (at 0x2f02) has returned the p_flag value is loaded as $sp+36. Therefore, the offset of the p_flag is $sp+20-($sp+36) = 16 bytes. However, $r2 contains the address of the kinfo_struct and not the actual contents. To access the value of the p_flag we will have to use a pointer as illustrated below:
(gdb) printf "0x%x\n", *(int *)($pinfo+16)0x5802 |
The value of P_TRACED is 0×800. Therefore, a logical end with the current value should return 0×800 (or 2048 in base 10) when the flag is set:
(gdb) print (*(int *)($pinfo+16) & 0x800)$5 = 2048 |
The flag is correctly set (since we have a debugger attached to the process). The next step is to clear it:
(gdb) set $pflag = (*(int *)($pinfo+16))(gdb) set *(int *)($pinfo+16) = $pflag & ~0x800 |
Let’s print the value one more time to verify that it’s properly cleared:
(gdb) print (*(int *)($pinfo+16) & 0x800)$6 = 0 |
Now that the flag is cleared we can continue executing the process:
(gdb) continueContinuing..Breakpoint 1, 0x35b60672 in sysctl ()(gdb) |
The breakpoint is hit again because the application is running the sysctl check inside a while loop. We need to have GDB execute all the commands we used above every time a breakpoint is triggered. To accomplish that we can use the “commands” gdb command: GDB commands for the sysctl breakpoint:
commands 1silentset $pinfo=$r2continueend |
GDB commands for the breakpoint after sysctl has returned:
commands 2silentset $pflag = (*(int *)($pinfo+16))set *(int *)($pinfo+16) = $pflag & ~0x800set $pinfo=-1continueend |
On the above commands make sure to replace the numbers 1 and 2 with the correct breakpoint numbers. GDB prints the breakpoint number every time a breakpoint is set. We can also use the “info breakpoints” commands to display all the breakpoints.
Now we can resume execution.
(gdb) contContinuing............. |
The application runs without detecting the debugger :)
[转]iOS Anti-Debugging Protections的更多相关文章
- Android/iOS Remote debugging
简单介绍 使用下面方法可以定位webview中的元素,无法定位view中的元素. 原文地址:http://mp.weixin.qq.com/s/y_UfdgjT_pkKgYivJmqt7Q webvi ...
- iOS remote debug & Android remote debug & Chrome & APP
iOS remote debug & Android remote debug & Chrome & APP iOS remote debugging 如何在 iOS 真机上调 ...
- Topics
Topics Introduction (starting with old devices) How to handle a new Firmware How to set up your Mac ...
- [转载] Android逃逸技术汇编
本文转载自: http://blogs.360.cn/360mobile/2016/10/24/android_escape/ 摘 要 传统逃逸技术涉及网络攻防和病毒分析两大领域,网络攻防领域涉 ...
- [原]OS X 10.9 Mavericks - Virtual Serial Port Issues
If want to do iOS kernel debugging on A4 device, first you should install Virtual COM port (VCP) dri ...
- iOS开发笔记11:表单键盘遮挡、浮点数价格格式化显示、省市区选择器、View Debugging
1.表单键盘遮挡 应用场景为一个collectionView上有多个textfield.textView供用户填写信息. 之前输入项较少时,采取的方法比较粗暴,didSelectItemAtIndex ...
- An iOS zero-click radio proximity exploit odyssey
NOTE: This specific issue was fixed before the launch of Privacy-Preserving Contact Tracing in iOS 1 ...
- iOS - YYAdd对UIDevice的拓展
YYKit中对UIDevice的拓展,accumulate knowledge !!! 首先 #include <sys/socket.h> #include <sys/sysctl ...
- IOS 开发教程
http://www.raywenderlich.com/category/ios http://www.raywenderlich.com/50310/storyboards-tutorial-in ...
随机推荐
- Android自己定义组件系列【8】——面膜文字动画
我们掩盖文字动画Flash中非经货共同体共同,由于Android应用程序开发人员做你想要做这个动画在应用程序中去?本文中,我们看的是如何自己的定义ImageView来实现让一张文字图片实现文字的遮罩闪 ...
- S2SH新手框架建立具体过程
S2SH集成框架新手学习总结 第一章:S2SH框架新手搭建准备工作仅仅都须要导入那些文件 第二篇:S2SH框架新手搭建具体过程 版本号信息:Struts2.3+Hibernate4.3.6+Sprin ...
- 关于JavaScript中计算精度丢失的问题
摘要: 由于计算机是用二进制来存储和处理数字,不能精确表示浮点数,而JavaScript中没有相应的封装类来处理浮点数运算,直接计算会导致运算精度丢失. 为了避免产生精度差异,把需要计算的数字升级(乘 ...
- 什么时候需要使用Double? double、float、decimal的区别
原文:什么时候需要使用Double? double.float.decimal的区别 float:浮点型,含字节数为4,32bit,数值范围为-3.4E38~3.4E38(7个有效位) double: ...
- Android Bluetooth Stack: Bluedroid(五岁以下儿童):The analysis of A2DP Source
1. A2DP Introduction The Advanced Audio Distribution Profile (A2DP) defines the protocols and proced ...
- AngularJS html5Mode与ASP.NET MVC路由
AngularJS html5Mode与ASP.NET MVC路由共存 前言 很久之前便听说AngularJS,非常酷,最近也比较火,我也在持续关注这个技术,只是没有认真投入学习.前不久公司找我们部门 ...
- 用javascript把扑克牌理理顺!
打扑克的人都知道,比如斗地主! 我们一般都会按照顺序把随机摸过来的牌从小到大的顺序在手上理整齐(记得小时候打牌两副牌手都抓不过来),这篇随笔就是想通过实现这个功能来熟悉下js中排序数组等相关知识. 用 ...
- atitit.ajax bp dwr 3.该票据安排使用的流量汇总 VO9o.....
atitit.ajax bp dwr 3.该票据安排使用的流量汇总 VO9o..... 1. 安装配置 1 1.1. 下载 dwr.jar 1M 1 1.2. 配置注解方式..web.xml 1 2 ...
- 百度地图 api 功能封装类 (ZMap.js) 本地搜索,范围查找实例
百度地图 api 功能封装类 (ZMap.js) 本地搜索,范围查找实例 相关说明 1. 界面查看: 吐槽贴:百度地图 api 封装 的实用功能 [源码下载] 2. 功能说明: 百度地图整合功能分享修 ...
- 开源的.Net ORM微型框架SuperHelper
SuperHelper——灵活通用的.开源的.Net ORM微型框架 SuperHelper是博主利用业余时间编写的一个ORM微型框架,除了可以提高开发效率,与其它ORM框架相比,博主更加喜欢Supe ...