日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

frida hook so层、protobuf 数据解析

發布時間:2024/7/23 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 frida hook so层、protobuf 数据解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

手機安裝 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 数据解析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。