IOS 验证App完整性探究

时间紧迫,直接开始

首先查看Mach-O文件,看能否找到能判断的信息。

  • 看到有个UUID字段,验证一下是否能拿来判断。


    image.png
/**
 获取指令中的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中的代码签名数据偏移找到代码签名数据的地址,但是这签名数据的结构比较复杂,有兴趣的可以研究一下。
image.png

image.png

直接从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的值。

未重签名前:
image.png

重签名之后:
image.png

结论:重签名和篡改后SHA256的值会改变。

最终结论:可以对Mach-O执行hash运算或者签名验签等等操作来验证App是否完整。

PS:此次研究没有考虑手机越狱的情况,越狱的话可以直接使用Tweak以动态库插入,可能SHA256的值就不会改变,因为Mach-O数据没有改变,不过这只是猜想,并没有验证。

你可能感兴趣的:(IOS 验证App完整性探究)