目的:探究某次元的信息加密方式
抓包
如图,在应用中我们搜索一点东西,能抓到这些内容,在句号之前是固定344长度的数据,句号后面是不固定长度的数据,可以猜测这些内容是我们搜索出来的东西的密文。句号之前固定长度的数据应该是key
flutter恢复
我们解包apk可以发现它是flutter框架的app,那程序的主要内容都在libapp.so里面,这个文件内容庞大且没有符号表,手动定位我们需要的函数是不现实的。
blutter配置
这时候就需要用上我们的blutter工具
直通车https://github.com/worawit/blutter
下好过后进入blutter目录执行初始化环境的脚本
python ../scripts/init_env_win.py
这一步是为了避免出现ICU库报错
然后需要GIT和CMAKE,并在VS的工具中把CMAKE相关扩展都装上
其他问题没遇到,不知道还有没有其他环境支持
使用
把flutter框架的apk解包
python <blutter工具路径> xxx\lib\arm64-v8a <输出路径>
然后就能得到如下文件
其中,ida_script
文件夹下的addNames.py
就是我们用来恢复符号表的关键
在IDA中打开我们的libapp.so,然后File -> Script file / Alt + F7
,选择刚刚的addNames.py
文件即可
逆向
刚刚我们已经恢复了符号表,那就需要在libapp.so中定位关键逻辑了
前文提到过我们猜测那里是加密后的数据,那就搜索Encrypt或者Decrypt函数
根据函数名,几乎可以确定加密主体有AES和RSA,
然后我们知道是native层的函数,函数地址也知道,逐条分析逆向的难度也不小,可以用frida尝试去hook
var addr = Module.findBaseAddress('libapp.so');
console.log(addr); // 打印出基地址
var funcAddr = addr.add(0x822d84);
console.log(funcAddr);
Interceptor.attach(funcAddr, {
onEnter: function (args) {
console.log("hook 传入参数------------------")
console.log('arg0,',hexdump(args[0]))
// console.log('arg1,',hexdump(args[1]))
// console.log('arg2,',hexdump(args[2]))
},
onLeave: function (retval) {
console.log("-------hook 返回值-------------")
console.log('返回值:', hexdump(retval, {length:2000}))
}
})
// onEnter和onLeave都是回调函数,在目标函数被调用,且尚未执行函数体之前触发
// args 是一个数组,包含了传递给目标函数的所有参数
// retval 代表目标函数的返回值
最终我们可以在decrypt的0x822d84处函数hook出密文和明文
- 这里函数后面的hex数据都是在libapp.so的基地址上的偏移量
那么这个函数就肯定有用,我们多hook几次仔细看看hook日志
可以发现,我们每次hook这个函数,启动程序后frida一共会hook两次arg0和返回值分别为
arg0的值是固定的,都是密文,返回值一共有两次,第一次是固定16字节的字符串,第二次是明文
tips:
AES的key一般是16字节
分别去hook 822d84里的函数
这个是就是关键加密函数,且是根据刚刚的hook结果,我们知道第一次这个函数返回16字节的key,第二次返回明文。所以我们要根据这个函数去追
交叉引用可以发现只有8ab12c调用了两次,那么这就是最关键的整个加密函数
hook出来的完整密文,以"."分开,前面334字节是key,后面是加密数据
理一下这里面的内容,可以得到:
334字节key -> RSA解密 -> ?
加密数据 -> AES解密? -> 明文
这里就有两个问题,RSA要解密需要私钥;AES解密需要确定其模式,找到key以及可能需要iv
我们先去尝试处理key
过程1 处理key
0x8ab12c函数里我们按0x822d84函数两次调用作分割
由于第二次调用返回值已经是明文了,那么我们只需要关注第一次调用之前和第一次、第二次调用之间的内容
这个函数名尤为扎眼,分配RSAkey,那么hook这个函数试试
var addr = Module.findBaseAddress('libapp.so');
console.log(addr);
var funcAddr = addr.add(0x832214);
console.log(funcAddr);
Interceptor.attach(funcAddr, {
onEnter: function (args) {
console.log("hook 传入参数------------------")
console.log('arg0,',hexdump(args[0]))
},
onLeave: function (retval) {
console.log("-------hook 返回值-------------")
console.log('返回值:', hexdump(retval))
}
})
这个函数的传入参数就是RSA的私钥,破案了
我们取长一点,把完整私钥提取出来,且hook了好几次,确定了私钥是固定的
var addr = Module.findBaseAddress('libapp.so');
console.log(addr);
var funcAddr = addr.add(0x832214);
console.log(funcAddr);
Interceptor.attach(funcAddr, {
onEnter: function (args) {
console.log("hook 传入参数------------------")
console.log('arg0,',hexdump(args[0],{length:0x1000}))
},
})
得到完整私钥:
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxm2Kzu9L/FNX42em9Xo73JXtJCtrhleKN9jqclpK6/Iyah/T
UjH5RCNItWiTKHg6LcsGxZs9+4fP6uU8oO5Qp1akaOrJTg3QTQQFyRxDrv+LN/nL
6/MpSf3SnihyVPQWwlkj3yHZWrVC9HI3q2JmzGV/kzwnpVIj2as8zl4cO7OZr0F9
bR+G4jblqLPmB/x/BBOGrWWCxn+YI2RVHw23dev9jql284eN/KV4tlDlbtoJGy4+
Cb7nEV/THvRVZYbHAp+fMY0+NyqyslLX/btJqT8eSH6Hb8c+BSC77Dry4G8/m/wU
YPdvXiL3cVhZmEaqjs8rUafGyQmW3mrflAbIJwIDAQABAoIBAQCbRhUdIbzAUyev
V+kapvA5CUlsyF133wDV+vRbT7TZNcmlqgnfhCOe4k1/R7oALTS5qOo/r9+s+PYG
xiPPey26BN7bCv9ECSM7YS511ZxRUL9MqjidBscEk49BHD17pRY6Ny8O6JoBlV4z
kz1k67etsq9GNAiCIejT6F/IzXQicmO5MaJWCjBNSP+IPTvd5NW3DUNlt2NcCBvO
2sCgSq2Z4B0IdNWeSvd4ZmA2qSkqk60A8glNR4HdRTG9VWR0fUOd/qzpN1vjBUKM
aIHeUX50NCRdK8EGqrVOCq4uUgBRj7bjt0DOb9ck4vYxgBdkyK4HMYsAGdirYKxd
DkseicqhAoGBAOZ750Vky38kq3MucAE/uaFpaDUSeOKDy03fumM6TLlkeAxnTQZW
NBDzlrqNgQPMLu+tmm+ZsEN5buF8C2oKc97+Rz21rvrOufr4sX2PqHfj/kerYGq/
NzX1jpRbqsmcs+3JxveeozHuXBbOFpd8teCGZPFPHREFDe4sZFtFmwX1AoGBANxl
JNlKIz0TVmPgGCUZ4j8BRBiLPHMeFkUoam6Djou2iJLYey3ZNHhyMiRER7Smia0f
Y/QjqJIeSRWQlZExu6s9ijl4VSmoh4hLanOxxAE+gFnuhgK4XwMV0hvHbqSaupQd
fkULZ+t3rGKzt+t+0ob7xx+LjYWEwpLsKCQKRKgrAoGADLPvfyea/5rpyCNbEPaO
KJNCpwopl3JkFhqqjyV7bQxYgXaADEVcAUMrn4SFA8yRGaybwmLaEB31OoA3sNR6
pmOlUYVd63zRSz/BqIXuZw0tyo1rdvaq+FJcVVjoBMyaLhTc3nDj1bCpaqhZHmhF
Lea6UYJmu7VnmyTfMxiW/rECgYAh4MJLTGQiTUioTZgoi9QFT1KCW1TNdUCDHPVP
S5Wr0EEqIXC92XeBVDx06rIDCN586ChbLOgKnfEqCXGUQgrRBcKrlt2wa6F5x+3z
Hs48Srk8Gbgrzt97/+yuLHfLgaVQg0AXqOsufNTYzztkTbha23T+WltEvOWT5A0/
jPyExQKBgCGbq62piyIEeMNoP/SoLvh4hTq/eeNw5yCcLEsLrgt45Xb/2YgeyXWv
xTXl4c8bPdZTFYQ9A7IUYvhizpH032tDouqCsvgu3KtDO/pW6IteL17YBco7fRMQ
JhBuQjGDCMEGEJW76GwlXj/xUW32TN/5KeQXtHHZ4z2lZlJLU81B
-----END RSA PRIVATE KEY-----
写脚本试试解密key,试了几个填充方式过后发现为PKCS1_no_padding
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import serialization, hashes
import base64
def RSA_Decrypt(private_key_pem, ciphertext_base64):
# 加载私钥
private_key = serialization.load_pem_private_key(
private_key_pem.encode(),
password=None,
backend=default_backend()
)
# 解码 Base64 编码的密文
ciphertext = base64.b64decode(ciphertext_base64)
# 使用 PKCS1 无填充方式解密
plaintext = private_key.decrypt(
ciphertext,
padding.PKCS1v15()
)
return plaintext
private_key_pem = """
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAxm2Kzu9L/FNX42em9Xo73JXtJCtrhleKN9jqclpK6/Iyah/T
....
JhBuQjGDCMEGEJW76GwlXj/xUW32TN/5KeQXtHHZ4z2lZlJLU81B
-----END RSA PRIVATE KEY-----
"""
ciphertext_base64 = "gsXZuK1P5zPQYUG3v/6pFLQhvxHzKpbLdYCPcoqAR8fzq48YOz04N+fqwd/Rev+K+8srxUkEiWUvRday9f6uGWzbsB1XbpodWyOTLctMIkh+lYSAIjEkIKRR/ebTK/oo2kIiifsTPpeDySCQPKsFQtkz8QayWhcVxkXRQKBqBTfxg6jQY+gRVYJtIcajks7CB7Pii7MbaDDnEBnnip8r2y/AdqbyOaIXJgjF6NxtROnxgE3dRZTuyyuZtUqCytMWb2xRicVqkrg03tHU0r2pUBI3dihSaQF+FQ448RYYuxQePDVU6IEuVSwcYQZbgu8QcG3/n0cdYbq0RFcBDs1xFg=="
plaintext = RSA_Decrypt(private_key_pem, ciphertext_base64)
print(plaintext.decode())
# PD6QJUZVT2STJ641
这里刚好是16字节的数据
过程2 得到明文
16字节的数据,结合hook 822d84函数时两次调用第一次返回值为16字节数据,可以猜测这个16字节就是第一次调用的返回值
验证
我们重新hook一组
密文对上了,那么我将前面334字节密文key解密,应该就和左边hook的结果一致
没问题,我们推测成功
这个十六字节的数据大概率就是解密加密数据时的AES key
通过1
,能发现是有分配IV的,那么可以确定是AES-CBC模式
而在设想中,AES的key我们已经找到,那么就需要找IV,这里的2
有reverse,比较扎眼,hook试试
可以发现,确实只是把key做了一个反向操作
第一次调用和第二次调用822d84之间,把其他疑似函数都hook尝试了,没有找到我们需要的iv
那么,我们又可以来猜,AES的key和iv就是互为反向的16字节字符串
尝试成功,转成UTF-8导出
总结
还是比较复杂的过程,hook和看逻辑给我看麻了,不过最后搞成功
把加密数据提取出来,以"."为分界线分为key_enc
和cipher
,最终逻辑为
key_enc -> RSA私钥解密(PKCS1_Nopadding) -> AES_KEY
AES_KEY -> 反向 -> AES_IV
cipher -> AES_CBC解密 -> 明文