文章列表 iOS Hook UDID 验证
Post
Cancel

iOS Hook UDID 验证

几个月前有朋友想做一个 Fake UDID 的越狱插件,我就研究了一下,做了个插件。今天又有朋友问我类似的需求,我想还是直接写篇文章帮助有需要的人。

这个需求是这样的,有些 App (特别是企业签名的)在安装的时候会要求你先装一个描述文件来获取你手机的 UDID,然后用这个 UDID 来验证你的设备是否在他们的数据库里有记录,我们希望能够每次安装描述文件的时候都返回一个新的 Fake UDID 来迷惑 App 的服务器以为我们是新安装的机型。

这个功能同样可以满足另一个需求,就是你想要下载某个 ADHoc 签名的 App,但是你的设备并不在分发列表里,这时候你可以解析一下签名查看列表,然后在安装前验证 UDID 时返回一个列表内的 UDID。虽然越狱设备根本不需要搞这么麻烦。。。

我通过调试发现做这个验证的是 ManagedConfiguration 框架,进程名 profiled,框架中 MCHTTPTransaction 类的 data 字段就是发送给 UDID 请求网站的回调服务器的数据,包含 UDID 等数据,它使用 PKCS#7 签名。这个字段使用 NSString 无法正确解码,所以用 Objective-C 字符串的方式没办法编辑它。

我最初尝试直接返回未签名的 XML,在类似于 fir.im 这种没有做签名校验的网站是可以通过的,但是在 udid.io 这种做了签名校验的网站通不过。
所以我尝试找它的签名函数,也确实成功拿到了,但是没有找到计算和签名这个 data 字段的函数,故而条路放弃。

我尝试在 udid.io 被回调之前在 Charles 中修改掉 UDID 的值,该网站依然返回正确响应,这可以证明 XML 内容和签名是无关的。

因为无法解码,那么这样一来最简单的方式就是直接修改 HEX。响应体中 XML 里 UDID 元素的样式如下:

1
<key>UDID</key>\n\t<string>4194e4e27qdf84df725d487431fce8e11fd991</string>

那么我们可以先找到 <key>UDID</key>\n\t<string> 的位置,然后向后偏移 40(UDID 的长度)再检查是不是 </string>, 如果符合这种情况,就把中间的 UDID 替换掉。

直接贴代码吧,完整工程在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
static short const kUDIDLength = 40;
static short const kPrefixLength = 25;
static short const kSuffixLength = 9;

/// <key>UDID</key>\n\t<string>
static uint8_t const kPrefix[kPrefixLength] = {
    0x3C, 0x6B, 0x65, 0x79, 0x3E, 0x55, 0x44, 0x49,
    0x44, 0x3C, 0x2F, 0x6B, 0x65, 0x79, 0x3E, 0x0A,
    0x09, 0x3C, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67,
    0x3E
};

/// </string>
static uint8_t const kSuffix[kSuffixLength] = {
    0x3C, 0x2F, 0x73, 0x74, 0x72, 0x69, 0x6E, 0x67, 0x3E
};

static NSString *generateRandomString(NSInteger length) {
    /// 总长度 40, 其中 25 位数字, 其他是字母. (其实网站并不会验证这个规则...)
    NSMutableString *randomizedText = [NSMutableString stringWithString:@"df9249d4418qe1e79c87d1a58fe4247434eff1d1"];
    NSString *buffer = nil;
    for (NSInteger i = randomizedText.length - 1, j; i >= 0; i--) {
        j = arc4random() % (i + 1);
        buffer = [randomizedText substringWithRange:NSMakeRange(i, 1)];
        [randomizedText replaceCharactersInRange:NSMakeRange(i, 1) withString:[randomizedText substringWithRange:NSMakeRange(j, 1)]];
        [randomizedText replaceCharactersInRange:NSMakeRange(j, 1) withString:buffer];
    }
    return [randomizedText copy];
}

static NSData *replacedUUIDData(NSData *data) {
    NSUInteger minLength = kSuffixLength + kUDIDLength + kSuffixLength;
    if (data.length <= minLength) {
        return data;
    }

    uint8_t *buffer = (uint8_t *)[data bytes];
    uint32_t bufferSize = 0;
    uint8_t *bufferBegin = buffer;
    uint8_t *bufferEnd = buffer + data.length;
    while (bufferBegin != bufferEnd && bufferSize < data.length) {
        if (0 == memcmp(bufferBegin, kPrefix, kPrefixLength)) {
            if (0 == memcmp(bufferBegin + kPrefixLength + kUDIDLength, kSuffix, kSuffixLength)) {
                NSString *fakeUDID = generateRandomString(40);
                HBLogDebug(@"Found UDID location, trying to replace it with %@", fakeUDID);
                strncpy((char *)bufferBegin + kPrefixLength, [fakeUDID UTF8String], kUDIDLength);
                break;
            }
        }
        ++bufferBegin;
        ++bufferSize;
    }
    
    return [NSData dataWithBytes:buffer length:data.length];
}

%hook MCHTTPTransaction

- (void)setData:(id)arg1 {
    %log;
    %orig(replacedUUIDData(arg1));
}

%end
本文采用 CC BY 4.0 授权。

博客重新开张,说点什么

破解 FirmStream

Comments powered by Disqus.