1. 防止 DYLD_INSERT_LIBRARIES 动态插入
* 可以防 dumpdecrypt 砸壳;
* 可以防越狱环境下tweak插件安装.
DYLD_INSERT_LIBRARIES : 通过bundle id 获取进程, 插入动态库!
1.1 dyld.cpp
// pruneEnvironmentVariables
if ( gLinkContext.processIsRestricted ) {
pruneEnvironmentVariables(envp, &apple);
// set again because envp and apple may have changed or moved
setContext(mainExecutableMH, argc, argv, envp, apple);
}
// processIsRestricted
if ( issetugid() || hasRestrictedSegment(mainExecutableMH) ) {
gLinkContext.processIsRestricted = true;
}
// hasRestrictedSegment
static bool hasRestrictedSegment(const macho_header* mh)
{
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(macho_header));
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_SEGMENT_COMMAND:
{
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
//dyld::log("seg name: %s\n", seg->segname);
if (strcmp(seg->segname, "__RESTRICT") == 0) {
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if (strcmp(sect->sectname, "__restrict") == 0)
return true;
}
}
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return false;
}
// seg->segname, "__RESTRICT"
// sect->sectname, "__restrict"
1.2 开始防护
在 MachO 中添加一个 restrict Section, 防止 tweak 插件通过 DYLD_INSERT_LIBRARIES 插入动态库!
-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null
-
查看编译后的 MachO
第一层攻破
但是通过 synalyzeit 十六进制编辑器可以修改 restrict segment, 攻破第一层防护!第二层防护
通过在代码中检测, restrict segment 是否被修改, 做二次防护.
1.3 反修改MachO 中的 restrict 段
#import <mach-o/loader.h>
#import <mach-o/dyld.h>
// ARM and x86_64 are the only architecture that use cpu-sub-types
#define CPU_SUBTYPES_SUPPORTED ((__arm__ || __arm64__ || __x86_64__) && !TARGET_IPHONE_SIMULATOR)
#if __LP64__
#define LC_SEGMENT_COMMAND LC_SEGMENT_64
#define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT
#define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO
#define macho_segment_command segment_command_64
#define macho_section section_64
#define macho_header mach_header_64
#else
#define macho_header mach_header
#define LC_SEGMENT_COMMAND LC_SEGMENT
#define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64
#define LC_ENCRYPT_COMMAND LC_ENCRYPTION_INFO_64
#define macho_segment_command segment_command
#define macho_section section
#endif
@implementation XXX
static bool hasRestrictedSegment(const struct macho_header* mh)
{
const uint32_t cmd_count = mh->ncmds;
const struct load_command* const cmds = (struct load_command*)(((char*)mh)+sizeof(struct macho_header));
const struct load_command* cmd = cmds;
for (uint32_t i = 0; i < cmd_count; ++i) {
switch (cmd->cmd) {
case LC_SEGMENT_COMMAND:
{
const struct macho_segment_command* seg = (struct macho_segment_command*)cmd;
printf("segmentname=%s\n", seg->segname);
//dyld::log("seg name: %s\n", seg->segname);
if (strcmp(seg->segname, "__RESTRICT") == 0) {
const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command));
const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects];
for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
if (strcmp(sect->sectname, "__restrict") == 0)
return true;
}
}
}
break;
}
cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize);
}
return false;
}
+ (void)load {
// 获取 MachO 的 imageHeader
// dyld 启动 App 的时候, 最先加载的是自己的 MachO, index = 0(也可以 LLDB: image list 来查看)
struct mach_header *mach_header = _dyld_get_image_header(0);
NSLog(@"%d", mach_header->filetype);
if(hasRestrictedSegment(mach_header)) {
NSLog(@"防护成功");
}else{
NSLog(@"防护被攻破, rescrict 二进制被改");
}
}
2. 防 LLDB 调试
lldb 之所以能够调试手机上的 app, 原因在于 xcode 连接手机后, 在手机上安装了 debugsever!
在xcode 中路径
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/DeviceSupport/10.0/DeveloperDiskImage.dmg/usr/bin/debugserver
在iphone 中路径
/Developer/usr/bin/debugserver
- debugserver 使用
$ /Developer/usr/bin/debugserver *:12346 -a 3705(pid)
- lldb 启用
$ lldb
$ process connect connect://localhost:12346 // USB链接
$ exit //退出 lldb
-
配置 debugserver 权限
导出权限文件 - 如果调试报错, 需要添加以下权限
$ ldid -e debugserver > debugserver.entitlements
// 添加字段
get-task-allow YES
task-for-pid-allow YES
// 用新的描述文件重签debugserver
$ ldid -S debugserver.entitlements debugserver
// 将新的 debugserver Copy 到手机 /usr/bin 目录下
$ scp -P 12345 xxx/debugserver root@localhost:/usr/bin/
2.1 ptrace (process trace) 进程跟踪 - 专防 dubugserver(xcode, lldb)
此函数提供了一个进程, 监听控制另外一个进程, 并且可以检测被控制进程的内存的寄存器里面的数据! 它可以用来实现断点调试和系统调用跟踪.
debugserve r的实现就是基于 ptrace (OSX).
#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_ */
只需要在防护的地方, 调用一下方法, 就能防止 xcode 调试, lldb, 调试, debugserver 调试!
/*
int _request: ptrace 要做的事
pid_t _pid : 进程 id
caddr_t _addr:地址 默认写0
int _data :数据, 默认写0
*/
ptrace(PT_DENY_ATTACH, 0, 0, 0);
2.2 攻破 ptrace - fishhook
#import "InjectCode.h"
#import "fishhook.h"
#import "PtraceHeader.h"
@implementation InjectCode
// 定义函数指针. 保存原来函数地址
int (* ptrace_p) (int _request, pid_t _pid, caddr_t _addr, int _data);
// 定义新的函数
int myPtrace (int _request, pid_t _pid, caddr_t _addr, int _data){
if(_request != PT_DENY_ATTACH){
return myPtrace(_request, _pid, _addr, _data);
}
// 如果拒绝加载, 破坏此防护
return 0;
}
+ (void)load {
// rebinding
struct rebinding ptraceBD; //
ptraceBD.name = "ptrace"; // 函数符号
ptraceBD.replacement = myPtrace; // 新函数地址
ptraceBD.replaced = (void *)&ptrace_p; // 原始函数地址的指针
// 创建数组
struct rebinding rebinds[]={ptraceBD};
// 重绑定
rebind_symbols(rebinds, 1);
}
@end
2.3 防 ptrace 针对 fishhook 加固
防fishhook rebind 系统函数的杀手锏 -- 自定义动态库.
通过动态库注入方式注入的动态库, 都在项目动态库加载之后!
#import "AntiFishhookCode.h"
#import "PtraceHeader.h"
@implementation AntiFishhookCode
+ (void)load {
/*
int _request: ptrace 要做的事
pid_t _pid : 进程 id
caddr_t _addr:地址 默认写0
int _data :数据, 默认写0
*/
ptrace(PT_DENY_ATTACH, 0, 0, 0);
NSLog(@"我就是搞你 --- fishhook!!!");
}
@end
3. 防护 - 反调试 - sysctl
sysctl
// 检测是否被调试
BOOL isDebugger () {
// 控制码
int name[4]; // 里面放字节码, 查询信息
name[0] = CTL_KERN; // 内核查看
name[1] = KERN_PROC; // 查询进程
name[2] = KERN_PROC_PID; // pid
name[3] = getpid(); // 查询 pid
/** int sysctl(
int *,
u_int, // size
void *, // 返回结果(kinfo_proc)
size_t *,// 结构体大小
void *, //
size_t
); */
// 接收进程查询结果的结构体
struct kinfo_proc info;
// 结构体的大小
size_t info_size = sizeof(info);
int error = sysctl(name, sizeof(name)/sizeof(*name), &info, &info_size, 0, 0);
// 0就是没有错误, 其他就是错误码
assert(error == 0);
// 判断二进制某一位 是1 还是0, 按位与 00001000000
// != 0, 就是正在被调试
if((info.kp_proc.p_flag & P_TRACED) != 0) {
return YES;
}
return NO;
}
if (isDebugger()) {
NSLog(@"被调试了-------");
}else{
NSLog(@"安全运行");
}
3.1 fishHook 攻破 sysctl
#import "fishhook.h"
#import <sys/sysctl.h>
// 原始函数地址
int (*sysctl_p)(int *, u_int, void *, size_t *, void *, size_t);
// 自定义新函数
int mySysctl(int *name, u_int nameSize, void *info, size_t *infoSize, void *newInfo, size_t newInfoSize) {
if (nameSize == 4
&& name[1] == CTL_KERN
&& name[2] == KERN_PROC
&& name[3] == KERN_PROC_PID
&& info
&& (int) *infoSize == sizeof(struct kinfo_proc)
) {
int err = sysctl_p(name, nameSize, info, infoSize, newInfo, newInfoSize);
// 判断info
struct kinfo_proc *myInfo = (struct kinfo_proc *)info;
if ((myInfo->kp_proc.p_flag & P_TRACED) != 0) {
// 如果设置了防调试, 在此破解
myInfo->kp_proc.p_flag ^= P_TRACED;
}
return err;
}
return sysctl_p(name, nameSize, info, infoSize, newInfo, newInfoSize);
}
@implementation InjectCode
+ (void)load {
struct rebinding rebind;
rebind.name = "sysctl";
rebind.replacement = mySysctl;
rebind.replaced = (void *)&sysctl_p;
rebind_symbols((struct rebinding []){rebind} , 1);
}
@end
3.2 防护 - 反调试 (sysctl) 加固
使用动态库的方式, 来防护 !!!