手机安装 app ,设置代理,然后开始抓包。

发现数据没法解密,查看请求的 url 是 http://lbs.jt.sh.cn:8082/app/rls/monitor,使用 jadx 反编译 app 后搜索这个 url(提示:可以只搜索 url 中一部分,因为请求的 url 可能时好几部分拼接而成的),这里搜索 rls/monitor

点进去,然后在 右键 ---> 查找用例

再点进去

127 行是 添加 post data,和上面抓包结果可以对应上,所以这部分代码就是需要分析的代码。

查看 com.shjt.map.data.rline.Response,可以看到 Protoc.Response response = Protoc.Response.parseFrom(Native.decode2(bytes));

在查看 decode2 函数,可以看到是 native 类型的函数,是在 so 库中

解压 apk 文件,找到 so 库文件 libnative.so ,使用 ida pro 打开,然后搜索 java_ 开头的函数

点进去,然后按 F5 查看伪代码:

protobuf 语法中文翻译:https://colobu.com/2017/03/16/Protobuf3-language-guide/

Protobuf 正向流程

Protobuf 进阶——使用 Python 操作 Protobuf:https://blog.csdn.net/a464057216/article/details/54932719

proto.exe 编译命令,自动生成 python 程序:protoc --python_out=. addressbook.proto
编译 addressbook.proto 文件,生成 addressbook_pb2.py
利用 proto.exe 反解数据 protoc.exe --decode_raw < D:\a.bin

protoc 命令帮助:

protoc -help
Usage: protoc [OPTION] PROTO_FILES
Parse PROTO_FILES and generate output based on the options given:-IPATH, --proto_path=PATH   Specify the directory in which to search forimports.  May be specified multiple times;directories will be searched in order.  If notgiven, the current working directory is used.If not found in any of the these directories,the --descriptor_set_in descriptors will bechecked for required proto file.--version                   显示版本号-h, --help                  帮助信息--encode=MESSAGE_TYPE       从标准输入读取文本格式信息,然后从标准输出中输出二进制数据,需要指定 PROTO_FILES--deterministic_output      When using --encode, ensure map fields aredeterministically ordered. Note that this orderis not canonical, and changes across builds orreleases of protoc.--decode=MESSAGE_TYPE       从标准输入中读取2进制数据,然后以文本方式输出到标准输出,需要指定 PROTO_FILES--decode_raw                从标准输入中读取任意的protocol数据,然后以 tag/value的格式输出到标准输出,不需要指定 PROTO_FILES --descriptor_set_in=FILES   Specifies a delimited list of FILESeach containing a FileDescriptorSet (aprotocol buffer defined in descriptor.proto).The FileDescriptor for each of the PROTO_FILESprovided will be loaded from theseFileDescriptorSets. If a FileDescriptorappears multiple times, the first occurrencewill be used.-oFILE,                     Writes a FileDescriptorSet (a protocol buffer,--descriptor_set_out=FILE defined in descriptor.proto) containing all ofthe input files to FILE.--include_imports           When using --descriptor_set_out, also includeall dependencies of the input files in theset, so that the set is self-contained.--include_source_info       When using --descriptor_set_out, do not stripSourceCodeInfo from the FileDescriptorProto.This results in vastly larger descriptors thatinclude information about the originallocation of each decl in the source file aswell as surrounding comments.--dependency_out=FILE       Write a dependency output file in the formatexpected by make. This writes the transitiveset of input file paths to FILE--error_format=FORMAT       Set the format in which to print errors.FORMAT may be 'gcc' (the default) or 'msvs'(Microsoft Visual Studio format).--fatal_warnings            Make warnings be fatal (similar to -Werr ingcc). This flag will make protoc returnwith a non-zero exit code if any warningsare generated.--print_free_field_numbers  Print the free field numbers of the messagesdefined in the given proto files. Groups sharethe same field number space with the parentmessage. Extension ranges are counted asoccupied fields numbers.--plugin=EXECUTABLE         Specifies a plugin executable to use.Normally, protoc searches the PATH forplugins, but you may specify additionalexecutables not in the path using this flag.Additionally, EXECUTABLE may be of the formNAME=PATH, in which case the given plugin nameis mapped to the given executable even ifthe executable's own name differs.--cpp_out=OUT_DIR           Generate C++ header and source.--csharp_out=OUT_DIR        Generate C# source file.--java_out=OUT_DIR          Generate Java source file.--js_out=OUT_DIR            Generate JavaScript source.--kotlin_out=OUT_DIR        Generate Kotlin file.--objc_out=OUT_DIR          Generate Objective-C header and source.--php_out=OUT_DIR           Generate PHP source file.--python_out=OUT_DIR        Generate Python source file.--ruby_out=OUT_DIR          Generate Ruby source file.@<filename>                 Read options and filenames from file. If arelative file path is specified, the filewill be searched in the working directory.The --proto_path option will not affect howthis argument file is searched. Content ofthe file will be expanded in the position of@<filename> as in the argument list. Notethat shell expansion is not applied to thecontent of the file (i.e., you cannot usequotes, wildcards, escapes, commands, etc.).Each line corresponds to a single argument,even if it contains spaces.

注意:window Termimal 只能执行 cmd 命令,没法执行 linux 命令,cmder ( https://cmder.net/ ) 即可以执行 cmd 命令,也可以执行 linux 的一些命令,安装 cmder 然后执行反解数据

示例 protobuf 二进制数据:https://api.bilibili.com/x/v2/dm/web/seg.so?type=1&oid=168855206&pid=98919207&segment_index=1

点击后会下载一个 seg.so 的文件,然后执行反解命令:protoc.exe --decode_raw < "seg.so"

注意:因为没有 proto 文件,所以反解数据后,值是对的,但是没有 key,

反解 Protobuf 方法

方法一:还原 .proto 文件:

  • 1.利用 protoc.exe 反解析 protobuf 数据
  • 2.根据反解析出来的数据,还原出 .proto 文件
  • 3.用 protoc.exe 编译 .proto 文件,生成 py 程序
  • 4.用 py 程序可以轻松序列化和反序列化

方法二:利用 blackboxprotobuf 库直接操作 protobuf 数据,不需要还原 .proto 文件

# -*- coding: utf-8 -*-
# @Author  : 佛祖保佑, 永无 bug
# @Date    :
# @File    : temp.py
# @Software: PyCharm
# @description : XXXimport blackboxprotobufdef main():seg_so = Nonewith open('d:/seg.so', 'rb') as f:seg_so = f.read()msg, typ = blackboxprotobuf.protobuf_to_json(seg_so, message_type=None)print(msg)print(typ)if __name__ == '__main__':main()pass

加解密相关知识:

hook加密类:
各加密类的用法,key iv 明文 密文等是如何获取的,再hook对应的类和方法
AES https://www.cnblogs.com/widgetbox/p/11611201.html
RSA https://blog.csdn.net/qq_22075041/article/details/80698665
DES https://www.jianshu.com/p/bf6b4afaf41e
MD5 SHA等摘要算法 https://blog.csdn.net/baidu_34045013/article/details/80687557
HMAC摘要算法 https://blog.csdn.net/cdzwm/article/details/6973345

android的rsa加密填充方式是RSA时,是NoPadind RSA/ECB/NoPadding,
而标准jdk里填充是RSA时,是指PKCS1填充,RSA/ECB/PKCS1Padding,要注意
RSA加密科普 https://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html 
RSA加密科普 https://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
RSA密钥长度关系 https://cloud.tencent.com/developer/article/1199963
python rsa加密库 https://pycryptodome.readthedocs.io/en/latest/src/examples.html#generate-an-rsa-key
公私钥ASN.1结构 https://blog.csdn.net/wzj_whut/article/details/86477568
ASN.1、PKCS、PEM间的关系 https://blog.csdn.net/qq_39385118/article/details/107510032

AES 加密:一种对称加密,加密和解密时需要:密匙(key)iv加密模式 三个参数,加密时明文需要先做对齐处理,kv 和 iv 有长度规定(AES-128、AES-192和AES-256),明文长度要为16的倍数,否则要给明文后面加0补齐长度。

可以看到

  • 函数 j_aes_key_setup 用来构造 aes
  • 函数 j_aes_encrypt_cbc 用来解密

所以需要 hook 这两个函数

首先分析 j_aes_key_setup 这个函数,一直追进去,然后找到 export 的函数名,

可以看到函数名为 _Z13aes_key_setupPKhPji,hook 的时候需要 hook 这个函数名,同理可以找到 j_aes_encrypt_cbc hook 时 export 的函数名为 _Z15aes_encrypt_cbcPKhjPhPKjiS0_

frida hook js 代码如下:

Interceptor 使用方法文档:https://frida.re/docs/javascript-api/#interceptor

function printstack() {console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
}function hook_so() {console.log("\r");var Requester = Java.use('com.shjt.map.view.layout.realtime.LineLayout$Requester');Requester.request.implementation = function (p1) {this.request(p1)}var Req = Java.use('com.shjt.map.data.rline.Request');Req.toString.implementation = function (p1) {//send(this.mBuilder.build().toByteArray())var tmp = this.toString()send('11111111:' + tmp)return tmp}var ByteString = Java.use('com.android.okhttp.okio.ByteString')var Native = Java.use('com.shjt.map.tool.Native');Native.decode2.implementation = function (pp) {console.log("str :" + Java.use('java.lang.String').$new(pp));// 因为字节数组中有的转化成字符串也是不可见的,所以转成 16进制console.log("hex :" + ByteString.of(pp).hex());console.log("array :" + JSON.stringify(pp));return this.decode2(pp)}var soBaseAddress = Module.findBaseAddress("libnative.so");if (soBaseAddress) {// 查找 aes_key_setup 函数var aes_key_setup = Module.findExportByName("libnative.so", '_Z13aes_key_setupPKhPji');if (aes_key_setup) {console.log("找到 aes_key_setup")Interceptor.attach(aes_key_setup, {onEnter: function (args) {// console.log("aes_key_setup args 类型" + typeof args);// console.log("aes_key_setup args[0] " + typeof args[0].readByteArray(16) + " " + args[0].readByteArray(16));console.log("aes_key_setup args[0] ", args[0].readByteArray(16));console.log("aes_key_setup args[1] ", args[1].readByteArray(16));console.log("aes_key_setup args[2] ", args[2].toInt32());},onLeave: function (retval) {console.log("aes_key_setup 返回值:" + retval);}})} else {console.log("没找到 aes_key_setup")}// 查找 aes_encrypt_cbc 函数var aes_encrypt_cbc = Module.findExportByName("libnative.so", '_Z15aes_encrypt_cbcPKhjPhPKjiS0_');if (aes_encrypt_cbc) {console.log("找到 aes_encrypt_cbc")Interceptor.attach(aes_encrypt_cbc, {onEnter: function (args) {// console.log("aes_encrypt_cbc args 类型" + typeof args);// console.log("aes_encrypt_cbc args[0] " + typeof args[0].readByteArray(16) + " " + args[0].readByteArray(16));console.log("aes_encrypt_cbc args[0] ", args[0].readByteArray(16));console.log("aes_encrypt_cbc args[1] ", args[1].toInt32());console.log("aes_encrypt_cbc args[2] ", args[2].readByteArray(16));console.log("aes_encrypt_cbc args[3] ", args[3].readByteArray(16));console.log("aes_encrypt_cbc args[4] ", args[4].toInt32());console.log("aes_encrypt_cbc args[5] ", args[5].readByteArray(16));},onLeave: function (retval) {console.log("aes_encrypt_cbc 返回值:" + retval);}})} else {console.log("没找到 aes_encrypt_cbc")}}
}function main() {Java.perform(hook_so);
}setImmediate(main);

j_aes_key_setup((const unsigned __int8 *)v18, (unsigned int *)v15, 128) 函数有三个参数

  • 第一个参数 和 第二个参数都是指针,
  • 第三个参数 是一个 int 整数

j_aes_encrypt_cbc((const unsigned __int8 *)p, v11, v12, (const unsigned int *)v15, 128, (const unsigned __int8 *)v17); 函数有 6 个参数

  • 第一个参数:指针类型
  • 第二个参数:signed int 类型,是个整数
  • 第三个参数:指针类型
  • 第四个参数:指针类型
  • 第五个参数:int 类型,是个整数
  • 第六个参数:指针类型

frida 关于指针的操作:https://frida.re/docs/javascript-api/#nativepointer

frida js 中指针为什么用 readByteArray 来处理???

因为 AES 最终处理时,都是转换成 "字节数组" 来处理的,所以使用 readByteArray 来处理

为什么是读取 16 字节???

因为 AES 长度有规定 ( 128、192、256 ),可以看到 j_aes_key_setup 和 j_aes_encrypt_cbc 函数参数中都有 128,128bit / 8 = 16Byte,所有暂时可以假定是读取 16 字节。

要不就使用 ida pro 动态调试 so ,确定参数的值,这个属于另外技术范畴不在展开。。。

启动 frida-server

查看 apk 包名

运行 js 脚本进行 hook。执行命令:frida -U -F com.xxx.map -l .\hook_so.js --no-pause

可以看到 j_aes_key_setup((const unsigned __int8 *)v18, (unsigned int *)v15, 128) 函数有三个参数

  • v18 里面存的数据是  2f d3 02 8e 14 a4 5d 1f 8b 6e b0 b2 ad b7 ca af
  • v15 里面存的数据是  02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  • 第三个参数 是 128

j_aes_encrypt_cbc((const unsigned __int8 *)p, v11, v12, (const unsigned int *)v15, 128, (const unsigned __int8 *)v17); 函数有 6 个参数

  • p 参数值  0a 27 0a 18 2f 70 72 6f 74 6f 63 2e 52 65 71 75       key值
  • v11 参数值  48
  • v12 参数值  00 00 00 00 20 00 00 00 61 62 6c 65 2d 61 6e 79
  • v15 参数值  8e 02 d3 2f 1f 5d a4 14 b2 b0 6e 8b af ca b7 ad
  • 128
  • v17  75 4c 8f d5 84 fa cf 62 10 37 6b 2b 72 b0 63 e4              iv值

decode2 参数的 16进制数据:

现在 key、iv、16 进制数据都有了,可以尝试下解密:

Python 的 AES 加密与解密:https://www.cnblogs.com/niuu/p/10107212.html

AES 加密方式有五种:ECB, CBC, CTR, CFB, OFB

从安全性角度推荐 CBC 加密方法,下面是 CBC、ECB 两种加密方法的 python 实现

python 在 Windows下使用AES时要安装的是pycryptodome 模块   pip install pycryptodome 

# 先导入所需要的包
pip3 install Crypto
# 再安装pycrypto
pip3 install pycrypto
from Crypto.Cipher import AES  # 就成功了

python 在 Linux下使用AES时要安装的是pycrypto模块   pip install pycrypto 

  • CBC 加密需要一个十六位的 key (密钥) 和 一个十六位 iv(偏移量)
  • ECB 加密不需要 iv

AES CBC 加密的python实现

from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex# 如果text不足16位的倍数就用空格补足为16位
def add_to_16(text):if len(text.encode('utf-8')) % 16:add = 16 - (len(text.encode('utf-8')) % 16)else:add = 0text = text + ('\0' * add)return text.encode('utf-8')# 加密函数
def encrypt(text):key = '9999999999999999'.encode('utf-8')mode = AES.MODE_CBCiv = b'qqqqqqqqqqqqqqqq'text = add_to_16(text)cryptos = AES.new(key, mode, iv)cipher_text = cryptos.encrypt(text)# 因为AES加密后的字符串不一定是ascii字符集的,输出保存可能存在问题,所以这里转为16进制字符串return b2a_hex(cipher_text)# 解密后,去掉补足的空格用strip() 去掉
def decrypt(text):key = '9999999999999999'.encode('utf-8')iv = b'qqqqqqqqqqqqqqqq'mode = AES.MODE_CBCcryptos = AES.new(key, mode, iv)plain_text = cryptos.decrypt(a2b_hex(text))return bytes.decode(plain_text).rstrip('\0')if __name__ == '__main__':e = encrypt("hello world")  # 加密d = decrypt(e)  # 解密print("加密:", e)print("解密:", d)

AES ECB 加密的 python 实现

"""
ECB没有偏移量
"""
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hexdef add_to_16(text):if len(text.encode('utf-8')) % 16:add = 16 - (len(text.encode('utf-8')) % 16)else:add = 0text = text + ('\0' * add)return text.encode('utf-8')# 加密函数
def encrypt(text):key = '9999999999999999'.encode('utf-8')mode = AES.MODE_ECBtext = add_to_16(text)cryptos = AES.new(key, mode)cipher_text = cryptos.encrypt(text)return b2a_hex(cipher_text)# 解密后,去掉补足的空格用strip() 去掉
def decrypt(text):key = '9999999999999999'.encode('utf-8')mode = AES.MODE_ECBcryptor = AES.new(key, mode)plain_text = cryptor.decrypt(a2b_hex(text))return bytes.decode(plain_text).rstrip('\0')if __name__ == '__main__':e = encrypt("hello world")  # 加密d = decrypt(e)  # 解密print("加密:", e)print("解密:", d)

测试:

# -*- coding: utf-8 -*-
# @Author  : 佛祖保佑, 永无 bug
# @Date    :
# @File    : temp.py
# @Software: PyCharm
# @description : XXXimport base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import binasciidef main():# with open('D:\monitor.bin', 'rb') as f:#     c = f.read()key = '2fd3028e14a45d1f8b6eb0b2adb7caaf'iv = '754c8fd584facf6210376b2b72b063e4'aes = AES.new(binascii.a2b_hex(key), AES.MODE_CBC, binascii.a2b_hex(iv))hex_str = '8509209294464b3e84a122800c9419068fa44cb5827e4df3db42212a6054243a55793243b8d6479773d67ab74749611d987ab38c274bf716a2c66a8f233e9683667af7e84119d371b9926abc6f8294b266534ddb25f8ef015a16c60b770d3198'plaintext = aes.decrypt(binascii.a2b_hex(hex_str))print(plaintext)if __name__ == '__main__':main()pass

把上面 key、iv、hex 替换下,然后运行,程序不报错,说明 传递参数正确。

下面就是写代码,请求URL得到 respone 数据,然后解密数据得到 protobuf 格式的二进制数据,再解析 protobuf 数。。。略略略略略

frida hook so层、protobuf 数据解析相关推荐

  1. 逆向分析某App其Frida、Xposed、Root检测及protobuf数据解析

    1.前言 接到客户需求需要分析某海外语音社交App其房间数据和榜单数据,该app除了部分hook检测外,还有个protobuf挺有意思的,现将该项目的整个流程还原 需要的工具如下: 一个app:链接: ...

  2. frida hook so层方法大全

    文章转载,仅供学习,如有需要请支持原文章创作:https://kevinspider.github.io/fridahookso/ 1.感谢 2. frida env https://github.c ...

  3. 看雪3万课程笔记-FRIDA高级API实用方法:Frida Hook Native层

    一.使用工具: apk:攻防世界中CTF题,安装后图表显示黑客精神 xman.apk 工具:IDA,jadx 二.知识点: 1.Module.findBaseAddress("libmyjn ...

  4. 安卓逆向高阶之frida hook java层

    文章目录 1. 初次hook Java 层函数 2. hook 修改函数返回值 3. hook调用静态函数和非静态函数 4. hook设置成员变量 5. hook内部类 6. hook 动态加载dex ...

  5. frida hook java层常用模板

    文章转载于 安卓逆向菜鸟修炼记(微信公众号),个人感觉很实用,记录下来方便回顾,想看原文的请移步公众号. 1.JAVA层HOOK普通方法 import frida, sysjscode =" ...

  6. frida hook so层常用的方法

    1.toInt32() toInt32()是Frida中的一个函数,用于将传入的值转换为32位有符号整数.如果无法转换,则返回0.该函数需要传入一个参数,返回要转换的值. 适用于需要对整数数据类型进行 ...

  7. protobuf 数据解析的2种方法

    方法1: message person { required int32 age = 1; required int32 userid = 2; optional string name = 3; } ...

  8. Frida hook零基础教程

    1. 环境搭建 1. 准备frida服务端环境 Releases · frida/frida · GitHub 根据手机具体版本下载对应文件并解压,Android手机一般是arm64架构.将解压后的f ...

  9. 安卓逆向_24( 一 ) --- Hook 框架 frida( Hook Java层 和 so层) )

    From:Hook 神器家族的 Frida 工具使用详解:https://blog.csdn.net/FlyPigYe/article/details/90258758 详解 Hook 框架 frid ...

最新文章

  1. MFC中显示 .bmp格式的位图
  2. 用什么表示python异常_求大神给讲讲python的异常问题!以下是我的代码!没看出来有什么不同的呀?...
  3. linux ubuntu systemd-udevd进程 cpu占用过高 解决方法
  4. Agile Manifesto and principle
  5. maven手动添加第三方的jar包
  6. return view详解
  7. 33. 搜索旋转排序数组(013)二分查找+思路详解+来干了这杯代码!!!!!!
  8. C++ 数据指针(-)
  9. Hadoop ecosystem
  10. html新增的选择器,HTML5新增的选择器
  11. php 后端刷新页面
  12. Python 爬取 42 年高考数据,告诉你高考为什么这么难?
  13. php 钉钉 免登,免登的正确使用方式
  14. 走近汇编理解与内核编程
  15. linux程序执行时内存情况
  16. ttc文件linux安装,Linux当中如何安装字体?
  17. 苹果说全是假的,市面不存在原装贴膜
  18. Java实现猜拳小游戏
  19. objectbox No value passed for parameter ‘order‘
  20. 翻车!误删/usr/lib/引发的血案,从棺材边成功抢救的过程分享。

热门文章

  1. 2018最新Java面试78题:数据结构+网络+NoSQL+分布式架构
  2. 论文浅尝 - ICLR2020 | You Can Teach an Old Dog New Tricks!关于训练知识图谱嵌入
  3. 一文读懂最强中文NLP预训练模型ERNIE
  4. 【HTML/CSS】单位小结
  5. mysql数据库之忘记root密码
  6. 如何配置Xshell连接Ubuntu
  7. 解读OC中的load和initialize
  8. 匹夫细说C#:庖丁解牛迭代器,那些藏在幕后的秘密
  9. sql多行合成一行的解决方法
  10. JSTL标签库中fmt标签,日期,数字的格式化