现在的位置: 首页 > 综合 > 正文

iOS Anti-Debugging Protections

2014年12月04日 ⁄ 综合 ⁄ 共 9232字 ⁄ 字号 评论关闭

from:http://www.coredump.gr/articles/ios-anti-debugging-protections-part-2/

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 ./sysctl
Reading
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)
run
Starting
program: /private/var/mobile/sysctl
Reading
symbols for shared libraries ...................... done
Looping
forever
Breakpoint
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)
finish
Run
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:

  1. Sum the bytes for each of the struct elements that precede the p_flag
  2. 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, sp
00002e6c   
f5ad7d05    sub.w   sp, sp, #532    @ 0x214
00002e70   
f24010c0    movw    r0, 0x1c0
00002e74   
f2c00000    movt    r0, 0x0
00002e78       
4478    add r0, pc
00002e7a       
6800    ldr r0, [r0, #0]
00002e7c       
6800    ldr r0, [r0, #0]
00002e7e       
9084    str r0, [sp, #528]
00002e80       
2001    movs    r0, #1
00002e82   
f2c00000    movt    r0, 0x0
00002e86       
210e    movs    r1, #14
00002e88   
f2c00100    movt    r1, 0x0
00002e8c       
2200    movs    r2, #0
00002e8e   
f2c00200    movt    r2, 0x0
00002e92   
f24013ec    movw    r3, 0x1ec
00002e96   
f2c00300    movt    r3, 0x0
00002e9a       
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: _getpid
00002ea8       
2104    movs    r1, #4
00002eaa   
f2c00100    movt    r1, 0x0
00002eae       
ab04    add r3, sp, #16
00002eb0       
2200    movs    r2, #0
00002eb2   
f2c00200    movt    r2, 0x0
00002eb6   
f10d0914    add.w   r9, sp, #20 @ 0x14
00002eba   
f50d7c00    add.w   ip, sp, #512    @ 0x200
00002ebe       
9083    str r0, [sp, #524]
00002ec0       
4660    mov r0, ip
00002ec2       
9203    str r2, [sp, #12]
00002ec4       
464a    mov r2, r9
00002ec6   
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: _sysctl
00002ed6   
f1100f01    cmn.w   r0, #1  @ 0x1
00002eda       
d10c    bne.n   0x2ef6
00002edc   
f24000f1    movw    r0, 0xf1
00002ee0   
f2c00000    movt    r0, 0x0
00002ee4       
4478    add r0, pc
00002ee6   
f000e884    blx 0x2ff0  @ symbol stub for: _perror
00002eea   
f64f70ff    movw    r0, 0xffff
00002eee   
f6cf70ff    movt    r0, 0xffff
00002ef2   
f000e878    blx 0x2fe4  @ symbol stub for: _exit
00002ef6   
f240103a    movw    r0, 0x13a
00002efa   
f2c00000    movt    r0, 0x0
00002efe       
4478    add r0, pc
00002f00       
6800    ldr r0, [r0, #0]
00002f02       
9909    ldr r1, [sp, #36]
00002f04   
f4016100    and.w   r1, r1, #2048   @ 0x800
00002f08       
6800    ldr r0, [r0, #0]
00002f0a       
9a84    ldr r2, [sp, #528]
00002f0c       
4290    cmp r0, r2
00002f0e       
9102    str r1, [sp, #8]
00002f10       
d103    bne.n   0x2f1a
00002f12       
9802    ldr r0, [sp, #8]
00002f14   
f50d7d05    add.w   sp, sp, #532    @ 0x214
00002f18       
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)
continue
Continuing.
.
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
1
silent
set
$pinfo=$r2
continue
end

GDB commands for the breakpoint after sysctl has returned:

commands
2
silent
set
$pflag = (*(int *)($pinfo+16))
set
*(int *)($pinfo+16) = $pflag & ~0x800
set
$pinfo=-1
continue
end

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)
cont
Continuing.
............

The application runs without detecting the debugger :)

抱歉!评论已关闭.