如何使用Apk-Medit对APK进行内存搜索和数据修改

如何使用Apk-Medit对APK进行内存搜索和数据修改

本文转自 Alpha_h4ck 并作补充

写在前面的话

内存修改往往是在游戏中实现作弊的最简单方法,而且内存也是我们在安全测试过程中必须要去面对和检测的一个内容。虽然现在社区也已经有类似GameGuardian这种的工具了,但现在还没有针对非Root设备的类似工具。Apk-Medit这款工具是一个针对可调式APK的内存搜索和数据修改工具,该工具专为移动端游戏安全测试而设计,我们可以在非Root设备(无需NDK)中使用该工具。

工具安装

首先,我们需要访问该项目的【GitHub Releases页面】来下载该项目源码。下载完成之后,我们需要将代码拷贝至目标安卓设备的/data/local/tmp/目录下:

1
2
3
$ adb push medit /data/local/tmp/medit

medit: 1 file pushed. 29.0 MB/s (3135769 bytes in 0.103s)

代码构建

我们可以使用make命令来完成代码的构建,这里要求使用Go编译器。代码构建完成之后,使用adb连接设备,它将会把构建好的代码推送至目标Android设备的/data/local/tmp/目录下:

1
2
3
4
5
6
7
$ make

GOOS=linux GOARCH=arm64 GOARM=7 go build -o medit

/bin/sh -c "adb push medit /data/local/tmp/medit"

medit: 1 file pushed. 23.7 MB/s (3131205 bytes in 0.126s)

工具命令

搜索

在内存中搜索特定的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> find 999982

Search UTF-8 String...

Target Value: 999982([57 57 57 57 56 50])

Found: 0!

------------------------

Search Word...

parsing 999982: value out of range

------------------------

Search Double Word...

Target Value: 999982([46 66 15 0])

Found: 1!

Address: 0xe7021f70

我们还可以指定目标数据类型,比如说字符串、dword和qword等等:

1
2
3
4
5
6
7
8
9
> find dword 999996

Search Double Word...

Target Value: 999996([60 66 15 0])

Found: 1!

Address: 0xe7021f70

过滤

我们可以对搜索结果进行过滤,并匹配当前的搜索值:

1
2
3
4
5
6
7
8
9
> filter 993881

Check previous results of searching dword...

Target Value: 993881([89 42 15 0])

Found: 1!

Address: 0xe7021f70

数据修改

我们可以直接修改目标地址的数据值:

1
2
3
> patch 10

Successfully patched!

ps命令

寻找目标进程,如果只有一个的话,我们可以使用ps命令来自动指定:

1
2
3
4
5
> ps

Package: jp.aktsk.tap1000000, PID: 4398

Target PID has been set to 4398.

绑定进程

如果目标PID是通过ps命令设置的,我们就可以跟目标进程进行绑定,并通过ptrace来终止App内的所有进程:

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
> attach

Target PID: 4398

Attached TID: 4398

Attached TID: 4405

Attached TID: 4407

Attached TID: 4408

Attached TID: 4410

Attached TID: 4411

Attached TID: 4412

Attached TID: 4413

Attached TID: 4414

Attached TID: 4415

Attached TID: 4418

Attached TID: 4420

Attached TID: 4424

Attached TID: 4429

Attached TID: 4430

Attached TID: 4436

Attached TID: 4437

Attached TID: 4438

Attached TID: 4439

Attached TID: 4440

Attached TID: 4441

Attached TID: 4442

如果目标PID没有设置的话,我们就需要在命令行中专门指定了:

1
> attach <pid>

解绑进程

解绑已绑定的进程:

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
> detach

Detached TID: 4398

Detached TID: 4405

Detached TID: 4407

Detached TID: 4408

Detached TID: 4410

Detached TID: 4411

Detached TID: 4412

Detached TID: 4413

Detached TID: 4414

Detached TID: 4415

Detached TID: 4418

Detached TID: 4420

Detached TID: 4424

Detached TID: 4429

Detached TID: 4430

Detached TID: 4436

Detached TID: 4437

Detached TID: 4438

Detached TID: 4439

Detached TID: 4440

Detached TID: 4441

Detached TID: 4442

导出

显示内存导出数据:

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
> dump 0xf0aee000 0xf0aee300

Address range: 0xf0aee000 - 0xf0aee300

----------------------------------------------

00000000 34 32 20 61 6e 73 77 65 72 20 28 74 6f 20 6c 69 |42 answer (to li|

00000010 66 65 20 74 68 65 20 75 6e 69 76 65 72 73 65 20 |fe the universe |

00000020 65 74 63 7c 33 29 0a 33 31 34 20 70 69 0a 31 30 |etc|3).314 pi.10|

00000030 30 33 20 61 75 64 69 74 64 20 28 61 76 63 7c 33 |03 auditd (avc|3|

00000040 29 0a 31 30 30 34 20 63 68 61 74 74 79 20 28 64 |).1004 chatty (d|

00000050 72 6f 70 70 65 64 7c 33 29 0a 31 30 30 35 20 74 |ropped|3).1005 t|

00000060 61 67 5f 64 65 66 20 28 74 61 67 7c 31 29 2c 28 |ag_def (tag|1),(|

00000070 6e 61 6d 65 7c 33 29 2c 28 66 6f 72 6d 61 74 7c |name|3),(format||

00000080 33 29 0a 31 30 30 36 20 6c 69 62 6c 6f 67 20 28 |3).1006 liblog (|

00000090 64 72 6f 70 70 65 64 7c 31 29 0a 32 37 31 38 20 |dropped|1).2718 |

000000a0 65 0a 32 37 31 39 20 63 6f 6e 66 69 67 75 72 61 |e.2719 configura|

000000b0 74 69 6f 6e 5f 63 68 61 6e 67 65 64 20 28 63 6f |tion_changed (co|

000000c0 6e 66 69 67 20 6d 61 73 6b 7c 31 7c 35 29 0a 32 |nfig mask|1|5).2|

000000d0 37 32 30 20 73 79 6e 63 20 28 69 64 7c 33 29 2c |720 sync (id|3),|

000000e0 28 65 76 65 6e 74 7c 31 7c 35 29 2c 28 73 6f 75 |(event|1|5),(sou|

000000f0 72 63 65 7c 31 7c 35 29 2c 28 61 63 63 6f 75 6e |rce|1|5),(accoun|

退出

如需退出该工具,可以使用exit命令或按下Ctrl+D:

1
2
3
> exit

Bye!

工具测试

我们可以使用make命令来运行测试代码:

1
$ make test

工具使用Demo

image

image

【实用教程】记Simplehook+Lspatch+Shizuku 免root使用教程 番茄为例

【实用教程】记Simplehook+Lspatch+Shizuku 免root使用教程 番茄为例

本文转自 w1234567890 并作补充

发现论坛好多小伙伴有了simplehook配置却不知道怎么用,而且搜了一下论坛也没有免root用hook的简洁教程,今天就来以番茄为例子写一个教程,希望对大家有帮助。

注:此教程以番茄为例子,面向不方便真机root,虚拟机不会弄root,lsp的小伙伴,全程免root,可以在真机丝滑使用。 需要root的有大把教程,基本和免root大同小异,我结尾也附了一些截图就不详细说了,看不懂的搜一下。

目录


受害者

需要工具

教程开始

受害者:

需要工具:

simplehook配置 在下面

番茄(5.9.3.32)点我下载

Simplehook(normal版) 点我下载

Lspatch 点我下载

Shizuku 点我下载

教程开始:

注意,因为我看到一个番茄的hook配置,所以此教程才以番茄为例子,其他软件也可以,别较真

一.安装以上四个软件

二.Shizuku配置激活

1.下载安装

2.按官方教程配对启动(推荐用无线配对)

点我查看官方教程

3.成功后看最上面显示shizuku正在运行即可

4.点管理授权应用,勾选Lspatch

三.Lspatch配置教程

1.打开LSPatch,看到shizuku服务可用

2.点管理页点击+号,找到番茄安装包或者安装到手机直接搜索

3.然后选本地模式修补,签名选用lv2然后点击开始修补

ps:注意看提示打开番茄,需要先打开LSPatch,不然会闪退

4.如图最后一行路径(我记得刚开始有弹窗让你选修补路径)就是安装包所在,安装即可

5.重进LSPatch,再次进入管理页,就会看到番茄

6.长按番茄,然后选择模块作用域,把下载的simplehook勾选上就行了

四.Simplehook配置

1.点首页右下角加号

2.复制如下番茄(测试软件)配置(可能论坛问题,分享的代码配置有朋友说格式错误,所以用附件分享)

1
>[{"packageName":"com.dragon.read","appName":"番茄免费小说","versionName":"5.9.3.32","description":"","configs":"[{"mode":2,"className":"com.dragon.read.widget.m","methodName":"a","params":"*","enable":false},{"mode":2,"className":"com.dragon.read.reader.ad.readflow","methodName":"*","params":"*","enable":false},{"mode":1,"className":"com.dragon.read.user.model.VipInfoModel","methodName":"<init>","params":"java.lang.String,java.lang.String,java.lang.String,Z,Z,I,Z,com.dragon.read.rpc.model.VipSubType","resultValues":"1893211199s,1s,1s,true,true,,true,"}]","id":3}]

点我下载 提取码:pBVj

(注:实际上可以不管这些配置,框架自带的足够用了)

3.点导入配置

4.就会出现番茄图标

5.长按点启动即可

注:填写完simplehook配置之后,需要清除番茄数据,然后再重新打开。如果发现未生效,重复操作即可。

ps:番茄验签好像有问题,我昨天测试的时候一切正常,今天弄又提示不安全了,用虚拟机又可以了。不过没事免root教程就这样,大家可以换软件测试。

————

真机测试图(图一昨日成功图二今日失败):

虚拟机测试和配置(一切正常,会员功能可用):

注:此篇文章是原作者怕被查水表,所以把图片都隐去了,但是工具使用都是没问题的,确实是在应用未加固或加固能力不强时可用的

非root环境下frida持久化的两种方式及脚本

非root环境下frida持久化的两种方式及脚本

本文转自 八重嘤 并作补充

frida是一个非常好用的hook框架,但使用中有两个问题,一是非root手机使用挺麻烦的,二是frida相较于其他HOOK框架没那么持久。网上的持久化大多基于xposed、刷ROM或者是virtualapp,前面两个是比较重量级,不够轻便。虚拟化技术本身就自带风险,很容易被检测到。

在Android端,网上教程里大部分都是使用frida server来进行hook,其实还有一种使用方法为 frida gadget,此方法需要将frida-gadget.so注入到apk中,纯手动的话过于麻烦,所以这里实现两个脚本,分别用修改smali、修改so来注入目标。

我使用的frida-gadget版本为14.2.18。有其他版本的需求,需要替换tools下的so文件

方法一 调试apk中含有so

此方法相对简单。原理来自于古早的静态注入方式:Android平台感染ELF文件实现模块注入

而这种注入方式有工具可以快速实现:How to use frida on a non-rooted device

该方法优点在于可以让gadget是第一个启动的,缺点是没有so的apk不能用。

1.效果

首先运行注入脚本,获得注入且重签名后的apk。直接安装。

image

将frida_script.js push 到/data/local/tmp。frida_script.js为你的hook代码:

1
2
3
4
Java.perform(function () {
var Log = Java.use("android.util.Log");
Log.e("frida-OOOK", "Have fun!");
});//android 不要使用console.log

打开app即可看到效果,app每次启动都会成功的打印LOG:

image

不想使用持久化(本地js脚本),也可以通过电脑连接:

image

不使用持久化,就不必添加config文件,所以脚本执行不需要执行-persistence,执行下面的就可以:

1
python LIEFInjectFrida.py apkfile  outdir  libnative-lib.so  -apksign

2.代码

工具详细代码:https://github.com/nszdhd1/UtilScript/blob/main/LIEFInjectFrida.py

运行脚本记得安装lief(pip install lief)

其实关键代码就几行:

1
2
3
4
5
6
for soname in injectsolist: #遍历apk中指定SO有哪几种架构,并添加gadget.so为依赖库。
if soname.find("x86") != -1:
continue
so = lief.parse(os.getcwd()+"\\"+soname)
so.add_library("libfrida-gadget.so")
so.write(soname+"gadget.so")

方法二 apk中没有so

在实际情况下,并不是所有的apk都有so。没有so,方法一便没有用武之地了。

此方法呢,是通过修改smali,调用System.loadLibrary来加载so。该原理更简单,但是有一个弊端就是时机不够靠前,没有办法hook Activity 启动之前的代码。

手动修改太麻烦,还是写一个脚本自动化注入。

此方法优点是原理简单,缺点是脚本实现麻烦,容易写bug

1. 效果

首先运行注入脚本,获得注入且重签名后的apk。直接安装。

image

image

frida_script.js代码同上,同样也可以使用电脑连接:

image

2. 代码

工具详细代码:https://github.com/nszdhd1/UtilScript/blob/main/SmaliInjectFrida.py

关键代码:

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
def get_launchable_activity_aapt(self): #通过aapt找到apk的启动activity
aapt_path = os.path.join(self.toolPath, 'aapt.exe')
cmd = '%s dump badging "%s" ' % (aapt_path, self.apkpath)
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
out,err = p.communicate()
cmd_output = out.decode('utf-8').split('\r')
for line in cmd_output:
#正则,pattern.search正常,pattern.match就会有问题=-=懒得解决了
pattern = re.compile("launchable-activity: name='(\S+)'")
match = pattern.search(line)
if match:
# print match.group()[27:-1]
return match.group()[27:-1]

def injectso(self):
target_activity = self.get_launchable_activity_aapt()
for dex in self.dexList:
print(dex)
if self.dexDecompile(dex):
smali_path = os.path.join(self.decompileDir,target_activity.replace('.','\\'))+".smali"
print(smali_path)
with open(smali_path, 'r') as fp:
lines = fp.readlines()
has_clinit = False
start = 0
for i in range(len(lines)):
#start是获取smali中,可以添加代码的位置
if lines[i].find(".source") != -1:
start = i
#找到初始化代码
if lines[i].find(".method static constructor <clinit>()V") != -1:
if lines[i + 3].find(".line") != -1:
code_line = lines[i + 3][-3:]
lines.insert(i + 3, "%s%s\r" % (lines[i + 3][0:-3], str(int(code_line) - 2)))
print("%s%s" % (lines[i + 3][0:-3], str(int(code_line) - 2)))
#添加相关代码
lines.insert(i + 4, "const-string v0, \"frida-gadget\"\r")
lines.insert(i + 5, "invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\r")
has_clinit = True
break
#如果碰上本身没有clinit函数的apk,就需要自己添加
if not has_clinit:
lines.insert(start + 1, ".method static constructor <clinit>()V\r")
lines.insert(start + 2, ".registers 1\r")
lines.insert(start + 3, ".line 10\r")
lines.insert(start + 4, "const-string v0, \"frida-gadget\"\r")
lines.insert(start + 5, "invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V\r")
lines.insert(start + 6, "return-void\r")
lines.insert(start + 7, ".end method\r")

with open(smali_path, "w") as fp:
fp.writelines(lines)
self.dexCompile(dex)

Frida 持久化检测特征

我因为方便,将frida js 放在了/data/local/tmp下,如果直接放在app的沙箱下,这就是一个稳定的hook框架了。

既然做了持久化,就要从防御者角度看看哪些方面可以检测到应用被注入了。

首先,当然是内存中会有frida-gadget.so。但这个so可以被重命名(我可以命名为常见的模块,比如libBugly.so),所以检测/proc/pid/maps下是否有frida-gadget并不准确。因为frida有一个config文件,是持久化必须存在的。所以检测libs下是否有lib*.so和lib*.config.so是一种较为可行的方法。但是,如果你不使用持久化,或者去github上找到frida的源码修改gaget.vala(ps.这一点是合理的猜想,还未验证过),就可以让防御者检测不到。

1
2
3
4
5
6
7
8
9
10
11
#gaget.vala 代码片段
if ANDROID
if (!FileUtils.test (config_path, FileTest.EXISTS)) {
var ext_index = config_path.last_index_of_char ('.');
if (ext_index != -1) {
config_path = config_path[0:ext_index] + ".config.so";#修改这里,就可以检测不到。需要保持后缀不变(例如改成symbols.so)
} else {
config_path = config_path + ".config.so";
}
}
#endif

除去端口检测这种几乎没什么用的,还有一种比较可行的是内存扫描,扫描内存中是否有LIBFRIDA_GADGET关键词,具体实现网上有教程我就不介绍了。

灰产-如何破解请求验签(下)

灰产-如何破解请求验签(下)

image

写在前面

再来个前情提要:灰产-如何破解请求验签(上)

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

阿哈!Showmak……啊不,是新的 Sign 替换后不久就,两周左右就又被破解了呢——

“我们新的加签,可是从多个随机数池取值生成的 sign1,然后把sign1和多个参数混合在一起按时间戳不同取模排列生成的sign2,怎么可能……”

啊,虽然很想吐槽这反派一般被反击了感叹自己部署被打乱了的感觉,但是来不及为已经逝去的 sign2 感到悲伤了,接下来赶到战场的是……

是我这个苦力!

我打灰产?真的假的?要上吗?

image

总而言之

总而言之,在解释了旧 Sign 是怎么泄露的后,调查新 Sign 是怎么泄露的这个任务又又又落到我这里了= =,已经不想吐槽了

实现漏洞

“怎么实现的……额,实际上我们等于是接了一个生成库,你可以理解为类似SDK,里面有各种 Sign1 和 Sign2 的实现方式,什么移位啊取模啊都多久前的了,我都快忘了,你想看的话文档给你你可以看一下”

“有没有可能是这个实现的模块泄露了或者可以被调用啥的?”

“我觉得没有吧,感觉也有可能是 Hook 了,但就不知道是怎么弄的”

然后直到最后我除了在研发显示屏上看到了一眼实现文档,我再也没看到那实现方式一眼了

image

摸索开始了——

首先通过请求日志我发现,灰产用到的 sign1 有一批量重复的(说好的随机池随机取呢),但是生成的 sign2 是不重复的(sign1 只作为一个参数参与生成 sign2 的计算),我们并没有对 sign1 进行校验,所以导致这个 sign1 可以在请求中被替换,重复利用。

顺带一提的是,在其他应用里很早就出现这个情况了,不是我的话都还没发现……

image

定向Hook

sign1 的问题因为没有校验可以重复利用解决了,那 sign2 呢?

前端没有找到泄露 sign2 计算点,因为前端都可能没用新的 Sign;

应用端源码没看到 sign2 的计算方式,因为做了较好的混淆;

……

那选择就只有一个了!(折断拐杖)

对它使用 Hook 吧!

image

早在 MobSF移动安全测试框架|自动化静动态app分析框架 的使用中,我就接触了 Frida,why?因为一开始装不上,并且因为种种原因(MobSF 自带证书冲突等)执行动态分析的效果很差,虽然后来还是能试出来。

扯远了,回到现在,利用 Frida 对应用进行 Hook,我是在 Genymotion 上做的,当然,由于应用配置的关系,你可能需要先:

这里就要说了,为什么涉及到 Brida?

因为一开始是想用 Brida 直接 Hook 调用函数,传到 BurpSuite 直接修改请求,但是后来发现你根本不知道是哪个函数计算 sign2 ,别说改了,Hook 都不知道 Hook 谁,怎么办?

image

java.security.MessageDigest

还记得旧 Sign 最后的加密算法吗?SHA1-Hash

再看看新 Sign,尤其是 sign2 的样式,分明也是 SHA1!

嘛,灵光一现:不如利用 js 跟踪“java.security.MessageDigest”的调用,并输出加密入参与之后的值,js 参考:

image

入参值反解出来是一堆字节码,需要用 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
public class exchange {
public static void main(String[] args) {
byte[] b = { 108, ……, 102,
105, ……, 105,
111, ……, 107,
45, ……, 61,
50, ……, 52,
32, ……, 117,
115, ……, 118,
105, ……, 110,
118, ……, 73,
70, ……, 78,
74, ……, 85,
85, ……, 108,
107, ……, 107,
45, ……, 110,
61, ……, 48,
67, ……, 45,
116, ……, 112,
61, ……, 104,
73, ……, 57 };
String s = new String(b);
System.out.println(s);
}
}



Output:
……data={"userId":"……"}……sign1=GORNJPBOGXJXZQRRKYKYZNGZXZSYOUUSCMCM……timestamp=1714030269543……

sign1&timestamp

ok,入参也出来了,现在只剩一个问题:sign1 在入参中的位置。

其实到这里直接去遍历都可以,但是我还是采集了几份 sign1 在 sign2 生成位置与 timestamp 的关系进行比对,然后发现比对了个寂寞——差一位数的时间戳里 sign1 的插入位置可以相同的 = =:

image

所以不整了,收工!

image

写在后面

下篇也写完了,感觉好像也就这样,后续把加固啥的弄上议程吧,不然好像还有些硬编码问题也是头疼,但还要验证一下效果。

沉默,然后……事已至此,先吃(pao)饭(lu)吧

image

参考引用

灰产-如何破解请求验签(上)

MobSF移动安全测试框架|自动化静动态app分析框架

Genymotion_A11_libhoudini

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

brida从0配置

安卓逆向 - Frida Hook(抓包实践)

Frida java层自吐加密算法

灰产-如何破解请求验签(上)

灰产-如何破解请求验签(上)

image

前情提要

先来个前情提要:灰产-如何对APK修改后重签

在写那篇文章的我,是否会想到我竟然还有有关“灰产”的下文来写?而且就标题来说,还有分上下两篇!所以这4个月不到的时间我又经历了什么呢?

image

写在前面

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

让我们把时间拨回到年后。

年后回来一阵子,接到业务风控同学的反馈,我们有用户发现自己的账户被盗了,自己购买了不是自己的商品(不产生金额)并对这个商品统一匿名评价……

我第一反应卧槽哥们我们应用不是做了端校验,不同设备必须验证码登录,怎么连手机一起丢了才有后面能说漏洞导致账密泄漏的事?

image

后面通过网关日志、应用日志、接口日志等排查,发现这个用户的登录凭证(token)有从境外服务器请求服务的情况,可以基本确认是 token 的泄漏;

再和用户沟通以及进一步的深入排查,我们发现 token 的泄漏源于用户被钓鱼,这里细节不表;被社工得到的 token 会先从 ip 1 被收集并转发给 ip 2 进行购买请求与评论……

这里涉及到一个问题,我们的请求在各端做了验签,Sign 参数会根据不同端走不同的计算方式,想要更改 token 并发起购买请求,不重新计算 Sign是不可能通过校验的,只有两种可能:

  • 我们的 Sign 计算方式被破解
  • 我们的 Sign 计算方式可被任意调用

总而言之

总而言之,这个任务又落到我这里了= =

会议讨论对齐的时候,研发一致认为是在 H5 端的 Sign 计算方式太简单了,如何计算已经被破解,应该换上现在端上更安全的 Sign 计算就可以了!

直接破解一个计算方法理论上是不太容易的,我更在意的是是否是哪个地方泄漏了 Sign 的计算方法,或者在什么地方的计算代码可以被调用,直接入参入值然后得到结果。

但这里就要提到另一个事情了,研发不告诉你!

他们只表述这个好像比较简单就能破,但问他们怎么破的,不知道;问他们在哪里可能泄漏了,不知道:

“大概是从前端 JS 泄漏的?你看我们这里都做了混淆了,你看是不是就是这个位置,sign 关键词都命中了……”“可是为啥我调试/下断点没反应啊?”

image

后来嘛,在把钓鱼的口子堵了一下后,换上“更安全”的 Sign 计算方式后,喜闻乐见的钓鱼的口子也被绕过了,“更安全”的 Sign 计算也被破解了……

然后呢?

遭殃的还是我好吧,本来他们都认为可行的解决办法没用的时候,我就不得不花时间和精力来看看不在我 OKR 上的问题了

image

泄漏发现

算研发很配合安全工作,虽然告诉了我一个错误的泄漏位置,但起码提醒了我可能是 JS 就已经泄露了,没必要一来就去逆向找源码。

开始的思路还是去利用前端给到的 JS 复用:Burp Suite作为代理,复用目标站点的js代码处理指定的内容

后来发现 JSFlash 必须要能够指定调用代码函数才行,前端 JS 混淆了,指向不出来,直接分析不了

然后我就在前端登录页 Source - js 找到了 Sign 计算点,并且下断点步进调试看到了所有 Sign 的计算入参:

image

image

它端验证

在前端 JS 调试的过程中能看到有一个 secret 参数是不会暴露在 request 里的,其他端的如何获得?

Jadx 逆向反编译一下应用端,以 SIGN 为关键词,在源代码中发现了硬编码在其他端使用的 secret,以及使用的 Sign 最后加签算法:

image

image

写在后面

上篇就到这里,写的好像很简单,解过程也没遇到什么麻烦,但是还得说和大伙配合的还是不好,我还是认为不管是锻炼或者检验个人能力也好,工作任务忙不想接手深入这个事情也好,如果是以把事情完成的结论来说,相互配合是很重要的。

以旧 Sign 为例,大家说的好像每个人都知道这个是怎么生成的,但就是没人和你说,告诉的还是错误的位置,即使真的可能知道是怎么生成的不知道是在哪里泄露的,那信息汇通还是更重要的。研发完全可以告诉你“我猜是在前端 JS 泄漏的,位置是在这里,他的调用是这样的……”给你演示出来,如果真的在前端直接 Debug 下断点走一遍,就会知道一开始的位置告诉错了,并且一下就把泄漏点找出来了。

不会浪费双方的时间的。

image

参考引用

灰产-如何对APK修改后重签

Burp Suite作为代理,复用目标站点的js代码处理指定的内容

安卓逆向 - Frida Hook(抓包实践)

安卓逆向 - 基础入门教程

本文转自小馒头yy 并作补充

一、引言

上篇文章:安卓逆向 - 基础入门教程_小馒头yy的博客-CSDN博客 介绍了Frida的安装、基本使用,今天我们来看看Frida常用Hook和基于Frida抓包实践。

二、Frida常用 Hook脚本

1、hook java.net.URL

1
2
3
4
5
6
7
8
function hook1() {
var URL = Java.use('java.net.URL');
URL.$init.overload('java.lang.String').implementation = function (a) {
console.log('加密前:' + a)
showStacks()
this.$init(a)
}
}

2、hook okhttp3 HttpUrl

1
2
3
4
5
6
7
8
9
10
function hookOkhttp3() {
var Builder = Java.use('okhttp3.Request$Builder');
Builder.url.overload('okhttp3.HttpUrl').implementation = function (a) {
console.log('a: ' + a)
var res = this.url(a);
showStacks()
console.log("res: " + res)
return res;
}
}

3、hook okhttp3 addHeader

1
2
3
4
5
6
7
8
9
10
11
function hook() {
var Builder = Java.use("okhttp3.Request$Builder");
Builder["addHeader"].implementation = function (str, str2) {
console.log("key: " + str)
console.log("val: " + str2)
showStacks()
var result = this["addHeader"](str, str2);
console.log("result: " + result);
return result;
};
}

4、打印堆栈

1
2
3
4
5
function showStacks() {
Java.perform(function () {
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
});
}

5、hook Base64

1
2
3
4
5
6
7
8
9
function hookBase() {
// Base64
var Base64Class = Java.use("android.util.Base64");
Base64Class.encodeToString.overload("[B", "int").implementation = function (a, b) {
var rc = this.encodeToString(a, b);
console.log(">>> Base64 " + rc);
return rc;
}
}

6、hook HashMap

1
2
3
4
5
6
7
8
function hookMap() {
var Build = Java.use("java.util.HashMap");
Build["put"].implementation = function (key, val) {
console.log("key : " + key)
console.log("val : " + val)
return this.put(key, val)
}
}

三、某麦网抓包实践

本篇以某麦网帖子详情接口,演示如何基于Frida hook抓包

1、安装某麦网8...apk

2、搭建Frida hook 环境,注入 hook java.net.URL脚本

3、点进帖子详情打印出如下堆栈,我们可以根据打印出的信息,跟栈分析该接口的请求头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
https://acs.m.taobao.com/gw/mtop.damai.wireless.discovery.detail.get/1.4/?source=10101&version=6000168&type=originaljson&data=%7B%22contentId%22%3A%2211088650%22%2C%22appType%22%3A%221%22%2C%22source%22%3A%2210101%22%2C%22osType%22%3A%222%22%2C%22pageSize%22%3A%2230%22%2C%22pageIndex%22%3A%221%22%2C%22version%22%3A%226000168%22%2C%22channel_from%22%3A%22damai_market%22%7D&appType=1&osType=2&channel_from=damai_market
java.lang.Exception
at java.net.URL.<init>(Native Method)
at tb.yy0.m(Taobao:1)
at anet.channel.request.a.p(Taobao:2)
at anet.channel.session.TnetSpdySession.w(Taobao:18)
at anetwork.channel.unified.NetworkTask.sendRequest(Taobao:6)
at anetwork.channel.unified.NetworkTask.run(Taobao:44)
at anetwork.channel.unified.UnifiedRequestTask$a.proceed(Taobao:15)
at com.taobao.orange.sync.NetworkInterceptor.intercept(Taobao:30)
at anetwork.channel.unified.UnifiedRequestTask$a.proceed(Taobao:7)
at anetwork.channel.unified.UnifiedRequestTask$3.run(Taobao:2)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:428)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
at java.lang.Thread.run(Thread.java:761)

4、使用Jadx打开某麦网apk

从at tb.yy0.m(Taobao:1)跟栈分析

image

根据调用栈往上走,定位到如下位置,注意这行代码:

1
ALog.f("awcn.TnetSpdySession", "", aVar.n(), "request headers", aVar.g());

image

代码注释很清楚了 request headers,我们跟进 hook aVar.g() 这个方法

1
2
3
public Map<String, String> g() {
return Collections.unmodifiableMap(this.f);
}

hook之,对象输出可以使用 JSONObject转一下

1
var JSONObject = Java.use("com.alibaba.fastjson.JSONObject");
1
2
3
4
5
6
7
8
9
function hook6() {
var JSONObject = Java.use("com.alibaba.fastjson.JSONObject");
var a = Java.use("anet.channel.request.a");
a["g"].implementation = function () {
var result = this["g"]();
console.log("result : " + JSONObject.toJSON(result).toString());
return result;
};
}

打印出如下内容:

image

请求 URL、 请求方法这边都写得很清楚啦。

image

收工!

Burpsuite插件-Brida

Burpsuite插件-Brida

本文转自sam.li 并作补充

通过一道CTF题目来认识一下Frida
objection
前面两篇通过对Frida的了解,以及利用objection来分析,这篇来了解一下分析后实际利用,以及通过实现插件自动化的方式来利用。

Brida介绍

https://github.com/federicodotta/Brida

Brida components

image

官方使用文档翻译

时间记录,20230725;

测试版本:0.5

虽然说官方文档是最新的,但是使用文档的截图依旧跟新版本的插件不一致,然后能够查到的资料也是比较久的内容,不过大体功能是差不多,但是还是自己操作一遍后记录一下相关问题;

这里只记录自己测试的环境,其他环境参考官方文档;

Android

  1. 安装frida和其server,并启动具体可以参考通过一道CTF题目来认识一下Frida
  2. 配置brida

在配置之前先简单了解一下brida的UI及其功能;

image

1 主面板

这里包含所有brida工具和配置

  1. Configurations
  2. JS Editor
  3. Hooks and functions
  4. Graphical analysis
  5. Graphical hooks
  6. Custom plugins
  7. Generate stubs
  8. Debug export

2 brida按钮面板

Brida按钮面板由三个不同的部分组成:

  • 顶部(前两行)是 Pyro4 服务器的状态(启动/停止)和应用程序的状态(hook状态)
  • (在两条黄线上方的按钮)在中间有一组固定在Brida所有选项卡上的按钮。这些按钮用于执行常规任务,如启动/停止 Pyro4 服务器、生成/附加/停止/分离应用程序(使用 frida-compile 编译)、重新加载 JS 文件、编译重新加载JS、分离所有hook、清除log等。
  • (黄线下)底部有一组按钮,取决于特定的Brida子选项卡

3 brida控制台

log输出

brida工具介绍

Configurations

配置界面

从上往下依次是

  1. Pyro服务状态
  2. 应用状态
  3. 是否使用虚拟的python环境
  4. python可以执行文件的路径
  5. Pyro服务地址(默认)
  6. Pyro端口(默认)
  7. frida-complile可以执行文件的路径
  8. 是否使用旧版本的frida-complile,尽量使用10以下的版本,一开始用默认版本会出现问题,hook上了,但是总有奇怪的报错
  9. 包含所有Brida JS文件的文件夹,第一次需要先生成一下默认的JS文件
  10. 需要hook的应用名/PID,如果为应用名,则在连接的时候是点按钮面板的Spawn application,如果为PID,则Attach;
  11. Frida连接方式
  12. 如果配置是远程连接frida,需要配置对应的地址和端口;

image

JS Editor

image

集成到Burp Suite中的JS编辑器,以便能够编辑Brida JS文件并直接从Burp Suite添加自定义hook和导出函数。编辑器具有JS语法突出显示。

Hooks and functions

image

此选项卡包括许多默认hooks和方法,可以通过按钮启用/执行。这些Frida脚本包括最新的Android和iOS平台的hook能力,以绕过和检查安全功能。

Graphical analysis

image

Graphical analysis

这里功能类似于objection的能力集成进来:

1
2
3
4
5
6
7
8
load tree = android hooking list classes //展示所有class 

搜索 = android hooking search methods [search_name] //在内存中所有已加载的方法中搜索包含特定关键词的方法
//但是这里Android的用起来会导致应用闪退,官方提了这个,只有iOS支持;

双击对应的class = android hooking watch class com.xxx.xxx //hook指定类, 会打印该类下的所以调用
右键Inspect = android hooking watch class_method com.xxx.xxx.methodName --dump-args --dump-backtrace --dump-return //hook指定类, 会打印该类下的所以调用
右键change return vule = android hooking set return_value com.xxx.xxx.methodName false //设置返回值

实际使用

参考:通过一道CTF题目来认识一下Frida

对函数进行打印,然后替换返回值,但是这里官方提供的返回值没有byte

image

Graphical hooks

image

管理之前hook的所有内容;

Custom plugins

image

强大的功能;

可以理解成自定义生成burpsuite插件,有四种可自定义插件类型;

  • IHttpListener:能让所有请求,通过正则匹配去替换/修改指定的内容,并让这个匹配到的内容通过frida构造的hook脚本处理(加解密、重新生成sign等)
  • IMessageEditorTab:将自定义选项卡添加到Burp Suite请求/响应窗格,以便能够使用Frida导出的函数解密/解码/处理请求/响应(或其中的一部分)(然后加密/编码/处理修改并替换原始请求/响应,如果有)
  • IContextMenu:将自定义上下文菜单选项添加到Burp Suite的右键菜单中,用于在请求和响应(或其中的一部分)上调用Frida导出的函数
  • JButton:添加调用/启用 Frida 导出函数的按钮

Generate stubs

image

如果内部自定义插件引擎无法解决复杂情况,则可以从外部Burp Suite插件使用Brida引擎。此选项卡生成 Java 和 Python 存根,可以复制并粘贴到外部插件中,以便允许 Burp Suite 和 Frida 使用 Brida 进行通信。

Debug export

image

此选项卡可用于在 Brida 插件中使用 Frida 导出的函数之前对其进行调试。为了使用 Brida 自定义插件(或使用 Brida 的外部插件),有必要将 Frida 代码放入一些由插件调用的 Frida 导出函数中。在此选项卡中,可以直接调用 Frida 导出,以便轻松调试。

使用问题记录

Windows环境

Spawn/Attach application时报错:entrypoint must be inside the project root
【移动安全】Frida + Burp -> Brida | APP加解密 | CN-SEC 中文网

把Frida的js文件放到burpsuite应用的目录下;

在Debug export中Run export是报错[frida.core.RPCException] unable to find method ‘test’
排查了很久,只找到了frida-compile版本过高这种情况,只能测试一下低版本是否会有问题,用npm在burpsuite应用的目录下执行npm install frida-compile@9.5.2,然后将安装的目录配置到frida-compile path中就能解决;

使用了低版本的frida-compile后报错java.io.IOException: Cannot run program

1
2
"C:\AAAA\agent\BurpSuiteCommunity\node_modules\.bin\frida-compile": 
CreateProcess error=193, %1 不是有效的 Win32 应用程序。

一开始配置没有带.cmd

image

image

image

UnicodeEncodeError: ‘gbk’ codec can’t encode

1
character '\u03f1' in position 49: illegal multibyte sequence**

刚好遇到一个反编译后用中文混淆的,这里用objection和python执行相关处理的时候就会报编码错误,在对应路径相关代码输出处加上utf-8编码输出就能解决;然后objection需要修改后重新编译;

image

源码解析

java调用python脚本

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# -*- coding: utf-8 -*-
import frida
import codecs
import Pyro4
import sys

#reload(sys)
#sys.setdefaultencoding('utf-8')

class Unbuffered(object):
def __init__(self, stream):
self.stream = stream
def write(self, data):
self.stream.write(data)
self.stream.flush()
def writelines(self, datas):
self.stream.writelines(datas)
self.stream.flush()
def __getattr__(self, attr):
return getattr(self.stream, attr)

@Pyro4.expose
class BridaServicePyro:
def __init__(self, daemon):
self.daemon = daemon

def attach_application(self,pid,frida_script,device,host_port_device_id):

self.frida_script = frida_script

if pid.isnumeric():
self.pid = int(pid)
else:
self.pid = pid

if device == 'remote':
self.device = frida.get_remote_device()
elif device == 'usb':
self.device = frida.get_usb_device()
elif device == 'local':
self.device = frida.get_local_device()
elif device == 'device':
self.device = frida.get_device(host_port_device_id)
else:
self.device = frida.get_device_manager().add_remote_device(host_port_device_id)

self.session = self.device.attach(self.pid)

with codecs.open(self.frida_script, 'r', 'utf-8') as f:
source = f.read()

self.script = self.session.create_script(source)
self.script.load()

return

def spawn_application(self,application_id,frida_script,device,host_port_device_id):

self.application_id = application_id
self.frida_script = frida_script

if device == 'remote':
self.device = frida.get_remote_device()
elif device == 'usb':
self.device = frida.get_usb_device()
elif device == 'local':
self.device = frida.get_local_device()
elif device == 'device':
self.device = frida.get_device(host_port_device_id)
else:
self.device = frida.get_device_manager().add_remote_device(host_port_device_id)

self.pid = self.device.spawn([self.application_id])

self.session = self.device.attach(self.pid)

with codecs.open(self.frida_script, 'r', 'utf-8') as f:
source = f.read()

self.script = self.session.create_script(source)
self.script.load()

return

def resume_application(self):

self.device.resume(self.pid)

return

def reload_script(self):

with codecs.open(self.frida_script, 'r', 'utf-8') as f:
source = f.read()

self.script = self.session.create_script(source)
self.script.load()

return

def disconnect_application(self):

self.device.kill(self.pid)
return

def detach_application(self):

self.session.detach()
return

def callexportfunction(self, methodName, args):
method_to_call = getattr(self.script.exports, methodName)

# Take the Java list passed as argument and create a new variable list of argument
# (necessary for bridge Python - Java, I think)
s = []
for i in args:
s.append(i)

return_value = method_to_call(*s)
return return_value

@Pyro4.oneway
def shutdown(self):
print('shutting down...')
self.daemon.shutdown()

# Disable python buffering (cause issues when communicating with Java...)
sys.stdout = Unbuffered(sys.stdout)
sys.stderr = Unbuffered(sys.stderr)

host = sys.argv[1]
port = int(sys.argv[2])
daemon = Pyro4.Daemon(host=host,port=port)

#daemon = Pyro4.Daemon(host='127.0.0.1',port=9999)
bs = BridaServicePyro(daemon)
uri = daemon.register(bs,objectId='BridaServicePyro')

print("Ready.")
daemon.requestLoop()

brida从0配置

brida从0配置

本文转自how=time 并作补充

前言

因为最近测的一个app,做了ssl pinning。查了一下资料,可以使用frida从内存中hook
住加密函数,用来解密传输信息。而brida是连接frida与burp的桥梁。使用了brida就可以正常使用burp抓取加密数据包。而网上关于brida的安装环境都不是最新的。brida原本默认是py2的,现在0.4版本已经支持py3的环境。接下来,我就带大家从0开始安装环境吧。

基础环境

  • 系统 win10
  • python 3.8.3
  • java 13
  • 夜神模拟器 android版本7.0
  • burp 2020.5 crack
  • node 14.4.0

基本上是一个全新的环境。adb,frida也都没有配置。问题不大。

设置与准备

1.brida下载安装

github上的release

此时两个status还未启动起来,那需要我们去配置frida的环境了。

或者burpsuite中BAPP store安装

2.adb设置

adb是连接模拟器和电脑的桥梁

adb的版本需要和nox的版本一致。我们可以直接使用everything去查找nox的adb,然后配置环境变量即可。

3.frida设置

frida分为server和client

客户端安装frida

这一步比较容易,pip安装即可

我们可以设置pip国内源来加快安装

1.进入c盘用户文件夹,新建pip的目录。pip目录下新建pip.ini的文件。

2.在pip.ini中添加下面代码:

1
2
3
4
5
6
[global]
timeout=6000
index-url=https://pypi.tuna.tsinghua.edu.cn/simple
trusted-host=pypi.tuna.tsinghua.edu.cn

# pip源同样适用python3和python2。(适用python2的firda下载包时可能会出现卡死,可以试试这个方法改善)
安装第三方库
1
2
3
pip install Pyro4
pip install frida-tools
npm install frida-compile #这个生成frida-conpile.cmd.burpsuite中需要填写frida compile path

服务端安装frida

查看模拟器cpu版本

1
getprop ro.product.cpu.abi

根据cpu版本选择frida-server

将下载好的文件解压并放入/data/local/tmp 模拟器中。在adb 进入模拟器,文件赋值777后启动server

1
2
3
4
5
adb push C:\Users\howti\Desktop\frida-server-12.9.7-android-x86 /data/local/tmp
adb shell
cd /data/local/tmp/
chmod 777 frida-server-12.9.7-android-x86
./frida-server-12.9.7-android-x86

android版本最好使用android7,不然会一些报错。

什么都不显示即可

将frida端口转发

1
2
adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

4.burp设置

设置代理

设置brida

  • Python binary path: 就填入python3的路径(使用brida0.4就可以支持py3)
  • Pyro host: 默认
  • Pyro port: 默认
  • frida-compile: (frida-compile的二进制文件的路径)之前npm安装的包,里面有frida-compile.cmd .使用everything搜索frida 然后填入.
  • Frida JS files folder: 包含带有所有Frida和Brida钩子和导出文件的Frida工具JavaScript文件的文件夹的路径。我们可以使用默认文件夹

运行brida

我之前的环境找不到了,我用自己本机的环境结个图吧

  • 获取 android包名

  • 启动server 和Spawn application

正常启动,全绿。
配置完成
这里给出一件启动server的脚本(图中用的startfridaservice.py)
python3 startfridaserver.py 即可

链接:https://pan.baidu.com/s/11IdhpG8I5vMb1q33-7C4qg
提取码:46c0

Frida java层自吐加密算法

Frida java层自吐加密算法

本文转自愧怍 并作补充

代码

针对 java 层加密算法,能 hook 到 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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
const config = {
showStacks: false,
showDivider: true,
}

Java.perform(function () {
// console.log('frida 已启动');
function showStacks(name = '') {
if (config.showStacks) {
console.log(Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Throwable').$new(name)))
}
}

function showDivider(name = '') {
if (config.showDivider) {
console.log(`==============================${name}==============================`)
}
}

function showArguments() {
console.log('arguments: ', ...arguments)
}

const ByteString = Java.use('com.android.okhttp.okio.ByteString')
const Encode = {
toBase64(tag, data) {
console.log(tag + ' Base64: ', ByteString.of(data).base64())
// console.log(tag + ' Base64: ', bytesToBase64(data));
},
toHex(tag, data) {
console.log(tag + ' Hex: ', ByteString.of(data).hex())
// console.log(tag + ' Hex: ', bytesToHex(data));
},
toUtf8(tag, data) {
console.log(tag + ' Utf8: ', ByteString.of(data).utf8())
// console.log(tag + ' Utf8: ', bytesToString(data));
},
toAll(tag, data) {
Encode.toUtf8(tag, data)
Encode.toHex(tag, data)
Encode.toBase64(tag, data)
},
toResult(tag, data) {
Encode.toHex(tag, data)
Encode.toBase64(tag, data)
},
}

const MessageDigest = Java.use('java.security.MessageDigest')
{
let overloads_update = MessageDigest.update.overloads
for (const overload of overloads_update) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
Encode.toAll(`${algorithm} update data`, arguments[0])
return this.update(...arguments)
}
}

let overloads_digest = MessageDigest.digest.overloads
for (const overload of overloads_digest) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
const result = this.digest(...arguments)
if (arguments.length === 1) {
Encode.toAll(`${algorithm} update data`, arguments[0])
} else if (arguments.length === 3) {
Encode.toAll(`${algorithm} update data`, arguments[0])
}

Encode.toResult(`${algorithm} digest result`, result)
return result
}
}
}

const Mac = Java.use('javax.crypto.Mac')
{
Mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) {
return this.init(key, AlgorithmParameterSpec)
}
Mac.init.overload('java.security.Key').implementation = function (key) {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
const keyBytes = key.getEncoded()
Encode.toAll(`${algorithm} init Key`, keyBytes)
return this.init(...arguments)
}

// let overloads_update = Mac.update.overloads;
// for (const overload of overloads_update) {
// overload.implementation = function () {
// const algorithm = this.getAlgorithm();
// showDivider(algorithm);
// showStacks(algorithm);
// Encode.toAll(`${algorithm} update data`, arguments[0]);
// return this.update(...arguments);
// };
// }

let overloads_doFinal = Mac.doFinal.overloads
for (const overload of overloads_doFinal) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
const result = this.doFinal(...arguments)
if (arguments.length === 1) {
Encode.toAll(`${algorithm} update data`, arguments[0])
} else if (arguments.length === 3) {
Encode.toAll(`${algorithm} update data`, arguments[0])
}

Encode.toResult(`${algorithm} doFinal result`, result)
return result
}
}
}

const Cipher = Java.use('javax.crypto.Cipher')
{
let overloads_init = Cipher.init.overloads
for (const overload of overloads_init) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)

if (arguments[0]) {
const mode = arguments[0]
console.log(`${algorithm} init mode`, mode)
}

if (arguments[1]) {
const className = JSON.stringify(arguments[1])
// 安卓10以上私钥是有可能输出不了的
if (className.includes('OpenSSLRSAPrivateKey')) {
// const keyBytes = arguments[1];
// console.log(`${algorithm} init key`, keyBytes);
} else {
const keyBytes = arguments[1].getEncoded()
Encode.toAll(`${algorithm} init key`, keyBytes)
}
}

if (arguments[2]) {
const className = JSON.stringify(arguments[2])
if (className.includes('javax.crypto.spec.IvParameterSpec')) {
const iv = Java.cast(arguments[2], Java.use('javax.crypto.spec.IvParameterSpec'))
const ivBytes = iv.getIV()
Encode.toAll(`${algorithm} init iv`, ivBytes)
} else if (className.includes('java.security.SecureRandom')) {
}
}

return this.init(...arguments)
}
}

// let overloads_update = Cipher.update.overloads;
// for (const overload of overloads_update) {
// overload.implementation = function () {
// const algorithm = this.getAlgorithm();
// showDivider(algorithm);
// showStacks(algorithm);
// Encode.toAll(`${algorithm} update data`, arguments[0]);
// return this.update(...arguments);
// };
// }

let overloads_doFinal = Cipher.doFinal.overloads
for (const overload of overloads_doFinal) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
const result = this.doFinal(...arguments)
if (arguments.length === 1) {
Encode.toAll(`${algorithm} update data`, arguments[0])
} else if (arguments.length === 3) {
Encode.toAll(`${algorithm} update data`, arguments[0])
}

Encode.toResult(`${algorithm} doFinal result`, result)
return result
}
}
}

const Signature = Java.use('java.security.Signature')
{
let overloads_update = Signature.update.overloads
for (const overload of overloads_update) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
Encode.toAll(`${algorithm} update data`, arguments[0])
return this.update(...arguments)
}
}

let overloads_sign = Signature.sign.overloads
for (const overload of overloads_sign) {
overload.implementation = function () {
const algorithm = this.getAlgorithm()
showDivider(algorithm)
showStacks(algorithm)
const result = this.sign()
Encode.toResult(`${algorithm} sign result`, result)
return this.sign(...arguments)
}
}
}
})