1. 核心类和方法名称混淆
新建 .pch 文件, 对核心类名, 方法名称进行宏定义, 在应用程序启动的预编译阶段, 会对对应的宏进行替换, 完成混淆!
hopper.png
2. 字符串常量加密
// 采用 位运算直接换算成结果, 不会进入字符串常量区
#define STRING_ENCRYPT_KEY 0xAC
static NSString * AES_KEY() {
unsigned char key[] = {
('e' ^ STRING_ENCRYPT_KEY),
('f' ^ STRING_ENCRYPT_KEY),
('2' ^ STRING_ENCRYPT_KEY),
('r' ^ STRING_ENCRYPT_KEY),
('2' ^ STRING_ENCRYPT_KEY),
('#' ^ STRING_ENCRYPT_KEY),
('\0' ^ STRING_ENCRYPT_KEY)
};
unsigned char *tem = key;
while (((*tem) ^ STRING_ENCRYPT_KEY) != '\0') {
tem++;
}
return [NSString stringWithUTF8String:(const char *)key];
}
stringEncrypt.png
3. 加密 CCCrypt
- 找到函数对应的动态库
// 添加 symbol breakpoint
$ bt
$ image list //Symbols/usr/lib/system/libcommonCrypto.dylib
libcommonCrypto.dylib`CCCrypt:
-> 0x197db75c4 <+0>: stp x24, x23, [sp, #-0x40]!
0x197db75c8 <+4>: stp x22, x21, [sp, #0x10]
0x197db75cc <+8>: stp x20, x19, [sp, #0x20]
0x197db75d0 <+12>: stp x29, x30, [sp, #0x30]
- 使用 dlopen , dlsym 调用 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
unsigned char str[]={
('a' ^ 'C'),
('a' ^ 'C'),
('a' ^ 'C'),
('a' ^ 'r'),
('a' ^ 'p'),
('a' ^ 't'),
('a' ^ '\0'),
};
unsigned char *p = str;
while (((*p) ^= 'a') != '\0') {
p++;
}
// 句柄
// 根据 CCCrypt 函数指针来调用函数, 避免反编译出现函数名称
// RTLD_LAZY: 懒加载模式
void *handle = dlopen("/usr/lib/system/libcommonCrypto.dylib", RTLD_LAZY);
if (!handle) {
return nil;
}
// 获取函数指针
// const char *__symbol: 函数名称
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];
}]]
如此就可以在反汇编中隐藏 CCCrypt 函数了!!!
4. 通过汇编调用中断触发 ptrace, 防断点调试
通过之前封装动态库方式反调试的方法, 仍然会被符号断点获取到, 本节将使用汇编调用系统中断方式, 调用 ptrace, 可以破断点调试!
asm(
"mov x0, #31\n" // PT_DENY_ATTACH: 31
"mov x1, #0\n"
"mov x2, #0\n"
"mov x3, #0\n"
"mov w16, #26\n" // 这个26代表 ptrace 出自 <sys/syscall>
"svc #0x80" // 触发中断
);
可以用同样的方法, 触发 exit
asm(
"mov x0, #0\n"
"mov w16, #1\n"
"svc #0x80"
);
5. 重签名防护
重签名原理已经提到 embedded.mobileprovision, 中的Entitlements字段中的application-identifier, 可以封装工具通过检测bundleid来防止重签名!!!
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]];
NSLog(@"embeddedProvisioningLines:%@",embeddedProvisioningLines);
for (int i = 0; i < embeddedProvisioningLines.count; i++) {
if ([embeddedProvisioningLines[i] rangeOfString:@"application-identifier"].location != NSNotFound) {
NSInteger fromPosition = [embeddedProvisioningLines[i+1] rangeOfString:@"<string>"].location+8;
NSInteger toPosition = [embeddedProvisioningLines[i+1] rangeOfString:@"</string>"].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{
asm(
"mov X0,#0\n"
"mov w16,#1\n"
"svc #0x80"
);
}
break;
}
}
}
ps:
防 Class-dump
代码逻辑混淆
网友评论