极略三国请求签名算法分析
想要对一个 app 搞事情,首先就要分析它的流量情况,直接祭出 Charles 。
我们看到所有的大部分请求都有 seed
、sign
、user_id
、dy_udid
等字段。
将请求导入到 Paw 中,重试几次发现可以正常通过。
请求重放
从这简单的几步,我们已经知道了如下信息:
- 每个请求都有时间戳和签名
- 服务器只判断请求本身合不合法,不检查请求时间戳是否在允许范围内
现在我们要从 app 入手看签名算法了。
将砸壳后的 app 拖到 Hopper Disassembler 中,搜寻字符串。这里提示一个小技巧,从请求上分析我们知道有很多字段可以用来定位,稍稍思考一下,就知道sign
、user_id
、dy_udid
的使用场景比seed
多很多,所以这里我们搜索seed
来定位。
这一搜很快啊,马上就看到了目标:
查找 References
看看有哪些地方用到了:
这里可以看到类似 sub_1004acbf4
等函数名,这告诉我们,代码经过了混淆,之后的分析会比较麻烦。随手点进一个看看实现,好家伙,我直接好家伙,这就是我们要找的代码。
在这附近能看到sign
,看到sign
我就兴奋起来,当时我就念了两句诗。
还没来分析算法呢,我就看到了一串奇怪的字符串出现在sign
的附近,没错,就是N@ZRYhAj7~),@fcz.Sntz87*pn0uu=G6
。男人的第六感告诉我,这个东西不对劲,很有问题。
让我们来全局搜索一下这个字符串,发现有另外一个类似的字符串79w+RI4O(khk.#]$SJZo$VJ@pP-M,)GQ
。
这不是我们的目标,暂时放过它,我们查看N@ZRYhAj7~),@fcz.Sntz87*pn0uu=G6
的References
。
嘿,巧了,有一个 显示了类名的方法,直接点进去看看。
看到了signatureWithDictionary:secretKey
和sign
,我说这个是签名算法的密钥,谁赞同,谁反对?
将这段转义伪码,我们看到结构很清晰:
loc_100c5fe84:
r26 = r4;
r25 = r3;
r20 = r0;
if ([r4 objectForKey:r2] == 0x0) {
objc_msgSend([NSDate date], *(&@selector(setHidesWhenStopped:) + 0xcd0));
asm { fcvtzs x8, d0 };
[*(r27 + 0xfe8) stringWithFormat:r2];
[r26 setObject:r2 forKey:r3];
}
r21 = @selector(setObject:forKey:);
[r20 signatureWithDictionary:r26 secretKey:[NSString stringWithUTF8String:*0x1016282b0]];
objc_msgSend(r26, r21);
r26 = [r20 convertRequestToStringWithDictionary:r26];
if (r25 != 0x0) {
r0 = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%@"]]];
r24 = r0;
r1 = @selector(setHTTPMethod:);
}
else {
r0 = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:r24]];
r24 = r0;
[r0 setHTTPMethod:@"POST"];
[r26 dataUsingEncoding:0x4];
r1 = @selector(setHTTPBody:);
r0 = r24;
}
objc_msgSend(r0, r1);
r0 = [NSURLConnection sendSynchronousRequest:r2 returningResponse:r3 error:r4];
if (r0 == 0x0) goto loc_100c601f0;
其中signatureWithDictionary:
的伪码如下:
/* @Class DYCocoaHelper_objc */
+(void *)signatureWithDictionary:(void *)arg2 secretKey:(void *)arg3 {
// ignore useless code
r0 = [arg2 allKeys];
r20 = [r0 sortedArrayUsingSelector:@selector(compare:)];
r23 = [NSMutableArray arrayWithCapacity:[r0 count]];
var_110 = q0;
r1 = @selector(countByEnumeratingWithState:objects:count:);
var_140 = r1;
var_138 = r20;
r0 = objc_msgSend(r20, r1);
if (r0 != 0x0) {
r25 = r0;
r20 = *var_110;
do {
r21 = 0x0;
do {
if (*var_110 != r20) {
objc_enumerationMutation(var_138);
}
r19 = @selector(stringWithFormat:);
[var_128 urlEncodedString:[var_130 objectForKey:*(var_118 + r21 * 0x8)]];
objc_msgSend(@class(NSString), r19);
objc_msgSend(r23, r24);
r21 = r21 + 0x1;
} while (r21 < r25);
r0 = objc_msgSend(var_138, var_140);
r25 = r0;
} while (r0 != 0x0);
}
var_60 = **___stack_chk_guard;
r0 = [var_128 signatureWithString:[r23 componentsJoinedByString:@"&"] secretKey:var_148];
if (**___stack_chk_guard != var_60) {
r0 = __stack_chk_fail();
}
return r0;
}
signatureWithString:
伪码如下
/* @class DYCocoaHelper_objc */
+(void *)signatureWithString:(void *)arg2 secretKey:(void *)arg3 {
[NSString stringWithFormat:@"%@%@"];
r0 = [self md5:r2];
return r0;
}
下面就由我来给大家翻译翻译,什么叫签名算法:
- 首先有个等待加密的请求体,是个字典对象,我们暂时叫它
reqBody
- 往
reqBody
里面塞一个时间戳seed
,由当前时间格式化输出
- 用
signatureWithDictionary:
得到sign
,并且把sign
也塞到reqBody
- 拿到
reqBody
的所有key
- 将
key
按字典顺序排序
- 用
urlEncodedString
输出string
- 用
signatureWithString:
获取sign
- 把
string
和N@ZRYhAj7~),@fcz.Sntz87*pn0uu=G6
做字符串合并
- md5 加密
- 把
reqBody
转化为String
,并发射出去
随手用 Node.js 写了一个实现:
sgk-test.zip
(1.16 KB, 下载次数: 86)
分析暂时告一段落,传统逆向讲究点到为止。被我们放过的目标79w+RI4O(khk.#]$SJZo$VJ@pP-M,)GQ
,是 Response 的签名算法。同样的 md5 加盐,对结构体的处理和 Request 一样。这里就留给大家自己分析啦。