1. 防止 DYLD_INSERT_LIBRARIES 動態(tài)插入
* 可以防 dumpdecrypt 砸殼;
* 可以防越獄環(huán)境下tweak插件安裝.
DYLD_INSERT_LIBRARIES : 通過bundle id 獲取進程, 插入動態(tài)庫!
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 插入動態(tài)庫!
-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)
此函數(shù)提供了一個進程, 監(jiān)聽控制另外一個進程, 并且可以檢測被控制進程的內存的寄存器里面的數(shù)據! 它可以用來實現(xiàn)斷點調試和系統(tǒng)調用跟蹤.
debugserve r的實現(xiàn)就是基于 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 :數(shù)據, 默認寫0
*/
ptrace(PT_DENY_ATTACH, 0, 0, 0);
2.2 攻破 ptrace - fishhook
#import "InjectCode.h"
#import "fishhook.h"
#import "PtraceHeader.h"
@implementation InjectCode
// 定義函數(shù)指針. 保存原來函數(shù)地址
int (* ptrace_p) (int _request, pid_t _pid, caddr_t _addr, int _data);
// 定義新的函數(shù)
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"; // 函數(shù)符號
ptraceBD.replacement = myPtrace; // 新函數(shù)地址
ptraceBD.replaced = (void *)&ptrace_p; // 原始函數(shù)地址的指針
// 創(chuàng)建數(shù)組
struct rebinding rebinds[]={ptraceBD};
// 重綁定
rebind_symbols(rebinds, 1);
}
@end
2.3 防 ptrace 針對 fishhook 加固
防fishhook rebind 系統(tǒng)函數(shù)的殺手锏 -- 自定義動態(tài)庫.
通過動態(tài)庫注入方式注入的動態(tài)庫, 都在項目動態(tài)庫加載之后!
#import "AntiFishhookCode.h"
#import "PtraceHeader.h"
@implementation AntiFishhookCode
+ (void)load {
/*
int _request: ptrace 要做的事
pid_t _pid : 進程 id
caddr_t _addr:地址 默認寫0
int _data :數(shù)據, 默認寫0
*/
ptrace(PT_DENY_ATTACH, 0, 0, 0);
NSLog(@"我就是搞你 --- fishhook!!!");
}
@end
3. 防護 - 反調試 - sysctl
sysctl
// 檢測是否被調試
BOOL isDebugger () {
// 控制碼
int name[4]; // 里面放字節(jié)碼, 查詢信息
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>
// 原始函數(shù)地址
int (*sysctl_p)(int *, u_int, void *, size_t *, void *, size_t);
// 自定義新函數(shù)
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) 加固
使用動態(tài)庫的方式, 來防護 !!!