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

news2025/1/13 6:10:15

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

目录

    • 一、JS逆向目标-会当临绝顶
    • 二、JS逆向分析-不识庐山真面目
    • 三、JS逆向测试-只缘身在此山中
    • 四、JS反逆向-柳暗花明又一村
    • 五、oklink逆向完整代码下载
    • 六、作者Info

一、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!

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/436025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis --- 常用命令、Java中操作Redis

一、Redis常用命令 1.1、字符串string操作命令 Redis 中字符串类型常用命令&#xff1a; SET key value 设置指定key的值 GET key 获取指定key的值 SETEX key seconds value 设置指定key的值&#xff0c;并将 key 的过期时间设为 seconds 秒 SETNX key value 只有在 key 不…

Java入坑之抽象类、设计模式与接口

目录 一、抽象类 1.1定义 1.2特点 1.3使用场景 1.4抽象方法 1.5抽象类的实现 1.6开-闭原则 1.7匿名类 二、设计模式&#xff08;了解&#xff09; 2.1定义 2.2分类 2.3模板设计模式 2.4单例模式 三、接口 3.1定义 3.2语法格式 3.3接口实现 3.4接口类型变量 …

cyberdefenders------------Insider

cyberdefenders------------Insider 防守更聪明&#xff0c;而不是更难 0x01 前言 ​ CyberDefenders 是一个蓝队培训平台&#xff0c;专注于网络安全的防御方面&#xff0c;以学习、验证和提升网络防御技能。使用cyberdefenders的题目来学习恶意流量取证&#xff0c;题目来…

GBDT算法原理及实战

1.什么是GBDT算法 GBDT(Gradient Boosting Decision Tree)&#xff0c;全名叫梯度提升决策树&#xff0c;是一种迭代的决策树算法&#xff0c;又叫 MART(Multiple Additive Regression Tree)&#xff0c;它通过构造一组弱的学习器(树)&#xff0c;并把多棵决策树的结果累加起来…

手把手教你实现控制数组某一个属性之和不能超过某一个数值变量

大家好啊&#xff0c;最近有个小任务&#xff0c;就是我表格多选后&#xff0c;某一项关于栏目数量之和不能超过其他变量 先看图&#xff1a; 代码就是&#xff1a; 这里有一个点就是我需要累加数量之和&#xff0c;其实遍历循环累加也可以 我这里用的是reduce方法 0代表设置…

机器学习实战:Python基于LDA线性判别模型进行分类预测(五)

文章目录 1 前言1.1 线性判别模型的介绍1.2 线性判别模型的应用 2 demo数据演示2.1 导入函数2.2 训练模型2.3 预测模型 3 LDA手写数字数据演示3.1 导入函数3.2 导入数据3.3 输出图像3.4 建立模型3.5 预测模型 4 讨论 1 前言 1.1 线性判别模型的介绍 线性判别模型&#xff08;…

vue2使用sync修饰符父子组件的值双向绑定

1、使用场景 当我需要对一个 prop 进行“双向绑定的时候&#xff0c;通常用在封装弹窗组件的时候来进行使用&#xff0c;当然也会有其他的使用场景&#xff0c;只要涉及到父子组件之间需要传参的都可以使用&#xff0c;尽量不要使用watch监听来进行修改值&#xff0c;也不要尝试…

GCC编译器的使用

源文件需要经过编译才能生成可执行文件。GCC是一款强大的程序编译软件&#xff0c;能够在多个平台中使用。 1. GCC编译过程 主要分为四个过程&#xff1a;预处理、编译、汇编、链接。 1.1 预处理 主要处理源代码文件中以#开头的预编译指令。 处理规则有&#xff1a; &…

怎么使用midjourney?9个步骤教你学会AI创作

人工智能生成艺术作品的时代已经来临&#xff0c;互联网上到处都是试图创造完美提示的用户&#xff0c;以引导人工智能创造出正确的图像——有时甚至是错误的图像。听起来很有趣&#xff1f;Midjourney 是一种更常见的 AI 工具&#xff0c;人们用它只用几句话就能创造出梦幻般的…

【Linux系统编程】15.fcntl、lseek、truncate

目录 fcntl lseek 参数fd 参数offset 参数whence 返回值 应用场景 测试代码1 测试结果 测试代码2 测试结果 查看文件方式 truncate 参数path 参数length 测试代码3 测试结果 fcntl 获取文件属性、修改文件属性。 int flgsfcntl(fd,F_GETFL); //获取 flgs|…

微服务架构是什么?

一、微服务 1、什么是微服务&#xff1f; 微服务架构&#xff08;通常简称为微服务&#xff09;是指开发应用所用的一种架构形式。通过微服务&#xff0c;可将大型应用分解成多个独立的组件&#xff0c;其中每个组件都有各自的责任领域。在处理一个用户请求时&#xff0c;基于…

DOM事件流

DOM事件流 1. 常用事件绑定方式1.1 对象属性绑定1.2 addEventListener()绑定1.3 两种方式区别 2. 事件流2.1 概念2.2 事件顺序2.2.1 捕获阶段2.2.2 目标阶段2.2.3 冒泡阶段 3. 阻止事件冒泡3.1 event.stopPropagation()3.2 stopPropagation与stopImmediatePropagation区别 4. 事…

“科技助力财富增值 京华四季伴您一生”,北银理财深化线下线上客户交流互动

2023年4月12日&#xff0c;北银理财有限责任公司&#xff08;以下简称“北银理财”&#xff09;携手东方财富网启动北银理财财富号&#xff0c;首次采用线上直播及线下主题演讲相结合的方式&#xff0c;在上海举办以“科技助力财富增值&#xff0c;京华四季伴您一生”为主题的机…

6、springboot快速使用

文章目录 1、最佳实践1.1、引入场景依赖1.2、查看自动配置了哪些&#xff08;选做&#xff09;1.3、是否需要修改配置1、修改配置2、自定义加入或者替换组件3、自定义器 XXXXXCustomizer 2、开发小技巧2.1、Lombok1、引入坐标2、在IDEA中安装lombok插件&#xff08;新版默认安装…

趣说数据结构 —— 前言

趣说数据结构 —— 前言 一次偶然的机会&#xff0c;翻到当初自己读大学的时候教材&#xff0c;看着自己当初的勾勾画画&#xff0c;一时感触良多。 很值得一提的是&#xff0c;我在封面后第一页&#xff0c;写着自己的专业和名字的地方下面&#xff0c;写着几行这样的字&…

leetcode刷题(6)

各位朋友们大家好&#xff0c;今天是我的leetcode刷题系列的第六篇。这篇文章将与队列方面的知识相关&#xff0c;因为这些知识用C语言实现较为复杂&#xff0c;所以我们就只使用Java来实现。 文章目录 设计循环队列题目要求用例输入提示做题思路代码实现 用栈实现队列题目要求…

Vue2-黑马(七)

目录&#xff1a; &#xff08;1&#xff09;router-路由嵌套 &#xff08;2&#xff09;router-路由跳转 &#xff08;3&#xff09;router-导航菜单 &#xff08;1&#xff09;router-路由嵌套 我们有这样的需求&#xff0c;我们已经显示了主页&#xff0c;但是主页里面有&…

SpringBoot数据库换源

文章目录 前言一. baomidou提供换源注解 DS二. 手动数据源切换三. AOP自动换源 前言 笔者知道有三种方式: baomidou提供的DS自定义AOP自动换源实现AbstractRoutingDataSource手动换源 一. baomidou提供换源注解 DS 注意 1.不能使用事务&#xff0c;否则数据源不会切换&…

云原生入门

云原生入门. 云原生是一种设计和构建应用程序的方法&#xff0c;它充分利用了云计算的优势&#xff0c;如弹性、可扩展性、自动化和敏捷性。云原生应用程序不仅可以在云中运行&#xff0c;而且是为云而生的&#xff0c;它们采用了一些新式的技术和架构模式&#xff0c;使得应用…

零基础入门python好学么

python对于零基础的小伙伴算是非常友好的了~ python以简单易学著称~ Python简洁&#xff0c;高效的特点&#xff0c;大大提升了程序员的编码速度&#xff0c;极大的提高了程序员的办公效率&#xff0c;比如用其他编程语言5、6行代码才能整明白的&#xff0c;用Python可能1-2行就…