freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

APP测试0基础——APP加解密对抗
掌控安全EDU 2025-02-26 11:32:00 88358
所属地 海外

前言

某APP,访问后为APP下载页面,从而下载到目标APP进行分析。

安装frida,上传frida-server

# frida-16.6.6-cp37-abi3-win_amd64
pip install frida  
pip install frida-tools
pip install Pyro4

下载frida-server,这里需要与你python安装的frida版本一致,并且需要查看模拟器架构

adb shell getprop ro.product.cpu.abi # 查看位数

img

上传frida-server(没连接设备记得先adb connect 连接设备,这里不赘述了)

adb push frida-server /data/local/tmp/

img

启动frida-server,(frida-server是我自己进行了一下重命名)

adb root
adb shell
cd /data/local/tmp
chmod +x frida-server
./frida-server

另起一个cmd,查看模拟器进程以验证frida是否连接成功:有进程相关信息即成功连接。

img

frida 实战测试

抓包发现数据包都是加密内容,这怎么搞,下播了!:

img

先查看正在运行的进程以及包名信息

frida-ps -Ua

img

这里我的程序对应的包名是:calm.pjtuep.zzdokmht

接下来编写hook脚本,输出APP在进行数据交互过程中,出现的字符串以及相关的调用栈信息:

Java.perform(function () {
var StringCls = Java.use("java.lang.String"); // 获取 Java String 类
// Hook getBytes() 方法(无参数版本)
StringCls.getBytes.overload().implementation = function () {
var result = this.getBytes(); // 调用原始 getBytes() 方法
// 过滤掉短字符串,只记录长度大于 16 的字符串
if (this.length() > 16) {  
// 获取调用堆栈信息
console.log("[*] Stack trace:==========>\n" +
Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Exception").$new()
)
);
console.log("[*] getBytes() called with ==============>: " + this); // 输出当前字符串
}
return result; // 返回原始结果,确保不影响正常执行
};
});

为什么要hook getBytes方法以及代码解释:

img

frida -U -f "calm.pjtuep.zzdokmht" -l hook_key.js # 使用-f参数会重新载入APP,加载hook脚本,退出可输入exit回车即可。

img

可以看到,在响应包中的加密数据,被hook脚本hook到,并打印出了调用栈信息,其中最为可疑也最为明显的文件是:ApiEncryptUtil.java

img

因此使用jadx-gui反编译apk,找到ApiEncryptUtil.java

img

代码分析:AES加解密,a为解密函数,b为加密函数

public static String a(String str) {
try {
byte[] decode = Base64.decode(str, 0); // 先 Base64 解码
byte[] bytes = "0nxG8fD2kqlrEv5M".getBytes(); // AES 密钥
byte[] bytes2 = "u0r3GcsdXsYmAfhT".getBytes(); // AES IV
SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, "AES"); // 生成密钥
IvParameterSpec ivParameterSpec = new IvParameterSpec(bytes2); // 生成 IV
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // 使用 AES-CBC
cipher.init(2, secretKeySpec, ivParameterSpec); // 2 = 解密模式
return new String(cipher.doFinal(decode), C.UTF8_NAME); // 解密并转换回字符串
} catch (Exception e2) {
e2.printStackTrace();
return "";
}
}
public static String b(String str) {
try {
byte[] bytes = str.getBytes(); // 获取字符串的字节数组
byte[] bytes2 = "0nxG8fD2kqlrEv5M".getBytes(C.UTF8_NAME); // AES 密钥
byte[] bytes3 = "u0r3GcsdXsYmAfhT".getBytes(C.UTF8_NAME); // AES IV
SecretKeySpec secretKeySpec = new SecretKeySpec(bytes2, "AES"); // 生成密钥
IvParameterSpec ivParameterSpec = new IvParameterSpec(bytes3); // 生成 IV
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // 使用 AES-CBC
cipher.init(1, secretKeySpec, ivParameterSpec); // 1 = 加密模式
return new String(Base64.encode(cipher.doFinal(bytes), 0)); // 加密后 Base64 编码
} catch (Exception e2) {
e2.printStackTrace();
return "";
}
}


编写hook脚本,尝试输出加/解密数据:

Java.perform(function(){
var targetClass =Java.use('c.h.a.m.r');   //包路径根据自己电脑反编译的结果填写
// 调用a解密函数,输出内容
targetClass.a.implementation=function(str){
console.log("解密前数据========>: "+ str + "\n\n\n");
var result =this.a(str);
console.log('解密后数据========>: '+ result + "\n\n\n");
return result;
}
// 调用b加密函数,输出内容
targetClass.b.implementation=function(str){
console.log("加密前数据=======>: "+ str + "\n\n\n");
var result =this.b(str);
console.log('加密后数据========>: '+ result + "\n\n\n");
return result;
}
});

img

img

img

img

整个数据传输过程:

img

这样,我们就可以看到解密和加密数据了,但是我们要在burp中测试的话,还是太麻烦了,因此还需要用到更简单的方法。

Burpy + firda 实现burp上自动加解密

接下来使用Burpy插件,实现burp上自动加解密,方便我们测试。

查看设备名,获取设备名方便脚本编写

我的mumu模拟器设备在127.0.0.1:16384上,因此是这个设备:Device(id=”127.0.0.1:16384”, name=”PGBM10”, type=’usb’),这个一会编写脚本用得上

>>> import frida
>>> frida.get_device_manager().enumerate_devices()
[Device(id="local", name="Local System", type='local'), Device(id="socket", name="Local Socket", type='remote'), Device(id="barebone", name="GDB Remote Stub", type='remote'), Device(id="127.0.0.1:16384", name="PGBM10", type='usb')]

img

设置端口转发

adb forward tcp:27043 tcp:27043
adb forward tcp:27042 tcp:27042
# 查看连接情况
adb forward --list
# 帮助
adb --help | findstr "forward"

img

进入模拟器启动frida-server

adb root
adb shell
ali:/data/local/tmp # ./frida-server

编辑rpc hook脚本,方便调用APP上的加解密方法进行加解密:

我的文件名为:decrypt1.js

Java.perform(function () {
var targetClass = Java.use('c.h.a.m.r');
rpc.exports = {
init: function () {
console.log("[Frida] rpc.exports 初始化成功!");
return "rpc.exports 已加载";
},
enc: function (str) {
try {
console.log("************ 开始加密 ***********");
console.log("加密前数据: " + str);
var result = targetClass.b(str);
console.log("加密后数据: " + result);
return result || ""; // **防止返回 null**
} catch (e) {
console.log("[Frida] enc 方法执行出错: " + e);
return ""; // **防止卡住**
}
},
dec: function (str) {
try {
console.log("************ 开始解密 ***********");
console.log("解密前数据: " + str);
var result = targetClass.a(str);
console.log("解密后数据: " + result);
return result || ""; // **防止返回 null**
} catch (e) {
console.log("[Frida] dec 方法执行出错: " + e);
return ""; // **防止卡住**
}
}
};
});

burpy.py脚本编写:

import frida
import time
from urllib.parse import unquote, parse_qs, quote
import json
class Burpy:
# 初始化,获取模拟器进程,启动和连接进程
def __init__(self):
device = self._get_android_usb_device()
pid = device.spawn("calm.pjtuep.zzdokmht") # 进程包名
self.session = device.attach(pid) # 附加到目标进程
device.resume(pid) # 让进程继续运行
self.rpc = self.load_rpc() # 加载 RPC 脚本
# 获取模拟器设备
def _get_android_usb_device(self):
for x in frida.get_device_manager().enumerate_devices():
if "PGBM10" in x.name: # 根据设备名,返回设备相关信息。
print(f"设备信息=====> {x}")
return x
# 加载远程调用rpc的hook脚本文件
def load_rpc(self):
# 这里打开的文件路径貌似只能写绝对路径,用相对路径会报错?
with open("D:\\secTools\\BurpSuite V2023.2.2\\BurpSuite V2023.2.2\\Burpy\\js\\decrypt1.js",encoding="utf-8", errors="ignore") as f:
script = self.session.create_script(f.read())
script.load()
self.rpc = script.exports_sync
time.sleep(3)   # 等待APP加载完全
self.rpc.init() # 检测rpc调用
return self.rpc
# 对body数据处理,只返回data字段的数据
def convert_to_json(self, body):
try:
if body.strip().startswith('{') and body.strip().endswith('}'):
json_body = json.loads(body)
if 'data' in json_body:
return json_body['data']
else:
parsed_data = parse_qs(body)
return parsed_data['data'][0]
except Exception as e:
print(f"转换失败:{str(e)}")
return None
# 重新构建请求和响应body,方便发包和查看响应
def rebuild_body(self, body, data):
try:
if body.strip().startswith('{') and body.strip().endswith('}'):
json_body = json.loads(body)
if 'data' in json_body:
json_body['data'] = data
# print(str(json_body).encode("gbk").decode("gbk"))
return str(json_body).encode("gbk").decode("gbk") # 在repeater中响应内容发生了中文乱码,不清楚为啥?
else:
parsed_data = parse_qs(body)
string_body = "timestamp=" + str(parsed_data['timestamp'][0]) + "&" + "data=" + data + "&" + "sign=" + str(parsed_data['sign'][0])
return string_body
except Exception as e:
print(f"转换失败:{str(e)}")
return None  
# 加密函数
def encrypt(self, header, body):
try:
process_data = self.convert_to_json(body)
enc_data = self.rpc.enc(process_data)
body = self.rebuild_body(body, enc_data)
except Exception as e:
print(f"无法找到 enc 方法:{str(e)}")
return header, body
# 解密函数
def decrypt(self, header, body):
try:
process_data = self.convert_to_json(body)
dec_data = self.rpc.dec(process_data)
body = self.rebuild_body(body, dec_data)
except Exception as e:
print(f"无法找到 dec 方法:{str(e)}")
return header,body

这里编写burpy.py脚本,要根据实际情况进行编写,进程包名和获取模拟器设备的代码要根据自己所查看的信息进行编写。而代码也是一样,因为目前抓的这个包中,发现请求包中还有一个timestamp、sign,而响应包是一个json格式数据,其中也还有其他参数,而分析发现请求和响应中都只对data字段进行了加密处理,因此在发送数据包和解密响应包的时候,需要对请求体和响应体做拼接参数处理和加解密处理,这样才能顺利完成一次自动加解密请求和响应,具体细节分析代码可得知。

img

设置burpy:

enable processor这个勾选上可以方便在Intruder爆破模块中使用。

img

填好python路径以及burpy.py路径后,在编写好burpy.py以及hook脚本,并确认无误后,就可以点击Start server按钮,这个时候模拟器就会自动打开APP,hook脚本以及python脚本也会开始运行,在控制台也能够看到一些输出(如果你写了输出日志的代码的话。)

此时在历史记录中,右键->扩展->Burpy->选择decrypt函数就会弹出解密后内容的窗口:

img

img

控制台也会输出内容,因为我写了对应代码展示其解密过程。

img

将数据包发送到repeater中,先右键将请求包内容解密,然后点击发送,即可实现自动加解密:

img

img

控制台日志:

img

改包后发送:

img

img

也是正常的。

然后如果你想调试你的代码,在改动了burpy.py后,可以点击Reload Script重新加载代码,此时会重新连接模拟器,重新打开APP。

一些奇怪的问题

在用Burpy插件的时候发现:一旦python脚本运行出错,burp就会卡死白屏,这个时候只能关闭burp,重新开一个,然后配置代理重来,感觉这一点有点麻烦,还有点击select file无反应,不知道是什么情况,路径什么的都是手动填的… …

img

响应内容在repeater中出现中文乱码,而用burpy插件打开的窗口下不会乱码 :(

img

img

一些概念解释

什么是frida?

Frida是一款基于Python + JavaScript 的hook框架,本质是一种动态插桩技术。可以用于Android、Windows、iOS等各大平台,其执行脚本基于Python或者Node.js写成,而注入代码用JavaScript写成,这么说太抽象了,简单理解就是有frida这么一个工具,它可以在APP(用APP举例了)执行的过程中,给这个APP注入一些JS代码,方便我们去调试查看APP内部的情况比如数据、调用的函数等等,或者修改一些APP运行过程中的参数,比如拦截APP中的金币值,改成我们想要的金币值等等。

img

什么是hook技术?

Hook 本质上是一种动态代码注入函数劫持技术,允许开发者或攻击者在程序运行时拦截、修改或替换函数的行为。例如:

  • 拦截系统 API 调用(如sendrecvopenread

  • 修改应用逻辑(如篡改游戏内的金币、血量)

  • 监视应用行为(如捕获键盘输入、获取应用内存数据)

实际上就有点像中间人攻击,比如在APP运行过程中,通过hook技术对一些事件进行监听/篡改,比如玩家A了一下野怪,野怪掉血,我们可以通过hook技术把这一事件钩出来,查看玩家血量/野怪血量/玩家这一刀多少攻击力等等(监视应用行为);并且我们可以通过hook技术,修改玩家的一刀的攻击力,实现一刀999等等(修改应用逻辑);并且可以查看玩家A出去这一刀具体调用了哪个函数方法(拦截/查看系统API调用)。

那么frida hook就很好理解了,就是用frida进行hook呗。

img

为什么要进行adb forward转发端口?

adb forward tcp:27042 tcp:27042和类似的命令用于将 Android/模拟器 设备上的端口(如 27042)映射到主机上的相应端口。这对于frida来说是必要的,因为 Frida 通过网络端口进行通信,尤其是当它在远程设备或模拟器上运行时。

解释:

adb forward命令:此命令将设备上的端口转发到主机上的端口。它创建了一条通过 ADB 连接的端口映射路径。

2704227043端口

  • frida-server会在 Android 设备上启动并监听特定的端口(通常是 27042),用于与主机上的frida客户端进行通信。

  • 端口27043也常用来进行附加连接,尤其是如果你在多个进程或服务之间进行交互时。

为什么需要转发端口?

frida-server通信:Frida 客户端需要与frida-server进行通信,通常会通过 TCP/IP 端口进行交互。adb forward命令通过将端口转发到主机上的相应端口,允许主机上的 Frida 客户端与 Android 设备上的frida-server进行通信。

远程通信:当frida-server在设备上运行时,它会监听设备上的端口(例如 27042)。adb forward允许你从主机访问该端口,就像你直接连接到设备一样。通过这种方式,你可以控制设备上的进程,进行 hook 操作,调试等。

端口的必要性

  • frida-server在 Android 设备上运行并监听 27042 端口。

  • frida客户端通过该端口与设备通信。如果没有端口转发,主机就无法访问设备上的frida-server,导致无法进行 hook 操作或与设备进行其他调试任务。

总结:转发端口是为了方便主机上的frida客户端 与 Android/模拟器上的frida-server frida服务端进行通信,方便我们对设备上的进程(如APP)进行调试/hook/连接。

参考

frida-server下载安装:https://blog.csdn.net/yongmonkey/article/details/124123736

frida:https://github.com/frida/frida/releases

https://cloud.tencent.com/developer/article/2348148

jadx-gui下载:https://github.com/skylot/jadx/releases

burpy下载:https://github.com/mr-m0nst3r/Burpy/releases

https://mp.weixin.qq.com/s/Fwq96_VKIduXAbrPp8kshw

https://github.com/hookmaster/frida-all-in-one

https://sunny250.github.io/2021/02/14/hook%E7%A5%9E%E5%99%A8FRIDA/

https://www.kancloud.cn/alex_wsc/android/506821

https://blog.csdn.net/weixin_39190897/article/details/115354102

本文作者:Track-我是大白

更多SRC实战内容、实战经验分享、渗透技巧、漏洞挖掘&漏洞证书获取技巧,关注我,订阅更多精彩内容~

# 漏洞 # 渗透测试 # 黑客 # web安全 # CTF
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 掌控安全EDU 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
安全平台
掌控安全EDU LV.6
这家伙太懒了,还未填写个人描述!
  • 71 文章数
  • 71 关注者
记录某SRC邀请处逻辑越权到组织管理员漏洞
2025-03-26
掌控安全学院“从零赚赏金黑客训练营第二期”开营啦
2025-03-25
(云安全)前端敏感信息泄露到接管整个云服务器
2025-03-12
文章目录