小程序安全防护-微信网关&小程序加固突破(下)

小程序安全防护-微信网关&小程序加固突破(下)

image

写在前面

以下都是情节模拟,并非具体情况,请细加甄别

在上次对某克的小程序测试后,我得出了目前微信对小程序的防护措施(加固+网关)的方式,无法拦截掉本地动态调试的判断,但不知道是业务催得紧还是老板不死心还是微信又推产品了,总之,老板让我去上海跑个小差,参加一下微信的线下公开课,和他们好好讨论一下这个问题……

image

事前准备

纵使在公开课上对来会嘉宾介绍的非常有利,下会后当我当面提出 DevTools 本地动态调试的方法时,商务解释不了推给产品,产品解释不了提问经理,经理找来研发同学,肯定了我的说法,即:

  1. 目前微信网关更倾向于流量监测和风控行为的评估,请求在客户端封装后发往微信网关,再由网关转发至原服务端,这点与认知相同;

  2. 小程序加固目前针对的个体是.wxpkg的包,方法和我们认知的混淆、加壳一致(三级防护增大30-40%体积,五级防护增加三倍体积),无法动态调试指的是获得这个包后无法在本地微信开发工具中调试;

  3. 关于前端通过DevTools进行调试得到js中参数值和代码逻辑的防护,与他们的技术沟通过表示只能在js中加入禁止调试的代码(但也讨论了可以绕过)

即使这样还无法让老板信服,很简单,如果这几点是真的,那就把某特这个被微信誉为2025年上半年最佳小程序安全防护实践的项目弄下来试试

image

使用工具

  • 微信 PC 客户端最新版本

  • WMPFDebugger

  • Cheat Engine(CE)

  • 等等(市面上真的不少)

“受害者”入场

  • 小程序:某特

开始动手

某特

书接上回,某特的小程序要求微信版本不低于4.1.1(后来知道是不低于最新版本微信),但实际上这个限制可以绕过,甚至可以不绕过

你可以通过 Cheat Engine(CE),去搜寻低版本的地址,如 3.8.0.33 的十六进制为 63080021,找到对应地址后改成 64010224(4.1.2.36的十六进制),即可绕过版本限制,版本转换

image

然后你会发现提示你需要在移动端打开……但这个绕过也很容易,其实这个就是类似 wx.getSystemInfo() 这种接口获取设备信息,获取到字段为 windows,就跳转到限制页面了,你都可以打开 Debugger 了,在跳转前查到调用接口,修改参数值就可以了

image

image

可以看到确实是做了混淆的,但是可以被动态调试,即使混淆也能看到传参过程和内容的,只要肯有耐心,但这里我就不继续了,我的目的已经达到了,我又不是灰产= =

image

写在后面

纵使这番,老板也就消停了一会,两三周后又来问我,能不能和微信信用分关联啥的了,我只能说,安全没有否决业务阻拦生产的权利(至少在这家公司没有),只能识别到风险给足建议了,只是看(老板你)能不能说服业务了

image

小程序安全防护-微信网关&小程序加固突破(上)

小程序安全防护-微信网关&小程序加固突破(上)

image

写在前面

以下都是情节模拟,并非具体情况,请细加甄别

在25年中,业务以“专门下载 APP 太慢啦,我们要小程序操作”为由,施压产研,希望要求把开门等业务重要逻辑重新放回小程序,于是老板找我评估一下安全性

拜托,即使我是23年才来,我也是半接手把开门从小程序“入土”的,现在又要改回去,开什么玩笑!

“我也理解你,所以微信那边给出了一个方案,说是接微信网关和小程序加固就可以保证安全性,你看看”

然后我就被拉入了相关群组,也就意味着去测试微信网关等一系列小程序安全措施了

image

事前准备

其实之前在推业务下线小程序的时候,我就做过测试,论证了为什么小程序的相关实现不安全,因为从根本来说,小程序可以被理解为是微信内置浏览器渲染出的前端而已,是可以被动态调试的,一如浏览器 F12 后 Debugger

当时我还研究过 .wxapkg 的逆向拆解,还原代码之类的,可以参考:

拼图碎片:反编译小程序、数据加密解密、sign值绕过

但其实完全没必要这么麻烦……

使用工具

话说回来,明明企业微信自己都带 DevTools = =

“受害者”入场

就来两个已经接入和采用方案的小程序试试呗

image

  • 小程序:某克、某特

开始动手

某特

先假装不敌,败下阵来

image

某特的小程序要求微信版本不低于4.1.1(后来知道是不低于最新版本微信),但实际上这个限制可以绕过,先不浪费时间,没事,还有下篇呢

某克

完全不是对手,技术力感觉不如我这家公司= =

主要采集了三个请求和响应点:商品分类、个人主页、个人主页-地址簿

商品分类

image

image

可以明显地看到请求头和请求体是明文的,没有用特殊协议,也没有进行加密

个人主页

image

image

在 events 中可以看到本地客户端信息和与 api.nike.com.cn 的关联信息

个人主页-地址簿

image

image

作为用户隐私信息之一的地址信息,耐克是用 GET 请求,通过 upmid 去分配用户存储路径返回的

隐藏请求

image

image

在测试的过程中,仅发现了“sa?project=MiniProgram”下的 Payload 类似加密,但解析过后发现,仅仅是 Base64 编码后 URL 编码了一下,不算加密,也不是微信自身的协议形式:

image

image

动态调试

以 upmid 为着手点,动态调试得到用户地址的获取过程:

image

写在后面

说实话,某克测下来给我感觉完全不行啊,好像都没什么防护措施,后续也向微信那边再次确认了,他们的措辞是“可能是因为接入的是已经很久前的版本了巴拉巴拉”,那么就请看下篇吧

image

禁止别人调试自己前端代码的 5 种方法

禁止别人调试自己前端代码的 5 种方法

本文转自 Toypipi 并作补充

无限 debugger

前端页面防止调试的方法主要是通过不断 debugger 来疯狂输出断点,因为 debugger 在控制台被打开的时候就会执行。由于程序被 debugger 阻止,所以无法进行断点调试,所以网页的请求也是看不到的。 以下是使用无限 debugger 方式阻止代码调试的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 基础禁止调试代码
*/
(() => {
function ban() {
setInterval(() => {
debugger;
}, 50);
}
try {
ban();
} catch (err) {}
})();

不过,如果仅仅是加上面那么简单的代码,对于一些技术人员而言作用不大。可以通过控制台中的 Deactivate breakpoints 按钮或者使用快捷键 Ctrl + F8 关闭无限 debugger。这种方式虽然能去掉碍眼的 debugger,但是无法通过左侧的行号添加 breakpoint。

禁止断点的对策

如果将 setInterval 中的代码写在一行,就能禁止用户断点,即使添加 logpoint 为 false 也无用。当然即使有些人想到用左下角的格式化代码,将其变成多行也是没用的。

1
2
3
4
5
6
7
8
9
10
(() => {
function ban() {
setInterval(() => {
debugger;
}, 50);
}
try {
ban();
} catch (err) {}
})();

忽略执行的代码

可以通过添加 add script ignore list 需要忽略执行代码行或文件,也可以达到禁止无限 debugger。 不过,我们可以通过将 debugger 改写成 Function (“debugger”)(); 的形式来应对,Function 构造器生成的 debugger 会在每一次执行时开启一个临时 js 文件。当然使用的时候,为了更加的安全,最好使用加密后的脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 加密前
(() => {
function ban() {
setInterval(() => {
Function("debugger")();
}, 50);
}
try {
ban();
} catch (err) {}
})();

// 加密后
eval(
(function (c, g, a, b, d, e) {
d = String;
if (!"".replace(/^/, String)) {
for (; a--; ) e[a] = b[a] || a;
b = [
function (f) {
return e[f];
},
];
d = function () {
return "w+";
};
a = 1;
}
for (; a--; )
b[a] && (c = c.replace(new RegExp("\b" + d(a) + "\b", "g"), b[a]));
return c;
})(
'(()=>{1 0(){2(()=>{3("4")()},5)}6{0()}7(8){}})();',
9,
9,
"block function setInterval Function debugger 50 try catch err".split(" "),
0,
{}
)
);

终极增强防调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(() => {
function block() {
if (
window.outerHeight - window.innerHeight > 200 ||
window.outerWidth - window.innerWidth > 200
) {
document.body.innerHTML = "检测到非法调试,请关闭后刷新重试!";
}
setInterval(() => {
(function () {
return false;
})
["constructor"]("debugger")
["call"]();
}, 50);
}
try {
block();
} catch (err) {}
})();

其他防调试技术

除此之外,我们还可以通过【禁止右键菜单】、【禁止 F12 快捷键】、【检测开发者工具】等手段来禁止调试。

禁止右键菜单

禁止右键菜单是目前比较常见的一种禁止调试的方法,因为浏览器默认右键菜单是可以调出开发者工具的。可以通过以下 JS 代码实现:

1
2
3
document.oncontextmenu = function () {
return false;
};

这样一来,当用户在富文本编辑器的区域内右键时,就不会弹出菜单,从而避免了用户调出开发者工具的情况。

禁止 F12 快捷键

F12 快捷键是打开开发者工具的常用快捷键,禁止这个快捷键也是一种禁止调试的方法。可以通过以下 JS 代码实现:

1
2
3
4
5
document.onkeydown = function (e) {
if (e.keyCode === 123) {
return false;
}
};

这样一来,当用户按下 F12 键时,就会被拦截,从而避免了用户打开开发者工具的情况。

检测开发者工具

还有一种方法是通过检测开发者工具是否打开来实现禁止调试的目的,可以通过以下 JS 代码实现:

1
2
3
4
5
setInterval(function () {
if (typeof console.clear !== "undefined") {
location.reload();
}
}, 1000);

这段代码会每隔 1 秒检测一次当前页面是否打开了开发者工具,如果发现开发者工具已经打开,则会刷新当前页面,从而避免用户使用开发者工具对页面进行调试。

【爬虫】网站反debugger、内存爆破以及网站限制开发者工具

【爬虫】网站反debugger、内存爆破以及网站限制开发者工具

本文转自 NPE~ 并作补充

常见情形

目前网站反爬手段有很多,比如:网站反debugger、内存爆破、限制开发者工具等

情况一:debugger(右键不在此停止)

问题: 有部分网站做了反debugger处理,当我们打开开发者工具的时候会直接断点停在某个位置。

image

  • 解决办法:右键点击,选择永不在此停止。然后放过断点,即可继续调试。

前提:该debugger构造函数需要没有内容。如果网站开发者自定义的debugger构造函数后面有内容,是在写入数据的话(不断写入空列表、其他字符等),我们跳过断点会导致持续写入,导致内存溢出,页面卡死。

image

情况二:内存爆破

绝大多数网站反debugger会更加高深,会使用到内存爆破以及混淆技术。导致我们通过简单的右键不在此停止无法破解。

我们使用右键不在此停止,并且跳过断点后,会导致浏览器卡死。

情况三:右键被管理员禁用

部分网站会直接限制我们使用开发者工具,我们右键或者F12打开均无效,都被禁用。

我们想右键,打开开发者工具时,发现被禁用。

image

情况四:监测函数执行时间

image

我们通过快捷键强制打开开发者工具,发现页面提示非法调试。

image

image

最终发现页面是通过判断当前window大小以及函数运行时间来确认是否存在非法调试。

解决办法

方法一:控制台-右键不再此停止

如果我们打开开发者工具后,发现自动进入断点。

  1. 首先尝试,鼠标右击永不在此停止
  2. 放过该断点,即可继续调试

方法二:本地注入JS,覆盖之前老的JS

如果方法一无效:放过断点后会导致浏览器页面卡死。即可使用本地注入JS方法,将debugger构造器置空,覆盖之前的JS。

  • 分析浏览器卡死原因:网站开发者自定义的debugger构造器不为空,在持续写入数据。
  • 本地注入JS不能刷新网站,否则会导致我们本地注入的JS失效。

分析原因:浏览器卡死原因

我们单步执行断点,发现进入了VM虚拟环境,并且该JS是进行了混淆的。

image

下面这种有特殊字符+数字+字母的,基本就是进行过JS混淆的:

image

对于这种混淆过的JS代码,我们需要对其进行还原:

还原方法:复制JS代码,然后在控制台执行即可

1
2
//我们需要进行分段还原,否则一次性全部执行,会报not a function
[s(0, -259, -213, 0, "I#ue") + f(1271, 974, 1135, "e!3a") + "r"](r[f(1221, 1495, 1373, "v&9u")](r[g(1467, 0, 0, "&UOm")], r[n(0, "ujyL", -6)]))[s(0, 13, 12, 0, "7Iko")](r[g(1540, 0, 0, "nX(R")]);

image

还原后JS代码:‘constructor’ apply stateObject => 可以推测出,该自定义构造函数在初始化时,还做了加载某个对象的操作。及时我们右键点击了不再此停止,放过断点,但是依然会有源源不断的对象进行加载,最后导致浏览器卡死。

解决办法:本地注入JS

我们已经定位到了浏览器卡死是因为自定义的debugger构造器不断注入对象导致,那么我们就可以本地执行代码,替换该JS片段,让debugger构造器返回空,无法实现注入对象。

  1. 新建代码片段

image

1
2
3
4
5
6
7
8
9
10
// 将debugger构造函数替换置空,防止内存爆破
// 1. after_debugger_handle 接收我们的构造器
// 2. 判断构造器如果构造了一个debugger的东西,我们就将其置空
after_debugger_handle = Function.prototype.constructor;
Function.prototype.constructor = function(a){
if (a == "debugger") {
return function(){};
}
return after_debugger_handle(a);
};
  1. 点击下方的执行按钮,执行代码片段

image

控制台未报错,表明注入成功,此时我们放过断点,发现浏览器不再卡死,即可继续调试。

注意:

  1. 注入后不能刷新页面,否则页面重新加载会导致之前的注入失效。

image

  1. 注入需要等网页数据全部加载完成后再进行,否则可能会报注入失败,JS函数找不到。

拓展:JS混淆(对网页的JS代码进行处理加密)

概念

网站开发者为了防止我们对网站进行调试或者逆向分析,会对JS代码进行处理、保护。

  • 实际就是对JS代码进行编码、加密处理等。

JS压缩混淆:删除无用空白、缩短变量名

JavaScript 压缩混淆主要通过删除无用的空白字符、注释、缩短变量名等方式减小代码体积。压缩混淆不仅可以减少文件大小,还能在一定程度上增加代码阅读难度。

OB混淆:插入不透明谓词(函数逻辑)

OB 混淆(Opaque Predicate Obfuscation)是一种复杂的混淆技术,通过插入不透明谓词来掩盖代码的真实逻辑,使逆向工程师难以理解代码的实际功能。

原始代码:

1
2
3
if (x > 10) {
console.log("x is greater than 10");
}

OB混淆后:

1
2
3
4
5
6
function isTrue() {
return Math.random() > 0.5; // 不透明谓词
}
if (isTrue() || x > 10) {
console.log("x is greater than 10");
}

在这个示例中,isTrue 函数的返回值是随机的,不透明谓词使得控制流变得更加难以预测。逆向工程师必须理解 isTrue 函数的实现才能准确分析代码逻辑。

变量混淆:重命名函数方法,改为a、b、c等

变量混淆通过重命名变量和函数名来增加代码的阅读难度,使得代码更难以理解。混淆后的变量名通常是无意义的短字符,如 a、b、c 等。

字符串混淆:对字符串进行编码和转换

字符串混淆通过对字符串进行编码和转换,使得字符串的真实内容难以直接查看。常用的字符串混淆技术包括 Base64 编码和字符替换。

属性加密:对部分字段,如:用户名、年龄等进行加密

属性加密通过对对象属性进行加密,保护对象的内部数据。常用的加密方法包括简单的 XOR 操作和更复杂的加密算法。

控制流平坦化:引入虚拟机+状态机,加大代码逻辑复杂度

控制流平坦化通过重构代码的控制流,使得代码逻辑更加复杂和难以理解。平坦化技术常用于保护程序的执行流程。控制流平坦化通过引入虚拟机和状态机来重构代码的控制流。

案例:原始的条件语句被转换为一个状态机,控制流被重构成一系列状态和转换。这种方法使得代码的控制流更加复杂,增加了理解和分析的难度。

原始代码:

1
2
3
4
5
if (x > 10) {
console.log("x is greater than 10");
} else {
console.log("x is 10 or less");
}

控制流平坦化处理后代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const states = [0, 1];
let state = 0;

function execute() {
switch (state) {
case 0:
if (x > 10) {
state = 2;
return;
} else {
state = 3;
return;
}
case 1:
console.log("x is greater than 10");
state = 4;
return;
case 2:
console.log("x is 10 or less");
state = 4;
return;
case 4:
// end state
return;
}
}

while (state !== 4) {
execute();
}

案例

可以看到下方对有一串字母以及数字,这种情况一般就是JS混淆,让我们无法知晓他JS源代码是什么样子。

image

解决办法:

  • 如果该网站使用的是开源的JS混淆代码,我们可以通过工具进行还原。
  • 如果网站使用的是自定义的混淆逻辑,我们只有手动通过控制台进行处理还原(直接复制混淆代码,然后放入控制台,回车即可进行还原。)

image

参考文章

https://blog.csdn.net/weixin_52392194/article/details/141159872

教你怎么实现钉钉蓝牙打卡考勤机的远程打卡

教你怎么实现钉钉蓝牙打卡考勤机的远程打卡

本文转自 小刀 并作补充

许多公司都手机考勤了,规定要在公司的蓝牙考勤机5米内才可以打卡签到,绝大多数是用模拟仿真软件完成的,但手机软件常常由于升级了就不能用,十分不便,因此这儿教各位怎样用USB蓝牙盒子(蓝牙打卡神器)模拟信号打卡,硬件配置可以永久用,而且安卓系统和IOS系统都能够用,也比较简单,新手有一定计算机技术和硬件知识就能完成。

准备

1、计算机(WIN7系统以上,64位)

2、USB蓝牙盒子(必须选购)

3、安卓手机

操作步骤

第一步。下载《23款蓝牙盒子设置工具 V1.0.1》这个软件到电脑,安装好驱动软件再打开软件。

下载链接:https://wwc.lanzouq.com/iKPiD1ay326h

密码:8888

image

第二步。安卓手机下载“nrf”这个APP。手机打开“nrf”,查周边的蓝牙信号。有的公司很有可能周边蓝牙信号比较多,一定要找到考勤机相匹配的那一个信号,假如没法鉴别,就只有一个一个试了。有前提的可以查询机器设备包装盒子,或是设施的反面,都是有MAC地址。

如果是钉钉的蓝牙打卡机,找蓝牙信号带TomTom(如图红框)就是,截图就可以破解。

nrf下载链接:https://wwc.lanzouq.com/iywyH04gn61g

image

第三步。打开《23款蓝牙盒子设置工具 V1.0.1》这个修改器软件,将USB蓝牙盒子插进电脑USB口。显示“已连接”,在软件填好MAC地址和广播一(MAC地址不用填“:”,只填数字和英文字母就可以),最后点“写入配置”就可以。

image

第四步。提示“写入成功”就可以把蓝牙打卡器拔下,可以配合转接头插手机上,用手机给打卡器供电,这时发送出来的蓝牙信号,就和公司的信号彻底一样。手机上只需插着这个打卡器,就象在公司一样打卡签到了。

这个方式如果考勤机器发出的蓝牙广播每天都会变一个参数值就没用了感觉,这篇文章也不是用来参考绕过考勤的,而是用才参考有一个用户能到蓝牙机器前,打开程序获取广播报,发送相关包给到远端,远端可以模拟信号发起请求或直接传参(得到广播发出的 serialData,远端模拟一个客户端请求发送给服务端)

win11系统第一次开机跳过联网的办法(3种方法)

win11系统第一次开机跳过联网的办法(3种方法)

本文转自jaray 并作补充

预装Win11家庭中文版由于微限制必须要联网激活,需要使用微软账户登入才可以继续开机过程,联网后系统会自动激活。

  新电脑,安装以后,会要求激活,我打电话问了厂家,居然告诉我,让我用新电脑连手机,使用手机的热点,去激活电脑。在没有网络的情况下,现在连电脑都无法激活,厂家这么说,我不知道是他们不愿意说,还是真的不知道,我是无法相信这种说法,我在网上搜了很多方法,带图的,结束“调出任务管理器找到 Network Connection Flow进程”,这种方法是不行的,根本没有这个进程。

  还有一种方法是:打开任务管理器,这个打开方法下面有,简单看完各个硬件参数,这个方法也扯了。

  由于很多厂家基本都是联网后就不符合无理由退货的条件了,所以我整理了以下的几种跳过联网的办法,可以参考。

一、win11初次开机跳过联网激活方法1

1、按下Shift+F10或者是Fn+Shift+F10快捷键调出命令提示符窗口;

2、输入taskmgr,并按下回车键;

3、接着就会出现任务管理器页面,我们点击“详细信息”;

4、找到“Network Connection Flow”进程或者是“网络连接流”进程,点击“结束任务”;

5、这样就可以跳过联网;

6、这种方法就是上面我提到过的方法,我这台电脑也不行。

二、win11初次开机跳过联网激活方法2

1、首次进入Windows11家庭版系统并进行设置,到联网界面,按下【Shift+F10】快捷键(无效可试下【FN+Shfit+F10】)

2、在出现的命令提示符页面输入OOBE\BYPASSNRO,按回车键,等待电脑重启完成

3、重启后,在联网界面会有“我没有Internet连接”选项,点击此选项即可跳过联网

三、通过修改注册表跳过联网界面3

1、按Shift键+F10键(不成功按Fn+Shift+F10);

2、弹出命令提示符窗口,输入regedit回;

3、然后在打开的注册表编辑器界面中,找到下方路径——计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\OOBE

4、接下来在注册表的右侧空白处鼠标右键,在弹出的右键菜单项中,新建一个DWORD(32位)值(D)。新建的值重命名为: BypassNRO,双击打开BypassNRO这个值,然后在编辑DWORD(32位)值窗口,将数值数据修改为【1】,再点击【确定】;

5、最后在cmd窗口再输入命令:logoff,回车;

6、可以跳过联网。

end.

防御GFW主动探测的实用指南

防御GFW主动探测的实用指南

本文转自Great Firewall Report 并作补充

在近期的IMC’20的工作中(论文, 演讲),我们揭示了中国的防火长城采用流量分析主动探测相结合的手段来检测和封锁Shadowsocks服务器。

在这篇短文中,我们将分别向技术小白和翻墙软件开发者提供防御GFW主动探测的实用建议。 我们还将介绍Len et al.展示的partitioning oracle攻击的缓解办法。 如果在遵循了本文的建议后,你的Shadowsocks服务器仍被封锁,请将封锁情况汇报给GFW Report以及相应的开发者。

给用户的建议

根据我们的测试和来自开发者的报告,在采用了适当的配置后,以下两个Shadowsocks实现的最新版本已经可以抵御来自GFW的主动探测:Shadowsocks-libevOutlineVPN

针对Shadowsocks-libev的使用建议

如果你决定使用Shadowsocks-libev,我们强烈建议你根据这篇教程来部署一台抗封锁的Shadowsocks-libev服务器。我们会时刻更新那篇教程的,以应对之后新出现的针对Shadowsocks的识别和攻击。

如果你已经拥有了一台Shadowsocks-libev服务器,你可以根据以下规则来确认你的服务器是否配置得可以对抗GFW的主动检测和封锁。

截止2021年1月,你需要做到以下几点来防止你的Shadowsocks-libev服务器被封锁:

  1. 确保你的服务器版本为v3.3.1及以上。你可以通过以下命令查看服务器的版本ss-server -h
  2. 使用AEAD ciphers, 而不用 stream ciphers。换句话说,仅在以下几种加密方式中进行选择:chacha20-ietf-poly1305 (推荐), aes-256-gcm, aes-192-gcm或者aes-128-gcm

为了缓解针对Shadowsocks的partitioning oracle攻击,你需要:

  1. 使用一个长的随机密码。这样的密码可以用以下命令在终端生成: openssl rand -base64 16;
  2. 禁用UDP模式。

注意:针对客户端没有特殊的要求,任何与Shadowsocks-libev服务器兼容的客户端均可。

针对OutlineVPN的使用建议

为了防止你的OutlineVPN服务器被GFW封锁,你需要做到以下几点:

  1. 使用最新版的从官网下载的服务端。
  2. 使用最新版的从官网下载的客户端。

注意:

  1. Outline会自动生成一个长的,随机的密码,因此你不必像为Shadowsocks-libev那样手动配置密码。
  2. Outline服务端会自动更新,因此你不必手动升级服务端。
  3. Outline只采用chacha20-ietf-poly1305这一种AEAD cipher作为加密方式,因此你不必手动选择加密方式。

给翻墙软件开发者的建议

下面我们介绍我们发现的GFW的最新审查能力,并附上我们给翻墙软件开发者的相应建议。这些建议不仅对Shadowsocks,而且对其他许多翻墙软件都有用。 我们欢迎你加入我们的讨论,分享你的想法,评论,疑惑和关切。

正确的校验

首先,我们强烈建议翻墙软件的开发者们彻底废除不具备校验的加密构造。仅仅有保密性是不够的。

  • 对于新开发的翻墙软件来说,这意味着根本不应考虑支持不具备校验的加密构造。
  • 对于现存的翻墙软件来说,这意味着开发者应该勇敢地移除所有与不具备教研的加密构造相关的代码,即使以不向下兼容为代价。

我们这看似大胆的建议实际上出于合理的原因。如我们在论文中所介绍的, 一些类型的GFW主动探测会利用Shadowsocks的stream ciphers的malleability。这已经不是第一次不具备校验的加密结构造成漏洞了。事实上,不具备校验的加密结构是许多Shadowsocks和其他翻墙软漏洞的根本来源。

早在2015年8月,BreakWa11发现了一个关于Shadowsocks的stream ciphers的漏洞。这个漏洞是由于缺乏数据完整性保护而造成的(英文总结)。 在2020年,类似的漏洞又被发现存在于V2Ray中(总结)。

当Shadowsocks开发者试图引入one time auth模式来缓解2015年的那个漏洞时,另一个因数据长度缺乏完整性保护的主动探测又被引入了英文总计)。

2020年2月,Zhejiang Peng发现了一个关于Shadowsocks stream ciphers的灾难性的漏洞,(英文总结)。 利用使用了stream cipher的Shadowsocks服务器作为decryption oracle,攻击者可以在不需要密码的情况下,完全解密Shadowsocks会话。

其实早在2017年2月,AEAD ciphers就已经成为了Shadowsocks协议的一部分。而校验问题也理应在那时就被解决了。但实际情况是,截止2021年,大量的服务器仍然因为使用被废弃的stream ciphers而存在着安全隐私漏洞,以及被准确识别的风险。

这样的现象表明,在实际操作中,许多的用户不能够正确的选择加密方式。这可能与使用过时的教程或一键脚本有关。 我们因此鼓励开发者从Shadowsocks各实现中彻底移除stream ciphers,帮助用户做出正确的选择。

使用同时基于nonces和时间的重放过滤器

我们建议翻墙软件的开发者们采用同时基于nonces和时间的重放过滤器。 因为采用基于时间的重放过滤器需要对Shadowsocks协议进行根本性地变动,我们建议开发者至少要此采用基于nonces的过滤器,并且做到:

  1. 要么建议用户在每次过滤器重置后修改密码;
  2. 要么开发一个机制,可以让重放过滤器在软件重启后依然记得之前的nonces。

这些建议是基于以下的研究发现和推论。 如论文的section 3.5所介绍的, GFW既可以在观察到一个合法连接的瞬间对其进行重放;也可以等待三周甚至更长时间后才重放。 因此,一个更加合理的主动探测模型应该允许审查者在任意时长后对合法连接进行重放。

这样的一个主动探测模型揭示了纯粹基于nonces的重放过滤所须要面对的不对称性。GFW仅用少量资源就可以记录一些合法的连接,并且在经过任意的时长后再重放它们;但与此同时,Shadowsocks需要大量的资源和相对复杂的实现来永久性地记住所有的合法链接,直至密码被更换。 注意,Shadowsocks服务器必须在重启后还记住这些nonces;否则重放过滤器不会过滤基于重启前的合法连接的重放。

幸运的是,这个不公平的局面可以通过同时引入基于时间的重放过滤机制来扭转:服务器只需要处理并验证时间戳未过期的连接,就像VMess服务器那样。 如此一来服务器就不需要永久性地记住所有合法连接中的nonces。

我们还想强调,对于那些仅仅短暂暴露变化的监听端口的翻墙服务器,重放过滤仍是必要的。因为GFW可以瞬时重放合法连接中的第一个数据包,而这时暴露的监听端口因为未完成数据传输,依然是开启的。

让服务器的反应保持一致

我们建议开发者们确保翻墙服务器在正常运行时,和遇到不合法的连接时都反应一致。理想情况下,可以按照Frolov et al.的建议,让服务器遇到错误连接时“read forever”。 这是因为,审查者会故意触发协议的边边角角等特殊情况,来识别服务器指纹。

除了我们在Shadowsocks-libev和OutlineVPN中发现的服务器反应指纹,Frolov et al.还展示了包括Shadowsocks-python和OutlineVPN在内的多种翻墙软件可以通过关闭连接时的TCP flags和连接时长来识别。studentmain报告,直至2020年12月, 许多的Shadowsocks实现仍存在着我们在Shadowsocks-libev和Outline中发现的问题。

Frolov et al.建议代理服务器在遇到错误连接时不要立即关闭连接,而是“read forever”。这样做不但避免泄漏超时值,而且使得服务器发送与正常连接关闭时相同的TCP flags来关闭错误连接。

进一步说,“reading forever”本身不会带来更加独特的指纹。因为Frolov et al.发现互联网上大量的服务器都会有无限超时值(“infinite timeout”)的特征。David Fifield调查显示,许多流行的翻墙软件已经采取了“read forever”策略。这些软件包括OSSH,obfs4,Outline和Lampshade。

强制采用强密码

Len et al. 于2020年展示了针对Shadowsocks服务器的partitioning oracle攻击。利用在Shadowsocks中使用的non-committing AEAD,攻击者可以更高效地猜出密码。我们因此建议开发者强制采用强密码。一种可能的实现方式是要求用户密码的熵高于一定值。

主动探测你的实现

如果你是一个不同于Shadowsocks-libev和Outline的翻墙软件开发者或贡献者,我们鼓励你检查同样的漏洞是否也存在于你的Shadowsocks实现中。我们开源了我们在论文Section 5.1中用到的prober 模拟器

鸣谢

我们想要感谢来自Jigsaw的Vinicius Fortuna,来自APNIC的Robert Mitchell和Dan Fidler,以及来自Qv2ray的DuckSoft和Student Main对本文提供的反馈。

联系

这篇报告首发于GFW Report。我们还在APNIC blog,net4people以及ntc.party同步更新了博文。

我们鼓励您或公开地或私下地分享您的评论或疑问。我们私下的联系方式可见GFW Report的页脚。

Android逆向-获取APP签名

Android逆向-获取APP签名

本文转自Taardisaa 并作补充

很久以前开的blog,关于如何获取APP签名。不知道为啥要写这个了。

Android逆向-APP签名

生成JKS签名

Android studio 如何生成jks签名文件 - 简书 (jianshu.com)

打开AndroidStudio

1
Build-->Generate Signed APK-->APK

然后Key store path选择Create New

然后设置好存储路径,密码也设置一下(偷懒写个123456)

Key的别名就叫key,密码一样简单。

然后剩下的Certificate全填taardisCountry Code填11451。

反正创建成功后,就在选定路径下出现了jks密钥文件。

APK签名

将APK魔改,重新打包后,需要重新签名。

参考:Android之通过 apksigner 对 apk 进行 手动签名_恋恋西风的博客-CSDN博客

1
apksigner.bat sign --verbose --ks D:\Android\Keystore\taardis.jks --v1-signing-enabled false --v2-signing-enabled true --ks-key-alias key --ks-pass pass:123456 --key-pass pass:123456 --out D:\Android\Frida\gadget\bs.apk D:\Android\Frida\gadget\b.apk

成功后提示:

1
Signed

获取APK签名

首先APK解包:

1
apktool d <apk>

然后在 META-INF 文件夹拿到 CERT.RSA 文件。之后:

1
keytool -printcert -file CERT.RSA

不过Keytool似乎是Java的工具,不管了现在用不上。

JEB/JADX

这种反编译器也能直接看到APK的签名信息。

MT APP签名检查及绕过

L-JINBIN/ApkSignatureKillerEx: 新版MT去签及对抗 (github.com)

从“去除签名验证”说起 - 腾讯云开发者社区-腾讯云 (tencent.com)

过签名校验(2) – MT 的 IO 重定向实践 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

MT提供的签名绕过方式能够实现对API和APK方式的绕过。但是对于SVC的则无能为力。

1
2
3
4
String signatureExpected = "3bf8931788824c6a1f2c6f6ff80f6b21";
String signatureFromAPI = md5(signatureFromAPI());
String signatureFromAPK = md5(signatureFromAPK());
String signatureFromSVC = md5(signatureFromSVC());

API检测

PackageManager直接获得签名。

1
2
3
4
5
6
7
8
9
private byte[] signatureFromAPI() {
try {
@SuppressLint("PackageManagerGetSignatures")
PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
return info.signatures[0].toByteArray();
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}

APK检测

找到APP私有文件夹下的base.apk,然后得到签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private byte[] signatureFromAPK() {
try (ZipFile zipFile = new ZipFile(getPackageResourcePath())) {
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (entry.getName().matches("(META-INF/.*)\\.(RSA|DSA|EC)")) {
InputStream is = zipFile.getInputStream(entry);
CertificateFactory certFactory = CertificateFactory.getInstance("X509");
X509Certificate x509Cert = (X509Certificate) certFactory.generateCertificate(is);
return x509Cert.getEncoded();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

SVC检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private byte[] signatureFromSVC() {
try (ParcelFileDescriptor fd = ParcelFileDescriptor.adoptFd(openAt(getPackageResourcePath()));
ZipInputStream zis = new ZipInputStream(new FileInputStream(fd.getFileDescriptor()))) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (entry.getName().matches("(META-INF/.*)\\.(RSA|DSA|EC)")) {
CertificateFactory certFactory = CertificateFactory.getInstance("X509");
X509Certificate x509Cert = (X509Certificate) certFactory.generateCertificate(zis);
return x509Cert.getEncoded();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}

绕过

Java层的东西不多

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private static void killOpen(String packageName) {
try {
// Native层Hook
System.loadLibrary("SignatureKiller");
} catch (Throwable e) {
System.err.println("Load SignatureKiller library failed");
return;
}
// 读取/proc/self/maps读取APP路径
String apkPath = getApkPath(packageName);
if (apkPath == null) {
System.err.println("Get apk path failed");
return;
}
// 读取自身APK文件(私有目录下的base.apk)
File apkFile = new File(apkPath);
// 在APP私有目录下创建origin.apk文件
File repFile = new File(getDataFile(packageName), "origin.apk");
try (ZipFile zipFile = new ZipFile(apkFile)) {
// 将APK中的origin.apk给提取出来(origin.apk是MT去签是生成的,是初始没有被去签的APK)
String name = "assets/SignatureKiller/origin.apk";
ZipEntry entry = zipFile.getEntry(name);
if (entry == null) {
System.err.println("Entry not found: " + name);
return;
}
// 读取出来
if (!repFile.exists() || repFile.length() != entry.getSize()) {
try (InputStream is = zipFile.getInputStream(entry); OutputStream os = new FileOutputStream(repFile)) {
byte[] buf = new byte[102400];
int len;
while ((len = is.read(buf)) != -1) {
os.write(buf, 0, len);
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
// 传入底层so Hook
hookApkPath(apkFile.getAbsolutePath(), repFile.getAbsolutePath());
}

然后看Native层,实际上是XHook,用于替换libc函数。

1
mt_jni.c

实际上就做了个字符串替换,有意将原本要打开的APK替换成origin.apk

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const char *apkPath__;
const char *repPath__;

int (*old_open)(const char *, int, mode_t);
static int openImpl(const char *pathname, int flags, mode_t mode) {
//XH_LOG_ERROR("open: %s", pathname);
if (strcmp(pathname, apkPath__) == 0){
//XH_LOG_ERROR("replace -> %s", repPath__);
return old_open(repPath__, flags, mode);
}
return old_open(pathname, flags, mode);
}

JNIEXPORT void JNICALL
Java_bin_mt_signature_KillerApplication_hookApkPath(JNIEnv *env, __attribute__((unused)) jclass clazz, jstring apkPath, jstring repPath) {
apkPath__ = (*env)->GetStringUTFChars(env, apkPath, 0);
repPath__ = (*env)->GetStringUTFChars(env, repPath, 0);

xhook_register(".*\\.so$", "openat64", openat64Impl, (void **) &old_openat64);
xhook_register(".*\\.so$", "openat", openatImpl, (void **) &old_openat);
xhook_register(".*\\.so$", "open64", open64Impl, (void **) &old_open64);
xhook_register(".*\\.so$", "open", openImpl, (void **) &old_open);

xhook_refresh(0);
}

通过Hook open函数,可以把基于APK读取的签名方式给绕过。

下面提供一个绕过基于PackageManager的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
private static void killPM(String packageName, String signatureData) {
// 构造一个假的签名
Signature fakeSignature = new Signature(Base64.decode(signatureData, Base64.DEFAULT));
Parcelable.Creator<PackageInfo> originalCreator = PackageInfo.CREATOR;
Parcelable.Creator<PackageInfo> creator = new Parcelable.Creator<PackageInfo>() {
@Override
public PackageInfo createFromParcel(Parcel source) {
PackageInfo packageInfo = originalCreator.createFromParcel(source);
if (packageInfo.packageName.equals(packageName)) { //
if (packageInfo.signatures != null && packageInfo.signatures.length > 0) {
packageInfo.signatures[0] = fakeSignature; // 将虚假的签名放入packageInfo,取代原来的
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (packageInfo.signingInfo != null) {
Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners();
if (signaturesArray != null && signaturesArray.length > 0) {
signaturesArray[0] = fakeSignature;
}
}
}
}
return packageInfo;
}

@Override
public PackageInfo[] newArray(int size) {
return originalCreator.newArray(size);
}
};
try {
// 用假的creator替换原来的PackageInfo.CREATOR
findField(PackageInfo.class, "CREATOR").set(null, creator);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// 解除某些Android系统API的使用限制?
HiddenApiBypass.addHiddenApiExemptions("Landroid/os/Parcel;", "Landroid/content/pm", "Landroid/app");
}
try {
// 清空签名缓存
Object cache = findField(PackageManager.class, "sPackageInfoCache").get(null);
// noinspection ConstantConditions
cache.getClass().getMethod("clear").invoke(cache);
} catch (Throwable ignored) {
}
try {
// 清空签名缓存
Map<?, ?> mCreators = (Map<?, ?>) findField(Parcel.class, "mCreators").get(null);
// noinspection ConstantConditions
mCreators.clear();
} catch (Throwable ignored) {
}
try {
// 清空签名缓存
Map<?, ?> sPairedCreators = (Map<?, ?>) findField(Parcel.class, "sPairedCreators").get(null);
// noinspection ConstantConditions
sPairedCreators.clear();
} catch (Throwable ignored) {
}
}

参考

Java Keytool 介绍 - 且行且码 - 博客园 (cnblogs.com)

获取Android应用签名的几种方式 - 简书 (jianshu.com)

Android studio 如何生成jks签名文件 - 简书 (jianshu.com)

apktool重新打包时报错_apktool 忽略错误信息__y4nnl2的博客-CSDN博客

Android之通过 apksigner 对 apk 进行 手动签名_恋恋西风的博客-CSDN博客

apksigner | Android 开发者 | Android Developers (google.cn)

APP 测试 - LSPosed 绕过 SSL 证书抓包

APP 测试 - LSPosed 绕过 SSL 证书抓包

本文转自LuckySec 并作补充

前言

前面介绍了通过《VirtualXposed 绕过 SSL 证书抓包》,这个方法有些局限性,比如不能在电脑上的《夜神模拟器》、《雷电模拟器》运行,需要一部测试手机等因素,对于部分人来说可能不太方便,还是更喜欢都在电脑上完成这系列操作,可以尝试用 LSPosed 绕过 SSL 证书抓包。

0x01 工具简介

LSPosed 是一个基于 Riru/Zygisk 的 ART Hook 框架,该框架利用 LSPlant 挂钩框架提供与 OG Xposed 一致的 API, 支持 Android 8.1 ~ 13。

Xposed 是一个模块框架,可以在不接触任何 APK 的情况下改变系统和应用程序的行为。利用 Xposed 的 TrustMeAlready 模块插件,可以防止软件检测抓包,绕过大部分 ssl-pinning,保证 APP 抓包的可续性能。

0x02 下载安装

特别提示:在安装之前需要注意以下几点:

0x03 抓包教程

注意:如果用雷电模拟器请使用 9.0.19 或之前的版本,避免不必要的问题发生。

以夜神模拟器为例,添加并运行一个 Android 9 的虚拟机。

image

在模拟器设置里将虚拟机设置为网络桥接模式、开启 ROOT(默认开启),设置好后重启虚拟机。

image

在夜神模拟器虚拟机里安装 Magisk.apkMagisk Terminal Emulato.apkapp-debug.apk(安装成功不显示在主界面)、LSPosed-manager.apk

image

打开 Magisk Terminal Emulator.apk,按照如下步骤操作:输入 m 按回车 > 再输入 y 按回车 > 超级用户授权允许 > 再输入 1 按回车 > 输入 a 按回车 > 再输入 1 按回车 > 完毕。

image

上述步骤完成后,重启模拟器,打开 Magisk.apk 可以发现 Magisk 安装成功。

image

打开 Magisk.apk > 点击右上角齿轮按钮 > 界面往下滑动,找到 Zygisk 选项打开并重启模拟器虚拟机。

image

接着将 LSPosed-v1.8.6-6712-zygisk-release.zip 复制到模拟器文件夹里面。打开 Magisk.apk > 底部模块选项 > 从本地安装 > 选择模拟器文件夹内的 LSPosed-v1.8.6-6712-zygisk-release.zip 卡刷包。

image

重启模拟器虚拟机后,打开 LSPosed-manager.apk,可以发现 LSPosed 安装成功了。

image

然后在夜神模拟器虚拟机里安装 TrustMeAlready-v1.11.apk,安装这个 apk 主界面图标可能会卡在安装的动画,不必在意,忽略即可。

image

接着打开 LSPosed-manager.apk 的底部模块选项,点击 TrustMeAlready,启动模块,选择要测试的 APP。

image

使用 BurpSuite 工具开启代理抓包,设置监听地址为同一局域网 IP 地址,端口自定义,不与电脑其他端口冲突使用即可。

image

在夜神模拟器手机系统设置中将 WiFi 的代理设置为 BurpSuite 监听器的地址。

image

最后,打开要测试的 APP,刷新功能页面,在 BurpSuite 中即可看到抓取的 HTTP/HTTPS 网络数据包。

image

参考文章

How to install Xposed/EdXposed/LSPosed + Magisk with Genymotion Desktop?

How to install Xposed/EdXposed/LSPosed + Magisk with Genymotion Desktop?

本文转自Genymotion Help Center 并作补充

Warning

GENYMOBILE assumes no liability whatsoever resulting from the download, install and use of Xposed, EdXposed, LSPosed and Magisk. Use at your own risk.

Note

Because Xposed and EdXposed are no longer maintained, we strongly recommend not using them anymore.

Android 5.0 - 7.1

Prerequisites

  • Xposed framework
  • Xposed installer

Installation

  1. Drag’n drop the Xposed framework zip file (xposed-vXX-sdkXX-x86.zip) to your virtual device display to flash the device.
  2. Drag’n drop Xposed Installer APK (XposedInstaller_*.apk). This should install and launch Xposed Installer application. At this stage, it will display that the Xposed framework is installed but disabled:

image

  1. Reboot the device with adb reboot command. Do not reboot from *Xposed Installer* as this will freeze the device.

  2. Launch Xposed installer. It should display “Xposed Framework version XX is active”:

image

Android 8.0

Xposed only works with Android 5.0 to 7.1. For Android 8.0, you need to use Magisk + Edxposed instead.

Prerequisites

Installation

Step 1: Install Magisk

  1. Drag’n Drop Magisk Manager apk: Magisk-v23.0.apk. Magisk Manager will install and open. Close it for now.
  2. Drag’n Drop Magisk_rebuilt_1c8ebfac_x86.zip and flash it.
  3. When flashing is complete, reboot the device.
  4. Launch Magisk Manager. It will request ROOT access, select “Remember choice forever” and click Allow:

image

It is possible that the popup opens in the background and is covered by Magisk Manager main window. If so press back to access the popup and allow ROOT:

image

  1. You will then be prompted with an update to apply, accept it:

image

  1. The device will reboot one more time. Launch Magisk Manager again, you should now be informed that Magisk is now installed in 1c8ebfac(23015) version:

image

Step 2: Install Riru

Important

Do not install the Riru version available in the Magisk Manager app. Use the old Riru v25 version provided in this article (see prerequisite).

  1. Drag’n drop the Riru archive onto the instance display: riru-v25.4.4-release.zip. Do not flash it! The archive must be installed from Magisk Manager.
  2. Launch Magisk Manager app and click on the last icon in the bottom toolbar to go to the module section:

image

  1. Click “install from storage”:

image

  1. Go to the Download folder from the menu:

image

  1. Select the Riru archive, riru-v25.4.4-release.zip
  2. Reboot the device

Riru version 25 should now be present in the installed modules list in Magisk Manager:

image

Important

Make sure NOT to update to Riru v26 as it does not work with EdXposed right now.

Step 3: Install EdXposed

  1. You can install EdXposed framework from Magisk Manager. Go to Magisk Manager module manager:

image

  1. Open the search widget and input “Edxposed”. Select Riru - EdXposed:

image

  1. Install the module:

image

  1. Reboot the device.

  2. Drag’n drop Edxposed manager APK file (EdXposedManager-4.5.7-45700-org.meowcat.edxposed.manager-release.apk) to the device display.

  3. Reboot the device

Edxposed manager should launch and display “Edxposed framework is active”:

image

Android 8.1 and above

Edxposed and Xposed are no longer maintained and there are no builds for Android 12 and above.

Instead, we will use LSPosed and Magisk for Android 8.1 and above.

Prerequisite

Installation

Step 1: Install Magisk

  1. Drag’n Drop Magisk Manager apk: Magisk-v23.0.apk. Magisk Manager will install and open. Close it for now.
  2. Drag’n Drop the flashable archive:
    • Magisk_rebuilt_1c8ebfac_x86.zip if you use Android 8.1 - 10
    • Magisk_rebuilt_1c8ebfac_x86_64.zip if you use Android 11 and above on a PC or an old Mac Intel
    • Magisk_rebuilt_1c8ebfac_arm64.zip if you use a mac M1/M2
  3. When flashing is complete, reboot the device.
  4. Launch Magisk Manager. It will request ROOT access, select “Remember choice forever” and click Allow:

image

It is possible that the popup opens in the background and is covered by Magisk Manager main window. If so press back to access the popup and allow ROOT:

image

  1. You will then be prompted with an update to apply, accept it:

image

  1. The device will reboot one more time. Launch Magisk Manager again, you should now be informed that Magisk is now installed in 1c8ebfac(23015) version:

image

Step 2: Install Riru

Important

Do not install the Riru version available in the Magisk Manager app. Use the old Riru v25 version provided in this article (see prerequisite).

  1. Drag’n drop the Riru archive onto the instance display: riru-v25.4.4-release.zip. The flashing process will fail, but this is normal. The archive must be installed from Magisk Manager.
  2. Launch Magisk Manager app and click on the last icon in the bottom toolbar to go to the module section:

image

  1. Click “install from storage”:

image

  1. Go to the Download folder from the menu:

image

  1. Select the Riru archive, riru-v25.4.4-release.zip

  2. Reboot the device

Riru version 25 should now be present in the installed modules list in Magisk Manager:

image

Step 3: Install Riru - LSPosed

  1. Drag and drop the LSPosed archive to the device. Do not flash it!
  2. Open Magisk Manager, go to the plugin manager page:

image

  1. click Install from storage and select LSPosed-v1.8.6-6712-riru-release.zip:

image

  1. Reboot the device when prompted

  2. Drag’n Drop LSPosed_manager.apk, LSPosed manager should open:

image