免责声明:本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!

通过几个简单的案例对JS逆向从入门到放弃的基础篇的前6章的知识加以运用和巩固。

某道翻译

网址:aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLw==

首先抓包,分析请求的类型,参数特点,返回值特点。

image-20220223111325964
image-20220223111325964

通过网络面板Fetch/XHR,找到需要的网络请求。

查看返回结果为明文,不存在加密。

1
{"translateResult":[[{"tgt":"I love you","src":"我爱你"}]],"errorCode":0,"type":"zh-CHS2en","smartResult":{"entries":["","I love you\r\n"],"type":1}}

查看参数,大部分参数都是常量,只有salt, sign, Its, bv看着像是动态生成的。Its从名字和内容看,推测是13位的时间戳。sign和bv都是32位,且是字母和数字的混合,初步推算是md5加密。salt前13位跟Its一样,多了一位,推测是14位时间戳?🤪

接下来断点调试参数生成的逻辑。通常有2种方式:第一种是根据参数名称,全局搜索查找相应的JS代码;第二种是根据请求的Initiator调用栈逐步分析参数生成的位置;第三种打上XHR/fetch Breakpoints断点进行调试,然后分析Call Stack。

第一种方法,打开开发者工具的全局搜索,搜索sign关键词:

image-20220223140405239
image-20220223140405239

然后很轻松的找到了发送XHR请求的参数data这个object

image-20220223140657071
image-20220223140657071

鼠标悬停到generateSaltSign(t)这个方法上,然后会展示点击进入到这个方法:

image-20220223142733567
image-20220223142733567

可以看到代码比较简单,将它改写成Python代码:

1
2
3
4
5
6
7
8
9
10
11
def generate_salt_sign(content):
n = content[:5000]
t = md5(navigator["appVersion"])
r = str(int(time() * 1000))
i = r + str(random.randint(0, 9))
return {
"lts": r,
"bv": t,
"salt": i,
"sign": md5("fanyideskweb" + n + i + "Y2FYu%TNSbMCxc3t2u^XT")
}

其中的生成sign的最后一串常量会根据用户的Cookie发生变化。我们之前的推测基本都是对的,除了salt。salt是13位时间戳加上随机的一个数字。

有了几个关键参数的生成代码,我们从Network面板复制出XHR请求的cURL,然后找一个在线的cURL to Python工具,快速生成Python代码,然后补上我们写好的generate_salt_sign方法,运行代码,结果如下:

image-20220223145314773
image-20220223145314773
某度翻译

网址:aHR0cHM6Ly9mYW55aS55b3VkYW8uY29tLw==

老规矩,先抓包。

image-20220223150801765
image-20220223150801765

找到一个XHR请求,返回结果为明文,不需要解密。参数有2个sign和token,token目测是md5。用第一种方式,打开开发者工具,全局搜索sign,发现搜索出来的结果非常多,主要是存在很多干扰,比如assign。

image-20220223152341564
image-20220223152341564

这里介绍一个小技巧,如果搜索出来的干扰比较多的时候,不妨搜索“**,关键词:**”试试,为什么如此操作?因为发送XHR请求的时候,data都放在一个object中,我们知道,JS的Object格式都长这样:

1
2
3
4
object = {
key1: value,
key2: value2
}

然后JS文件基本都会压缩,所以要搜索的参数自然前边有个逗号,后边有个封号。我们按照这种方式搜索,结果如下:

image-20220223154110965
image-20220223154110965

可以看到明显搜索结果更加精确了,结果也少了。然后我们逐条分析,最终定位到我们参数生成的代码地方,然后打上断点开始调试:

image-20220223154356745
image-20220223154356745

没有想到的是token竟然是一个常量。我们看他的值:

image-20220223154444310
image-20220223154444310

那就更简单了,只需要找到sign的生成方法即可。可以看到sign是调用了一个L方法生成,接受一个参数e,这个e是我们传入的要翻译的text。我们点击进入L方法:

image-20220223154647115
image-20220223154647115

好了,接下来就到了扣JS和补全JS的环节了。我们扣出来这个方法,然后用node去执行:

image-20220223162130736
image-20220223162130736

报错,i未定义,我们分析代码,补全i的定义:

image-20220223163824429
image-20220223163824429

运行接着报错,意料之中,

image-20220223162330599
image-20220223162330599

u没有定义,我们从这一行代码可以看到,u的值来自于window这个object:

image-20220223162424396
image-20220223162424396

这个l是一个固定的字符串,值为”gtk”,然后u这是取得object的这个gtk属性,也是一个定植,那么我们补全这个window即可:

image-20220223162840470
image-20220223162840470
image-20220223163853288
image-20220223163853288

然后接着运行代码,接着报错:

image-20220223163920006
image-20220223163920006

n这个方法没有定义,我们回到源码,将n的定义拷贝进来:

image-20220223164310865
image-20220223164310865

然后再次运行代码,终于拿到了sign的值:

image-20220223165713366
image-20220223165713366

这里很明显看到我们扣出来的JS比较大, 如果对代码进行分析然后改写成Python会比较麻烦,所以利用JS逆向之调用JS的两种方式,将JS代码用express框架做成一个服务供Python端去调用。效果如下:

image-20220223165732284
image-20220223165732284

然后就是比较机械性的工作了,复制cURL转成Python代码,然后修改sign通过走接口去获取,最后贴一张最终执行的效果图:

image-20220223170807972
image-20220223170807972
总结

通过几个简单的案例,回顾了下开发者面板的一些使用方式,包括全局搜索,断点调试跟踪,调用栈分析,扣JS以及Python调用JS的几种方式。若需要代码,扫描加微信即可。

image-20230517010053227