几个月前有朋友想做一个 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