您现在的位置是:首页 >技术交流 >JS逆向 - 破解oklink加密参数及加密数据网站首页技术交流

JS逆向 - 破解oklink加密参数及加密数据

小鸿的摸鱼日常 2023-05-30 16:00:02
简介JS逆向 - 破解oklink加密参数及加密数据

版权声明:原创不易,本文禁止抄袭、转载,侵权必究!

一、JS逆向目标-会当临绝顶

oklink简介:
oklink是一个区块链机构,包括eth、btc、tron、polygon、bsc等区块链浏览器,它们的交易、地址、标签等数据大部分可以在该网站找到

JS逆向实战目标:
假设你有一个任务,需要根据各链的地址把每个地址的标签数据爬取下来,方便你另一个同事-机器学习/数据挖掘工程师建模分析该地址的一些隐含信息,比如该地址是否是洗钱地址,是否是黑客地址等

JS逆向实战示例地址:

https://www.oklink.com/cn/eth/address/0xdac17f958d2ee523a2206206994597c13d831ec7


如上所述,我们就用太坊的地址-即以0x开头的42位长度地址作为示例

JS逆向实战示例页面:
在这里插入图片描述
:红色框里面就是我们要爬取的数据

加密参数
请求头加密参数:
在这里插入图片描述

加密数据:
在这里插入图片描述

二、JS逆向分析-不识庐山真面目

抓取数据一般有几种方式,网页元素定位(css/xpath/re等)、接口请求(ajax、fetch等)、自动化模拟(selenium/palywrigth等)等;在其返回的json标签数据是经过加密的,这时候我们可能会想用网页元素定位的方式去抓取数据;但对其网页html进行分析时,会发现定位不到标签数据

如果用自动化模拟的方式抓取数据,性价比太低了,假如有10万条数据,那么就要模拟点击10万次,数据量越大,爬取速度越慢,这可不行;为了使效果最优,我们只有选择通过JS逆向破解其加密参数了

JS逆向分析一般有几种方式:全局搜索(也可局部搜索)、断点调式(ajax断点/DOM断点/监听器断点等)、hook(钩子/拦截技术)等

全局搜索是我们进行逆向分析最直接,也是最简单、首选的方式,我们先看看该接口的API是啥,如下图
在这里插入图片描述

如上图所示,该API接口为:

https://www.oklink.com/api/explorer/v1/eth/address/0xdac17f958d2ee523a2206206994597c13d831ec7/more?t=1680606330657

其中有一个网址参数t,是一个13位长度的时间戳

而请求头加密参数为:

x-apikey: LWIzMWUtNDU0Ny05Mjk5LWI2ZDA3Yjc2MzFhYmEyYzkwM2NjfDI3OTE3MTc0NDE3NjQxNjU=

该参数看起来好像是base64加密,我们使用在线工具解密试试:

在线加密解密工具网址:

https://33tool.com/base64/

在这里插入图片描述

使用base64解密后的字符串为:

-b31e-4547-9299-b6d07b7631aba2c903cc|2791717441764165

如果直接解密,我们用该解密后的字符串直接请求,请求是失败的,那么加密过程肯定还经过一些其他的加密或编码逻辑处理

使用全局搜索,如下图:
在这里插入图片描述

输入参数x-apikey进行全局搜索,接着进行格式化,查看JS代码:
在这里插入图片描述

很不错,看来能够直接搜索到,变量名和方法名并没有经过JS混淆,对爬虫还是比较友好的

在这里插入图片描述
观察JS代码可轻易看出,在通过setRequestHeader()方法给参数x-apikey设置值时,是使用getApiKey()方法实现的,使用快键键ctrl+f搜索方法名getApiKey:
在这里插入图片描述

观察getApiKey()方法里的代码逻辑,可以轻易看出最后返回的是this.comb(e, t),this类似于python中的self,方法comb有两个参数,e是由方法encryptApiKey()得到的,而t参数首先获取当前时间,是一个13位长度的时间戳,然后再调用方法encryptTime(t),对时间参数t进行加密逻辑处理,作为comb的第二个参数

先看看encryptApiKey()是怎样进行加密的:

{
    key: "encryptApiKey",
    value: function() {
        var t = this.API_KEY
          , e = t.split("")
          , n = e.splice(0, 8);
        return t = e.concat(n).join("")
    }
}

最开始的变量t是一个写死的字符串:

['a', '2', 'c', '9', '0', '3', 'c', 'c', '-', 'b', '3', ………………]

变量e是对变量t进行分割,返回一个由单个字符组成的列表:

['a', '2', 'c', '9', '0', '3', 'c', 'c', ………………]

然后删除列表e的前8个字符并返回,组成变量n,也是一个列表:

['a', '2', 'c', '9', '0', '3', 'c', 'c']

最后以列表e在前,列表n在后,进行无符号连接,得到最终变量t,也就是方法getApiKey()中的变量e

-b31e-4547-9299-b6d07b7631aba2c903cc

再来看看encryptTime()方法里的逻辑:

{
    key: "encryptTime",
    value: function(t) {
        var e = (1 * t + a).toString().split("")
          , n = parseInt(10 * Math.random(), 10)
          , r = parseInt(10 * Math.random(), 10)
          , o = parseInt(10 * Math.random(), 10);
        return e.concat([n, r, o]).join("")
    }
}

从JS代码可看出,此处的变量t是一个13位长度的整型时间戳,通过分析,此处的变量a也是一个写死的整型变量1111111111111,计算之后,转换为str类型再进行无符号分割成列表组成变量e,而变量n,r,o都是一个0-10之间的随机整数,最后把这四个变量无符号连接起来得到最终的返回值,也就是方法getApiKey()中的变量t

最后来看看最终的方法comb():

{
    key: "comb",
    value: function(t, e) {
        var n = "".concat(t, "|").concat(e);
        return window.btoa(n)
    }
}

这里需要注意一点,this.comb(e, t),这里的变量e,t传给方法comb之后,实参变量e变成了形参变量t,而实参变量t变成了形参变量e,然后以形参t在前,形参e在后的顺序使用连接符“|”进行连接,最终返回的变量是经过btoa()方法,也就是base64方法加密过的变量

由于此处的JS逆向是比较简单的,并没有逻辑混淆和JS混淆等,使用python编写代码即可完成,不需要再使用execujs、node.js等去模拟执行JS代码了

通过以上分析,逆向之后的代码如下:

import requests
import time
import random
import base64

def get_apikey():
    API_KEY = "a2c903cc-b31e-4547-9299-b6d07b7631ab"
    key1 = API_KEY[0:8]
    key2 = API_KEY[8:]
    new_key = key2 + key1
    current_time = int(time.time() * 1000)
    new_time = str(1 * current_time + 1111111111111)
    random1 = str(random.randint(0, 9))
    random2 = str(random.randint(0, 9))
    random3 = str(random.randint(0, 9))
    current_time = new_time + random1 + random2 + random3
    last_key = new_key + '|' + current_time
    x_apiKey = base64.b64encode(last_key.encode('utf-8'))
    return str(x_apiKey, encoding='utf-8')

运行get_apikey()方法:

LWIzMWUtNDU0Ny05Mjk5LWI2ZDA3Yjc2MzFhYmEyYzkwM2NjfDI3OTE3ODQ3MTU2NDMzMjk=

逆向代码编写完成

三、JS逆向测试-只缘身在此山中

接下里只剩测试了,因为api接口和逆向代码都要使用时间戳,为了保持时间戳一致性,我们将时间戳作为参数传递给get_apikey(now_time),再编写简单的代码进行测试,代码如下:

# -*- coding: utf-8 -*-
import requests
import time
import random
import base64


def get_apikey(now_time):
    API_KEY = "a2c903cc-b31e-4547-9299-b6d07b7631ab"
    key1 = API_KEY[0:8]
    key2 = API_KEY[8:]
    new_key = key2 + key1
    new_time = str(1 * now_time + 1111111111111)
    random1 = str(random.randint(0, 9))
    random2 = str(random.randint(0, 9))
    random3 = str(random.randint(0, 9))
    now_time = new_time + random1 + random2 + random3
    last_key = new_key + '|' + now_time
    x_apiKey = base64.b64encode(last_key.encode('utf-8'))
    return str(x_apiKey, encoding='utf-8')

now_time = int(time.time()) * 1000
headers = {
    'x-apikey': get_apikey(now_time),
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
}
api = f'https://www.oklink.com/api/explorer/v1/eth/address/0xdac17f958d2ee523a2206206994597c13d831ec7/more?t={now_time}'
res = requests.get(url=api, headers=headers)
print(res.json())

控制台输出(格式化一下):

{
    'code': 0, 
    'msg': '', 
    'detailMsg': '', 
    'data': 
        {
            'entityTags': 
                    ['QayvIUbQGpJhs4QOJk7Ccw==: dlWG6vsFQhA+YAnbzdnYNg==. igTdUMG1sXqlL+ISnaIU8Q=='], 
            'propertyTags': 
                    ['BYqzosCjwa3Hdj/jGp99Xg==', 'B3N0UYJLaM9LPazO98GU9Q==']
        }
}

在这里插入图片描述

现在可以正常返回response了,但从输出结果可看出,即使我们破解了请求头加密参数,它的响应response标签数据仍然是加密的,那么现在我们是不是要进一步破解该标签数据的加密逻辑呢?

四、JS反逆向-柳暗花明又一村

再进一步分析响应中标签数据的加密逻辑固然是可行的,但是我们想想,无论JS里面采取怎样的加密逻辑,其最初的字符串是不变的:

API_KEY = "a2c903cc-b31e-4547-9299-b6d07b7631ab"

如果我们使用该字符串来代替x-apikey直接进行请求,会发生什么样的效果呢?@>_<@

没错,我们把x-apikey直接写死成API_KEY:

now_time = int(time.time()) * 1000
headers = {
    'x-apikey': 'a2c903cc-b31e-4547-9299-b6d07b7631ab',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36'
}
api = f'https://www.oklink.com/api/explorer/v1/eth/address/0xdac17f958d2ee523a2206206994597c13d831ec7/more?t={now_time}'
res = requests.get(url=api, headers=headers)
print(res.json())

控制台输出(格式化一下):

{
    'code': 0, 
    'msg': '', 
    'detailMsg': '', 
    'data': 
        {
            'entityTags': 
                    ['DeFi: Tether. USDT Stablecoin'], 
            'propertyTags': 
                    ['ERC20', 'Tether USDT']
        }
}

观察一下,这种思路是可行的,而且这应该才是我们想要的方式,我们不仅得到解密后的真实数据,里面还包含了区块链地址所属类型:Defi、ERC20,而这个类型数据在网页上面是不可见的,只有通过接口请求数据才获取得到,至此,我们完成了JS反逆向,而不是JS逆向
在这里插入图片描述

那么这是为什么呢?竟然可以直接通过写死的字符串成功请求数据,而且得到的数据还是未经加密的数据

原因可能是这个网站采取了反向的JS逆向逻辑,使得那些太专注于JS逆向构建逻辑代码的数据抓取者会陷入一个误区,反而找不到其最真实而又简单直接的这种方式,被打了一个反包围

这种反向JS逆向逻辑算是比较另类的存在了,虽然比较简单,但值得我们注意和反思

五、oklink逆向完整代码下载

oklink逆向完整源码下载

免责声明:本篇文章仅供学习与研究使用,切勿用于违法途径!

六、作者Info

Author:小鸿的摸鱼日常,Goal:让编程更有趣!

专注于算法、爬虫,网站,游戏开发,数据分析、自然语言处理,AI等,期待你的关注,让我们一起成长、一起Coding!

版权说明:本文禁止抄袭、转载 ,侵权必究!

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。