关于python:JS-逆向百例webpack-改写实战G-某游戏-RSA-加密

9次阅读

共计 10034 个字符,预计需要花费 26 分钟才能阅读完成。

关注微信公众号:K 哥爬虫,QQ 交换群:808574309,继续分享爬虫进阶、JS/ 安卓逆向等技术干货!

申明

本文章中所有内容仅供学习交换,抓包内容、敏感网址、数据接口均已做脱敏解决,严禁用于商业用途和非法用处,否则由此产生的所有结果均与作者无关,若有侵权,请分割我立刻删除!

逆向指标

  • 指标:G 某游戏登录
  • 主页:aHR0cHM6Ly93d3cuZ205OS5jb20v
  • 接口:aHR0cHM6Ly9wYXNzcG9ydC5nbTk5LmNvbS9sb2dpbi9sb2dpbjM=
  • 逆向参数:
    Query String Parameters:

    password: kRtqfg41ogc8btwGlEw6nWLg8cHcCW6R8JaeM......

逆向过程

抓包剖析

来到首页,轻易输出一个账号密码,点击登陆,抓包定位到登录接口为 aHR0cHM6Ly9wYXNzcG9ydC5nbTk5LmNvbS9sb2dpbi9sb2dpbjM=,GET 申请,Query String Parameters 里,明码 password 被加密解决了。

加密入口

间接搜寻关键字 password 会发现后果太多不好定位,应用 XHR 断点比拟容易定位到加密入口,无关 XHR 断点调试能够查看 K 哥往期的教程:【JS 逆向百例】XHR 断点调试,Steam 登录逆向,如下图所示,在 home.min.js 里能够看到要害语句 a.encode(t.password, s)t.password 是明文明码,s 是工夫戳。

跟进 a.encode() 函数,此函数依然在 home.min.js 里,察看这部分代码,能够发现应用了 JSEncrypt,并且有 setPublicKey 设置公钥办法,由此能够看出应该是 RSA 加密,具体步骤是将明文明码和工夫戳组合成用 | 组合,通过 RSA 加密后再进行 URL 编码失去最终后果,如下图所示:

RSA 加密找到了公钥,其实就能够间接应用 Python 的 Cryptodome 模块来实现加密过程了,代码如下所示:

import time
import base64
from urllib import parse
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import PKCS1_v1_5


password = "12345678"
timestamp = str(int(time.time() * 1000))
encrypted_object = timestamp + "|" + password
public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDq04c6My441Gj0UFKgrqUhAUg+kQZeUeWSPlAU9fr4HBPDldAeqzx1UR92KJHuQh/zs1HOamE2dgX9z/2oXcJaqoRIA/FXysx+z2YlJkSk8XQLcQ8EBOkp//MZrixam7lCYpNOjadQBb2Ot0U/Ky+jF2p+Ie8gSZ7/u+Wnr5grywIDAQAB"
rsa_key = RSA.import_key(base64.b64decode(public_key))  # 导入读取到的公钥
cipher = PKCS1_v1_5.new(rsa_key)                        # 生成对象
encrypted_password = base64.b64encode(cipher.encrypt(encrypted_object.encode(encoding="utf-8")))
encrypted_password = parse.quote(encrypted_password)
print(encrypted_password)

即使是不应用 Python,咱们同样能够本人援用 JSEncrypt 模块来实现这个加密过程(该模块应用办法可参考 JSEncrypt GitHub),如下所示:

/*
援用 jsencrypt 加密模块,如果在 PyCharm 里间接应用 require 援用最新版 jsencrypt,运行可能会提醒 jsencrypt.js 里 window 未定义,间接在该文件定义 var window = this; 即可,也能够应用和网站用的一样的 2.3.1 版本:https://npmcdn.com/jsencrypt@2.3.1/bin/jsencrypt.js
也能够将 jsencrypt.js 间接粘贴到此脚本中应用,如果提醒未定义,间接在该脚本中定义即可。*/

JSEncrypt = require("jsencrypt")

function getEncryptedPassword(t, e) {var jsEncrypt = new JSEncrypt();
    jsEncrypt.setPublicKey('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDq04c6My441Gj0UFKgrqUhAUg+kQZeUeWSPlAU9fr4HBPDldAeqzx1UR92KJHuQh/zs1HOamE2dgX9z/2oXcJaqoRIA/FXysx+z2YlJkSk8XQLcQ8EBOkp//MZrixam7lCYpNOjadQBb2Ot0U/Ky+jF2p+Ie8gSZ7/u+Wnr5grywIDAQAB');
    var i = e ? e + "|" + t : t;
    return encodeURIComponent(jsEncrypt.encrypt(i));
}

var password = "12345678";
var timestamp = (new Date).getTime();
console.log(getEncryptedPassword(password, timestamp));

webpack 改写

本文的题目是 webpack 改写实战,所以很显然本文的目标是为了练习 JavaScript 模块化编程 webpack 代码的改写,当初大多数站点都应用了这种写法,然而并不是所有站点都像本文遇到的站点一样,能够很容易应用其余办法来实现的,往往大多数站点须要你本人扒下他的源码来还原加密过程,无关 JavaScript 模块化编程,即 webpack,在 K 哥往期的文章中有过具体的介绍:爬虫逆向根底,了解 JavaScript 模块化编程 webpack

一个规范的 webpack 整体是一个 IIFE 立刻调用函数表达式,其中有一个模块加载器,也就是调用模块的函数,该函数中个别具备 function.call() 或者 function.apply() 办法,IIFE 传递的参数是一个列表或者字典,外面是一些须要调用的模块,写法相似于:

!function (allModule) {function useModule(whichModule) {allModule[whichModule].call(null, "hello world!");
    }
}([function module0(param) {console.log("module0:" + param)},
    function module1(param) {console.log("module1:" + param)},
    function module2(param) {console.log("module2:" + param)},
]);

察看这次站点的加密代码,会发现所有加密办法都在 home.min.js 外面,在此文件结尾能够看到整个是一个 IIFE 立刻调用函数表达式,function e 外面有要害办法 .call(),由此能够判断该函数为模块加载器,前面传递的参数是一个字典,外面是一个个的对象办法,也就是须要调用的模块函数,这就是一个典型的 webpack 写法,如下图所示:

接下来咱们通过 4 步实现对 webpack 代码的改写,将原始代码扒下来实现加密的过程。

1、找到 IIFE

IIFE 立刻调用函数表达式,也称为立刻执行函数,自执行函数,将源码中的 IIFE 框架抠出来,后续将有用的代码再往里面放:

!function (t) {}({})

2、找到模块加载器

后面咱们曾经讲过,带有 function.call() 或者 function.apply() 办法的就是模块加载器,也就是调用模块的办法,在本例中,function e 就是模块加载器,将其抠下来即可,其余多余的代码能够间接删除,留神外面用到了 i,所以定义 i 的语句也要抠下来:

!function (t) {function e(s) {if (i[s])
            return i[s].exports;
        var n = i[s] = {exports: {},
            id: s,
            loaded: !1
        };
        return t[s].call(n.exports, n, n.exports, e),
            n.loaded = !0,
            n.exports
    }
    var i = {};}({})

3、找到调用的模块

从新来到加密的中央,第一个模块是 3,n 外面的 encode 办法最终返回的就是加密后的后果,如下图所示:

第二个模块是 4,能够看到模块 3 外面的 this.jsencrypt.encrypt(i) 办法实际上是调用的第 3340 行的办法,该办法在模块 4 外面,这里定位在模块 4 的办法,能够在浏览器开发者工具 source 页面,将鼠标光标放到该函数后面,始终往上滑动,直到模块结尾,也能够应用 VS Code 等编辑器,将整个 home.min.js 代码粘贴过来,而后抉择折叠所有代码,再搜寻这个函数,即可疾速定位在哪个模块。

确定应用了 3 和 4 模块后,将这两个模块的所有代码扣下来即可,大抵代码架构如下(模块 4 具体的代码太长,已删除):

!function (t) {function e(s) {if (i[s])
            return i[s].exports;
        var n = i[s] = {exports: {},
            id: s,
            loaded: !1
        };
        return t[s].call(n.exports, n, n.exports, e),
            n.loaded = !0,
            n.exports
    }
    var i = {};}(
    {4: function (t, e, i) {},
        3: function (t, e, i) {
            var s;
            s = function (t, e, s) {function n() {
                    "undefined" != typeof r && (this.jsencrypt = new r.JSEncrypt,
                        this.jsencrypt.setPublicKey("-----BEGIN PUBLIC KEY----- 略 -----END PUBLIC KEY-----"))
                }

                var r = i(4);
                n.prototype.encode = function (t, e) {
                    var i = e ? e + "|" + t : t;
                    return encodeURIComponent(this.jsencrypt.encrypt(i))
                },
                    s.exports = n
            }.call(e, i, e, t),
                !(void 0 !== s && (t.exports = s))
        }
    }
)

这里须要咱们了解一个中央,那就是模块 3 的代码里有一行 var r = i(4);,这里的 i3: function (t, e, i) {},传递过去的 i,而模块 3 又是由模块加载器调用的,即 .call(n.exports, n, n.exports, e) 外面的某个参数就是 i,后面在解说根底的时候曾经说过,.call 的第一个参数指定的是函数体内 this 对象的指向,并不代表真正参数,所以第一个 n.exports 并不是参数,从第二个参数即 n 开始算,那么 i 其实就是 .call(n.exports, n, n.exports, e) 外面的 e,所以 var r = i(4); 实际上就是模块加载器 function e 调用了模块 4,因为这里模块 4 是个对象,所以这里最好写成 var r = i("4");,这里是数字,所以能够胜利运行,如果模块 4 名字变成 func4 或者其余名字,那么调用时就必须要加引号了。

4、导出加密函数

目前要害的加密代码曾经剥离结束了,最初一步就是须要把加密函数导出来供咱们调用了,首先定义一个全局变量,如 eFunc,而后在模块加载器前面应用语句 eFunc = e,把模块加载器导出来:

var eFunc;

!function (t) {function e(s) {if (i[s])
            return i[s].exports;
        var n = i[s] = {exports: {},
            id: s,
            loaded: !1
        };
        return t[s].call(n.exports, n, n.exports, e),
            n.loaded = !0,
            n.exports
    }
    var i = {};
    eFunc = e
}(
    {4: function (t, e, i) {},
        3: function (t, e, i) {}}
)

而后定义一个函数,传入明文明码,返回加密后的明码:

function getEncryptedPassword(password) {var timestamp = (new Date).getTime();
    var encryptFunc = eFunc("3");
    var encrypt = new encryptFunc;
    return encrypt.encode(password, timestamp)
}

其中 timestamp 为工夫戳,因为咱们最终须要调用的是模块 3 外面的 n.prototype.encode 这个办法,所以首先调用模块 3,返回的是模块 3 外面的 n 函数(能够在浏览器运行代码,一步一步查看后果),而后将其 new 进去,调用 n 的 encode 办法,返回加密后的后果。

自此,webpack 的加密代码就剥离结束了,最初调试会发现 navigator 和 window 未定义,定义一下即可:

var navigator = {};
var window = global;

这里扩大一下,在浏览器外面 window 其实就是 global,在 nodejs 里没有 window,然而有个 global,与浏览器的 window 对象类型类似,是全局可拜访的对象,因而在 nodejs 环境中能够将 window 定义为 global,如果定义为空,可能会引起其余谬误。

残缺代码

GitHub 关注 K 哥爬虫,继续分享爬虫相干代码!欢送 star!https://github.com/kgepachong/

以下只演示局部要害代码,不能间接运行! 残缺代码仓库地址:https://github.com/kgepachong…

JavaScript 加密要害代码架构

办法一:webpack 改写源码实现 RSA 加密:

var navigator = {};
var window = global;
var eFunc;

!function (t) {function e(s) {if (i[s])
            return i[s].exports;
        var n = i[s] = {exports: {},
            id: s,
            loaded: !1
        };
        return t[s].call(n.exports, n, n.exports, e),
            n.loaded = !0,
            n.exports
    }

    var i = {};
    eFunc = e;
}(
    {4: function (t, e, i) {},
        3: function (t, e, i) {}}
)

function getEncryptedPassword(password) {var timestamp = (new Date).getTime();
    var encryptFunc = eFunc("3");
    var encrypt = new encryptFunc;
    return encrypt.encode(password, timestamp)
}

// 测试样例
// console.log(getEncryptedPassword("12345678"))

办法二:间接应用 JSEncrypt 模块实现 RSA 加密:

/*
援用 jsencrypt 加密模块,此脚适宜在 nodejs 环境下运行。1、应用 require 语句援用,前提是应用 npm 装置过;2、将 jsencrypt.js 间接粘贴到此脚本中应用,同时要将结尾 exports.JSEncrypt = JSEncrypt; 改为 je = JSEncrypt 导出办法。PS:须要定义 var navigator = {}; var window = global;,否则提醒未定义。*/

// ========================= 1、require 形式援用 =========================
// var je = require("jsencrypt")

// =================== 2、间接将 jsencrypt.js 复制过去 ===================
/*! JSEncrypt v2.3.1 | https://npmcdn.com/jsencrypt@2.3.1/LICENSE.txt */
var navigator = {};
var window = global;

// 这里是 jsencrypt.js 代码

function getEncryptedPassword(t) {var jsEncrypt = new je();
    jsEncrypt.setPublicKey('MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDq04c6My441Gj0UFKgrqUhAUg+kQZeUeWSPlAU9fr4HBPDldAeqzx1UR92KJHuQh/zs1HOamE2dgX9z/2oXcJaqoRIA/FXysx+z2YlJkSk8XQLcQ8EBOkp//MZrixam7lCYpNOjadQBb2Ot0U/Ky+jF2p+Ie8gSZ7/u+Wnr5grywIDAQAB');
    var e = (new Date).getTime();
    var i = e ? e + "|" + t : t;
    return encodeURIComponent(jsEncrypt.encrypt(i));
}

// 测试样例
// console.log(getEncryptedPassword("12345678"));

Python 登录要害代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-


import re
import json
import time
import random
import base64
from urllib import parse

import execjs
import requests
from PIL import Image
from Cryptodome.PublicKey import RSA
from Cryptodome.Cipher import PKCS1_v1_5

login_url = '脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler'
verify_image_url = '脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler'
check_code_url = '脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler'

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
session = requests.session()


def get_jquery():
    jsonp = ''
    for _ in range(21):
        jsonp += str(random.randint(0, 9))
    jquery = 'jQuery' + jsonp + '_'
    return jquery


def get_dict_from_jquery(text):
    result = re.findall(r'\((.*?)\)', text)[0]
    return json.loads(result)


def get_encrypted_password_by_javascript(password):
    # 两个 JavaScript 脚本,两种办法均可
    with open('gm99_encrypt.js', 'r', encoding='utf-8') as f:
    # with open('gm99_encrypt_2.js', 'r', encoding='utf-8') as f:
        exec_js = f.read()
    encrypted_password = execjs.compile(exec_js).call('getEncryptedPassword', password)
    return encrypted_password


def get_encrypted_password_by_python(password):
    timestamp = str(int(time.time() * 1000))
    encrypted_object = timestamp + "|" + password
    public_key = "脱敏解决,残缺代码关注 GitHub:https://github.com/kgepachong/crawler"
    rsa_key = RSA.import_key(base64.b64decode(public_key))  # 导入读取到的公钥
    cipher = PKCS1_v1_5.new(rsa_key)                        # 生成对象
    encrypted_password = base64.b64encode(cipher.encrypt(encrypted_object.encode(encoding="utf-8")))
    encrypted_password = parse.quote(encrypted_password)
    return encrypted_password


def get_verify_code():
    response = session.get(url=verify_image_url, headers=headers)
    with open('code.png', 'wb') as f:
        f.write(response.content)
    image = Image.open('code.png')
    image.show()
    code = input('请输出图片验证码:')
    return code


def check_code(code):
    timestamp = str(int(time.time() * 1000))
    params = {'callback': get_jquery() + timestamp,
        'ckcode': code,
        '_': timestamp,
    }
    response = session.get(url=check_code_url, params=params, headers=headers)
    result = get_dict_from_jquery(response.text)
    if result['result'] == 1:
        pass
    else:
        raise Exception('验证码输出谬误!')


def login(username, encrypted_password, code):
    timestamp = str(int(time.time() * 1000))
    params = {'callback': get_jquery() + timestamp,
        'encrypt': 1,
        'uname': username,
        'password': encrypted_password,
        'remember': 'checked',
        'ckcode': code,
        '_': timestamp
    }
    response = session.get(url=login_url, params=params, headers=headers)
    result = get_dict_from_jquery(response.text)
    print(result)


def main():
    # 测试账号:15434947408,明码:iXqC@aJt8fi@VwV
    username = input('请输出登录账号:')
    password = input('请输出登录明码:')

    # 获取加密后的明码,应用 Python 或者 JavaScript 实现均可
    encrypted_password = get_encrypted_password_by_javascript(password)
    # encrypted_password = get_encrypted_password_by_python(password)

    # 获取验证码
    code = get_verify_code()

    # 校验验证码
    check_code(code)

    # 登录
    login(username, encrypted_password, code)


if __name__ == '__main__':
    main()

正文完
 0