逆向实战(1)-某国石油招标投标网站研究(非对称加密、图片计算验证码)

该文章已生成可运行项目,

本次主要分析对象网址(base64):aHR0cHM6Ly93d3cuY25wY2JpZGRpbmcuY29tLyMvdGVuZGVycw==
在这里插入图片描述

一、接口数据分析
通过浏览器抓包,通过翻页得出page接口即为我们需要研究的对象
在这里插入图片描述
1)、发现请求参数加密
在这里插入图片描述
2)、发现服务端响应内容加密
在这里插入图片描述
二、加密入口分析
1)、通过page接口路径,全局搜索 “article/page”
在这里插入图片描述
2)、点击结果发现入口
在这里插入图片描述
3)、打上断点刷新进行调试,发现这个e对象包含的内容有点想请求参数,划重点:e
在这里插入图片描述
4)、这个e是怎么来的呢?我们往右边堆栈向上找,发现e对象的组成,好像跟页码相关;嗯~有东西!!!
在这里插入图片描述
5)、好,我们继续上一步,鼠标移动到原来的位置,发现是调用了函数名叫O的,点进去look look
在这里插入图片描述
6)、打上断点,继续调试,发现O函数接收两个参数,一个是路径,一个是之前的e;不难看出这是用本地“logo1”密钥对t对象进行了加密,然后向后台page接口post了一个请求,然后对返回值进行本地“logo2”密钥解密处理,好像一切都是那么Perfect;通过我大脑的姿势海洋得出,这是一个非对称加密,如RSA
在这里插入图片描述
可是为何这个加解密钥都是从本地存储中获取的呢???哇,这不是给我们这些高端逆向大神送分嘛,值得给该网站前端工程师加个鸡腿!以表鼓励!
我们继续!!!
在我们浏览器本地存储中发现密钥
在这里插入图片描述
7)、我们先清除一下,然后去掉之前的所有断点,刷新页面看看
在这里插入图片描述
发现页面没有数据,不管怎么刷新都没有数据,并且接口参数也显示false,响应5001;由此判断之前O函数即为加密解密函数
在这里插入图片描述
8)、确定了加密解密函数,下一步就是找出在哪里生成的密钥;上一步在当前页面刷新,观察本地存储发现没有添加密钥;那我们就往首页上找,一般这种需要复用的参数都会在第一次加载中就保存下来
在这里插入图片描述
9)、通过首页抓包,发现密钥
在这里插入图片描述
在这里插入图片描述
10)、通过逐一排查,发现加载的资源中有一个bj.css资源里面有东西,跟logo1和logo2能对上;我们离成功有进了一步
在这里插入图片描述
11)、我们可以对这个bj.css资源进行http请求,然后从响应中正则获取到密钥

三、扣代码阶段
1)、分析了那么久,我们就进入了扣js代码的重要环节了,继续努力,加油!!!
我们之前对O函数重新打上断点,然后把整个O函数扣下来,对该函数进行调试分析,

在这里插入图片描述
通过下一步调试,我们能够知道O函数传的两个参数e和t,t是一个参数对象;我们在node环境上执行一下

O = function (e, t) {
    var n = m["a"].encode(JSON.stringify(t))
        , i = new l.a;
    return i.setPublicKey(localStorage.getItem("logo1")),		// 设置公钥方法
        t = i.encryptLong(JSON.stringify(n)),		// 请求参数
        new Promise((function (n, i) {
                g.post(e, JSON.stringify(t), {		// 请求接口
                    headers: {
                        "Content-Type": "application/json;charset=UTF-8"
                    }
                }).then((function (e) {
                        if ("string" == typeof e) {
                            var t = new l.a;
                            if (t.setPrivateKey(localStorage.getItem("logo2")),
                                !e)
                                return !1;
                            var i = t.decryptLong(e)
                                , r = m["a"].decode(i)
                                , a = JSON.parse(r);
                            n(a)
                        } else
                            n(e)
                    }
                )).catch((function (e) {
                        i(e)
                    }
                ))
            }
        ))
};

var t = {
    "current": 1,
    "size": 10,
    "condition": {
        "columnId": 1,
        "title": "",
        "projectType": ""
    }
}

O("/article/page", t)

// 报错: var n = m["a"].encode(JSON.stringify(t))			ReferenceError: m is not defined
            ^

发现报错,不慌正常现象,显示m未定义,那我们在源码中向上找,找一下最近定义的m在哪里;发现m在最开始就定义了m=n(“e762”)
在这里插入图片描述
而且后面用到的i = new l.a;l也是在这里定义了,那就打上断点,刷新继续调试
在这里插入图片描述
n(“e762”)就是这个h©,ok! 我们扣下来;即m = h(“e762”);继续运行扣下来的代码
在这里插入图片描述
发现h里面的:u is not defined;不慌,在哪个地方报错,就在源码对应位置打断点,调试,一点一点来,向上找,上面定义了u是一个空对象,扣下来,继续运行一下,缺啥补啥
在这里插入图片描述

var u = {};

function h(c) {
    if (u[c])
        return u[c].exports;
    var n = u[c] = {
        i: c,
        l: !1,
        exports: {}
    };
    return e[c].call(n.exports, n, n.exports, h),
        n.l = !0,
        n.exports
};

var m = h("e762");

ReferenceError: e is not defined

e未定义;向上找,发现e是一个大的自执行函数传进来的,而最后传的是一个空的数组,空数组没有call方法,感觉不对!应该不是这个e,那我们就断点,下一步调试一下,看看call了哪个函数
在这里插入图片描述
call的是这个e762函数,整个函数都扣下来,别扣少了啊~~;大略扫一眼,这个函数应该就是跟加密有关的了
在这里插入图片描述
整理一下扣的代码,骚味修改一下运行
在这里插入图片描述

TypeError: r.d is not a function;断点继续,r.d调用的是h.d
在这里插入图片描述
h.o也有了,继续执行,哦哦哦通过了第一关
在这里插入图片描述
接下来是:ReferenceError: l is not defined;而l跟之前的m是一起定义的
回到之前断点位置,继续重复一样的步骤,f = n(“e2b4”), l = n.n(f);f的入口和m的入口是一样的,只是最终调的是名为e2b4的函数,扣下e2b4函数,其实从这里就能知道,就是RSA算法加密了,作为一名合格的扣水高手,我们要扣出精髓,对吧~,管他什么算法,我对着就是一顿乱扣;
在这里插入图片描述
运行,报错ReferenceError: navigator is not defined;navigator是个什么玩意儿: navigator 我也看不懂,反正就不是个什么好玩意儿;
对于我们逆向小菜来说,已经扣了这么久,是时候休息一会儿;在B站上面探索了一下世界,心中自然滴感叹大千世界多么美好啊 我们要好好学习天天进步,努力赚钱享受这美好的世界

回到我们的扣水环节

我们在js文件开头定义一下,这些都是浏览器需要的东西,我们用node模拟一下就好了
在这里插入图片描述
ok,过来了,n未定义,断点运行;n.n就是这个h.n,扣下来
在这里插入图片描述
在这里插入图片描述
到这里其实扣代码基本就到尾声了,运行之后显示 ReferenceError: localStorage is not defined;就是存储在本地的那两个密钥,我们可以用python脚本去请求,以及page接口我们也用python模拟一下

顺便我们把那个O函数改装一下,去掉后台交互的功能,暴露加密(encruption)和解密(decryption)两个接口给我们的脚本调用;这就是最终的扣的js代码,因为字数限制,所以只能截图展示,(hub地址):
在这里插入图片描述
四、整合逻辑阶段
1)、密钥获取

# 获取密钥
def get_css_pkey(RequestsSession):
    local_storage = {}
    timestamp = str(int(time.time() * 1000))
    headers = {
        "Machine_code": timestamp,
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
    }
    response = RequestsSession.get('https://www.******.com/cms/css/bj.css', headers=headers)
    text = response.text
    text_list = re.findall(r"url\(data:image/png;base64,(.*?)\);", text)

    public_key = text_list[0]
    private_key = text_list[1]

    local_storage["logo1"] = public_key
    local_storage["logo2"] = private_key
    local_storage["time"] = timestamp

    return local_storage

2)、验证码计算绕过
该网站会有不定时的验证码输入:
在这里插入图片描述
通过抓包,发现这个验证码是通过 validateCode/undefined这个接口来的,我们输入验证码后,是通过 /validateCode/4这个接口返回服务端进行校验的;完美!
为了方便简单,我们直接使用第三方的打码平台,当然作为一个始于20世纪并立足于21世纪的跨世纪当代顶流小码农,本人肯定不屑于使用付费平台,从小受苦的我,知道每一分钱都不能被资本主义薅走;但是资本主义的羊毛我有义务也有权力去薅;
以上都是废话。
我们继续ing
通过万能的BD,我在万千从中看见一顶绿,注册即送500积分,并且1元就是1000积分,靠!!!我不薅它羊毛都对不起自己了;
看了一下文档,非常简单
在这里插入图片描述
部分代码如下:

# 通过验证码识别
def pass_captcha_check(RequestsSession):
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
    }
    response = RequestsSession.get("https://www.*******.com/cms/validateCode/undefined", headers=headers)
    message = response.json().get("message")
    # 这个验证码校验时间不确定,本着不占用资本主义资源的初心(不想浪费我薅到的羊毛)
    # 过了就不管他了
    if message == "验证码错误。":
        img_data = response.json().get("data")
        img_base64 = "data:image/png;base64," + img_data
        payload = {
            "image": img_base64,
            # 你们别想着薅羊毛,我已经重置了密钥
            "token": "ih2K5xPJLwZaRkURSyeNDqSabShiDZKBIBLR3xG4_z4",
            "type": "50100"
        }

        response = RequestsSession.post("http://api.*****.com/api/YmServer/customApi", headers={'Content-Type': 'application/json'}, data=json.dumps(payload))

        code = response.json().get("data").get("data")

        RequestsSession.get(f"https://www.*******.com/cms/validateCode/{code}", headers=headers)

3)、请求参数加密
4)、携带加密参数请求page接口
5)、解密参数

# 获取数据
def get_page_data(RequestsSession, local_storage):
    public_key = local_storage.get("logo1")
    private_key = local_storage.get("logo2")
    timestamp = local_storage.get("time")

    headers = {
        "Accept": "application/json, text/plain, */*",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Content-Length": "174",
        "Content-Type": "application/json;charset=UTF-8",
        "Host": "www.*****.com",
        "Machine_code": timestamp,
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
    }

    e = {
        "current": "1",  # 页码
        "size": "10",  # 每页数据
        "condition": {
            "columnId": "1",  # 分类导航栏
            "title": "",
            "projectType": ""
        }
    }

    # 读取js文件
    with open(r"RSA_js.js", "r", encoding='utf8') as f:
        js_str = f.read()
    """
        因为扣代码扣多了,js代码里面有一下字符在python读取的时候会有编码问题
        所以在导入execjs前需要加上一些东西(如下):
        import subprocess
        from functools import partial
        subprocess.Popen = partial(subprocess.Popen, encoding="utf-8")
        import execjs
    """

    # 调用js文件里面的encruption加密方法
    data = execjs.compile(js_str).call('encruption', e, public_key)
    url = "https://www.*****.com/cms/article/page"
    # 请求page接口
    response = RequestsSession.post(url, data=data, headers=headers)
    content = response.text
    # 通过decryption解密方法,获取实际数据
    decrypt_data = execjs.compile(js_str).call('decryption', content, private_key)
    return decrypt_data

所有代码已经放到Hub 点我啊
6)、跟同事吹一下牛逼,又搞定一个网站(姑娘)

本文章已经生成可运行项目
评论 7
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值