关键词搜索

源码搜索 ×
×

【JS 逆向百例】层层嵌套!某加速商城 RSA 加密

发布2021-08-19浏览470次

详情内容

声明

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

逆向目标

  • 目标:某加速商城登录接口

  • 主页:aHR0cDovL3d3dy4xNXl1bm1hbGwuY29tL3BjL2xvZ2luL2luZGV4

  • 接口:aHR0cDovL3d3dy4xNXl1bm1hbGwuY29tL3BjL2xvZ2luL2NoZWNr

  • 逆向参数:
    Cookie:

    PHPSESSID=g9jp7sfpukg99v03gj69nr9r56
    

    Form Data:

    1. u[password]: 7aad70a547b016a07f2e20bee7b4936111e650aa5c419fafdfb28322......
    2. _csrfToken: 4bea63330c5ccdd37588321d027f4c40129687b0

逆向过程

抓包分析#

在首页点击登陆,来到登录页面,随便输入一个账号密码登陆,抓包定位到登录接口为 aHR0cDovL3d3dy4xNXl1bm1hbGwuY29tL3BjL2xvZ2luL2NoZWNr ,POST 请求,Form Data 里,密码 u[password] 被加密处理了,此外还有一个 _csrfToken 也是需要我们解决的,cookie 里面有一个 PHPSESSID,经过测试,如果不带此参数,最终的请求也是失败的。

01.png

参数逆向

首先看看 _csrfToken,先尝试直python教程接搜索一下它的值,可以发现其实在首页的源码里面就有,直接匹配拿过来即可:

02.png

再看一下 cookie 里面的 PHPSESSID,首先想到的,可能是第一次访问页面,Response Headers 返回的 Set-Cookie 得到的,查看第一次请求,确实是的,如果没有的话,需要清除缓存再访问(开发者工具 ——> Application ——> Storage ——> Clear site data)。

03.png

最后一个密码参数 u[password],肯定是通过 JS 加密得到的,直接 Ctrl+Shift+F 全局搜索,可以直接在 index 首页找到 RSA 加密的地方,埋下断点进行调试,最后的 res 正是加密后的密码:

04.png

我们将这段关键代码进行改写,封装成一个函数:

  1. function getEncryptedPassword(password) {
  2. var public_key = "00bdf3db924714b9c4ddd144910071c282e235ac51371037cf89fa08f28b9105b6326338ed211280154c645bf81bae4184c2b52e2b02b0953e7aa8b25a8e212a0b";
  3. var public_length = "10001";
  4. var rsa = new RSAKey();
  5. rsa.setPublic(public_key, public_length);
  6. return rsa.encrypt(password);
  7. }

这里主要用到的三个函数 RSAKey()setPublic()encrypt(),在开发者工具中,鼠标放到函数上,可以看到这里都是调用的 rsa.js 里面的方法,我们直接将整个文件剥离下来进行本地调试:

05.png

本地调试会发现提示 BigInteger 未定义,鼠标移到这个函数上面,可以发现是调用了 jsbn.js 里面的方法,同样的,直接将整个 jsbn.js 文件剥离下来进行本地调试。

这里其实在 rsa.js 文件的第一行有一句注释:// Depends on jsbn.js and rng.js,我们可以猜测 rsa.js 是可能依赖 jsbn.js 和 rng.js 这两个文件的。

06.png

07.png

有了 jsbn.js 的代码,再次进行调试,会发现又提示 navigator 和 SecureRandom 未定义,navigator 我们已经非常熟悉了,是浏览器的相关信息,一般情况下直接定义为空即可(navigator = {};);将鼠标移到 SecureRandom 函数上面,可以发现是调用了 rng.js 里面的方法,同样的,直接将整个 rng.js 文件剥离下来进行本地调试。这里就证实了前面我们的猜想,rsa.js 确实是依赖 jsbn.js 和 rng.js 的。

我们注意到,这里在 rng.js 文件的第一行,同样有一句注释:// Random number generator - requires a PRNG backend, e.g. prng4.js,表明 rng.js 是随机数生成器,需要 PRNG 后端,例如 prng4.js,在密码学中,PRNG 全称是 pseudorandom number generator,即伪随机数生成器,是指通过特定算法生成一系列的数字,使得这一系列的数字看起来是随机的,但是实际是确定的,所以叫伪随机数,感兴趣的朋友可以深入研究一下,在这里我们知道 rng.js 可能还依赖于 prng4.js,需要进一步调试才清楚。

08.png

09.png

rsa.js、jsbn.js、rng.js 都齐全了,再次本地调试,会发现 rng.js 里面的 rng_psize 未定义,鼠标放上去看到 rng_psize 就是一个定值 256,在右边的 Global 全局变量里也可以看到值为 256,尝试搜索一下 rng_psize,可以发现在 prng4.js 里面有定义 var rng_psize = 256;,果然和注释说得一样,rng.js 是依赖 prng4.js 的,但是这里似乎直接定义一下 rng_psize 就行了。

10.png

直接在本地代码定义一下 var rng_psize = 256;,再次进行调试,此时又会提示 rng.js 里缺少 prng_newstate() 对象,再次回到开发者工具,可以看到 prng_newstate() 是 prng4.js 里面的方法,果然 rng.js 和 prng4.js 的关系并不简单,同样的,我们也直接将整个 prng4.js 文件剥离下来进行本地调试。

11.png

12.png

再次调试,运行无误,可以成功拿到加密后的密码了:

13.png

逻辑总结#

  1. 加密入口可以在 index 首页找到,用到了 rsa.js 里面的三个加密函数 RSAKey()setPublic()encrypt()

  2. rsa.js 里的 BigInteger() 函数依赖 jsbn.js,SecureRandom() 函数依赖 rng.js;

  3. rng.js 里的变量 rng_psize 在 prng4.js 中定义,prng_newstate() 函数也依赖 prng4.js;

要将 rsa.js、jsbn.js、rng.js、prng4.js 这四个 JS 加密文件完整的剥离下来才能还原整个加密过程。

完整代码#

GitHub 关注 K 哥爬虫:https://github.com/kuaidaili,持续分享爬虫相关代码!欢迎 star !

以下只演示部分关键代码,完整代码仓库地址:https://github.com/kuaidaili/crawler/

参数 JS 加密关键代码#

  1. navigator = {};
  2. // ================== prng4.js begin ================== //
  3. function Arcfour() {}
  4. function ARC4init(key) {}
  5. function ARC4next() {}
  6. // 此处省略 N 个函数
  7. var rng_psize = 256;
  8. // ================== prng4.js end ================== //
  9. // ================== rng.js begin ================== //
  10. var rng_state;
  11. var rng_pool;
  12. var rng_pptr;
  13. function rng_seed_int(x) {}
  14. function rng_seed_time() {}
  15. // 此处省略 N 个函数
  16. function SecureRandom() {}
  17. SecureRandom.prototype.nextBytes = rng_get_bytes;
  18. // ================== rng.js end ================== //
  19. // ================== jsbn.js begin ================== //
  20. var dbits;
  21. var canary = 0xdeadbeefcafe;
  22. var j_lm = ((canary & 0xffffff) == 0xefcafe);
  23. function BigInteger(a, b, c) {}
  24. function nbi() {}
  25. // 此处省略 N 个函数
  26. // protected
  27. BigInteger.prototype.copyTo = bnpCopyTo;
  28. BigInteger.prototype.fromInt = bnpFromInt;
  29. BigInteger.prototype.fromString = bnpFromString;
  30. BigInteger.prototype.clamp = bnpClamp;
  31. BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
  32. BigInteger.prototype.drShiftTo = bnpDRShiftTo;
  33. BigInteger.prototype.lShiftTo = bnpLShiftTo;
  34. BigInteger.prototype.rShiftTo = bnpRShiftTo;
  35. BigInteger.prototype.subTo = bnpSubTo;
  36. BigInteger.prototype.multiplyTo = bnpMultiplyTo;
  37. BigInteger.prototype.squareTo = bnpSquareTo;
  38. BigInteger.prototype.divRemTo = bnpDivRemTo;
  39. BigInteger.prototype.invDigit = bnpInvDigit;
  40. BigInteger.prototype.isEven = bnpIsEven;
  41. BigInteger.prototype.exp = bnpExp;
  42. // public
  43. BigInteger.prototype.toString = bnToString;
  44. BigInteger.prototype.negate = bnNegate;
  45. BigInteger.prototype.abs = bnAbs;
  46. BigInteger.prototype.compareTo = bnCompareTo;
  47. BigInteger.prototype.bitLength = bnBitLength;
  48. BigInteger.prototype.mod = bnMod;
  49. BigInteger.prototype.modPowInt = bnModPowInt;
  50. // "constants"
  51. BigInteger.ZERO = nbv(0);
  52. BigInteger.ONE = nbv(1);
  53. // ================== jsbn.js end ================== //
  54. // ================== rsa.js begin ================== //
  55. function parseBigInt(str, r) {}
  56. function linebrk(s, n) {}
  57. function byte2Hex(b) {}
  58. function pkcs1pad2(s, n) {}
  59. function RSAKey() {}
  60. function RSASetPublic(N, E) {}
  61. function RSADoPublic(x) {}
  62. function RSAEncrypt(text) {}
  63. // protected
  64. RSAKey.prototype.doPublic = RSADoPublic;
  65. // public
  66. RSAKey.prototype.setPublic = RSASetPublic;
  67. RSAKey.prototype.encrypt = RSAEncrypt;
  68. //RSAKey.prototype.encrypt_b64 = RSAEncryptB64;
  69. // ================== rsa.js end ================== //
  70. function getEncryptedPassword(password) {
  71. var public_key = "00bdf3db924714b9c4ddd144910071c282e235ac51371037cf89fa08f28b9105b6326338ed211280154c645bf81bae4184c2b52e2b02b0953e7aa8b25a8e212a0b";
  72. var public_length = "10001";
  73. var rsa = new RSAKey();
  74. rsa.setPublic(public_key, public_length);
  75. return rsa.encrypt(password);
  76. }
  77. // 测试样例
  78. console.log(getEncryptedPassword("123456"))

Python 登录关键代码

  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. import execjs
  4. import requests
  5. from lxml import etree
  6. from PIL import Image
  7. index_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kuaidaili/crawler/'
  8. login_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kuaidaili/crawler/'
  9. code_url = '脱敏处理,完整代码关注 GitHub:https://github.com/kuaidaili/crawler/'
  10. headers = {
  11. 'Host': '脱敏处理,完整代码关注 GitHub:https://github.com/kuaidaili/crawler/',
  12. 'Referer': '脱敏处理,完整代码关注 GitHub:https://github.com/kuaidaili/crawler/',
  13. 'Origin': '脱敏处理,完整代码关注 GitHub:https://github.com/kuaidaili/crawler/',
  14. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36'
  15. }
  16. session = requests.session()
  17. def get_encrypted_password(password):
  18. with open('encrypt.js', 'r', encoding='utf-8') as f:
  19. yunmall_js = f.read()
  20. encrypted_password = execjs.compile(yunmall_js).call('getEncryptedPassword', password)
  21. return encrypted_password
  22. def get_csrf_token_cookie():
  23. response = session.get(url=index_url, headers=headers)
  24. tree = etree.HTML(response.text)
  25. csrf_token = tree.xpath("//input[@name='_csrfToken']/@value")[0]
  26. cookies = response.cookies.get_dict()
  27. # print(csrf_token, cookies)
  28. return csrf_token, cookies
  29. def get_very_code(cookies):
  30. response = session.get(url=code_url, cookies=cookies, headers=headers)
  31. with open('code.png', 'wb') as f:
  32. f.write(response.content)
  33. image = Image.open('code.png')
  34. image.show()
  35. very_code = input('请输入验证码: ')
  36. return very_code
  37. def login(csrf_token, very_code, cookies, username, encrypted_password):
  38. data = {
  39. 'u[loginType]': 'name',
  40. 'u[phone]': username,
  41. 'u[password]': encrypted_password,
  42. 'u[veryCode]': very_code,
  43. 'u[token]': '',
  44. '_csrfToken': csrf_token
  45. }
  46. header_info = {
  47. 'X-Requested-With': 'XMLHttpRequest',
  48. }
  49. headers.update(header_info)
  50. response = session.post(url=login_url, data=data, cookies=cookies, headers=headers)
  51. response.encoding = 'utf-8-sig'
  52. response_code = response.text
  53. # print(response_code)
  54. status_code = {
  55. '31': '恭喜,登陆成功。',
  56. '32': '登陆失败。',
  57. '33': '用户名或密码错误。',
  58. '35': '该用户已被管理员锁定。',
  59. '311': '该账号已绑定设备,请在绑定的设备登陆。',
  60. '20001': '验证码填写错误!'
  61. }
  62. try:
  63. print(status_code[response_code])
  64. except KeyError:
  65. print('请求超时!')
  66. def main():
  67. username = input('请输入登录账号: ')
  68. password = input('请输入登录密码: ')
  69. if len(password) > 32:
  70. raise Exception('请输入正确的密码!')
  71. encrypted_password = get_encrypted_password(password)
  72. csrf_token, cookies = get_csrf_token_cookie()
  73. very_code = get_very_code(cookies)
  74. login(csrf_token, very_code, cookies, username, encrypted_password)
  75. if __name__ == '__main__':
  76. main()

相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载