-
-
[原创](JS逆向)某qMusicSign参数vmp逆向分析
-
发表于: 2小时前 41
-
前言
文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口均已做脱敏处理。严正声明禁止用于商业和非法用途,否则由此产生的一切后果与作者本人无关。
- 站点:某Qmusic的vmp
- 加密点:载荷数据中的sign值
- 网址:aHR0cHM6Ly95LnFxLmNvbS8=
- 接口:aHR0cHM6Ly91Ni55LnFxLmNvbS9jZ2ktYmluL211c2ljcy5mY2c/
废话:上一篇的文章写的太糙了,这一篇尽量写的详细。
- 先抓包,找到需要逆向的参数,此次只是以学习目的分析sign的生成过程,其他参数不做分析,如图

- 我们直接全局搜索sing值

- 看到
sign: c发现这个参数在swich-case语句中,我们直接在他的上一步return处打上断点看下。

- 可以看到
r.data的明文值经过了P方法后生成了我们想要的加密参数,那么猫腻就是这个P方法了!我们先狠狠的进入这个p中!


- 稍微往上拉一下代码,可以看到它是在一个自执行函数中,把这个自执行函数折叠起来,可以看到它传入了一长传的字符串巴拉巴拉一堆东西。它比较鬼的是,他把
G._getSecuritySign赋值给P后它就给删除掉了,但其实对我们影响不大,我们把它整个复制出来。

- 新建一个代码片段,把前面拿到的明文值传进
P方法,点击执行后,发现它报错了,G is not defined,说明G未找到,我们回到原文去往上翻代码找G。

- 发现
G就在自执行函数上面定义的,我们直接复制下来放到代码片段中再执行。

- 没问题,现在已经可以随便执行,这样方便我们调试代码。
- 我们先简单看下这个代码的执行流程。

- 这就很明显了,我们把重点放在自执行函数中,进入函数看它先执行的代码位置。

- 把
e(长字符串)参数传入o函数中,o函数中又经过了t函数、n函数,叮叮咣咣的一顿,出来就变成了指令集,这个过程感兴趣的大佬可以自己单步执行去看一下过程,我看不明白,我就不乱说误导了~

- 出来看
n的返回值就已经变成了指令集。

- 接着进行单步它没有直接跳入i或者是
a函数,它是跳到了一个三元表达式,t我们传入的是一个false所以它会跳进a函数中,但是我看了一下,i函数和a函数俩个是一模一样的,我后边在进行逆推算法时也是在俩个函数中都进行了插桩,因为我记得在i函数中打印日志也会输出,这个各位大佬可以自行测试下,不知道有没有发生变化。

- 进入到
a函数中就是一个for的死循环,然后就是swich-case还好这个不是很多,也不是很繁琐,不需要费太多脑子,我们直接找call或者是apply,都还是比较明显的,现在这俩个位置打上日志断点看下日志。


- 可以看到,
apply一个都没有,call有12个,建议是在12个call位置全部都打上日志断点,6个在i方法,6个在a方法,也不是很多。唯一需要注意的点就是这个是++h,要注意打印日志的时候去减掉++的值,因为我们要看的是h当时的执行结果。

- 打好日志断点后运行,看控制台输出的日志,现在是有1027条日志,接下来才是重头戏,我们先把日志全部都复制出来,简单看一下。

- vmp想做算法还原,只能通过日志一点一点分析,代码执行的过程。拿到日志后可以选择从下往上逆推,也可以从头往下看。我是习惯先从下网上推一遍。
- 这个最后一步可以看出是通过toLowerCase()方法把所有字母都变成小写,得到我们想要的参数。

- 目前从这里网上看,发现好像是没关系,看不明白到底是在做什么,没有衔接点。如果遇到这种情况,俩种可能,一,是打的日志点不全,也就是桩插的不够,二,是跟我一样是小白,实战经验不够多,有的大佬通过一个字符串、数字等等,就能知道是在做什么运算,什么加密模式。
- 不过现在这种情况,就是插桩不够,因为我们只对
call的位置做了输出,我们只是了解了代码大概的执行流程,能确认我们找的位置是正确的,因为结果输出了。后续还会把所有运算的位置也都补上日志断点。 - 先接着往上看看,找个大概思路。


- 看这里返回一个长度为40的十六进制字符串,很有可能是
SHA-1哈希算法。toUpperCase()方法是把这个返回结果的小写字母都转为大写字母。 - 我们把加密的入参拿到加密网站测试下。

ok,我们现在总结下获取到的线索。- 线索1.最后的返回结果使用了
toLowerCase()方法。 - 线索2.入参先经过
SHA-1标准算法哈希并且使用toUpperCase()方法,但后边做了什么我们暂时看不出来。 - 为了方便分析,补完运算过程的日志数量有2216条。
- 我习惯把传入的参数先设置为
1,这样日志会少点,方便分析。

- 再看结尾的日志,是不是又变得清晰了一点?
sign参数是经过4部分相加得到。zzc像是固定值。- 目前除了
zzc其他部分的值我们都不知道是怎么来的,但往上看bE2qvaNBirDlY/djus20Y7b1URg这个值就是一点一点的相加得出来的,但是目前看还是有点莫名其妙,感觉日志衔接不起来。 - 举个简单的例子,在js代码中,想把数字转成字符串,就需要借助
string.fromCharCode()方法,反之,就需要使用charCodeAt(0)方法。但其实这里用到的都不是。即使在所有的String.fromCharCode()方法位置打上日志断点,这个日志还是衔接不起来,原因看下图。

- 其实是因为很多运算过后,经过了索引取值,这些位置我们没有打日志断点,所以日志衔接的不清不楚,把这些索引取值的位置都打上日志断点再看。
- 我这里日志已经变成3289条。


这个时候日志就非常清晰了,
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/这明显是base64所以无论怎么用string.fromCharCode()方法或者charCodeAt(0)方法都是得不到想要的结果。****************************************************·bE2qvaNBirDlYdjus20Y7b1URg·值生成过程简单分析**************************************************** * bE2qvaNBirDlYdjus20Y7b1URg 这个值,根据这个日志,简单分析下,通过上面的列表取到俩个值,81和24。 * 81 & 3 = 1 (取值后继续运算,3不确定是不是固定值) * 1 << 4 = 16 (取值后继续运算,4不确定是不是固定值) * 24 >> 4 = 1 (24是上面列表取到的返回值,4不确定是不是固定值) * 16 | 1 = 17 (16和1都是上面运算返回的结果,运算的结果拿来base64取值) * 24 & 15 = 8 (24是上面列表取到的返回值,15不确定是不是固定值) * 8 << 2 = 32 (8是上面的运算返回结果,2不确定是不是固定值,运算结果拿来base64) * 不确定的值都是第一次出现的值,这种值我们需要看更多的日志,来观察、确认。写出一套代码后,要拿多个日志结果套入代码的返回值,保证与浏览器日志结果输出一致。 * 还有个小坑,这个列表里的值是怎么来的?是变化的还是固定值?(这个各位大佬根据日志分析分析~) * 简单就提下这个思路,后续交给各位大佬自己手动操作分析试试~
日志量比较小,可以直接搜索
553B7D79定位的ret的位置往上分析。

- 定位到这儿后,往下看,就是上面分析的值
bE2qvaNBirDlYdjus20Y7b1URg用到20位长数组([ 108, 77, 170, 189, 163, 65, 138, 176, 229, 99, 247, 99, 186, 205, 180, 99, 182, 245, 81, 24 ])生成的过程,我就不在赘述,我相信各位一定没问题的,我们继续向上看! 553B7D79是一个列表,通过join()方法拼接起来的,那我们就看这个列表中的各个字符是怎么来的。

- 我们先确认生成的结果是没问题的,是我们最后加密要用到的,生成的方法就是前面那一串儿字符索引取值得到的,可以看到后四位是
7D79已经对应上,但是还有个疑问是索引的参数是怎么来的?为什么要取19,27,8,5的索引值? - 我们先再往上看日志。

- 各位大佬应该已经发现了,索引就是这个列表
[ 16, 1, 32, 12, 19, 27, 8, 5 ],那么前面的字符串是什么呢?肯定不是base64也不是其他的base模式。大佬们估计一看这个长度,这个样子,就知道,这不是哪个开头的吊SHA-1么!么错! - 废话就不说了,另外一个
84225B7和这个是一模一样的算法,唯一不一样的就是索引列表有区别[ 16, 1, 32, 12, 19, 27, 8, 5 ]。 - 到现在我们的算法还原其实就已经完成了,这俩个列表是固定的值。
84225B7和553B7D79的加密算法是一样的,就是利用入参经过SHA-1哈希后分别取2个固定列表的索引值。bE2qvaNBirDlYdjus20Y7b1URg最长的这个字符串算法,不难,按照我上面说的方法,多分析几个日志块儿,没问题的。整个日志中有多个列表都是固定的值,可以直接利用。- 最后就是把固定值
zzc和经过算法生成的三个字符串拼接起来,通过toLowerCase()方法转换即可。
看完文章一定要操实操实~
- 极其推荐做为小白新手入门
vmp案例!!! - 本文仅学习、分析sign参数的生成过程,不考虑其他参数以及实际用处。
***************************************贴上我丑陋的py代码**************************************************
_log = console.log;
const JScrypt = require('crypto-js');
function listToString(encryptSHA1, list) {
var lists = [];
for (let i = 0; i < list.length; i++) {
let list_text = encryptSHA1.charAt(list[i]);
lists.push(list_text);
};
return lists.join('');
}
function getSign(data) {
let encryptSHA1 = JScrypt.SHA1(data).toString()
let for_list1 = [23, 14, 6, 36, 16, 40, 7, 19];
let for_list2 = [16, 1, 32, 12, 19, 27, 8, 5];
let for_list3 = [2, 16, 2, 1];
let map_list = [89, 39, 179, 150, 218, 82, 58, 252, 177, 52, 186, 123, 120, 64, 242, 133, 143, 161, 121, 179];
let map_dict = {
'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7,
'8': 8, '9': 9, 'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,
'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15
}
let lists = [];
let Base64String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
let Base64str = "";
str_1 = "zzc"
str_2 = listToString(encryptSHA1, for_list1);
str_3 = listToString(encryptSHA1, for_list2);
for (let i = 0; i < 20; i++) {
var num = i * for_list3[0],
str = encryptSHA1.charAt(num),
str_num = map_dict[str],
num2 = str_num * for_list3[1],
num3 = i * for_list3[2],
num4 = num3 + for_list3[3],
str2 = encryptSHA1.charAt(num4),
str_num2 = map_dict[str2],
num5 = num2 + str_num2,
map_num = map_list[i],
num6 = num5 ^ map_num;
lists.push(num6);
}
for (let a = 0; a <= 5;) {
for (let i = 0; i < lists.length; i += 3, a += 1) {
let list = lists.slice(i, i + 3);
var num1 = a * 3,
num2 = num1 + 1,
num3 = num1 + 2,
num4 = list[0] >> 2, // 14
num5 = list[0] & 3,
num6 = num5 << 4,
num7 = list[1] >> 4,
num8 = num6 | num7, // 51
num9 = list[1] & 15,
num10 = num9 << 2,
num11 = list[2] >> 6,
num12 = num10 | num11, // 5
num13 = list[2] & 63; // 11
if (num13 != 0) {
Base64str += Base64String[num4] + Base64String[num8] + Base64String[num12] + Base64String[num13];
} else {
Base64str += Base64String[num4] + Base64String[num8] + Base64String[num12]
}
}
}
str_4 = Base64str.replace(/[\/+]/g,"");
sign = str_1 + str_2 + str_4 + str_3;
return sign.toLowerCase();
}
// 传入参数给getSign函数即可获得sign值
_log('sign值为::>', getSign('111'));
赞赏
赞赏
雪币:
留言: