001--代码混淆
代码混淆的作用:防止别人通过类名、方法名猜出其功能。
一般去混淆关键的信息功能!不能全工程代码混淆,效率会严重降低!
创建 PrefixHeader_pch 文件,用宏代替: 方法名、类名
//------------------------ PrefixHeader_pch ----------------
// PrefixHeader.pch
// 001--UserInfoDemo
#ifndef PrefixHeader_pch
#define PrefixHeader_pch
#define UserInfoClass CHDKSALI458
#define isVipWithAccount KDKSLQWIP45
#endif /* PrefixHeader_pch */
//------------------------ ViewController ----------------
#import "ViewController.h"
#import "UserInfo.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
//测试
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if ([[[UserInfoClass alloc] init] isVipWithAccount:@"hank123"]) {
NSLog(@"是VIP");
}else{
NSLog(@"不是VIP");
}
}
@end
//------------------------ UserInfo --> UserInfoClass ----------
#import
@interface UserInfoClass : NSObject
-(BOOL)isVipWithAccount:(NSString *)account;
@end
#import "UserInfo.h"
@implementation UserInfoClass
-(BOOL)isVipWithAccount:(NSString *)account{
if ([account isEqualToString:@"hank"]) {
return YES;
}
return NO;
}
@end
查看MacO文件,方法名和类名 已经替换
002--字符串加密
//NSString * const AES_KEY = @"IUKDSIEKD#";
//NSString * const AES_KEY = @"IUIDISTP#";
//对上面字符串的防护
-
//2.1、一次防护:包装成数组
虽然现在很难找到可以,但是查看MacO文件,找到后:字符串还是能看到!
-
//2.2、二次防护:包装成数组:采用^(异或)运算直接换算成结果.不会进入字符串常量区(理解为:字符串加密)
#define STRING_ENCRYPT_KEY 0xAC
static NSString * AES_KEY(){
unsigned char key[] = {
(STRING_ENCRYPT_KEY ^ 'I'),
(STRING_ENCRYPT_KEY ^ 'U'),
(STRING_ENCRYPT_KEY ^ 'I'),
(STRING_ENCRYPT_KEY ^ 'D'),
(STRING_ENCRYPT_KEY ^ 'I'),
(STRING_ENCRYPT_KEY ^ 'S'),
(STRING_ENCRYPT_KEY ^ 'T'),
(STRING_ENCRYPT_KEY ^ 'P'),
(STRING_ENCRYPT_KEY ^ '#'),
(STRING_ENCRYPT_KEY ^ '\0')
};
unsigned char * p = key;
while (((*p) ^= STRING_ENCRYPT_KEY) != '\0') p++;
return [NSString stringWithUTF8String:(const char *)key];
}
查看MacO文件,很难找到字符串,即使找到key,也看不到明文原字符串!
003--隐藏CCCrypt
符号断点防不住
image.list
libcommonCrypto,dylib
//到动态库 找CCCrypt_p这个函数
dlsym(handle, "CCCrypt");
- (NSString *)encryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
// 设置秘钥
NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
uint8_t cKey[self.keySize];
bzero(cKey, sizeof(cKey));
[keyData getBytes:cKey length:self.keySize];
// 设置iv
uint8_t cIv[self.blockSize];
bzero(cIv, self.blockSize);
int option = 0;
if (iv) {
[iv getBytes:cIv length:self.blockSize];
option = kCCOptionPKCS7Padding;
} else {
option = kCCOptionPKCS7Padding | kCCOptionECBMode;
}
// 设置输出缓冲区
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
size_t bufferSize = [data length] + self.blockSize;
void *buffer = malloc(bufferSize);
// 开始加密
size_t encryptedSize = 0;
//加密解密都是它 -- CCCrypt
//隐藏CCCrypt函数
unsigned char str[] = {
('a' ^ 'C'),
('a' ^ 'C'),
('a' ^ 'C'),
('a' ^ 'r'),
('a' ^ 'y'),
('a' ^ 'p'),
('a' ^ 't'),
('a' ^ '\0')
};
unsigned char * p = str;
while (((*p) ^= 'a') != '\0') p++;
//通过句柄 拿到动态库
void * handle = dlopen("/usr/lib/system/libcommonCrypto.dylib",RTLD_LAZY);
// //到动态库 找CCCrypt_p这个函数
// dlsym(handle, "CCCrypt");
CCCryptorStatus (* CCCrypt_p)(
CCOperation op, /* kCCEncrypt, etc. */
CCAlgorithm alg, /* kCCAlgorithmAES128, etc. */
CCOptions options, /* kCCOptionPKCS7Padding, etc. */
const void *key,
size_t keyLength,
const void *iv, /* optional initialization vector */
const void *dataIn, /* optional per op and alg */
size_t dataInLength,
void *dataOut, /* data RETURNED here */
size_t dataOutAvailable,
size_t *dataOutMoved)
__OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_2_0) = dlsym(handle, (const char *)str);
//拿到函数指针才去调用
if (!CCCrypt_p) {
return nil;
}
CCCryptorStatus cryptStatus = CCCrypt_p(kCCEncrypt,
self.algorithm,
option,
cKey,
self.keySize,
cIv,
[data bytes],
[data length],
buffer,
bufferSize,
&encryptedSize);
NSData *result = nil;
if (cryptStatus == kCCSuccess) {
result = [NSData dataWithBytesNoCopy:buffer length:encryptedSize];
} else {
free(buffer);
NSLog(@"[错误] 加密失败|状态编码: %d", cryptStatus);
}
return [result base64EncodedStringWithOptions:0];
}
004--使用汇编进行系统调用
-
上节回顾
#import "antiDebugCode.h"
#import "MyPtraceHeader.h"
@implementation antiDebugCode
+(void)load {
//运行就奔溃:但是防不住断点调试,能找到奔溃原因
ptrace(PT_DENY_ATTACH, 0, 0, 0);
}
@end
4.1、跳过断点调试
- (void)viewDidLoad {
[super viewDidLoad];
// 支付宝 exit!
// 架构的判断 DYLD 源码!!
// 我更本就不调用你的ptrace!! exit!! 无法用ptrace断点 找到原因,因为根本不会进入断点
//直接上汇编代码!! 通过中断 触发
// asm(
// "mov X0,#31\n"//31 PT_DENY_ATTACH
// "mov X1,#0\n"
// "mov X2,#0\n"
// "mov X3,#0\n"
// "mov w16,#26\n"//这个26代表着ptrace
// "svc #0x80"//触发中断
// );
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
//点击屏幕就退出
asm(
"mov X0,#0\n"
"mov w16,#1\n" //这个26代表着ptrace, #define SYS_exit 1
"svc #0x80"
);
}
4.2、点击屏幕不会进入断点
005--重签名防护
课程回顾
// 会用到的终端命令
cd /Users/zhangmeng/Library/MobileDevice/Provisioning\ Profiles // 目录到 此文件夹:存放一些描述文件 ls // 查看此文件夹
open . // 打开此文件夹 //查看 描述文件信息: 内容是签名加密的一个xml,苹果设备要信任此app security cms -D -i + 描述文件路径(齿轮配置文件, mobileprovision)
security cms -D -i embedded.mobileprovision //这个已经cd到当前目录 //查看 APP的签名信息 codesign -vv -d +APP路径
$ codesign -vv -d WeChat.app
$ security cms -D -i + 描述文件路径(齿轮配置文件, mobileprovision)//查看一个对应app的 描述文件信息:
//找到 签名字符: 5KN6964MFR
判断是否被重签名了,是就直接退出程序
#import "NSObject+Codesign.h"
@implementation NSObject (Codesign)
void checkCodesign(NSString *id){
// 描述文件路径
NSString *embeddedPath = [[NSBundle mainBundle] pathForResource:@"embedded" ofType:@"mobileprovision"];
// NSLog(@"Path:%@",embeddedPath);
// 读取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];
// NSLog(@"%@", fullIdentifier);
NSArray *identifierComponents = [fullIdentifier componentsSeparatedByString:@"."];
NSString *appIdentifier = [identifierComponents firstObject];
// NSLog(@"%@", appIdentifier);
// 对比签名ID
if ([appIdentifier isEqual:id]) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:appIdentifier forKey:@"key"];
[defaults synchronize];
}else{
//exit
asm(
"mov X0,#0\n"
"mov w16,#1\n"
"svc #0x80"
);
}
break;
}
}
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
checkCodesign(@"5KN6964MFR");
}
/*以上防护的破解方案:
对于反重签名防护,hook checkCodesign函数/
总结:防护的一些方法
1、重签名防护
2、ptrace防护(用汇编)
3、反hook防护(动态库提前调用)
4、混淆关键代码
5、防护class dump
001--代码混淆
代码混淆的作用:防止别人通过类名、方法名猜出其功能。
一般去混淆关键的信息功能!不能全工程代码混淆,效率会严重降低!
创建 PrefixHeader_pch 文件,用宏代替: 方法名、类名
002--字符串加密
字符串包装成数组:采用^(异或)运算直接换算成结果.不会进入字符串常量区(理解为:字符串加密