很久没有写博客了,今天将自己工作中要求做的逆向防护功能思路写出来
其实逆向App的逻辑流程很简单,设备越狱->静态分析->动态调试->有思路之后编写插件->重签名
既然有了别人逆向的流程,那就针对这个流程做一些防护
1. 反越狱
既然逆向的第一步是越狱,那我就检测越狱,只要手机越狱了,我就不让你用app
检测越狱的逻辑也很简单
- 检测能否打开cydia
+ (CXDefenseReasonType)hasCydiaInstalled {
return [[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://"]] ? CXDefenseReasonTypeCydiaInstalled : CXDefenseReasonTypeNone;
}
- 检测是否存在一些越狱相关的app
+ (CXDefenseReasonType)isContainSuspiciousApps {
NSArray *apps = @[@"/Applications/Cydia.app",
@"/Applications/blackra1n.app",
@"/Applications/FakeCarrier.app",
@"/Applications/Icy.app",
@"/Applications/IntelliScreen.app",
@"/Applications/MxTube.app",
@"/Applications/RockApp.app",
@"/Applications/SBSettings.app",
@"/Applications/WinterBoard.app"];
for (NSString *path in apps) {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
if ([path isEqualToString:@"/Applications/Cydia.app"]) {
return CXDefenseReasonTypeAppCydia;
}
if ([path isEqualToString:@"/Applications/blackra1n.app"]) {
return CXDefenseReasonTypeAppBlackra1n;
}
if ([path isEqualToString:@"/Applications/FakeCarrier.app"]) {
return CXDefenseReasonTypeAppFakeCarrier;
}
if ([path isEqualToString:@"/Applications/Icy.app"]) {
return CXDefenseReasonTypeAppIcy;
}
if ([path isEqualToString:@"/Applications/IntelliScreen.app"]) {
return CXDefenseReasonTypeAppIntelliScreen;
}
if ([path isEqualToString:@"/Applications/MxTube.app"]) {
return CXDefenseReasonTypeAppMxTube;
}
if ([path isEqualToString:@"/Applications/RockApp.app"]) {
return CXDefenseReasonTypeAppRock;
}
if ([path isEqualToString:@"/Applications/SBSettings.app"]) {
return CXDefenseReasonTypeAppSBSettings;
}
if ([path isEqualToString:@"/Applications/WinterBoard.app"]) {
return CXDefenseReasonTypeAppWinterBoard;
}
}
}
return CXDefenseReasonTypeNone;
}
- 检测是否存在越狱相关的文件夹
+ (CXDefenseReasonType)isContainSuspiciousSystemPath {
NSArray *systemPaths = @[@"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist",
@"/Library/MobileSubstrate/DynamicLibraries/Veency.plist",
@"/private/var/lib/apt",
@"/private/var/lib/apt/",
@"/private/var/lib/cydia",
@"/private/var/mobile/Library/SBSettings/Themes",
@"/private/var/stash",
@"/private/var/tmp/cydia.log",
@"/System/Library/LaunchDaemons/com.ikey.bbot.plist",
@"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist",
@"/usr/bin/sshd",
@"/usr/libexec/sftp-server",
@"/usr/sbin/sshd",
@"/etc/apt",
@"/bin/bash",
@"/Library/MobileSubstrate/MobileSubstrate.dylib"];
for (NSString *path in systemPaths) {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
if ([path isEqualToString:@"/Library/MobileSubstrate/DynamicLibraries/LiveClock.plist"]) {
return CXDefenseReasonTypePathLiveClock;
}
if ([path isEqualToString:@"/Library/MobileSubstrate/DynamicLibraries/Veency.plist"]) {
return CXDefenseReasonTypePathVeency;
}
if ([path isEqualToString:@"/private/var/lib/apt"] || [path isEqualToString:@"/private/var/lib/apt/"]) {
return CXDefenseReasonTypePathLibApt;
}
if ([path isEqualToString:@"/private/var/lib/cydia"]) {
return CXDefenseReasonTypePathLibCydia;
}
if ([path isEqualToString:@"/private/var/mobile/Library/SBSettings/Themes"]) {
return CXDefenseReasonTypePathThemes;
}
if ([path isEqualToString:@"/private/var/stash"]) {
return CXDefenseReasonTypePathStash;
}
if ([path isEqualToString:@"/private/var/tmp/cydia.log"]) {
return CXDefenseReasonTypePathCydiaLog;
}
if ([path isEqualToString:@"/System/Library/LaunchDaemons/com.ikey.bbot.plist"]) {
return CXDefenseReasonTypePathBbot;
}
if ([path isEqualToString:@"/System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist"]) {
return CXDefenseReasonTypePathStartup;
}
if ([path isEqualToString:@"/usr/bin/sshd"]) {
return CXDefenseReasonTypePathBinSshd;
}
if ([path isEqualToString:@"/usr/libexec/sftp-server"]) {
return CXDefenseReasonTypePathSftp;
}
if ([path isEqualToString:@"/usr/sbin/sshd"]) {
return CXDefenseReasonTypePathSbinSshd;
}
if ([path isEqualToString:@"/etc/apt"]) {
return CXDefenseReasonTypePathEtcApt;
}
if ([path isEqualToString:@"/bin/bash"]) {
return CXDefenseReasonTypePathBash;
}
if ([path isEqualToString:@"/Library/MobileSubstrate/MobileSubstrate.dylib"]) {
return CXDefenseReasonTypePathMobileSubstrate;
}
}
}
return CXDefenseReasonTypeNone;
}
2. 代码混淆
静态分析这一步一般包括
- 通过reveal分析页面,拿到VC相关的信息
- 通过class-dump导头文件,通过感兴趣的VC的方法名来做一些推测
- 或者通过Hopper/IDA查看相关的方法的信息
- 或者注入代码查看调用逻辑
所以,需要添加一些代码混淆来加大攻击者的分析的难度
具体的混淆工具网上有很多,可以搜一搜关键词confuse
3. 反调试
如果别人拿到了你app运行的流程逻辑,就需要动态调试来查看具体的参数之类的,这就需要做反调试阻止别人调试你的app
反调试有很多种方法,这里只简单介绍两种
- ptrace反调试
#pragma mark ********* PTrace *********
/*
lldb调试的原理:debugserver
在越狱环境下,通过lldb连接手机的debugserver,然后通过debugserver来调试某个app
而debugserver是通过ptrace函数来调试app
*/
#ifndef PT_DENY_ATTACH
//阻止调试器附加
#define PT_DENY_ATTACH 31
#endif
int ptrace(int _request, pid_t _pid, caddr_t _addr, int _data);
static __attribute__((always_inline)) void antiForCantAttach() {
ptrace(PT_DENY_ATTACH, 0, 0, 0);
}
- sysctl反调试
#pragma mark ********* sysctl *********
/*
当目标进程被调试时,会有一个标志位表明自己正在被调试
通过sysctl获取标志位来得到当前的调试状态
*/
static __attribute__((always_inline)) BOOL antiForAttachToExit() {
int name[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid()};
u_int nameLen = sizeof(name) / sizeof(*name);
struct kinfo_proc oldp;
size_t oldLenp = sizeof(oldp);
int result = sysctl(name, nameLen, &oldp, &oldLenp, NULL, 0);
if (result == -1) {
//错误处理
return NO;
}
if ((oldp.kp_proc.p_flag & P_TRACED) != 0) {
//检测到调试器,退出
return YES;
}
return NO;
}
这两种方案都能做到反调试,第二种检测到反调试能做自己的一些业务逻辑,具体怎么用就看个人了
4. 反注入
如果别人拿到了你app的逻辑要做破坏就需要hook你的方法,这就需要注入自己的代码,而现在一般的注入代码是通过 Theos
的 Logos
语法来编写插件,而这种方法注入代码依赖 MobileSubstrate.dylib
这个动态库,所以我们检测到这个动态库的话就认为有代码注入
@implementation CXInjectDetection
+ (void)load {
[self performSelectorOnMainThread:@selector(shared) withObject:nil waitUntilDone:NO];
#ifndef DEBUG
//注册回调,只要dylib被加载就会进入此回调
_dyld_register_func_for_add_image(&cx_image_added);
#endif
}
- (instancetype)init
{
self = [super init];
if (self) {
self.isInject = NO;
}
return self;
}
+ (instancetype)shared {
static dispatch_once_t onceToken;
static CXInjectDetection *inject;
dispatch_once(&onceToken, ^{
inject = [[CXInjectDetection alloc] init];
});
return inject;
}
#pragma clang diagnostic push
//"-Wunused-variable"这里就是警告的类型
#pragma clang diagnostic ignored "-Wunused-function"
static void cx_image_added(const struct mach_header *mh, intptr_t slide) {
Dl_info image_info;
int result = dladdr(mh, &image_info);
if (result == 0) {
return;
}
const char *image_name = image_info.dli_fname;
//检测到第三方库,直接杀掉
if (strstr(image_name, "DynamicLibraries") || strstr(image_name, "CydiaSubstrate")) {
[CXInjectDetection shared].isInject = YES;
}
}
#pragma clang diagnostic pop
@end
5. 反二次签名
攻击者注入代码成功之后就已经破坏了app的签名,想要安装到手机上只能做重签名,而做重签名需要mobileProvision文件,正常的从appstore下载的app包里是不存在mobileProvision文件的,且重签名的mobileProvision文件里的bundleId是不同于我们原有的bundleId的,所以简略点可以判定是否存在mobileprovision文件,更精细点可以判断bundleId是否一致
bool checkCodesign(NSString *id){
// 描述文件路径
NSString *embeddedPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
// 读取application-identifier 注意描述文件的编码要使用:NSASCIIStringEncoding
NSString *embeddedProvisioning = [NSString stringWithContentsOfFile:embeddedPath encoding:NSASCIIStringEncoding error:nil];
NSArray *embeddedProvisioningLines = [embeddedProvisioning componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
for (int i = 0; i < embeddedProvisioningLines.count; i++) {
if ([embeddedProvisioningLines[i] rangeOfString:@"application-identifier"].location != NSNotFound) {
NSInteger fromPosition = [embeddedProvisioningLines[i+1] rangeOfString:@""].location+8;
NSInteger toPosition = [embeddedProvisioningLines[i+1] rangeOfString:@""].location;
NSRange range;
range.location = fromPosition;
range.length = toPosition - fromPosition;
NSString *fullIdentifier = [embeddedProvisioningLines[i+1] substringWithRange:range];
NSArray *identifierComponents = [fullIdentifier componentsSeparatedByString:@"."];
NSString *appIdentifier = [identifierComponents firstObject];
// 对比签名ID
if (![appIdentifier isEqual:id]) {
//exit
return YES;
}
break;
}
}
return NO;
}
至此,逆向防护就完成了,但这些操作只能给攻击者增加难度,因为没有完美的防护
代码地址:iOSAppDefense
具体使用也很简单, 在didFinishLaunchingWithOptions
中调用[CXAppDefense appDefense]
即可,此处可根据自己的业务逻辑来决定是否展示具体的越狱信息VC
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
if ([self showJailBreakWindow]) {
return YES;
}
}
- (BOOL)showJailBreakWindow {
CXJailDetecteResult *result = [CXAppDefense appDefense];
if (result.isJailBreak) {
CXJailBreakVC *vc = [CXJailBreakVC new];
vc.result = result;
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
self.window.rootViewController = vc;
[self.window makeKeyAndVisible];
return YES;
}
return NO;
}
网友评论