iOS 攻防(二)ptrace
在破解一款App
的时候,在实际破解之前肯定是在做调试。LLDB
之所以能附加进程时因为debugserver
,而debugserver
附加是通过ptrace
函数来trace process
的。
ptrace
是系统函数,此函数提供一个进程去监听和控制另一个进程,并且可以检测被控制进程的内存和寄存器里面的数据。ptrace
可以用来实现断点调试和系统调用跟踪。
一、反调试ptrace
在iOS
中#import <sys/ptrace.h>
头文件不能直接导入,所以需要我们自己导出头文件引入调用。当然也可以声明ptrace
函数直接调用。
1.1 ptrace 头文件
- 直接创建一个
macOS
程序导入#import <sys/ptrace.h>
头文件,点进去拷贝生成一个.h
文件就可以了:
/*
* Copyright (c) 2000-2005 Apple Computer, Inc. All rights reserved.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. The rights granted to you under the License
* may not be used to create, or enable the creation or redistribution of,
* unlawful or unlicensed copies of an Apple operating system, or to
* circumvent, violate, or enable the circumvention or violation of, any
* terms of an Apple operating system software license agreement.
*
* Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
*/
/* Copyright (c) 1995 NeXT Computer, Inc. All Rights Reserved */
/*-
* Copyright (c) 1984, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* @(#)ptrace.h 8.2 (Berkeley) 1/4/94
*/
#ifndef _SYS_PTRACE_H_
#define _SYS_PTRACE_H_
#include <sys/appleapiopts.h>
#include <sys/cdefs.h>
enum {
ePtAttachDeprecated __deprecated_enum_msg("PT_ATTACH is deprecated. See PT_ATTACHEXC") = 10
};
#define PT_TRACE_ME 0 /* child declares it's being traced */
#define PT_READ_I 1 /* read word in child's I space */
#define PT_READ_D 2 /* read word in child's D space */
#define PT_READ_U 3 /* read word in child's user structure */
#define PT_WRITE_I 4 /* write word in child's I space */
#define PT_WRITE_D 5 /* write word in child's D space */
#define PT_WRITE_U 6 /* write word in child's user structure */
#define PT_CONTINUE 7 /* continue the child */
#define PT_KILL 8 /* kill the child process */
#define PT_STEP 9 /* single step the child */
#define PT_ATTACH ePtAttachDeprecated /* trace some running process */
#define PT_DETACH 11 /* stop tracing a process */
#define PT_SIGEXC 12 /* signals as exceptions for current_proc */
#define PT_THUPDATE 13 /* signal for thread# */
#define PT_ATTACHEXC 14 /* attach to running process with signal exception */
#define PT_FORCEQUOTA 30 /* Enforce quota for root */
#define PT_DENY_ATTACH 31
#define PT_FIRSTMACH 32 /* for machine-specific requests */
__BEGIN_DECLS
int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
__END_DECLS
#endif /* !_SYS_PTRACE_H_ */
- 直接声明函数:
int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
-
_request
:要处理的事情 -
_pid
:要操作的进程 -
_addr
和_data
:取决于_pid
参数,要传递的数据地址和数据本身。
1.2 ptrace调用
//告诉系统当前进程拒绝被debugserver附加
ptrace(PT_DENY_ATTACH, 0, 0, 0);
//ptrace(31, 0, 0, 0);
PT_DENY_ATTACH
表示拒绝附加,值为31
。如果仅仅是声明函数就传31
就好了。_pid
为0
表示当前进程。这里不传递任何数据。
分别在以下方法中调用
-
load
方法中调用:
+ (void)load {
ptrace(PT_DENY_ATTACH, 0, 0, 0);
}
-
constructor
中调用:
__attribute__((constructor)) static void entry() {
ptrace(PT_DENY_ATTACH, 0, 0, 0);
}
-
main
函数中调用:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
ptrace(PT_DENY_ATTACH, 0, 0, 0);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
-
didFinishLaunchingWithOptions
中调用:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ptrace(PT_DENY_ATTACH, 0, 0, 0);
return YES;
}
1
、2
、3
情况下Xcode
启动调试后调试直接断开,App
能正常操作不能调试。4
在调试情况下App
直接闪退,正常打开没问题。同时调用的情况下以第一次为准。
也就是说 :ptrace
在main
函数之后调用App
会直接闪退,main
以及之前调用会停止进程附加,以第一次调用为准。正常打开App
没有问题,只影响LLDB
调试。
通过上面的验证说明在程序没有加载的时候调用ptrace
会设置一个标志,后续程序就不会被附加了,如果在已经被附加了的情况下调用ptrace
会直接退出(因为这里ptrace
附加传递的pid
是0
主程序本身)。
PT_DENY_ATTACH
This request is the other operation used by the traced process; it allows a process that is not currently being traced to deny future traces by its parent. All other arguments are ignored. If the process is currently being traced, it will exit with the exit status of ENOTSUP; otherwise, it sets a flag that denies future traces. An attempt by the parent to trace a process which has set this flag will result in a segmentation violation in the parent.
原文地址
ENOTSUP含义如下:
define ENOTSUP 45 //Operation not supported
之前在手机端通过debugserver
附加程序直接报错11
,定义如下:
PT_DETACH 11 // stop tracing a process
二、 破解ptrace
ptrace
的特征:附加不了、Xcode运行闪退/停止附加、使用正常。
既然ptrace
可以组织调试,那么我们只要Hook
了这个函数绕过PT_DENY_ATTACH
的调用就可以了。首先想到的就是fishhook
。
#import "fishhook.h"
int (*ptrace_p)(int _request, pid_t _pid, caddr_t _addr, int _data);
int hp_ptrace(int _request, pid_t _pid, caddr_t _addr, int _data){
if (_request != 31) {//不是拒绝附加
return ptrace_p(_request, _pid, _addr, _data);
}
return 0;
}
void hp_hook_ptrace() {
struct rebinding ptrace_rb;
ptrace_rb.name ="ptrace";
ptrace_rb.replacement = hp_ptrace;
ptrace_rb.replaced = (void *)&ptrace_p;
struct rebinding bds[] = {ptrace_rb};
rebind_symbols(bds, 1);
}
+ (void)load {
hp_hook_ptrace();
}
这样就能够进行附加调试了。
三、防止ptrace被破解
3.1 提前Hook防止ptrace被Hook
既然ptrace
能够被Hook
,那么自己先Hook
住ptrace
。调用的时候直接调用自己存储的地址就可以了。我们可以在自己的项目中增加一个Framework
。这个库在Link Binary With Libraries
中尽可能的靠前。这与dyld
加载动态库的顺序有关。
具体可以参考这个案例
这样就可以不被ptrace
Hook
了。代码逻辑和1.2
中相同,只不过调用要换成ptrace_p
。
记的头文件中导出ptrace_p
:
CF_EXPORT int (*ptrace_p)(int _request, pid_t _pid, caddr_t _addr, int _data);
创建一个Monkey
工程,将3.1
生成的.app
包拖入工程重签名,这个时候主程序通过调用ptrace
已经不能阻止我们调试了,但是调用ptrace_p
的地方Monkey
Hook
不到了。
3.2 修改二进制破解提前Hook ptrace
在Monkey
的工程中打ptrace
符号断点:
这个时候可以看到是didFinishLaunchingWithOptions
中调用了ptrace_p
函数:
用Hopper
打开MachO
文件找到didFinishLaunchingWithOptions
方法:
然后一直点下去找到ptrace_p
是属于Inject.framework
的:
在.app
的Frameworks
中找到Inject.framework
用Hopper
打开,可以看到_rebind_symbols
,上面的参数是ptrace
:
这里我们可以直接修改ptrace
让先Hook
的变成另外一个函数,但是有风险点是App
内部调用ptrace_p
的时候如果没有判断空就crash
了。如果判断了可以这么处理。
还有另外一个方式是修改didFinishLaunchingWithOptions
代码中的汇编,修改blr x8
为NOP
这样就绕过了ptrace_p
的调用。
修改blr x8
为NOP
:
替换00 01 3F D6
为1F 20 03 D5
:
修改后就变为NOP
了:
再次运行就绕过了ptrace_p
的调用。这里不Hook
didFinishLaunchingWithOptions
的原因是它内部可能还调用其它方法。
3.3 汇编调用ptrace
既然二进制能被修改那么怎么能不暴露ptrace
的Hook
呢?
答案是采用汇编:
__attribute__((constructor)) void entry() {
#ifdef DEBUG
//
#else
#ifdef __arm__
__asm__(
"mov r0,#0x1F\n"
"mov r1,#0x0\n"
"mov r2,#0x0\n"
"mov r12,#0x1A\n"
"svc #0x80");
#endif
#ifdef __arm64__
__asm__("mov X0, #0x1A\n"
"mov X1, #0x1F\n"
"mov X2, #0x0\n"
"mov X3, #0x0\n"
"mov X16,#0x0\n"
"svc #0x80");
#endif
}
#endif
这里的1A
就是26
,也就是ptrace
,在#import <sys/syscall.h>
中有它的编号:
3.4 汇编ptrace破解
分析找到svc #0x80
修改为nop
就可以了,抖音的防护就是通过汇编调用的。
参考:
http://www.45fan.com/article.php?aid=18070914507358856843184900