抓包分析
先打开 app
搜索关键字进行分析,发现了其请求和响应都是加密的
入口定位
因为其加密参数为 request
, 因此使用 jadx
反编译之后直接搜索 request
尝试, 发现了以下结果
一个个点进去,发现第一个比较像,因此我们采用 frida hook
一下!
hook
代码如下, 本段代码中因为结果是一个 ByteArray
, 所以我们可以按照源码来找到转为字符串的方法。然后再对比一下抓包结果,发现这里就是加密入口
复制 let Native = Java .use ( "com.shjt.map.tool.Native" );
let Base64 = Java .use ( "android.util.Base64" )
Native[ "encode2" ]. implementation = function (bArr) {
console .log ( `Native.encode2 is called: bArr= ${ bArr } ` );
let result = this [ "encode2" ](bArr);
console .log ( `Native.encode2 result= ${ Base64 .encodeToString (result , 0 ) } ` );
return result;
};
知道了解密入口,我们还需要分析入参,因此,但是我们的入参是一个 ByteArray
, 因此我们可以仔细分析一下,查看 jadx
源码可知
其 byte
数组来自于 protoc
,因此想要还原我们可以查询一下文档 / 源码,文档地址为 Google.Protobuf.ByteString Class Reference
根据源码我们知道, 通过 ByteString.copyFrom
方法就可以还原为 ByteString
, 然后再通过 toStringUtf8
方法就可以获取还原好的字符串。
因此 frida
代码可以修改为如下代码:
复制 let Native = Java .use ( "com.shjt.map.tool.Native" );
let Base64 = Java .use ( "android.util.Base64" )
const ByteString = Java .use ( "com.google.protobuf.ByteString" );
Native[ "encode2" ]. implementation = function (bArr) {
console .log ( `Native.encode2 is called: bArr= ${ bArr } ` );
console .log ( `Native.encode2 is called: bArrStr= ${ ByteString .copyFrom (bArr) .toStringUtf8 () } ` );
let result = this [ "encode2" ](bArr);
console .log ( `Native.encode2 result= ${ Base64 .encodeToString (result , 0 ) } ` );
return result;
};
这个时候我们的入参也分析出来了!根据上面的步骤可以知道,加密方法是存在于 so
文件的,so
文件为 native
, 加密方法为 encode2
, 接下来就可以步入正题,开始进行 IDA
算法还原了
算法还原
首先我们先把 apk
解压,在 lib
里面找到我们需要的 so
文件,然后用 ida
打开
看到左边的函数很快就能定位的加密函数的,点进去然后按一下 tab
键就可以看到伪代码,那么就可以开始分析加密逻辑了!先简单看一下
好像加密方式是 aes CBC
,既然是 aes
, 那么我们就需要 iv
, padding
和 key
。 这边暂时先不还原算法,我们先要搞清楚刚刚提到的关键参数值到底是什么,因此我们可以借助 frida
进行 hook
,我们先猜测 aes
的秘钥相关设置都在 aes_key_setup
这个函数里面,先双击点进去
拿到导出函数的签名:_Z13aes_key_setupPKhPji
然后编写 frida hook so
层的代码, 这个是标准的模板。
复制 let aes_key_setup = Module .findExportByName ( "libnative.so" , "_Z13aes_key_setupPKhPji" )
Interceptor .attach (aes_key_setup , {
onEnter : (args) => {
} ,
onLeave : (retval) => {
}
})
然后我们因为要分析 key
, 所以肯定要分析参数,因此可以继续在 ida
里面看一下参数类型
可以看到前两个参数是指针类型(可以理解为内存地址,可以指向原本的变量),第三个参数是 int
类型,至于怎么打印,我们需要查阅 frida
的文档,地址为:JavaScript API | Frida • A world-class dynamic instrumentation toolkit
例如我们需要打印 int
就可以使用 toInt32
而本案例中,因为我们猜测他用的是 aes
,所以其实大概率就是 ByteArray
了,通过查询文档知道,如果我们需要读取 ByteArray
,那么我们应该使用 readByteArray
, 并且从伪代码中可以看到,这里使用的可能是 128
位的,所以换算为 bytes
那么就应该读取 16
位,因此我们的 hook
代码就可以修改为:
复制 let aes_key_setup = Module .getExportByName ( "libnative.so" , "_Z13aes_key_setupPKhPji" )
Interceptor .attach (aes_key_setup , {
onEnter : (args) => {
console .log ( "\n================================================ \n" )
console .log ( ">>> arg0\n" , args[ 0 ] .readByteArray ( 16 ))
console .log ( ">>> arg1\n" , args[ 1 ] .readByteArray ( 16 ))
console .log ( ">>> arg2\n" , args[ 2 ] .toInt32 ())
console .log ( "================================================ \n" )
} ,
onLeave : (retval) => {
}
})
如果 hook
不到的话,需要延迟 hook
,因为要等这个 so
文件加载之后才可以,然后点击搜索触发,可以看到已经 hook
到了
可以看到一下出现了两条记录,这个原因我猜测就是,请求的时候加密了,响应的时候解密了,那就会有两条,那这种情况就可以解释了!既然是 aes
, 那加解密的秘钥应该是一样的把,所以可以看到共同部分:2f d3 02 8e 14 a4 5d 1f 8b 6e b0 b2 ad b7 ca af
那么我们先猜测秘钥为 2fd3028e14a45d1f8b6eb0b2adb7caaf
然后我们接着 hook
一下 aes_encrypt_cbc
,打印函数类型的话,就需要经验和尝试了!
hook
代码:
复制 let aes_key_setup = Module .getExportByName ( "libnative.so" , "_Z15aes_encrypt_cbcPKhjPhPKjiS0_" )
Interceptor .attach (aes_key_setup , {
onEnter : (args) => {
console .log ( "\n================================================ \n" )
console .log ( ">>> arg0\n" , args[ 0 ] .readByteArray ( 16 ))
console .log ( ">>> arg1\n" , args[ 1 ] .toInt32 ())
console .log ( ">>> arg2\n" , args[ 2 ] .readByteArray ( 16 ))
console .log ( ">>> arg3\n" , args[ 3 ] .readByteArray ( 16 ))
console .log ( ">>> arg4\n" , args[ 4 ] .toInt32 ())
console .log ( ">>> arg5\n" , args[ 5 ] .readByteArray ( 16 ))
console .log ( "================================================ \n" )
} ,
onLeave : (retval) => {
}
})
这里有几个字符串值的分析一下:
0a230a182f70726f746f632e52657175
616e64726f69642e737570706f72742e
8e02d32f1f5da414b2b06e8bafcab7ad
754c8fd584facf6210376b2b72b063e4
经过多次搜索发现第一个和第二个在变化,所以先排除第一个和第二个。那么 iv
应该是以下这两个选择
8e02d32f1f5da414b2b06e8bafcab7ad
754c8fd584facf6210376b2b72b063e4
写一段解密的代码进行测试:
复制 import binascii
import blackboxprotobuf
from Crypto . Cipher import AES
from Crypto . Util . Padding import unpad
def aes_text ( hexdata , key : str , iv : str ):
aes = AES . new (key = binascii. a2b_hex (key), mode = AES.MODE_CBC, iv = binascii. a2b_hex (iv))
a2b = aes . decrypt (binascii. a2b_hex (hexdata))
message , typedef = blackboxprotobuf . protobuf_to_json ( unpad (a2b, 16 ), message_type = None )
print (message)
使用 key
是 iv
进行解密测试,我们加密后的数据经过抓包就可以得到: wGUFixSXEs26Hu/F1P0kGbiEywc3GzF+1E2azlIHBQkoUVu9H0WS4j40GqHQdfZj
然后因为这个是一个 base64
代码,所以我们需要先解开编码之后再转换为 16
进制
复制 import base64
data = base64 . b64decode ( "wGUFixSXEs26Hu/F1P0kGbiEywc3GzF+1E2azlIHBQkoUVu9H0WS4j40GqHQdfZj" ). hex ()
aes_text (
hexdata = data,
key = "2fd3028e14a45d1f8b6eb0b2adb7caaf" ,
iv = "8e02d32f1f5da414b2b06e8bafcab7ad"
)
第一个解密报错了,我们修改为第二个
复制 import base64
data = base64 . b64decode ( "wGUFixSXEs26Hu/F1P0kGbiEywc3GzF+1E2azlIHBQkoUVu9H0WS4j40GqHQdfZj" ). hex ()
aes_text (
hexdata = data,
key = "2fd3028e14a45d1f8b6eb0b2adb7caaf" ,
iv = "754c8fd584facf6210376b2b72b063e4"
)
发现解密成功了,所以可以得出结论:
key
为:2fd3028e14a45d1f8b6eb0b2adb7caaf
iv
为: 754c8fd584facf6210376b2b72b063e4