当前位置:   article > 正文

网页 js 逆向分析 ( v_jstools )、jshook ( 安卓上用js实现Hook )

jstools

1、网页 js 逆向分析( v_jstools )

From:https://mp.weixin.qq.com/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 差不多。但是功能更强大

下载、安装 v_jstools 插件

v_jstools 是一款浏览器插件,功能非常强大。工具地址:https://github.com/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处理后的代码,让混淆代码见鬼去吧。

它给的示例代码是这个:

  1. function fetch_hook(code, url) {
  2. var ast = parser.parse(code);
  3. function removedebugger(path) {
  4. path.replaceWith(t.identifier("/*debugger*/"))
  5. }
  6. traverse(ast, {
  7. DebuggerStatement: removedebugger
  8. });
  9. var {code} = generator(ast, {
  10. jsescOption: {
  11. minimal: true,
  12. }
  13. });
  14. return code
  15. }

这里简单说一下, fetch_hook 函数 接受2个参数,可以不用理会,如果你想hook特定的url,可以在函数的首行加入这样一段代码:

  1. if (url.indexOf("https://www.xxx.com.cn/xxx.js") == -1)
  2. {
  3. return code // 检测到不是这个 url 就直接返回原始代码
  4. }

再就是编写 ast 插件了,按照这样下就好:

  1. function removedebugger(path) {
  2. path.replaceWith(t.identifier("/*debugger*/"))
  3. }
  4. traverse(ast, {DebuggerStatement: removedebugger});

先写方法,再遍历,也可以按照我星球里的方式进行编写,都是一样的。

不过在我使用了几次以后,还是觉得线下处理混淆代码香,当然要看你的使用场景了

AST 的解密混淆

第三个功能是一些 AST的解密混淆,如图:

 把混淆代码复制到上面的白色框中,点击对应的按钮即可解混淆,也是非常的方便。这里能解混淆的有 sojson,ob混淆,jsfuck以及压缩代码等,都是比较常用的功能。

试用了一番,可以还原最新版的ob混淆,效果还是很棒的。当然一些修改特征了的代码无法还原,这个要做到通杀确实有些难度。

v_jstools 补环境

点击 打开配置按钮:

按图进行如下设置:

实战案例 1:知乎搜索

实战:https://www.zhihu.com/search?type=content&q=python

抓包,查看 请求参数

需要的加密参数:

等网站加载完毕后,我们再点开这个插件,选择 生成临时环境 命令:

点击后,会有个弹框提示已复制到剪切板。这样,临时环境就帮我们生成好了,新建一个js文件 v.js, 将生成的环境复制过来进来并保存。我们再将这个参数加密的代码也复制进来,保存到一起,具体请参考这篇文章:https://blog.csdn.net/qq523176585/article/details/123814707

在加密函数处打上断点,记录实参和结果。

实参: '7861d84e110af123f2fc8c05ff38601f';
结果: 'a8O0QQuy28xYcR28f0NBkQe0kXtYUuY0mLxq66L0S7Yp'
在v.js中构造好实参,并打印结果:console.log(b('7861d84e110af123f2fc8c05ff38601f'));

保存后,运行结果

经过仔细比对后可以发现,和网页上面的结果是一模一样的。非常的 nice。

基本就靠一些CV操作,就把结果弄出来了,可以说非常的简单。

如果你不想打印上面的日志,可以将这个函数进行改写:var v_console_log = function(){{}}

这个插件试用了几天,已经解决了很多网站的加密,几乎可以说js逆向有手就行,当然,你需要一些js逆向分析的功底。毕竟它只是帮你补环境。注意,这个并不是万能的,因此有效网站如果不行的话,可能需要想其他的办法了哈。

实战案例 2:同花顺

实战:https://q.10jqka.com.cn/

反爬参数如图

知识点:cookie 一般是两种,要么服务器生成,要么 js 生成,可通过如下方式判断

为啥要补环境:英文我们知道这个js文件代码内容会生成我们想要的参数,但是放到本地 nodejs 环境下运行不出结果,因为缺少浏览器环境特有的一些window/document/navigator/localstorage等参数,所以我们需要把这些缺少的浏览器环境补上,让这份js代码在本地nodejs环境下也能运行出结果来。请看这个图10秒钟

补环境分三部分:上、中、下。

  • 在最上面放好你补的环境参数,
  • 中间部分放好js代码,
  • 最下面部分放生成的目标参数,

补环境的好处:就是可以完全不用考虑内部的算法逻辑,让它能正常跑起来输出就行

详细分析流程

  • 1.先点击打开如下两个开关,然后打开配置页面

  • 2. 如下插件配置详情,勾选上总开关,DOM开关,以及常用的挂钩,然后关掉该配置页面

  • 3.直接看视频-缺啥补啥的方法补环境:https://www.bilibili.com/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 开发者工具也能够在错误代码处停住,这里就不详细介绍了,大概思路如下

文章与视频

JS hook 脚本

hook 又称钩子,可以在调用系统函数之前, 先执行我们的函数. 例如, hook eval

  1. eval_ = eval; // 先保存系统的eval函数
  2. eval = function(s){
  3. console.log(s);
  4. debugger;
  5. return eval_(s);
  6. }
  7. eval()
  8. // 可能会被检测到, 用这种方案来进行
  9. eval.toString = function(){return 'function eval() { [native code] }'}

对 Function 的 hook, 主要为了解决无限 debugger

  1. var qiaofu_function_constructor = (function(){}).__proto__.constructor;
  2. (function(){}).__proto__.constructor = function(arg){
  3. console.log("我爱你大大");
  4. if(arg ==='debugger'){
  5. return function(){}
  6. } else {
  7. return new qiaofu_function_constructor(arg);
  8. }
  9. }

上面都是 hook 的系统函数. 但有时, 我们需要hook某个属性. 此时应该怎么办?

  1. var v;
  2. Object.defineProperty(document, "cookie", {
  3. set: function(val) {
  4. console.log("有人来存cookie了");
  5. v = val;
  6. debugger;
  7. return val;
  8. },
  9. get() {
  10. console.log("有人提取cookie了∂
  11. debugger;
  12. return v;
  13. }
  14. });

在逆向时, 常用的主要有: hook eval 、hook Function 、hook JSON.stringify、JSON.parse 、hook cookie、hook window对象

Js 代码 替换

JS逆向|JavaScript代码线上替换(js 逆向 系列文章):https://blog.csdn.net/qq523176585/article/details/126258689

有的网站在用 debuger 调试之后,每一步都变得非常卡顿非常影响效率和心情,项目经过webpack处理了,请问这种有什么好的办法吗?

答案是:可以把源码保存到本地,然后用fiddler的atuo response替换线上的js调试

方法 1:使用谷歌浏览器自带的替换功能

Chrome 的 local overrides:https://zhuanlan.zhihu.com/p/36677472

方法 2:使用 fiddler 的 auto response 替换功能

fiddler 本地资源替换线上文件:https://www.cnblogs.com/shichangchun/p/10731297.html

方法 3:使用 reres 插件进行替换

Github地址:https://github.com/annnhan/ReRes

使用方法:找不到变量生成的位置?让插件来帮你轻松定位:https://blog.csdn.net/qq523176585/article/details/109508013

方法 4:使用 Netify 插件进行替换

插件下载地址:https://chrome.google.com/webstore/detail/netify/mdafhjaillpdogjdigdkmnoddeoegblj
使用方法:工具分析 | Akamai2.0 还原后的js线上替换方案分享:https://blog.csdn.net/qq523176585/article/details/125093543

方法 5:v_jstools 插件进行替换

使用方法:AST实战技巧|使用v神插件动态替换AST还原后的代码:https://blog.csdn.net/qq523176585/article/details/124395678

2、jshook ( 安卓上用Js实现Hook )

jshook相关文件下载地址:https://github.com/JsHookApp/Download

官网文档:https://doc.jshook.org/#/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.com/topjohnwu/Magisk
jshook属于xp模块,一般使用lsposed进行激活,https://github.com/LSPosed/LSPosed
安装lsposed后在模块中启用jshook,并勾选系统框架,重启手机后完成激活
如果没有lsposed也可以安装jshook的面具模块进行激活,jshook应用首页点击安装magisk模块前两个模块用于激活,同时,支持与lsposed共存
lsposed激活的情况下在jshook中启用hook服务后,需要在lsposed的模块作用域中也要勾选对应启用hook服务的应用

真机 免root 环境激活

如果你手机无法root,可以尝试使用lspatch https://github.com/LSPosed/LSPatch

lspatch激活xp模块的方式是给目标应用进行修补过签名验证并修改程序入口,入口处会加入注入xposed框架以及调用lsp相关服务

安装lspatch后jshook会显示激活

安装完lspatch后你需要先将原应用 提取成apk文件,可以使用 mt管理器 进行操作,lspatch中点击管理右下角+号新建修补,选择apk文件,修补完成后,用mt管理器找到修补后的apk文件,并安装,安装完成后在lspatch的管理中即可看到被修补成功的应用,点击这个应用,会出现菜单,选择模块作用域,勾选jshook即可完成激活

虚拟机环境激活

使用 "vmos、光速虚拟机" 等类似产品进行安装激活操作,操作方式同上两种流程一样

模拟器环境激活

示例:使用夜神模拟器,下载 https://www.yeshen.com/

添加模拟器选择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.com/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/

以下是常见的一个示例:

  1. XposedBridge.hookAllMethods(XposedHelpers.findClass("android.app.Application", runtime.classLoader), "onCreate", XC_MethodReplacement({
  2. replaceHookedMethod: function (param) {
  3. console.log('hook');
  4. return XposedBridge.invokeOriginalMethod(param.method, param.thisObject, param.args);
  5. }
  6. }));
  1. XposedBridge.hookAllMethods(XposedHelpers.findClass("android.app.Application", runtime.classLoader), "onCreate", XC_MethodHook({
  2. beforeHookedMethod: function (param) {
  3. console.log('hook before');
  4. },
  5. afterHookedMethod: function (param) {
  6. console.log('hook after');
  7. }
  8. }));

从示例中我们可以看到,几乎和java的写法一致,只是进行了简化

XposedHelpers.findClass(className,classLoader)

XposedHelpers.findClass('com.test.test',runtime.classLoader);

XposedBridge.hookAllConstructors(hookClass,callback)

  1. XposedBridge.hookAllConstructors(XposedHelpers.findClass('com.test.test',runtime.classLoader),XC_MethodHook({
  2.     beforeHookedMethod: function (param) {
  3.         console.log('hook before');
  4.     },
  5.     afterHookedMethod: function (param) {
  6.         console.log('hook after');
  7.     }
  8. }));

XposedHelpers.findAndHookConstructor(clazz,parameterTypesAndCallback)

  1. XposedHelpers.findAndHookConstructor(XposedHelpers.findClass('com.test.test',runtime.classLoader),'java.lang.String','java.lang.String',XC_MethodHook({
  2.     beforeHookedMethod: function (param) {
  3.         console.log('hook before');
  4.     },
  5.     afterHookedMethod: function (param) {
  6.         console.log('hook after');
  7.     }
  8. }));

XposedHelpers.findAndHookConstructor(className,classLoader,parameterTypesAndCallback)

  1. XposedHelpers.findAndHookConstructor('com.test.test',runtime.classLoader,'java.lang.String','java.lang.String',XC_MethodHook({
  2.     beforeHookedMethod: function (param) {
  3.         console.log('hook before');
  4.     },
  5.     afterHookedMethod: function (param) {
  6.         console.log('hook after');
  7.     }
  8. }));

XposedBridge.hookAllMethods(hookClass,methodName,callback)

  1. XposedBridge.hookAllMethods(XposedHelpers.findClass('com.test.test',runtime.classLoader),'method',XC_MethodHook({
  2.     beforeHookedMethod: function (param) {
  3.         console.log('hook before');
  4.     },
  5.     afterHookedMethod: function (param) {
  6.         console.log('hook after');
  7.     }
  8. }));

XposedBridge.hookMethod(hookMethod,callback)

  1. XposedBridge.hookMethod(param.method,XC_MethodHook({
  2.     beforeHookedMethod: function (param) {
  3.         console.log('hook before');
  4.     },
  5.     afterHookedMethod: function (param) {
  6.         console.log('hook after');
  7.     }
  8. }));

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 加壳的应用

以下是常用的示例,有一些企业壳做了特殊处理,可能不适用

  1. XposedBridge.hookAllMethods(XposedHelpers.findClass('android.app.ActivityThread', runtime.classLoader), 'performLaunchActivity', XC_MethodHook({
  2. beforeHookedMethod: function (param) {
  3. console.log('hook before');
  4. },
  5. afterHookedMethod: function (param) {
  6. console.log('hook after');
  7. var mInitialApplication = XposedHelpers.getObjectField(param.thisObject, 'mInitialApplication');
  8. var classLoader = XposedHelpers.callMethod(mInitialApplication, 'getClassLoader');
  9. XposedBridge.hookAllMethods(XposedHelpers.findClass('com.test.test', classLoader), 'test', XC_MethodHook({
  10. beforeHookedMethod: function (param) {
  11. console.log('hook before');
  12. },
  13. afterHookedMethod: function (param) {
  14. console.log('hook after');
  15. }
  16. }));
  17. }
  18. }));

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
        获取当前注入的脚本内容,注意,如果是加密脚本,获取的不会是解密后的文本
        返回值: string

runtime.appInfo
        返回值: ApplicationInfo

runtime.packageName
        返回值: string

runtime.processName
        返回值: string

runtime.classLoader
        返回值: ClassLoader

runtime.isFirstApplication
        返回值: boolean

runtime.coreVersionCode
        获取当前jshook的版本号
        返回值: int

runtime.coreType
        获取当前注入的类型(1:rhino 2:frida)
        返回值: int

app

用于获取当前hook应用的基本信息

app.isAppRoot()
        判断 App 是否有 root 权限
        返回值: boolean

app.isAppSystem()
        判断 App 是否是系统应用
        返回值: boolean

app.isAppForeground()
        判断 App 是否处于前台
        返回值: boolean

app.exitApp()
        关闭应用

app.getAppInfo()
        获取 App 信息
        返回值: object

app.openUrl(url)
        打开指定网址
        参数:url: string 网址

app.startActivity(activity)
        启动 Activity
        参数:activity: Activity

app.getActivityList()
        获取 Activity 栈链表
        返回值: List

app.finishActivity(activity)
        结束 Activity
        参数:activity: Activity

app.finishToActivity(activity)
        结束到指定 Activity
        参数:activity: Activity

app.startHomeActivity()
        回到桌面

app.dpToPx(value)
        dp转px
        参数:value: float
        返回值: int

app.pxToDp(value)
        px转dp
        参数:value: float
        返回值: int

app.getInternalAppDataPath()
        获取内存应用数据路径
        返回值: string

app.getExternalAppDataPath()
        获取外存应用数据路径
        返回值: string

app.getExternalAppObbPath()
        获取外存应用 OBB 路径
        返回值: string

app.getExternalStoragePath()
        获取外存路径
        返回值: string

base64

用于进行字符串base64编码解码

base64.encode(data)
        参数:data: string
        返回值: string

base64.encodeBytes(data)
        参数:data: byte[]
        返回值: byte[]

base64.decode(data)
        参数:data: string
        返回值: string

base64.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
        返回值: string

crypto.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
        返回值: string

crypto.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
        返回值: string

crypto.rc4EncryptBytes(key,data)
        参数:
        key: byte[]
        data: byte[]
        返回值: byte[]

crypto.rc4Decrypt(key,data)
        参数:
        key: string
        data: string
        返回值: string

crypto.rc4DecryptBytes(key,data)
        参数:
        key: byte[]
        data: byte[]
        返回值: byte[]

crypto.md5(data)
        参数:
        data: string
        返回值: string

crypto.md5Bytes(data)
        参数:
        data: byte[]
        返回值: string

crypto.sha1(data)
        参数:
        data: string
        返回值: string

crypto.sha1Bytes(data)
        参数:
        data: byte[]
        返回值: string

crypto.sha256(data)
        参数:
        data: string
        返回值: string

crypto.sha256Bytes(data)
        参数:
        data: byte[]
        返回值: string

device

用于获取当前设备的基础信息

device.isDeviceRooted()
        判断设备是否 rooted
        返回值: boolean

device.isAdbEnabled()
        判断设备 ADB 是否可用
        返回值: boolean

device.getSDKVersionName()
        获取设备系统版本号
        返回值: string

device.getSDKVersionCode()
        获取设备系统版本码
        返回值: int

device.getAndroidID()
        获取设备 AndroidID
        返回值: string

device.getMacAddress()
        获取设备 MAC 地址
        返回值: string

device.getManufacturer()
        获取设备厂商
        返回值: string

device.getModel()
        获取设备型号
        返回值: string

device.getABIs()
        获取设备 ABIs
        返回值: string

device.isTablet()
        判断是否是平板
        返回值: boolean

device.isEmulator()
        判断是否是模拟器
        返回值: boolean

device.isDevelopmentSettingsEnabled()
        开发者选项是否打开
        返回值: boolean

device.getScreenWidth()
        获取屏幕的宽度(单位:px)
        返回值: int

device.getScreenHeight()
        获取屏幕的高度(单位:px)
        返回值: int

device.getAppScreenWidth()
        获取应用屏幕的宽度(单位:px)
        返回值: int

device.getAppScreenHeight()
        获取应用屏幕的高度(单位:px)
        返回值: int

device.getScreenDensity()
        获取屏幕密度
        返回值: float

device.getScreenDensityDpi()
        获取屏幕密度 DPI
        返回值: int

device.isLandscape()
        判断是否横屏
        返回值: boolean

device.isPortrait()
        判断是否竖屏
        返回值: boolean

device.screenShot(activity)
        截屏
        参数:activity: Activity
        返回值: Bitmap

device.setClipboard(data)
        设置剪贴板内容
        参数:data: string

device.getClipboard()
        获取剪贴板内容
        返回值: string

file

用于对文件进行相关操作

file.isFile(path)
        参数:path: string
        返回值: boolean

file.isDir(path)
        参数:path: string
        返回值: boolean

file.isExists(path)
        参数:path: string
        返回值: boolean

file.read(path)
        参数:path: string
        返回值: string

file.readBytes(path)
        参数:path: string
        返回值: byte[]

file.write(path,content)
        参数:
        path: string
        content: string

file.writeBytes(path,content)
        参数:
        path: string
        content: byte[]

file.append(path,content)
        参数:
        path: string
        content: string

file.appendBytes(path,content)
        参数:
        path: string
        content: byte[]

file.copy(path,topath)
        参数:
        path: string
        topath: string

file.move(path,topath)
        参数:
        path: string
        topath: string

file.rename(path,newname)
        参数:
        path: string
        newname: string

file.delete(path)
        参数:path: string

file.getName(path)
        参数:path: string

file.getSize(path)
        返回值: long

file.zip(path,topath,passwd)
        参数:
        path: string
        topath: string
        passwd: string

file.zip(path,topath)
        参数:
        path: string
        topath: string

file.unzip(path,topath,passwd)
        参数:
        path: string
        topath: string
        passwd: string

file.unzip(path,topath)
        参数:
        path: string
        topath: string

http

用于进行网络请求,注意,需要被hook的应用拥有网络访问权限

http.get(url,headers,function)
        参数:
        url: string
        headers: object
        function: function

  1. http.get('http://xxxxx.com', {
  2. 'test': '1'
  3. }, {
  4. success: function (result) {
  5. console.log(result);
  6. },
  7. error: function (err) {
  8. console.log(err);
  9. }
  10. });

http.post(url,data,headers,function)
        参数:
        url: string
        data: object
        headers: object
        function: function

  1. http.post('http://xxxxx.com', {
  2. 'user': 'me'
  3. }, {
  4. 'test': '1'
  5. }, {
  6. success: function (result) {
  7. console.log(result);
  8. },
  9. error: function (err) {
  10. console.log(err);
  11. }
  12. });
  13. //或者
  14. http.post('http://xxxxx.com', JSON.stringify({
  15. 'user': 'me'
  16. }), {
  17. 'content-type': 'application/json'
  18. }, {
  19. success: function (result) {
  20. console.log(result);
  21. },
  22. error: function (err) {
  23. console.log(err);
  24. }
  25. });

注意事项: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
        返回值: string

json.gsonStringToClass(data,class)
        参数:
        data: string
        class: class
        返回值: class

json.toJSONString(data)
        参数:
        data: object

json.parseObject(data)
        参数:
        data: string
        返回值: JSONObject

json.parseArray(data)
        参数:
        data: string
        返回值: JSONArray

modmenu

这是一个mod菜单,可以定制简单的菜单悬浮窗,注意,你需要让jshook保持后台运行状态,并且授权悬浮窗权限,可以创建多个悬浮窗实例

modmenu.create(title,options,function)
        参数:
        title: string
        options: object
        function: function
        返回值: 实例

  1. modmenu.create('test mod',
  2. [
  3. {
  4. 'id': '1',
  5. 'type': 'category',
  6. 'title': 'category title'
  7. },
  8. {
  9. 'id': '2',
  10. 'type': 'switch',
  11. 'title': 'switch1 title',
  12. 'enable': true
  13. },
  14. {
  15. 'id': '3',
  16. 'type': 'switch',
  17. 'title': 'switch2 title'
  18. },
  19. {
  20. 'id': '4',
  21. 'type': 'webview',
  22. 'data': '<font color="red"><b>text</b></font>',
  23. //or
  24. //'url': 'http://xxxxx.com'
  25. },
  26. {
  27. 'id': '5',
  28. 'type': 'button',
  29. 'title': 'button title'
  30. },
  31. {
  32. 'id': '6',
  33. 'type': 'input',
  34. 'title': 'input title',
  35. 'val': 'default value'
  36. },
  37. {
  38. 'type': 'collapse',
  39. 'title': 'collapse title',
  40. 'item': [
  41. {
  42. 'id': '7',
  43. 'type': 'switch',
  44. 'title': 'switch title'
  45. }
  46. ],
  47. 'enable': true
  48. },
  49. {
  50. 'id': '8',
  51. 'type': 'slider',
  52. 'title': 'slider title',
  53. 'val': 88,
  54. 'min': 1,
  55. 'max': 100
  56. },
  57. {
  58. 'id': '9',
  59. 'type': 'checkbox',
  60. 'title': 'checkbox title',
  61. 'enable': true
  62. },
  63. {
  64. 'type': 'checkboxs',
  65. 'item': [
  66. {
  67. 'id': '10',
  68. 'type': 'checkbox',
  69. 'title': '1 title'
  70. },
  71. {
  72. 'id': '11',
  73. 'type': 'checkbox',
  74. 'title': '2 title'
  75. },
  76. {
  77. 'id': '12',
  78. 'type': 'checkbox',
  79. 'title': '33333333 title'
  80. }
  81. ]
  82. },
  83. {
  84. 'id': '13',
  85. 'type': 'radio',
  86. 'title': 'radio title',
  87. 'item': ['test', 'test2', 'test3'],
  88. 'check': 0
  89. }
  90. ]
  91. , {
  92. onchange: function (result) {
  93. //注意在这里需要进行一下转换
  94. result = JSON.parse(result);
  95. switch(result.id) {
  96. case '1':
  97. console.log('ok');
  98. break;
  99. }
  100. console.log(result);
  101. }
  102. })

[实例].close()

  1. var menu1 = modmenu.create('test mod', [], {
  2. onchange: function (result) {
  3. }
  4. });
  5. setTimeout(function () {
  6. menu1.close();
  7. }, 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

  1. var menu1 = modmenu.create('test mod',
  2. [
  3. {
  4. 'id': '4',
  5. 'type': 'webview',
  6. '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>',
  7. //or
  8. //'url': 'http://xxxxx.com'
  9. },
  10. ]
  11. , {
  12. onchange: function (result) {
  13. console.log(result);
  14. },
  15. webviewcallback: function (result) {
  16. console.log(result);
  17. var result = JSON.parse(result);
  18. if (result.method == 'onload') {
  19. console.log('init');
  20. }if (result.method == 'test') {
  21. menu1.webviewcall(result.id, '<font color="red" onclick="test();"><b>点我后:' + result.args + new Date().getTime() + '</b></font>');
  22. } else if (result.method == 'input') {
  23. dialog.input('标题', {
  24. ok: function (res) {
  25. console.log(res);
  26. menu1.webviewcall(result.id, res);
  27. },
  28. cancel: function () { }
  29. }, result.args);
  30. }
  31. }
  32. });

[实例].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
        返回值: string

storage.set(key,data)
        参数:
        key: string
        data: string

storage.del(key)
        参数:key: string

storage.clear()

convert

用于常用的转换

convert.stringToByte(data)
        参数:data: string
        返回值: byte[]

convert.byteToString(data)
        参数:data: byte[]
        返回值: string

convert.hexStringToByte(data)
        参数:data: string
        返回值: byte[]

convert.byteToHexString(data)
        参数:data: byte[]
        返回值: string

convert.byteToBitmap(data)
        参数:data: byte[]
        返回值: Bitmap

convert.bitmapToByte(data)
        参数:data: Bitmap
        返回值: byte[]

convert.byteToDrawable(data)
        参数:data: byte[]
        返回值: Drawable

convert.drawableToByte(data)
        参数:data: Drawable
        返回值: byte[]

convert.byteToInputStream(data)
        参数:data: byte[]
        返回值: InputStream

convert.inputStreamToByte(data)
        参数:data: InputStream
        返回值: byte[]

convert.byteToOutputStream(data)
        参数:data: byte[]
        返回值: OutputStream

convert.outputStreamToByte(data)
        参数:data: OutputStream
        返回值: byte[]

dialog

常用的对话框

dialog.input(title,callback,content)
        参数:
        title: string
        callback: function
        content: string

  1. dialog.input('标题', {
  2. ok: function (res) {
  3. console.log(res);
  4. },
  5. cancel: function () { }
  6. }, '默认值');

view

视图层操作

view.findViewByText(text)
        查找当前activity所有包含字符的TextView
        参数:
        text: string
        返回值: List<View>

  1. var time1 = setInterval(function () {
  2. var view1 = view.findViewByText('Text');
  3. if (view1.size() > 0) {
  4. clearInterval(time1);
  5. view1.get(0).setText('fuck');
  6. var p = view.getPosition(view1.get(0));
  7. keys.click(p.get(0), p.get(1));
  8. setTimeout(function () {
  9. keys.back();
  10. }, 2000);
  11. }
  12. }, 100);

view.getPosition(view)
        获取view的位置
        参数:view: View
        返回值: List<int>

view.findViewPositionByText(text)
        通过查找所有包含字符的TextView的位置
        参数:text: string
        返回值: List<List<int>>

view.runOnUiThread(function)
        在ui线程中执行
        参数:function: function

  1. view.runOnUiThread({
  2. run: function () {
  3. //...
  4. }
  5. });

keys

模拟按键操作,需要安装Magisk模块才可以调用

keys.click(x,y)
        模拟点击x,y位置
        参数:
        x: int
        y: int

keys.swipe(x,y,tox,toy,time)
        模拟滑动,从x,y位置滑动到tox,toy位置,time持续时间
        参数:
        x: int
        y: int
        tox: int
        toy: int
        time: int

keys.back()
        返回上一页

keys.home()
        返回首页

keys.enter()
        回车

keys.del()
        删除

keys.text(text)
        模拟输入文本,请注意调用这个需要确定处于文本输入焦点状态

colors

用于颜色字符串与数值的转换

colors.toString(color)
        返回颜色值的字符串,格式为 "#AARRGGBB"
        参数:color: int rgb颜色值
        返回值: string

colors.red(color)
        返回颜色color的R通道的值,范围0 ~ 255
        参数:color: string 字符串颜色值
        返回值: int

colors.green(color)
        返回颜色color的G通道的值,范围0 ~ 255
        参数:color: string 字符串颜色值
        返回值: int

colors.blue(color)
        返回颜色color的B通道的值,范围0 ~ 255
        参数:color: string 字符串颜色值
        返回值: int

colors.alpha(color)
        返回颜色color的Alpha通道的值,范围0 ~ 255
        参数:color: string 字符串颜色值
        返回值: int

colors.rgb(red,green,blue)
        返回这些颜色通道构成的整数颜色值
        参数:
        red: int 颜色的R通道的值
        green: int 颜色的R通道的值
        blue: int 颜色的R通道的值
        返回值: int

colors.argb(alpha,red,green,blue)
        返回这些颜色通道构成的整数颜色值
        参数:
        alpha: int 颜色的Alpha通道的值
        red: int 颜色的R通道的值
        green: int 颜色的R通道的值
        blue: int 颜色的R通道的值
        返回值: int

colors.parseColor(color)
        返回这些颜色通道构成的整数颜色值
        参数:
        color: string 字符串颜色值
        返回值: int

canvas

创建全屏的画板,可以在画板中进行图形绘制,支持创建多个画板

canvas.create(function)
        创建画板实例
        参数:
        function: function
        返回值: 实例

[实例].close()
        关闭画板

[实例].maxfps(fps)
        调整绘制最大帧数,默认为30
        参数:fps: int 帧数整数值

canvas.closeAll()
        关闭所有创建的画板

frida 简单示例

  1. var dw = device.getScreenWidth();
  2. var dh = device.getScreenHeight();
  3. var playerCount = 10;
  4. var players = [];
  5. var Canvas_Class = Java.use('android.graphics.Canvas');
  6. var Paint_Class = Java.use('android.graphics.Paint');
  7. var Paint_Style_Class = Java.use('android.graphics.Paint$Style');
  8. var Bitmap_Class = Java.use('android.graphics.Bitmap');
  9. var Bitmap_Config_Class = Java.use('android.graphics.Bitmap$Config');
  10. var canvas1 = canvas.create({
  11. ondraw: function () {
  12. var bitmap = Bitmap_Class.createBitmap(dw, dh, Bitmap_Config_Class.ARGB_8888.value);
  13. var canvast = Canvas_Class.$new(bitmap);
  14. var paint = Paint_Class.$new();
  15. paint.setStyle(Paint_Style_Class.STROKE.value);
  16. paint.setStrokeWidth(3);
  17. for (var i = 0; i < players.length; i++) {
  18. paint.setColor(colors.parseColor(players[i][4]));
  19. canvast.drawRect(
  20. players[i][0],
  21. players[i][1],
  22. players[i][0] + players[i][2],
  23. players[i][1] + players[i][3],
  24. paint
  25. );
  26. canvast.drawLine(dw / 2, 0, players[i][0] + players[i][2] / 2, players[i][1], paint);
  27. }
  28. return bitmap;
  29. }
  30. });
  31. function random(min, max) {
  32. return Math.floor(Math.random() * (max - min + 1)) + min;
  33. }
  34. function playerdata() {
  35. var newPlayers = [];
  36. for (var i = 0; i < playerCount; i++) {
  37. var playerWidth = random(30, 100);
  38. var playerHeight = 2 * playerWidth;
  39. var color = random(0, 1) ? "#ff0000" : "#00ff00";
  40. var player = [
  41. random(0, dw),
  42. random(0, dh),
  43. playerWidth,
  44. playerHeight,
  45. color
  46. ];
  47. newPlayers.push(player);
  48. }
  49. players = newPlayers;
  50. }
  51. setInterval(function() {
  52. dw = device.getScreenWidth();
  53. dh = device.getScreenHeight();
  54. }, 1000);
  55. setInterval(function() {
  56. playerdata();
  57. }, 100);

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/709465
推荐阅读
相关标签
  

闽ICP备14008679号