ProtoBuf逆向之某公交app

上一篇是web端的ProtoBuf逆向,这篇就找了个app的ProtoBuf看看练练手
目标app:aHR0cHM6Ly93d3cud2FuZG91amlhLmNvbS9hcHBzLzc0NTAxNjM=

抓包分析

随便搜索一个线路点进去,看到请求头和响应都是加密的

在这里插入图片描述

用jadx分析下请求头的加密参数request

在这里插入图片描述

记住这里其他两个函数,有"/protoc.Request.Sequence",还引用了proto的包

在这里插入图片描述

在这里插入图片描述

到这可以知道request的值是经过native函数加密后再由base64得到的。
用ida打开native so文件,定位encode2函数

在这里插入图片描述

可以看到是用了aes_cbc_128加密,那就是需要拿到密钥和iv
直接frida hook这两个函数

function main(){
    Java.perform(function(){
        var ByteString = Java.use("com.android.okhttp.okio.ByteString");
        var UtilsClass = Java.use("com.shjt.map.tool.Native");
        UtilsClass.encode2.implementation = function (a){
          console.log("入参: ", ByteString.of(a).hex());
          var value = this.encode2(a);
          console.log('结果: ', ByteString.of(value).hex());
          return value;    
        }
        inline_hook()
    })
}

function inline_hook() {
    var libnative_lib_addr = Module.findBaseAddress("libnative.so");
    if (libnative_lib_addr) {
        var aes_decrypt_cbc  = Module.findExportByName("libnative.so", "_Z15aes_encrypt_cbcPKhjPhPKjiS0_")
        var aes_key_setup = Module.getExportByName('libnative.so', '_Z13aes_key_setupPKhPji');
        var Native_encode2 = Module.getExportByName('libnative.so', 'Java_com_shjt_map_tool_Native_encode2');
        Interceptor.attach(aes_key_setup, {
            onEnter:function(args){
                console.log("================aes_key_setup=================")
                console.log('arg1:',args[0].readByteArray(16))
                console.log('arg2:',args[1].readByteArray(16))
                console.log('arg3:',args[2].toInt32())
            },
            onLeave:function(retval){
            }
        })

        Interceptor.attach(aes_decrypt_cbc, {
            onEnter: function (args) {
                console.log("================aes_decrypt_cbc=================")
                console.log('arg1:',args[0].readByteArray(16))
                console.log('arg2:',args[1].toInt32())
                console.log('arg3:',args[2].readByteArray(16))
                console.log('arg4:',args[3].readByteArray(16))
                console.log('arg5:',args[4].toInt32())
                console.log('arg6:',args[5].readByteArray(16))
            },
            onLeave: function (retval) {
                 console.log("retval is :", retval) 
            }
        })
    }
}

setImmediate(main);

结果

入参:  0a270a182f70726f746f632e526571756573742e53657175656e6365120b080112053930e8b7af1801
================aes_key_setup=================
arg1:            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  2f d3 02 8e 14 a4 5d 1f 8b 6e b0 b2 ad b7 ca af  /.....]..n......
arg2:            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  39 39 37 34 20 49 ed f1 ff ff ff ff 00 00 00 00  9974 I..........
arg3: 128
================aes_decrypt_cbc=================
arg1:            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  0a 27 0a 18 2f 70 72 6f 74 6f 63 2e 52 65 71 75  .'../protoc.Requ
arg2: 48
arg3:            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  00 6f 6d 2f 61 6e 64 72 6f 69 64 2f 6f 6b 68 74  .om/android/okht
arg4:            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  8e 02 d3 2f 1f 5d a4 14 b2 b0 6e 8b af ca b7 ad  .../.]....n.....
arg5: 128
arg6:            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  75 4c 8f d5 84 fa cf 62 10 37 6b 2b 72 b0 63 e4  uL.....b.7k+r.c.
retval is : 0x1
结果:  8d456e87c6801a2e18920732f80a1127e7a9548fd9f2f8b45edab8fd629e60bd804287b6fa4bb05fbc8b284ab0eb9783

明文,密文有了
iv和密钥就在这几个之间,两两组合试一试

2f d3 02 8e 14 a4 5d 1f 8b 6e b0 b2 ad b7 ca af
39 39 37 34 20 49 ed f1 ff ff ff ff 00 00 00 00
0a 27 0a 18 2f 70 72 6f 74 6f 63 2e 52 65 71 75
00 6f 6d 2f 61 6e 64 72 6f 69 64 2f 6f 6b 68 74
8e 02 d3 2f 1f 5d a4 14 b2 b0 6e 8b af ca b7 ad
75 4c 8f d5 84 fa cf 62 10 37 6b 2b 72 b0 63 e4

在这里插入图片描述

所以这个aes是标准的算法,没经过魔改的,再把加密后的结果base64一下就是请求的参数了,过程算法知道了,那参数来源呢,是怎么得到的

使用proto

上面说了用了proto的包,看里面的函数过程,明文是经过了proto协议的才到aes加密
那就0a270a182f70726f746f632e526571756573742e53657175656e6365120b080112053930e8b7af1801转成二进制写进文件,再用proto解码器解码看看

import binascii
# hex字符串转二进制
d = binascii.a2b_hex('0a270a182f70726f746f632e526571756573742e53657175656e6365120b080112053930e8b7af1801')
with open("req.bin", 'wb') as f:
    f.write(d)
protoc --decode_raw < req.bin

解码后得到

1 {
  1: "/protoc.Request.Sequence"
  2 {
    1: 1
    2: "90\350\267\257"
    3: 1
  }
}

果不其然,那接下来如何模拟发请求呢
经过上一篇后,现在很快就能编写proto文件了

syntax = "proto3";

message Msg22 {
  int32 field1 = 1;
  string field2 = 2;
  int32 field3 = 3;
}

message Msg11 {
  string field1 = 1;
  Msg22 msg22 = 2;
}

message SearchService{
  Msg11 msg11 = 1;
}

编译成python版本

protoc --python_out=. ./req.proto

目录下生成了req_pb2.py 拖入项目中,需要使用时就调用即可

import binascii
import req_pb2 as pb

# hex字符串转二进制
# d = binascii.a2b_hex('0a270a182f70726f746f632e526571756573742e53657175656e6365120b080112053930e8b7af1801')
# with open("req.bin", 'wb') as f:
#     f.write(d)

search_request = pb.SearchService.SearchRequest()  # 实例化对象
search_request.msg11.field1 = '/protoc.Request.Sequence'
search_request.msg11.msg22.field1 = 1
search_request.msg11.msg22.field2 = '90路'
search_request.msg11.msg22.field3 = 1
# 序列化请求数据
serialize_data = search_request.SerializeToString()
# print(serialize_data)
# 保存数据玮bin文件供后续对比使用
with open('my_req.bin', mode="wb") as f:
    f.write(serialize_data)

运行后生成my_req.bin文件,跟原来的对比一下

在这里插入图片描述

发送请求

import binascii
from urllib.parse import quote
import req_pb2 as pb
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64

def aes_encry(ori):
    key = '2fd3028e14a45d1f8b6eb0b2adb7caaf'
    iv = '754c8fd584facf6210376b2b72b063e4'
    aes = AES.new(binascii.a2b_hex(key), AES.MODE_CBC, binascii.a2b_hex(iv))
    return aes.encrypt(pad(ori, 16))

def aes_decry(ori):
    key = '2fd3028e14a45d1f8b6eb0b2adb7caaf'
    iv = '754c8fd584facf6210376b2b72b063e4'
    aes = AES.new(binascii.a2b_hex(key), AES.MODE_CBC, binascii.a2b_hex(iv))
    return unpad(aes.decrypt(ori), 16)

# hex字符串转二进制
# d = binascii.a2b_hex('0a270a182f70726f746f632e526571756573742e53657175656e6365120b080112053930e8b7af1801')
# with open("req.bin", 'wb') as f:
#     f.write(d)

search_request = pb.SearchService.SearchRequest()  # 实例化对象
search_request.msg1.field1 = '/protoc.Request.Sequence'
search_request.msg1.msg2.field1 = 1
search_request.msg1.msg2.field2 = '90路'
search_request.msg1.msg2.field3 = 1
# 序列化请求数据
serialize_data = search_request.SerializeToString()
# print(serialize_data)
# 保存数据玮bin文件供后续对比使用
# with open('my_req.bin', mode="wb") as f:
#     f.write(serialize_data)

# 对序列化后的数据aes加密
aes_data = aes_encry(serialize_data)
# b64 aes加密数据
b64_aes_data = base64.b64encode(aes_data)
# 请求体
post_data = 'request=' + quote(b64_aes_data.decode(), safe='')+'%0A'

header = {
    'Accept': 'application/json,application/xml,application/xhtml+xml,text/html;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh',
    'Connection': 'keep-alive',
    'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
    'User-Agent': 'Mozilla/5.0 (Linux; U; Android 6.0; zh-cn; Nexus 6P Build/MDA89D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
    'Host': 'lbs.jt.sh.cn:8082'
}
url = "http://lbs.jt.sh.cn:8082/app/rls/monitor"
response = requests.post(url, headers=header, data=post_data)
print(response.text)

运行后响应内容跟抓包的一样是加密的,用aes尝试解密

在这里插入图片描述

解完后很像protobuf格式,写进bin文件,再用proto解码看看

decry_data = aes_decry(response.content)
with open('resp.bin', mode="wb") as f:
    f.write(decry_data)
D:\pythonProject\xxxx>protoc --decode_raw < resp.bin
1 {
  1: "/protoc.Response.Dispatch"
  2 {
    1 {
      1: "90\350\267\257"
      2 {
        1 {
          1: "05:00"
          2: "23:41"
        }
        2: "\351\235\226\345\256\207\345\215\227\350\267\257\346\216\247\346\261\237\350\267\257"
        2: "\346\216\247\346\261\237\346\226\260\346\235\221"
        2: "\351\273\204\345\205\264\350\267\257\345\233\275\346\235\203\350\267\257"
        2: "\351\273\204\345\205\264\350\267\257\345\233\275\351\241\272\350\267\257"
        2: "\344\272\224\350\247\222\345\234\272(\347\277\224\346\256\267\350\267\257)"
        2: "\345\233\275\345\222\214\350\267\257\346\224\277\347\253\213\350\267\257"
        2: "\346\201\222\344\273\201\350\267\257\346\270\205\346\272\220\347\216\257\350\267\257"
        2: "\344\270\226\347\225\214\350\267\257\345\233\275\345\222\214\350\267\257"
        2: "\344\270\226\347\225\214\350\267\257\346\260\221\344\272\254\350\267\257"
        2: "\351\227\270\346\256\267\350\267\257\346\256\267\350\241\214\350\267\257"
        2: "\351\227\270\346\256\267\350\267\257\345\206\233\345\267\245\350\267\257"
        2: "\345\206\233\345\267\245\350\267\257\351\227\270\345\214\227\347\224\265\345\216\202"
        2: "\351\225\277\350\210\252\351\224\232\345\234\260"
        2: "\345\206\233\345\267\245\350\267\257\351\231\210\345\256\266\345\256\205"
        2: "\345\215\227\345\274\240\345\215\216\346\265\234(\351\200\270\344\273\231\350\267\257)"
        2: "\345\214\227\345\274\240\345\215\216\346\265\234(\351\200\270\344\273\231\350\267\257)"
        2: "\346\267\236\346\273\250\350\267\257\345\220\214\346\265\216\350\267\257"
        2: "\346\260\270\346\270\205\350\267\257\346\267\236\345\256\235\350\267\257"
        2: "\346\260\270\346\270\205\350\267\257\346\260\264\344\272\247\350\267\257"
        2: "\345\217\214\345\237\216\350\267\257\346\260\270\346\270\205\350\267\257"
        2: "\346\267\236\345\256\235\350\267\257\345\217\214\345\237\216\350\267\257"
        2: "\346\267\236\345\256\235\350\267\257\346\267\236\351\235\222\350\267\257"
      }
      2 {
        1 {
          1: "05:00"
          2: "23:10"
        }
        2: "\346\267\236\345\256\235\350\267\257\346\267\236\351\235\222\350\267\257"
        2: "\345\217\214\345\237\216\350\267\257\346\267\236\345\256\235\350\267\257"
        2: "\346\260\270\346\270\205\350\267\257\345\217\214\345\237\216\350\267\257"
        2: "\346\260\270\346\270\205\350\267\257\346\260\264\344\272\247\350\267\257"
        2: "\346\260\270\346\270\205\350\267\257\346\267\236\345\256\235\350\267\257"
        2: "\346\267\236\346\273\250\350\267\257\345\220\214\346\263\260\350\267\257"
        2: "\351\225\277\345\276\201\346\226\260\346\235\221"
        2: "\346\267\236\346\273\250\350\267\257\346\267\236\346\273\250\346\224\257\350\267\257"
        2: "\345\214\227\345\274\240\345\215\216\346\265\234(\351\200\270\344\273\231\350\267\257)"
        2: "\345\215\227\345\274\240\345\215\216\346\265\234(\351\200\270\344\273\231\350\267\257)"
        2: "\345\206\233\345\267\245\350\267\257\351\231\210\345\256\266\345\256\205"
        2: "\351\225\277\350\210\252\351\224\232\345\234\260"
        2: "\345\206\233\345\267\245\350\267\257\351\227\270\345\214\227\347\224\265\345\216\202"
        2: "\351\227\270\346\256\267\350\267\257\345\206\233\345\267\245\350\267\257"
        2: "\351\227\270\346\256\267\350\267\257\346\256\267\350\241\214\350\267\257"
        2: "\344\270\226\347\225\214\350\267\257\346\260\221\344\272\254\350\267\257"
        2: "\344\270\226\347\225\214\350\267\257\345\233\275\345\222\214\350\267\257"
        2: "\346\201\222\344\273\201\350\267\257\346\270\205\346\272\220\347\216\257\350\267\257"
        2: "\351\225\277\346\265\267\350\267\257\351\273\221\345\261\261\350\267\257"
        2: "\345\233\275\345\222\214\350\267\257\346\224\277\351\200\232\350\267\257"
        2: "\344\272\224\350\247\222\345\234\272(\347\277\224\346\256\267\350\267\257)"
        2: "\344\272\224\350\247\222\345\234\272(\351\273\204\345\205\264\350\267\257)"
        2: "\351\273\204\345\205\264\350\267\257\345\233\275\351\241\272\350\267\257"
        2: "\351\273\204\345\205\264\350\267\257\345\233\275\346\235\203\350\267\257"
        2: "\346\216\247\346\261\237\346\226\260\346\235\221"
        2: "\351\235\226\345\256\207\345\215\227\350\267\257\346\216\247\346\261\237\350\267\257"
      }
    }
    2 {
      1: "\346\262\252A-30515D"
      2: "15:26"
    }
    2 {
      1: "\346\262\252A-07660D"
      2: "15:33"
    }
    2 {
      1: "\346\262\252A-33990D"
      2: "15:41"
    }
  }
}

在这里插入图片描述

完整代码

import binascii
import json
from urllib.parse import quote
from google.protobuf.json_format import MessageToJson
import req_pb2 as pb
import resp_pb2 as pb2
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64

def aes_encry(ori):
    key = '2fd3028e14a45d1f8b6eb0b2adb7caaf'
    iv = '754c8fd584facf6210376b2b72b063e4'
    aes = AES.new(binascii.a2b_hex(key), AES.MODE_CBC, binascii.a2b_hex(iv))
    return aes.encrypt(pad(ori, 16))

def aes_decry(ori):
    key = '2fd3028e14a45d1f8b6eb0b2adb7caaf'
    iv = '754c8fd584facf6210376b2b72b063e4'
    aes = AES.new(binascii.a2b_hex(key), AES.MODE_CBC, binascii.a2b_hex(iv))
    return unpad(aes.decrypt(ori), 16)

# hex字符串转二进制
# d = binascii.a2b_hex('0a270a182f70726f746f632e526571756573742e53657175656e6365120b080112053930e8b7af1801')
# with open("req.bin", 'wb') as f:
#     f.write(d)

search_request = pb.SearchService()  # 实例化对象
search_request.msg11.field1 = '/protoc.Request.Sequence'
search_request.msg11.msg22.field1 = 1
search_request.msg11.msg22.field2 = '90路'
search_request.msg11.msg22.field3 = 1
# 序列化请求数据
serialize_data = search_request.SerializeToString()
# print(serialize_data)
# 保存数据玮bin文件供后续对比使用
# with open('my_req.bin', mode="wb") as f:
#     f.write(serialize_data)

# 对序列化后的数据aes加密
aes_data = aes_encry(serialize_data)
# b64 aes加密数据
b64_aes_data = base64.b64encode(aes_data)
# 请求体
post_data = 'request=' + quote(b64_aes_data.decode(), safe='')+'%0A'

header = {
    'Accept': 'application/json,application/xml,application/xhtml+xml,text/html;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh',
    'Connection': 'keep-alive',
    'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
    'User-Agent': 'Mozilla/5.0 (Linux; U; Android 6.0; zh-cn; Nexus 6P Build/MDA89D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
    'Host': 'lbs.jt.sh.cn:8082'
}
url = "http://lbs.jt.sh.cn:8082/app/rls/monitor"
response = requests.post(url, headers=header, data=post_data)
# print(response.text)

decry_data = aes_decry(response.content)
# with open('resp.bin', mode="wb") as f:
#     f.write(decry_data)

# 使用proto编写文件
root = pb2.SearchResponse()
root.ParseFromString(decry_data)
data = json.loads(MessageToJson(root))
print(data)

使用blackboxprotobuf

还有一种更快的方式,使用blackboxprotobuf
读取请求的二进制文件,通过blackboxprotobuf转为json,直接把关键词修改掉再转回去
像响应内容的key只是一个符号,可以通过value大概知道它的含义就行了

import blackboxprotobuf
import binascii
import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
from urllib.parse import quote

def aes_encry(ori):
    key = '2fd3028e14a45d1f8b6eb0b2adb7caaf'
    iv = '754c8fd584facf6210376b2b72b063e4'
    aes = AES.new(binascii.a2b_hex(key), AES.MODE_CBC, binascii.a2b_hex(iv))
    return aes.encrypt(pad(ori, 16))

def aes_decry(ori):
    key = '2fd3028e14a45d1f8b6eb0b2adb7caaf'
    iv = '754c8fd584facf6210376b2b72b063e4'
    aes = AES.new(binascii.a2b_hex(key), AES.MODE_CBC, binascii.a2b_hex(iv))
    return unpad(aes.decrypt(ori), 16)

with open(r"req.bin", "rb") as fp:
    data = fp.read()
    message, typedef = blackboxprotobuf.protobuf_to_json(data, message_type=None)
    bus_data = blackboxprotobuf.decode_message(data, message_type=None)[0]
    print(message)
    print(bus_data)
    print(typedef)
    bus_data['1']['2']['2'] = bytes('90路', 'utf-8')

serializedata = blackboxprotobuf.encode_message(bus_data, message_type=typedef)
print(serializedata)

# 对序列化后的数据aes加密
aesdata = aes_encry(serializedata)
# b64 aes加密数据
b64_aes_data = base64.b64encode(aesdata)
# 抓取
postdata = 'request=' + quote(b64_aes_data.decode(), safe='') + '%0A'
header = {
    'Accept': 'application/json,application/xml,application/xhtml+xml,text/html;q=0.9,image/webp,*/*;q=0.8',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'zh-CN,zh',
    'Connection': 'keep-alive',
    'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
    'User-Agent': 'Mozilla/5.0 (Linux; U; Android 6.0; zh-cn; Nexus 6P Build/MDA89D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30',
    'Host': 'lbs.jt.sh.cn:8082'
}
response = requests.post(url='http://lbs.jt.sh.cn:8082/app/rls/monitor', data=postdata, headers=header)
decry_data = aes_decry(response.content)

json_data, type_data = blackboxprotobuf.protobuf_to_json(decry_data, message_type=None)
print(json_data)

在这里插入图片描述

protobuf 是一种轻量级的数据交换格式,也是一种二进制序列化库。所谓的protobuf 逆向是指将已经序列化的protobuf 数据还原为可读的原始数据。 protobuf 逆向的过程首先需要获取到需要逆向protobuf 数据。通常情况下,可以通过网络数据包抓包工具,如Wireshark或Fiddler等,来获取经过序列化的protobuf 数据包。 获取到protobuf 数据包后,接下来需要将其解析成可读的数据。protobuf 提供了相应的解析器,可以根据proto文件定义的消息格式,将二进制数据转换成可读的原始数据。具体步骤包括:首先,通过proto文件定义,确定对象的属性和类型;然后,使用相应语言的protobuf API解析器,读取protobuf数据包,按照属性类型进行解析,将其还原为原始数据。 对于逆向来说,如果无法获取proto文件,可能会增加一些难度。此时可以通过猜测、分析数据特征、尝试不同的解析方式等方法进行逆向。借助于第三方工具,如Binwalk或Protodust等工具也可能会有一定的帮助,可以分析文件的结构和特征,进一步进行逆向分析。 在实际应用中,protobuf 逆向通常用于工程师对于确定网络通信协议、研究已有系统交互流程等方面。此外,对于一些恶意软件分析和安全研究领域,protobuf 数据的逆向分析也是一种重要的手段,可以帮助安全研究人员了解恶意软件的行为和目的。 总结来说,protobuf逆向是通过解析已经序列化的protobuf数据包,将其还原为可读的原始数据的过程。对于逆向过程,通常需要获取protobuf数据包,确定消息格式,并使用protobuf API解析器进行反序列化。对于无法获取proto文件的情况,可以通过分析数据特征和结构,尝试不同的解析方式进行逆向分析。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值