admin 管理员组文章数量: 887018
1、网页 js 逆向分析( v_jstools )
From:https://mp.weixin.qq/s/LisYhDKK_6ddF-19m1gvzg
Python 爬虫工具篇:必用的 Chrome 插件
- EditThisCookie 是一个 Cookie 管理器,可以很方便的添加,删除,编辑,搜索,锁定和屏蔽 Cookies。可以将登录后的 Cookies 先保存到本地,借助 cookielib 库,直接爬取登录后的数据。避免了抓包和模拟登录,帮助我们快速地进行爬虫。
- Web Scraper 是一款免费的、适用于任何人,包含没有任何编程基础的爬虫工具。操作简单,只需要鼠标点击和简单的配置,就能快速的爬取 Web 端的数据。它支持复杂的网站结构,数据支持文本、连接、数据块、下拉加载数据块等各种数据类型。此外,还能将爬取的数据导出到 CSV 文件中。
- Xpath Helper 是一种结构化网页元素选择器,支持列表和单节点数据获取,它可以快速地定位网页元素。对比 Beautiful Soup,由于 Xpath 网页元素查找性能更有优势,Xpath 相比正则表达式编写起来更方便。编写 Xpath 之后会实时显示匹配的数目和对应的位置,方便我们判断语句是否编写正确。
- Toggle JavaScript 插件可以用来检测当前网页哪些元素是通过 AJAX 动态加载的。使用它可以快速在容许加载 JS 、禁止加载 JS 两种模式中切换。
- User-Agent Switcher for Chrome 插件可以很方便的修改浏览器的 User-Agent。可以模拟不同的浏览器、客户端,包含 Android、IOS 去模拟请求。对于一些特殊网站,切换 User-Agent 可以更方便地进行数据爬取。
- JSON-handle 是一款功能强大的 JSON 数据解析 Chrome 插件。它以简单清晰的树形图样式展现 JSON 文档,并可实时编辑。针对数据量大的场景,可以做局部选取分析。
- 前端助手:功能和JSON-handle 差不多。但是功能更强大
- ReRes、ReRes、URLRedirector、XSwitch:重定向 URL。可以把请求的 js 文件重定向本地的js,方便进行调试。
- Console Importer:用一个命令,从控制台导入 JavaScript 和 CSS 资源。
下载、安装 v_jstools 插件
v_jstools 是一款浏览器插件,可以进行 web hook,功能非常强大。工具地址:https://github/cilame/v_jstools
浏览器打开上面的网站后,点击 code 按钮,选择 Download ZIP 选项,将文件下载下来,然后解压,会看到一个 v_jstools-main 的文件夹。
安装插件
- 谷歌浏览器地址栏输入:chrome://extensions/ 打开扩展程序页面,并打开开发者模式。如果是其他浏览器,请使用菜单栏打开。
- 打开后,点击左上角的 加载已解压的扩展程序 按钮,然后选择 v_jstools-main 文件夹,插件就已经加载进来了。
插件功能
在扩展程序中,打开该插件,看看支持哪些功能:
打开配置页面后,来到这里:
可以看到,有三个功能。
dom对象hook
第一个功能是 dom对象hook ,它内置了很多常见方法的hook,如eval,cookie,Function等,想hook哪个方法,直接打钩即可,非常的方便。有了这个,再也不用写油猴脚本啦。
ast hook替换
第二个功能是 ast hook替换,顾名思义,就是拦截某个js代码,通过ast处理后,再返回ast处理后的代码,让混淆代码见鬼去吧。
它给的示例代码是这个:
function fetch_hook(code, url) {
var ast = parser.parse(code);
function removedebugger(path) {
path.replaceWith(t.identifier("/*debugger*/"))
}
traverse(ast, {
DebuggerStatement: removedebugger
});
var {code} = generator(ast, {
jsescOption: {
minimal: true,
}
});
return code
}
这里简单说一下, fetch_hook 函数 接受2个参数,可以不用理会,如果你想hook特定的url,可以在函数的首行加入这样一段代码:
if (url.indexOf("https://www.xxx/xxx.js") == -1)
{
return code // 检测到不是这个 url 就直接返回原始代码
}
再就是编写 ast 插件了,按照这样下就好:
function removedebugger(path) {
path.replaceWith(t.identifier("/*debugger*/"))
}
traverse(ast, {DebuggerStatement: removedebugger});
先写方法,再遍历,也可以按照我星球里的方式进行编写,都是一样的。
不过在我使用了几次以后,还是觉得线下处理混淆代码香,当然要看你的使用场景了。
AST 的解密混淆
第三个功能是一些 AST的解密混淆,如图:
把混淆代码复制到上面的白色框中,点击对应的按钮即可解混淆,也是非常的方便。这里能解混淆的有 sojson,ob混淆,jsfuck以及压缩代码等,都是比较常用的功能。
试用了一番,可以还原最新版的ob混淆,效果还是很棒的。当然一些修改特征了的代码无法还原,这个要做到通杀确实有些难度。
v_jstools 补环境
点击 打开配置按钮:
按图进行如下设置:
实战案例 1:知乎搜索
实战:https://www.zhihu/search?type=content&q=python
抓包,查看 请求参数
需要的加密参数:
等网站加载完毕后,我们再点开这个插件,选择 生成临时环境 命令:
点击后,会有个弹框提示已复制到剪切板。这样,临时环境就帮我们生成好了,新建一个js文件 v.js, 将生成的环境复制过来进来并保存。我们再将这个参数加密的代码也复制进来,保存到一起,具体请参考这篇文章:https://blog.csdn/qq523176585/article/details/123814707
在加密函数处打上断点,记录实参和结果。
实参: '7861d84e110af123f2fc8c05ff38601f';
结果: 'a8O0QQuy28xYcR28f0NBkQe0kXtYUuY0mLxq66L0S7Yp'
在v.js中构造好实参,并打印结果:console.log(b('7861d84e110af123f2fc8c05ff38601f'));
保存后,运行结果
经过仔细比对后可以发现,和网页上面的结果是一模一样的。非常的 nice。
基本就靠一些CV操作,就把结果弄出来了,可以说非常的简单。
如果你不想打印上面的日志,可以将这个函数进行改写:var v_console_log = function(){{}}
这个插件试用了几天,已经解决了很多网站的加密,几乎可以说js逆向有手就行,当然,你需要一些js逆向分析的功底。毕竟它只是帮你补环境。注意,这个并不是万能的,因此有效网站如果不行的话,可能需要想其他的办法了哈。
实战案例 2:同花顺
- :https://blog.csdn/weixin_43411585/article/details/132440165
- :https://blog.csdn/qiulin_wu/article/details/133909883
实战:https://q.10jqka/
反爬参数如图
知识点:cookie 一般是两种,要么服务器生成,要么 js 生成,可通过如下方式判断
为啥要补环境:英文我们知道这个js文件代码内容会生成我们想要的参数,但是放到本地 nodejs 环境下运行不出结果,因为缺少浏览器环境特有的一些window/document/navigator/localstorage等参数,所以我们需要把这些缺少的浏览器环境补上,让这份js代码在本地nodejs环境下也能运行出结果来。请看这个图10秒钟
补环境分三部分:上、中、下。
- 在最上面放好你补的环境参数,
- 中间部分放好js代码,
- 最下面部分放生成的目标参数,
补环境的好处:就是可以完全不用考虑内部的算法逻辑,让它能正常跑起来输出就行
详细分析流程
-
1.先点击打开如下两个开关,然后打开配置页面
- 2. 如下插件配置详情,勾选上总开关,DOM开关,以及常用的挂钩,然后关掉该配置页面
- 3.直接看视频-缺啥补啥的方法补环境:https://www.bilibili/video/BV1FN4y1d7av/ 或者星球文章 后,我们知道生成 cookie 的 js 代码,如图 hook 到了cookie,然后堆栈回溯查看定位到具体的 js 文件内容
对比 cookie,发现 cookie 一致,说明找的位置正确。
- 4. 在 vscode 里面新建一个js文件,把 js 代码和希望输出的函数写好。还是3部分:
第 1 部分:待补的环境,把插件生成的临时环境放过来。后面
第 2 部分:复制 js 代码,也就是生成 cookie 的 js 文件。
第3部分:调用生成 cookie 的函数,生成 cookie
现在开始 "第1部分:待补的环境",使用插件生成临时环境
- 继续回到浏览器里面,先清掉缓存 cookie
- 然后刷新网页 ( 可以把所有断点全部删除 ),在滑动鼠标下滚下,防止无法正常生成临时环境,如下弹出,代表环境参数已经生成好
- 回到 vscode 里面,在刚才新建的 js 文件里面 ctrl+v 粘贴刚刚生成的临时环境,放在最上方
- 接下来,运行已经补好环境的 js 文件,最后发现可能会出现如下几种情况:
1. 生成的补环境,函数之间调用正常,可以直接使用;
2. 已经能生成正常cookie,但是不能用;例如:一直是固定的。
3. 完全生成不出 cookie 结果值;
下面针对两种情况进行分析:
- 一种是直接能用的,
- 一种是需要调试才能出结果的情况
方法 1:本案例操作流程之-生成临时环境-直接可以用的情况
1. 我们还是按之前的步骤清掉缓存,打开插件的勾选项,然后这里唯一要注意的点就是需要勾选script断点,然后用它生成的临时环境
2.刷新网页后,跳到目标生成cookie的js文件被断住的js后,我们取消script断点,直接下一步调试过去
3. 这时候点击插件的生成临时环境,可能会报错如下,生成的临时环境无法保存至剪贴板
4. 多点击几次生成临时环境就可以了,我们把它复制到本地js文件里面
5. ctrl+v粘贴到本地js文件中,运行能生成正常cookie,但是程序处于卡死无法退出状态
6.ctrl+s保存文件后,再次运行,这个时候已经能正常生成cookie值了,但是同样出现了我们上一篇文章介绍的虽然出来了cookie值,但是无法中断程序退出的现象,就是run后不能自动停掉程序,这时候我们可以尝试将setInterval()定时函数给置空试试,这是因为setinterval不会清除定时器队列,每重复执行1次都会导致定时器叠加,最终卡死你的网页(具体的大家可以调试看看)
7.我们在js代码的最上方的位置添加setInterval = function(){}将定时器置空即可,这时候能正常生成cookie了,并且把日志输出置空var v_console_log = function(){{}}
8.接下来我们验证下这份通过插件补的环境,与js代码生成的cookie最终能不能用,用python调用js文件试试,如下我们调用js生成的cookie验证,发现能成功拿到cookie,这说明我们用插件补环境也能用,而上面插件补环境我们只做了两个操作置空定时器,把日志输出关掉即可,接下来分析调试过程中,可能你生成的临时环境不能用的解决方法
方法 2:本案例操作流程之-生成临时环境-不可以直接用,需要调试补下
1.勾选Caught Exceptions,即使所发生运行时异常的代码在 try/catch 范围内,Chrome 开发者工具也能够在错误代码处停住,这里就不详细介绍了,大概思路如下
文章与视频
- 原文文章
- 十一姐b站视频
- 时光漫漫星球
JS hook 脚本
hook 又称钩子,可以在调用系统函数之前, 先执行我们的函数. 例如, hook eval
eval_ = eval; // 先保存系统的eval函数
eval = function(s){
console.log(s);
debugger;
return eval_(s);
}
eval()
// 可能会被检测到, 用这种方案来进行
eval.toString = function(){return 'function eval() { [native code] }'}
对 Function 的 hook, 主要为了解决无限 debugger
var qiaofu_function_constructor = (function(){}).__proto__.constructor;
(function(){}).__proto__.constructor = function(arg){
console.log("我爱你大大");
if(arg ==='debugger'){
return function(){}
} else {
return new qiaofu_function_constructor(arg);
}
}
上面都是 hook 的系统函数. 但有时, 我们需要hook某个属性. 此时应该怎么办?
var v;
Object.defineProperty(document, "cookie", {
set: function(val) {
console.log("有人来存cookie了");
v = val;
debugger;
return val;
},
get() {
console.log("有人提取cookie了∂
debugger;
return v;
}
});
在逆向时, 常用的主要有: hook eval 、hook Function 、hook JSON.stringify、JSON.parse 、hook cookie、hook window对象
Js 代码 替换
JS逆向|JavaScript代码线上替换(js 逆向 系列文章):https://blog.csdn/qq523176585/article/details/126258689
有的网站在用 debuger 调试之后,每一步都变得非常卡顿非常影响效率和心情,项目经过webpack处理了,请问这种有什么好的办法吗?
答案是:可以把源码保存到本地,然后用fiddler的atuo response替换线上的js调试
方法 1:使用谷歌浏览器自带的替换功能
Chrome 的 local overrides:https://zhuanlan.zhihu/p/36677472
方法 2:使用 fiddler 的 auto response 替换功能
fiddler 本地资源替换线上文件:https://wwwblogs/shichangchun/p/10731297.html
方法 3:使用 reres 插件进行替换
Github地址:https://github/annnhan/ReRes
使用方法:找不到变量生成的位置?让插件来帮你轻松定位:https://blog.csdn/qq523176585/article/details/109508013
方法 4:使用 Netify 插件进行替换
插件下载地址:https://chrome.google/webstore/detail/netify/mdafhjaillpdogjdigdkmnoddeoegblj
使用方法:工具分析 | Akamai2.0 还原后的js线上替换方案分享:https://blog.csdn/qq523176585/article/details/125093543
方法 5:v_jstools 插件进行替换
使用方法:AST实战技巧|使用v神插件动态替换AST还原后的代码:https://blog.csdn/qq523176585/article/details/124395678
2、jshook ( 安卓上用Js实现Hook )
jshook相关文件下载地址:https://github/JsHookApp/Download
官网文档:https://doc.jshook/#/README
介 绍
jshook 是对应用程序注入 rhino/frida,只需要会 js 就可以快速实现 hook,并且支持 java层 和native层,简化 hook 流程
- rhino 包含了 xposed 的 api,主要用于java层的hook;
- frida注入后js可以完全访问内存,支持java层和native层。
在 root 环境下可以安装 jshook 的 Magisk 模块,免root环境下使用Lspatch或者其他虚拟机环境。
频道: https://t.me/jshookapp
交流群: https://t.me/jshookgroup
安 装
真机 root 环境激活
确保手机已经拥有root权限,并且已经安装magisk,https://github/topjohnwu/Magisk
jshook属于xp模块,一般使用lsposed进行激活,https://github/LSPosed/LSPosed
安装lsposed后在模块中启用jshook,并勾选系统框架,重启手机后完成激活
如果没有lsposed也可以安装jshook的面具模块进行激活,jshook应用首页点击安装magisk模块前两个模块用于激活,同时,支持与lsposed共存
lsposed激活的情况下在jshook中启用hook服务后,需要在lsposed的模块作用域中也要勾选对应启用hook服务的应用
真机 免root 环境激活
如果你手机无法root,可以尝试使用
lspatch
https://github/LSPosed/LSPatchlspatch激活xp模块的方式是给目标应用进行修补
过签名验证
并修改程序入口
,入口处会加入注入xposed框架以及调用lsp相关服务安装lspatch后jshook会显示激活
安装完lspatch后你需要先将原应用
提取成apk文件
,可以使用mt管理器
进行操作,lspatch中点击管理
右下角+号
新建修补,选择apk文件,修补完成后,用mt管理器找到修补后的apk文件,并安装,安装完成后在lspatch的管理中即可看到被修补成功的应用,点击这个应用,会出现菜单,选择模块作用域
,勾选jshook即可完成激活
虚拟机环境激活
使用 "vmos、光速虚拟机" 等类似产品进行安装激活操作,操作方式同上两种流程一样
模拟器环境激活
示例:使用夜神模拟器,下载 https://www.yeshen/
添加模拟器选择
android9
,可以先安装magisk
,在这里 下载Magisk Terminal Emulator.apk
直接拖到模拟器中会自动安装,打开该应用依次输入以下指令即可完成安装magisk:inmagisk
y
1
a
1
完成后重启模拟器你可以安装上面root环境激活的方式继续操作
常见问题
目前加密脚本只能使用fridamod
框架运行
确定jshook在后台正常运行中且拥有悬浮窗权限,点击设置
中的日志悬浮窗
即可跳转悬浮窗权限申请,你可以新建脚本,在这里 测试悬浮窗功能
部分机型实时注入脚本会直接发生闪退情况,可以尝试使用启动配置
中的延时注入
功能,一般设置3000(3秒)左右,部分游戏应用需要更长的时间30000(30秒),在测试之前先取消勾选脚本,排除脚本原因。
连接 计算机
可以使用 Visual Studio Code 实时推送脚本到手机,或者从手机获取脚本到电脑。下载 vscode 插件: https://marketplace.visualstudio/items?itemName=JsonET.jshook-vscode-extension
安装后在
js
文件右键或者快捷键操作
Shift+Alt+D
: 推送脚本
Shift+Alt+C
: 获取脚本
Shift+Alt+F
: 清空日志
Shift+Alt+G
: 查看日志,自动刷新同步脚本时(脚本名+相对路径)与手机上一致即可,使用插件时需要jshook设置中启用服务端,使用快捷键后会出现输入ip的对话框,让电脑与手机在同一个wifi网络下。
Rhino
使用 rhino 调用 xposed 相关 api 的示例
java 调用xposed 相关 api 说明:https://api.xposed.info/reference/packages.html
而 jshook 中的 rhino 框架提供了以下 xposed 的 api:
XposedBridge
XposedHelpers
AndroidAppHelper
XC_MethodHook
XC_MethodReplacement
现在你只需要使用 rhino 语法用简单的方式去实现。
rhino 语法教程:https://p-bakker.github.io/rhino/tutorials/scripting_java/
以下是常见的一个示例:
XposedBridge.hookAllMethods(XposedHelpers.findClass("android.app.Application", runtime.classLoader), "onCreate", XC_MethodReplacement({
replaceHookedMethod: function (param) {
console.log('hook');
return XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args);
}
}));
XposedBridge.hookAllMethods(XposedHelpers.findClass("android.app.Application", runtime.classLoader), "onCreate", XC_MethodHook({
beforeHookedMethod: function (param) {
console.log('hook before');
},
afterHookedMethod: function (param) {
console.log('hook after');
}
}));
从示例中我们可以看到,几乎和java的写法一致,只是进行了简化
XposedHelpers.findClass(className,classLoader)
XposedHelpers.findClass('com.test.test',runtime.classLoader);
XposedBridge.hookAllConstructors(hookClass,callback)
XposedBridge.hookAllConstructors(XposedHelpers.findClass('com.test.test',runtime.classLoader),XC_MethodHook({
beforeHookedMethod: function (param) {
console.log('hook before');
},
afterHookedMethod: function (param) {
console.log('hook after');
}
}));
XposedHelpers.findAndHookConstructor(clazz,parameterTypesAndCallback)
XposedHelpers.findAndHookConstructor(XposedHelpers.findClass('com.test.test',runtime.classLoader),'java.lang.String','java.lang.String',XC_MethodHook({
beforeHookedMethod: function (param) {
console.log('hook before');
},
afterHookedMethod: function (param) {
console.log('hook after');
}
}));
XposedHelpers.findAndHookConstructor(className,classLoader,parameterTypesAndCallback)
XposedHelpers.findAndHookConstructor('com.test.test',runtime.classLoader,'java.lang.String','java.lang.String',XC_MethodHook({
beforeHookedMethod: function (param) {
console.log('hook before');
},
afterHookedMethod: function (param) {
console.log('hook after');
}
}));
XposedBridge.hookAllMethods(hookClass,methodName,callback)
XposedBridge.hookAllMethods(XposedHelpers.findClass('com.test.test',runtime.classLoader),'method',XC_MethodHook({
beforeHookedMethod: function (param) {
console.log('hook before');
},
afterHookedMethod: function (param) {
console.log('hook after');
}
}));
XposedBridge.hookMethod(hookMethod,callback)
XposedBridge.hookMethod(param.method,XC_MethodHook({
beforeHookedMethod: function (param) {
console.log('hook before');
},
afterHookedMethod: function (param) {
console.log('hook after');
}
}));
XposedHelpers.setStaticObjectField(clazz,fieldName,value)
XposedHelpers.setStaticObjectField(XposedHelpers.findClass('com.test.test',runtime.classLoader),'name','test');
XposedHelpers.setObjectField(obj,fieldName,value)
XposedHelpers.setObjectField(param.thisObject,'name','test');
XposedHelpers.getStaticObjectField(clazz,fieldName)
XposedHelpers.getStaticObjectField(XposedHelpers.findClass('com.test.test',runtime.classLoader),'name');
XposedHelpers.getObjectField(clazz,fieldName)
XposedHelpers.getObjectField(param.thisObject,'name');
XposedHelpers.callMethod(obj,methodName,args)
XposedHelpers.callMethod(param.thisObject,'method','123','456');
XposedHelpers.callStaticMethod(clazz,methodName,parameterTypes,args)
XposedHelpers.callStaticMethod(XposedHelpers.findClass('com.test.test',runtime.classLoader),'method','123','456');
XposedBridge.invokeOriginalMethod(method,thisObject,args)
XposedBridge.invokeOriginalMethod(param.method,param.thisObject,param.args);
更多同上,方式调用都差不多。
如何 hook 加壳的应用
以下是常用的示例,有一些企业壳做了特殊处理,可能不适用
XposedBridge.hookAllMethods(XposedHelpers.findClass('android.app.ActivityThread', runtime.classLoader), 'performLaunchActivity', XC_MethodHook({
beforeHookedMethod: function (param) {
console.log('hook before');
},
afterHookedMethod: function (param) {
console.log('hook after');
var mInitialApplication = XposedHelpers.getObjectField(param.thisObject, 'mInitialApplication');
var classLoader = XposedHelpers.callMethod(mInitialApplication, 'getClassLoader');
XposedBridge.hookAllMethods(XposedHelpers.findClass('com.test.test', classLoader), 'test', XC_MethodHook({
beforeHookedMethod: function (param) {
console.log('hook before');
},
afterHookedMethod: function (param) {
console.log('hook after');
}
}));
}
}));
API 说明
global
简易的调用全局函数
toast(message)
参数: message: string
alert(message)
参数: message: string
confirm(message,callback)
参数: message: string
参数: callback: function示例:
confirm('is ok?', {
ok: function () {
//...
},
cancel: function () {
//...
}
})
uuid()
说明:返回32位小写uuid
返回值:string
setTimeout(function,time)
参数:function: function,time: int 毫秒
返回值: int 事件标识
示例:
setTimeout(function () {
//...
}, 100);
clearTimeout(id)
参数:id: int 事件标识
setInterval(function,time)
参数:function: function,time: int 毫秒
返回值:int 事件标识
示例:setInterval(function () {
//...
}, 100);
clearInterval(id)
参数:id: int 事件标识
runtime
用于获取当前hook运行时相关基础信息
runtime.jsContent
获取当前注入的脚本内容,注意,如果是加密脚本,获取的不会是解密后的文本
返回值: stringruntime.appInfo
返回值: ApplicationInforuntime.packageName
返回值: stringruntime.processName
返回值: stringruntime.classLoader
返回值: ClassLoaderruntime.isFirstApplication
返回值: booleanruntime.coreVersionCode
获取当前jshook的版本号
返回值: intruntime.coreType
获取当前注入的类型(1:rhino 2:frida)
返回值: int
app
用于获取当前hook应用的基本信息
app.isAppRoot()
判断 App 是否有 root 权限
返回值: booleanapp.isAppSystem()
判断 App 是否是系统应用
返回值: booleanapp.isAppForeground()
判断 App 是否处于前台
返回值: booleanapp.exitApp()
关闭应用app.getAppInfo()
获取 App 信息
返回值: objectapp.openUrl(url)
打开指定网址
参数:url: string 网址app.startActivity(activity)
启动 Activity
参数:activity: Activityapp.getActivityList()
获取 Activity 栈链表
返回值: Listapp.finishActivity(activity)
结束 Activity
参数:activity: Activityapp.finishToActivity(activity)
结束到指定 Activity
参数:activity: Activityapp.startHomeActivity()
回到桌面app.dpToPx(value)
dp转px
参数:value: float
返回值: intapp.pxToDp(value)
px转dp
参数:value: float
返回值: intapp.getInternalAppDataPath()
获取内存应用数据路径
返回值: stringapp.getExternalAppDataPath()
获取外存应用数据路径
返回值: stringapp.getExternalAppObbPath()
获取外存应用 OBB 路径
返回值: stringapp.getExternalStoragePath()
获取外存路径
返回值: string
base64
用于进行字符串base64编码解码
base64.encode(data)
参数:data: string
返回值: stringbase64.encodeBytes(data)
参数:data: byte[]
返回值: byte[]base64.decode(data)
参数:data: string
返回值: stringbase64.decodeBytes(data)
参数:data: byte[]
返回值: byte[]
console
用于输出日志
console.log(message)
参数:message: object 会强转为string
crypto
用于进行字符串加密解密
crypto.encrypt(key,data,enctype,transformation)
参数:
key: string
data: string
enctype: CRYPTO_AES | CRYPTO_DES
transformation: 转换的名称 例如 DES/CBC/PKCS5Padding
返回值: stringcrypto.encryptBytes(key,data,enctype,transformation)
参数:
key: byte[]
data: byte[]
enctype: CRYPTO_AES | CRYPTO_DES
transformation: 转换的名称 例如 DES/CBC/PKCS5Padding
返回值: byte[]crypto.decrypt(key,data,enctype,transformation)
参数:
key: string
data: string
enctype: CRYPTO_AES | CRYPTO_DES
transformation: 转换的名称 例如 DES/CBC/PKCS5Padding
返回值: stringcrypto.decryptBytes(key,data,enctype,transformation)
参数:
key: byte[]
data: byte[]
enctype: CRYPTO_AES | CRYPTO_DES
transformation: 转换的名称 例如 DES/CBC/PKCS5Padding
返回值: byte[]crypto.rc4Encrypt(key,data)
参数:
key: string
data: string
返回值: stringcrypto.rc4EncryptBytes(key,data)
参数:
key: byte[]
data: byte[]
返回值: byte[]crypto.rc4Decrypt(key,data)
参数:
key: string
data: string
返回值: stringcrypto.rc4DecryptBytes(key,data)
参数:
key: byte[]
data: byte[]
返回值: byte[]crypto.md5(data)
参数:
data: string
返回值: stringcrypto.md5Bytes(data)
参数:
data: byte[]
返回值: stringcrypto.sha1(data)
参数:
data: string
返回值: stringcrypto.sha1Bytes(data)
参数:
data: byte[]
返回值: stringcrypto.sha256(data)
参数:
data: string
返回值: stringcrypto.sha256Bytes(data)
参数:
data: byte[]
返回值: string
device
用于获取当前设备的基础信息
device.isDeviceRooted()
判断设备是否 rooted
返回值: booleandevice.isAdbEnabled()
判断设备 ADB 是否可用
返回值: booleandevice.getSDKVersionName()
获取设备系统版本号
返回值: stringdevice.getSDKVersionCode()
获取设备系统版本码
返回值: intdevice.getAndroidID()
获取设备 AndroidID
返回值: stringdevice.getMacAddress()
获取设备 MAC 地址
返回值: stringdevice.getManufacturer()
获取设备厂商
返回值: stringdevice.getModel()
获取设备型号
返回值: stringdevice.getABIs()
获取设备 ABIs
返回值: stringdevice.isTablet()
判断是否是平板
返回值: booleandevice.isEmulator()
判断是否是模拟器
返回值: booleandevice.isDevelopmentSettingsEnabled()
开发者选项是否打开
返回值: booleandevice.getScreenWidth()
获取屏幕的宽度(单位:px)
返回值: intdevice.getScreenHeight()
获取屏幕的高度(单位:px)
返回值: intdevice.getAppScreenWidth()
获取应用屏幕的宽度(单位:px)
返回值: intdevice.getAppScreenHeight()
获取应用屏幕的高度(单位:px)
返回值: intdevice.getScreenDensity()
获取屏幕密度
返回值: floatdevice.getScreenDensityDpi()
获取屏幕密度 DPI
返回值: intdevice.isLandscape()
判断是否横屏
返回值: booleandevice.isPortrait()
判断是否竖屏
返回值: booleandevice.screenShot(activity)
截屏
参数:activity: Activity
返回值: Bitmapdevice.setClipboard(data)
设置剪贴板内容
参数:data: stringdevice.getClipboard()
获取剪贴板内容
返回值: string
file
用于对文件进行相关操作
file.isFile(path)
参数:path: string
返回值: booleanfile.isDir(path)
参数:path: string
返回值: booleanfile.isExists(path)
参数:path: string
返回值: booleanfile.read(path)
参数:path: string
返回值: stringfile.readBytes(path)
参数:path: string
返回值: byte[]file.write(path,content)
参数:
path: string
content: stringfile.writeBytes(path,content)
参数:
path: string
content: byte[]file.append(path,content)
参数:
path: string
content: stringfile.appendBytes(path,content)
参数:
path: string
content: byte[]file.copy(path,topath)
参数:
path: string
topath: stringfile.move(path,topath)
参数:
path: string
topath: stringfile.rename(path,newname)
参数:
path: string
newname: stringfile.delete(path)
参数:path: stringfile.getName(path)
参数:path: stringfile.getSize(path)
返回值: longfile.zip(path,topath,passwd)
参数:
path: string
topath: string
passwd: stringfile.zip(path,topath)
参数:
path: string
topath: stringfile.unzip(path,topath,passwd)
参数:
path: string
topath: string
passwd: stringfile.unzip(path,topath)
参数:
path: string
topath: string
http
用于进行网络请求,注意,需要被hook的应用拥有网络访问权限
http.get(url,headers,function)
参数:
url: string
headers: object
function: function
http.get('http://xxxxx', {
'test': '1'
}, {
success: function (result) {
console.log(result);
},
error: function (err) {
console.log(err);
}
});
http.post(url,data,headers,function)
参数:
url: string
data: object
headers: object
function: function
http.post('http://xxxxx', {
'user': 'me'
}, {
'test': '1'
}, {
success: function (result) {
console.log(result);
},
error: function (err) {
console.log(err);
}
});
//或者
http.post('http://xxxxx', JSON.stringify({
'user': 'me'
}), {
'content-type': 'application/json'
}, {
success: function (result) {
console.log(result);
},
error: function (err) {
console.log(err);
}
});
注意事项:headers中的key和value必须都是string类型,当data为object时为表单提交,header头部会加入content-type:application/x-www-form-urlencoded,如果提交content-type:application/json,需要确定data参数类型为string
json
用于对字符串进行json编码解码
json.toGsonString(data)
参数:
data: object
返回值: stringjson.gsonStringToClass(data,class)
参数:
data: string
class: class
返回值: classjson.toJSONString(data)
参数:
data: objectjson.parseObject(data)
参数:
data: string
返回值: JSONObjectjson.parseArray(data)
参数:
data: string
返回值: JSONArray
modmenu
这是一个mod菜单,可以定制简单的菜单悬浮窗,注意,你需要让jshook保持后台运行状态,并且授权悬浮窗权限,可以创建多个悬浮窗实例
modmenu.create(title,options,function)
参数:
title: string
options: object
function: function
返回值: 实例
modmenu.create('test mod',
[
{
'id': '1',
'type': 'category',
'title': 'category title'
},
{
'id': '2',
'type': 'switch',
'title': 'switch1 title',
'enable': true
},
{
'id': '3',
'type': 'switch',
'title': 'switch2 title'
},
{
'id': '4',
'type': 'webview',
'data': '<font color="red"><b>text</b></font>',
//or
//'url': 'http://xxxxx'
},
{
'id': '5',
'type': 'button',
'title': 'button title'
},
{
'id': '6',
'type': 'input',
'title': 'input title',
'val': 'default value'
},
{
'type': 'collapse',
'title': 'collapse title',
'item': [
{
'id': '7',
'type': 'switch',
'title': 'switch title'
}
],
'enable': true
},
{
'id': '8',
'type': 'slider',
'title': 'slider title',
'val': 88,
'min': 1,
'max': 100
},
{
'id': '9',
'type': 'checkbox',
'title': 'checkbox title',
'enable': true
},
{
'type': 'checkboxs',
'item': [
{
'id': '10',
'type': 'checkbox',
'title': '1 title'
},
{
'id': '11',
'type': 'checkbox',
'title': '2 title'
},
{
'id': '12',
'type': 'checkbox',
'title': '33333333 title'
}
]
},
{
'id': '13',
'type': 'radio',
'title': 'radio title',
'item': ['test', 'test2', 'test3'],
'check': 0
}
]
, {
onchange: function (result) {
//注意在这里需要进行一下转换
result = JSON.parse(result);
switch(result.id) {
case '1':
console.log('ok');
break;
}
console.log(result);
}
})
[实例].close()
var menu1 = modmenu.create('test mod', [], {
onchange: function (result) {
}
});
setTimeout(function () {
menu1.close();
}, 2000);
[实例].state()
显示悬浮窗底部状态栏,目前只有简单的包名显示
[实例].size(width,height)
调整悬浮窗大小,px值
参数:
width: int
height: int
[实例].position(type,x,y)
设置悬浮窗默认位置,type为1到9对应9个默认位置,x和y是px值
参数:
type: int
x: int
y: int
[实例].icon(img)
设置悬浮窗图标图片,img为图片base64字符串或者是图片url地址
参数:
img: string
[实例].webviewcall(id,args)
调用webview组件中的方法,id为webview组件id,args为参数string字符串
参数:
id: int
args: string
var menu1 = modmenu.create('test mod',
[
{
'id': '4',
'type': 'webview',
'data': '<!DOCTYPE html><html><body><input id="input1" type="text" value="123" /><div id="test"><font color="red" onclick="test();"><b>点我</b></font></div><script>window.inputset = function(res){document.getElementById("input1").value = res}; document.getElementById("input1").addEventListener("click", function() { jsCallMethod("input",document.getElementById("input1").value,function(res){inputset(res);}); });function test(){jsCallMethod("test","time: ",function(a){document.getElementById("test").innerHTML = a;});}</script></body></html>',
//or
//'url': 'http://xxxxx'
},
]
, {
onchange: function (result) {
console.log(result);
},
webviewcallback: function (result) {
console.log(result);
var result = JSON.parse(result);
if (result.method == 'onload') {
console.log('init');
}if (result.method == 'test') {
menu1.webviewcall(result.id, '<font color="red" onclick="test();"><b>点我后:' + result.args + new Date().getTime() + '</b></font>');
} else if (result.method == 'input') {
dialog.input('标题', {
ok: function (res) {
console.log(res);
menu1.webviewcall(result.id, res);
},
cancel: function () { }
}, result.args);
}
}
});
[实例].edgeHiden(enable)
icon图标拖动到屏幕边缘自动隐藏,默认不开启
参数:enable: boolean
modmenu.closeAll()
关闭所有创建的mod菜单
注意事项
-
每个webview组件网页加载完成都是触发webviewcallback result.method为onload的事件,可以在这里做一些脚本中的初始化处理,当webview中的input点击后触发脚本中的
dialog.input
等待用户输入,输入完成后回传给webview中在赋值到input中。 -
webview会注入
jsCallMethod
方法,为3个参数,method, args, callback
,示例中,并没有在callback回调中直接操作input,因为回调是独立的,不能获取到任何外部局部变量,你需要将视图更新事件注册到window中,才可以在callback回调中使用。
storage
用于本地数据的存储,数据存在被hook的应用私有路径中,
storage.get(key)
参数:key: string
返回值: stringstorage.set(key,data)
参数:
key: string
data: stringstorage.del(key)
参数:key: stringstorage.clear()
convert
用于常用的转换
convert.stringToByte(data)
参数:data: string
返回值: byte[]convert.byteToString(data)
参数:data: byte[]
返回值: stringconvert.hexStringToByte(data)
参数:data: string
返回值: byte[]convert.byteToHexString(data)
参数:data: byte[]
返回值: stringconvert.byteToBitmap(data)
参数:data: byte[]
返回值: Bitmapconvert.bitmapToByte(data)
参数:data: Bitmap
返回值: byte[]convert.byteToDrawable(data)
参数:data: byte[]
返回值: Drawableconvert.drawableToByte(data)
参数:data: Drawable
返回值: byte[]convert.byteToInputStream(data)
参数:data: byte[]
返回值: InputStreamconvert.inputStreamToByte(data)
参数:data: InputStream
返回值: byte[]convert.byteToOutputStream(data)
参数:data: byte[]
返回值: OutputStreamconvert.outputStreamToByte(data)
参数:data: OutputStream
返回值: byte[]
dialog
常用的对话框
dialog.input(title,callback,content)
参数:
title: string
callback: function
content: string
dialog.input('标题', {
ok: function (res) {
console.log(res);
},
cancel: function () { }
}, '默认值');
view
视图层操作
view.findViewByText(text)
查找当前activity所有包含字符的TextView
参数:
text: string
返回值: List<View>
var time1 = setInterval(function () {
var view1 = view.findViewByText('Text');
if (view1.size() > 0) {
clearInterval(time1);
view1.get(0).setText('fuck');
var p = view.getPosition(view1.get(0));
keys.click(p.get(0), p.get(1));
setTimeout(function () {
keys.back();
}, 2000);
}
}, 100);
view.getPosition(view)
获取view的位置
参数:view: View
返回值: List<int>
view.findViewPositionByText(text)
通过查找所有包含字符的TextView的位置
参数:text: string
返回值: List<List<int>>
view.runOnUiThread(function)
在ui线程中执行
参数:function: function
view.runOnUiThread({
run: function () {
//...
}
});
keys
模拟按键操作,需要安装Magisk模块才可以调用
keys.click(x,y)
模拟点击x,y位置
参数:
x: int
y: intkeys.swipe(x,y,tox,toy,time)
模拟滑动,从x,y位置滑动到tox,toy位置,time持续时间
参数:
x: int
y: int
tox: int
toy: int
time: intkeys.back()
返回上一页keys.home()
返回首页keys.enter()
回车keys.del()
删除keys.text(text)
模拟输入文本,请注意调用这个需要确定处于文本输入焦点状态
colors
用于颜色字符串与数值的转换
colors.toString(color)
返回颜色值的字符串,格式为 "#AARRGGBB"
参数:color: int rgb颜色值
返回值: stringcolors.red(color)
返回颜色color的R通道的值,范围0 ~ 255
参数:color: string 字符串颜色值
返回值: intcolors.green(color)
返回颜色color的G通道的值,范围0 ~ 255
参数:color: string 字符串颜色值
返回值: intcolors.blue(color)
返回颜色color的B通道的值,范围0 ~ 255
参数:color: string 字符串颜色值
返回值: intcolors.alpha(color)
返回颜色color的Alpha通道的值,范围0 ~ 255
参数:color: string 字符串颜色值
返回值: intcolors.rgb(red,green,blue)
返回这些颜色通道构成的整数颜色值
参数:
red: int 颜色的R通道的值
green: int 颜色的R通道的值
blue: int 颜色的R通道的值
返回值: intcolors.argb(alpha,red,green,blue)
返回这些颜色通道构成的整数颜色值
参数:
alpha: int 颜色的Alpha通道的值
red: int 颜色的R通道的值
green: int 颜色的R通道的值
blue: int 颜色的R通道的值
返回值: intcolors.parseColor(color)
返回这些颜色通道构成的整数颜色值
参数:
color: string 字符串颜色值
返回值: int
canvas
创建全屏的画板,可以在画板中进行图形绘制,支持创建多个画板
canvas.create(function)
创建画板实例
参数:
function: function
返回值: 实例[实例].close()
关闭画板[实例].maxfps(fps)
调整绘制最大帧数,默认为30
参数:fps: int 帧数整数值canvas.closeAll()
关闭所有创建的画板
frida 简单示例
var dw = device.getScreenWidth();
var dh = device.getScreenHeight();
var playerCount = 10;
var players = [];
var Canvas_Class = Java.use('android.graphics.Canvas');
var Paint_Class = Java.use('android.graphics.Paint');
var Paint_Style_Class = Java.use('android.graphics.Paint$Style');
var Bitmap_Class = Java.use('android.graphics.Bitmap');
var Bitmap_Config_Class = Java.use('android.graphics.Bitmap$Config');
var canvas1 = canvas.create({
ondraw: function () {
var bitmap = Bitmap_Class.createBitmap(dw, dh, Bitmap_Config_Class.ARGB_8888.value);
var canvast = Canvas_Class.$new(bitmap);
var paint = Paint_Class.$new();
paint.setStyle(Paint_Style_Class.STROKE.value);
paint.setStrokeWidth(3);
for (var i = 0; i < players.length; i++) {
paint.setColor(colors.parseColor(players[i][4]));
canvast.drawRect(
players[i][0],
players[i][1],
players[i][0] + players[i][2],
players[i][1] + players[i][3],
paint
);
canvast.drawLine(dw / 2, 0, players[i][0] + players[i][2] / 2, players[i][1], paint);
}
return bitmap;
}
});
function random(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function playerdata() {
var newPlayers = [];
for (var i = 0; i < playerCount; i++) {
var playerWidth = random(30, 100);
var playerHeight = 2 * playerWidth;
var color = random(0, 1) ? "#ff0000" : "#00ff00";
var player = [
random(0, dw),
random(0, dh),
playerWidth,
playerHeight,
color
];
newPlayers.push(player);
}
players = newPlayers;
}
setInterval(function() {
dw = device.getScreenWidth();
dh = device.getScreenHeight();
}, 1000);
setInterval(function() {
playerdata();
}, 100);
本文标签: 网页 JS vjstools hook 安卓上用
版权声明:本文标题:网页 js 逆向分析 ( v_jstools )、jshook ( 安卓上用js实现Hook ) 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1729142942h1323182.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论