时间紧迫,直接开始
首先查看Mach-O文件,看能否找到能判断的信息。
-
看到有个UUID字段,验证一下是否能拿来判断。
/**
获取指令中的uuid
@return uuid
*/
NSString *executableUUID()
{
const uint8_t *command = (const uint8_t *)(&_mh_execute_header + 1);
for (uint32_t idx = 0; idx < _mh_execute_header.ncmds; ++idx) {
if (((const struct load_command *)command)->cmd == LC_UUID) {
command += sizeof(struct load_command);
return [NSString stringWithFormat:@"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
command[0], command[1], command[2], command[3],
command[4], command[5],
command[6], command[7],
command[8], command[9],
command[10], command[11], command[12], command[13], command[14], command[15]];
} else {
command += ((const struct load_command *)command)->cmdsize;
}
}
return nil;
}
结论:由于验证时忘了截图,就不贴验证过程了,直接写结论吧。LC_UUID会在每次修改代码重新编译运行时,LC_UUID的值都会改变,但是直接对app进行重签名和篡改之后,发现是不会改变LC_UUID的值。
- 另外有个猜想但没验证,可以利用Load Commands中的代码签名数据偏移找到代码签名数据的地址,但是这签名数据的结构比较复杂,有兴趣的可以研究一下。
直接从Mach-O文件着手,验证Mach-O文件。
- 研究如何能在运行时获取Mach-O文件
/**
App Mach-O文件路径
@return Mach-O文件路径
*/
-(NSString *) machOPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *excutableName = [[NSBundle mainBundle] infoDictionary][@"CFBundleExecutable"];
NSString *tmpPath = [documentsDirectory stringByDeletingLastPathComponent];
NSString *appPath = [[tmpPath stringByAppendingPathComponent:excutableName]
stringByAppendingPathExtension:@"app"];
NSString *machOPath = [appPath stringByAppendingPathComponent:excutableName];
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0) {
NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
machOPath = [bundlePath stringByAppendingPathComponent:excutableName];
}
return machOPath;
}
- 用SHA256执行摘要
/**
数据SHA256
@param keyData 需要hash的数据
@return hash后的数据
*/
+ (NSString *)SHA256:(NSData *)keyData {
// const char *s = [self cStringUsingEncoding:NSASCIIStringEncoding];
// NSData *keyData = [NSData dataWithBytes:s length:strlen(s)];
uint8_t digest[CC_SHA256_DIGEST_LENGTH] = {0};
CC_SHA256(keyData.bytes, (CC_LONG)keyData.length, digest);
NSData *out = [NSData dataWithBytes:digest length:CC_SHA256_DIGEST_LENGTH];
NSString *hash = [out description];
hash = [hash stringByReplacingOccurrencesOfString:@" " withString:@""];
hash = [hash stringByReplacingOccurrencesOfString:@"<" withString:@""];
hash = [hash stringByReplacingOccurrencesOfString:@">" withString:@""];
return hash;
}
/**
* MachO使用SHA256执行摘要
*/
-(void) sha256MachO
{
//加密
NSString *encryptResult = [ViewController SHA256:[NSData dataWithContentsOfFile:[ViewController binaryPath]]];
if (encryptResult) {
NSLog(@"\nSHA256 成功-->%@",encryptResult);
}else{
NSLog(@"\nSHA256 失败");
}
}
-
验证修改代码SHA256的值是否改变(修改输出语句)
结论:修改代码摘要会改变,即使重新写回原来的代码,也无法变回原来的SHA256的值。
- 重点来了,验证重签名和篡改后SHA256的值。
未重签名前:
重签名之后:
结论:重签名和篡改后SHA256的值会改变。
最终结论:可以对Mach-O执行hash运算或者签名验签等等操作来验证App是否完整。
PS:此次研究没有考虑手机越狱的情况,越狱的话可以直接使用Tweak以动态库插入,可能SHA256的值就不会改变,因为Mach-O数据没有改变,不过这只是猜想,并没有验证。