分享

APP测试0基础 - APP加解密对抗

 新用户0118F7lQ 2025-05-04

图片

本文由掌控安全学院 -  我是大白 投稿

Track安全社区投稿~  

千元稿费!还有保底奖励~(https://bbs.)

前言

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

安装frida,上传frida-server

# frida-16.6.6-cp37-abi3-win_amd64pip install frida pip install frida-toolspip install Pyro4

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

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

img

img

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

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

img

img

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

adb rootadb shellcd /data/local/tmpchmod +x frida-server./frida-server

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

图片

img

frida 实战测试

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

图片

img
先查看正在运行的进程以及包名信息
frida-ps -Ua
img
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
img
frida -U -f 'calm.pjtuep.zzdokmht' -l hook_key.js # 使用-f参数会重新载入APP,加载hook脚本,退出可输入exit回车即可。
img
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
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:27043adb forward tcp:27042 tcp:27042# 查看连接情况adb forward --list# 帮助adb --help | findstr 'forward'
img
img
进入模拟器启动frida-server
adb rootadb shellali:/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 fridaimport timefrom urllib.parse import unquote, parse_qs, quoteimport jsonclass 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
img
响应内容在repeater中出现中文乱码,而用burpy插件打开的窗口下不会乱码 :(
img
img

图片

img

一些概念解释

什么是frida?

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

什么是hook技术?

Hook 本质上是一种动态代码注入函数劫持技术,允许开发者或攻击者在程序运行时拦截、修改或替换函数的行为。例如:
拦截系统 API 调用(如 `send``recv``open``read`
修改应用逻辑(如篡改游戏内的金币、血量)
监视应用行为(如捕获键盘输入、获取应用内存数据)
实际上就有点像中间人攻击,比如在APP运行过程中,通过hook技术对一些事件进行监听/篡改,比如玩家A了一下野怪,野怪掉血,我们可以通过hook技术把这一事件钩出来,查看玩家血量/野怪血量/玩家这一刀多少攻击力等等(监视应用行为);并且我们可以通过hook技术,修改玩家的一刀的攻击力,实现一刀999等等(修改应用逻辑);并且可以查看玩家A出去这一刀具体调用了哪个函数方法(拦截/查看系统API调用)。
那么frida hook就很好理解了,就是用frida进行hook呗。
img
img

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

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

解释:

adb forward 命令:此命令将设备上的端口转发到主机上的端口。它创建了一条通过 ADB 连接的端口映射路径。
27042  27043 端口
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./2021/02/14/hook%E7%A5%9E%E5%99%A8FRIDA
https://www./alex_wsc/android/506821
https://blog.csdn.net/weixin_39190897/article/details/115354102

申明:本公众号所分享内容仅用于网络安全技术讨论,切勿用于违法途径,

所有渗透都需获取授权,违者后果自行承担,与本号及作者无关,请谨记守法.

图片

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多