常见的iOS应用安全检测(整理)

对应用的安全性防护有越狱检测、注入检测、调试检测,函数名替换、代码逻辑混淆等,那么对于安全研究人员来说,没有严格意义上的防爆死守,只要是应用,他就会有被攻击的点,那么为了有效地、一定程度上地对应用被逆向分析破解造成一些困扰,最好是做全套,那么这篇文章简单的记录一些常见的越狱、注入等的检测以及一些很常见的anti反调试

越狱检测

0x0 _dyld_image_count&&_dyld_get_image_name

- _dyld_image_count返回dyld映射的当前的image数
- _dyld_get_image_name返回image名称,可以通过它来检索dyld的image名称

可以通过检测目录中是否有MobileSubstrate.dylib来确认是否越狱

void dylibCheck() {
uint32_t count = _dyld_image_count();
char *substrate = "/Library/MobileSubstrate/MobileSubstrate.dylib";
for(uint32_t i = 0; i < count; i++) {
const char *dyld = _dyld_get_image_name(i);
if (strcmp(dyld,substrate)==0) { 
NSLog(@"该设备已越狱"); 
        }
    } 
}

0x1 fork

fork()可以创建一个新的进程,由于在非越狱设备上不能够使用fork()系统调用,所以只能在越狱设备上使用

1
2
int pid = fork();
if(pid>=0) { NSLog(@”Jailbroken”); }

0x2 URL scheme

大部分越狱设备都会安装Cydia,当安装Cydia时会在设备上注册一个URL scheme,所以有一种检测方案是从应用程序调用Cydia的URL scheme并检查是否返回成功。

1
[NSURL URLWithString:@”cydia://package/com.example.package”]

0x3 opendir()

使用opendir()进行特定目录的访问, 并返回与之相关联的目录流,在GD框架运行opendir(/dev)可以判断是否可以访问/dev目录,以此判断设备是否越狱因为在非越狱设备上将返回NULL

0x4 stat() && lstat()

stat() && lstat()是两个系统调用函数,我们可以通过他们来判断是否安装了以下一部分常用越狱工具

1
2
3
4
5
6
7
8
9
10
11
12
13
/private/var/stash
/private/var/lib/apt
/private/var/lib/cydia
/usr/libexec/cydia
/Applications/Icy.app
/bin/bash
/private/var/tmp/cydia.log
/usr/libexec/sftp-server
/Applications/Loader.app
/Applications/Cydia.app
/usr/sbin/sshd
......

当然还有fopen和NSFileManager

通过Symbolic Link我们可以来检查一些特殊的链接从而判断是否被越狱

1
2
3
4
5
6
7
/Applications
/var/stash/Library/Ringtones
/var/stash/usr/include
/var/stash/Library/Wallpaper
/var/stash/usr/libexec
/var/stash/usr/share
/var/stash/usr/arm-apple-darwin9
1
2
3
4
struct stat s;
if (lstat("/Applications", &s) != 0) {
if(s.st_mode & S_IFLNK) { NSLog(@”Jailbroken”);
} }

动态检测

动态调试是逆向工程和分析各种平台软件的常见方法,他可以允许攻击者控制和修改运行时的局部变量或者对目标函数进行跟踪调试以便达到自己的一些目的。
当然出于保护我们的应用程序给攻击者在一定程度上造成一些阻碍,我们有一些常规的手段来检测它们(ps:常规的一些检测如果懂原理的话当然很好绕过so攻与防可以互相进步🙄)

0x0 注入检测

Mac可以通过DYLD_INSERT_LIBRARIES环境变量进行动态库的注入,通过设置可执行文件的header部分为_RESTRICT,__restrict,可以使dyld忽略DYLD_INSERT_LIBRARIES环境变量,从而达到反注入的要求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if ( removedCount != 0 ) {
dyld::log("dyld: DYLD_ environment variables being ignored because ");
switch (sRestrictedReason) {
case restrictedNot:
break;
case restrictedBySetGUid:
dyld::log("main executable (%s) is setuid or setgid\n", sExecPath);
break;
case restrictedBySegment:
dyld::log("main executable (%s) has __RESTRICT/__restrict section\n", sExecPath);
break;
case restrictedByEntitlements:
dyld::log("main executable (%s) is code signed with entitlements\n", sExecPath);
break;
}
}

具体做法是在Xcode的Other Linker Flags添加-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
,编译后的可执行文件中就可以看到
此处应有machoview的图

0x1 反动态调试

0x1.1 ptrace

ptrace也算是一个挺大众的手段了,他是一个系统调用,提供了一个父进程可以通过该机制观察和控制另一个进程的执行的机制,但在iOS中以另一种方法调用,可以阻止调试器对目标进程进行跟踪。

1
2
3
4
5
6
7
8
9
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* handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
ptrace_ptr_t ptrace_ptr = dlsym(handle, "ptrace");
ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);
dlclose(handle);

这是在swift上的实现方式

0x1.2 sysctl

sysctl提供了一些显示进程统计数据的变量,主要可以用来检索有关进程的信息,从而确定应用是否正在被调试,但不会阻止调试器附加到现有的进程

1
2
3
4
5
6
7
8
int mib[4];
struct kinfo_proc info;
size_t info_size = sizeof(info); info.kp_proc.p_flag = 0;
mib[0] = CTL_KERN; mib[1] = KERN_PROC; mib[2] = KERN_PROC_PID; mib[3] = getpid();
if (sysctl(mib, 4, &info, &info_size, NULL, 0) == -1) {
perror("perror sysctl"); exit(-1);
}
return ((info.kp_proc.p_flag & P_TRACED) != 0);

0x1.3 syscall

还有一种系统调用的ptrace,我们可以通过syscall(26,31,0,0)来调用ptrace

0x1.4 isatty

还有一种确定lldb是否附加的方法就是isatty,如果文件描述符附加到控制台,就返回1,否则返回0

1
2
3
4
5
if (isatty(1)) {
NSLog(@"Being Debugged isatty");
}else{
NSLog(@"isatty() bypassed");
}

还有一种通过系统调用实现的方法就是ioctl

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

0x1.5 task_get_exception_ports

task_get_exception_ports是一个在用户态可以使用的task API,可以查询task的异常端口,而mach的异常消息都会发送到异常端口,所以我们通过它来验证调试器是否设置了这样的端口

1
2
3
4
5
6
7
8
9
10
11
struct ios_execp_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 count;
};
struct ios_execp_info *info = malloc(sizeof(struct ios_execp_info));
kern_return_t kr = task_get_exception_ports(mach_task_self(), EXC_MASK_ALL, info->masks, &info->count, info- >ports, info->behaviors, info->flavors);
for (int i = 0; i < info->count; i++) {
if (info->ports[i] !=0 || info->flavors[i] == THREAD_STATE_NONE) { NSLog(@"Being debugged... task_get_exception_ports");
}else{
NSLog(@"task_get_exception_ports bypassed");
}
}

0x1.6 anti IDA

mach-o文件格式详细的知道了加载器是如何加载文件到内存的,但是iOS没有完全按照格式来加载,对可执行文件合法性的验证只是做到了segment一层,对于
section一层并没有验证,而ida则完全依靠mach-o的格式来分析文件,so,稍作修改便可以导致ida不能加载而iOS却能正常运行

文章目录
  1. 1. 越狱检测
    1. 1.1. 0x0 _dyld_image_count&&_dyld_get_image_name
    2. 1.2. 0x1 fork
    3. 1.3. 0x2 URL scheme
    4. 1.4. 0x3 opendir()
    5. 1.5. 0x4 stat() && lstat()
    6. 1.6. 0x5 Symbolic Link
  2. 2. 动态检测
    1. 2.1. 0x0 注入检测
    2. 2.2. 0x1 反动态调试
      1. 2.2.1. 0x1.1 ptrace
      2. 2.2.2. 0x1.2 sysctl
      3. 2.2.3. 0x1.3 syscall
      4. 2.2.4. 0x1.4 isatty
      5. 2.2.5. 0x1.5 task_get_exception_ports
      6. 2.2.6. 0x1.6 anti IDA
|