某次元信息加密方式探究

查看 60|回复 10
作者:Tsh4d0wT   

目的:探究某次元的信息加密方式

抓包


image.png (441.4 KB, 下载次数: 0)
下载附件
2025-4-14 17:21 上传

如图,在应用中我们搜索一点东西,能抓到这些内容,在句号之前是固定344长度的数据,句号后面是不固定长度的数据,可以猜测这些内容是我们搜索出来的东西的密文。句号之前固定长度的数据应该是key
flutter恢复


image.png (24.53 KB, 下载次数: 0)
下载附件
2025-4-14 17:21 上传

我们解包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 xxx\lib\arm64-v8a
然后就能得到如下文件


image.png (22.98 KB, 下载次数: 0)
下载附件
2025-4-14 17:22 上传

其中,ida_script文件夹下的addNames.py就是我们用来恢复符号表的关键
在IDA中打开我们的libapp.so,然后File -> Script file / Alt + F7,选择刚刚的addNames.py文件即可
逆向
刚刚我们已经恢复了符号表,那就需要在libapp.so中定位关键逻辑了
前文提到过我们猜测那里是加密后的数据,那就搜索Encrypt或者Decrypt函数


image.png (51.96 KB, 下载次数: 0)
下载附件
2025-4-14 17:22 上传



image.png (25.75 KB, 下载次数: 0)
下载附件
2025-4-14 17:22 上传

根据函数名,几乎可以确定加密主体有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的基地址上的偏移量



    image-20250319232709055.jpg (239.07 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:28 上传

    那么这个函数就肯定有用,我们多hook几次仔细看看hook日志
    可以发现,我们每次hook这个函数,启动程序后frida一共会hook两次arg0和返回值分别为


    image-20250320123535606.jpg (348.17 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:29 上传



    image-20250320123547946.jpg (391.91 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:30 上传

    arg0的值是固定的,都是密文,返回值一共有两次,第一次是固定16字节的字符串,第二次是明文
    tips:AES的key一般是16字节
    分别去hook 822d84里的函数


    image.png (103.66 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:30 上传

    这个是就是关键加密函数,且是根据刚刚的hook结果,我们知道第一次这个函数返回16字节的key,第二次返回明文。所以我们要根据这个函数去追


    image.png (43.04 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:30 上传

    交叉引用可以发现只有8ab12c调用了两次,那么这就是最关键的整个加密函数
    hook出来的完整密文,以"."分开,前面334字节是key,后面是加密数据
    理一下这里面的内容,可以得到:
    334字节key -> RSA解密 -> ?
    加密数据 -> AES解密? -> 明文
    这里就有两个问题,RSA要解密需要私钥;AES解密需要确定其模式,找到key以及可能需要iv
    我们先去尝试处理key
    过程1 处理key
    0x8ab12c函数里我们按0x822d84函数两次调用作分割
    由于第二次调用返回值已经是明文了,那么我们只需要关注第一次调用之前和第一次、第二次调用之间的内容


    image.png (14.76 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:30 上传

    这个函数名尤为扎眼,分配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))
        }
    })


    image-20250320125509767.jpg (390.98 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:31 上传

    这个函数的传入参数就是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一组


    image.png (364.69 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:32 上传

    密文对上了,那么我将前面334字节密文key解密,应该就和左边hook的结果一致


    image-20250320131418439.jpg (329.45 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:33 上传

    没问题,我们推测成功
    这个十六字节的数据大概率就是解密加密数据时的AES key


    image.png (142.47 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:33 上传

    通过1,能发现是有分配IV的,那么可以确定是AES-CBC模式
    而在设想中,AES的key我们已经找到,那么就需要找IV,这里的2有reverse,比较扎眼,hook试试


    image-20250320134637707.jpg (299.22 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:34 上传

    可以发现,确实只是把key做了一个反向操作
    第一次调用和第二次调用822d84之间,把其他疑似函数都hook尝试了,没有找到我们需要的iv
    那么,我们又可以来猜,AES的key和iv就是互为反向的16字节字符串


    image.png (330.29 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:34 上传

    尝试成功,转成UTF-8导出


    image.png (238.87 KB, 下载次数: 0)
    下载附件
    2025-4-14 17:34 上传

    总结
    还是比较复杂的过程,hook和看逻辑给我看麻了,不过最后搞成功
    把加密数据提取出来,以"."为分界线分为key_enc和cipher,最终逻辑为
    key_enc -> RSA私钥解密(PKCS1_Nopadding) -> AES_KEY
    AES_KEY -> 反向 -> AES_IV
    cipher -> AES_CBC解密 -> 明文

    下载次数, 函数

  • Tsh4d0wT
    OP
      


    feiyuya 发表于 2025-4-14 22:31
    大佬 blutter 这个用的python版本是多少,我这边用的一直提示elftools这个包装不上,我看介绍是让装这个pye ...

    我自用的python版本是3.8.10,blutter是当时装的最新的
    你可能是没有执行blutter的初始化环境脚本
    也可以排查一下是不是没有配置git和cmake,vs中有没有配置相关cmake扩展
    Tsh4d0wT
    OP
      


    menglv 发表于 2025-4-15 19:49
    apk有吗?新版好像不是flutter了。

    通过网盘分享的文件:囧次元.apk
    链接: https://pan.baidu.com/s/1qR-G5W1zHUyLu8u8uJjnaQ?pwd=sh4d 提取码: sh4d
    --来自百度网盘超级会员v4的分享
    RabbitBearLove   

    非常的详细,感谢分享
    好好学习下
    feiyuya   

    学到了大佬,感谢大佬分享
    feiyuya   

    大佬 blutter 这个用的python版本是多少,我这边用的一直提示elftools这个包装不上,我看介绍是让装这个pyelftools,这个装上了执行还是报错
    PhoenixOe   

    学到了大佬,感谢大佬分享
    3266384950   

    牛牛牛,
    xhr2025   

    大佬有空再看看 请求头里的 authentication
    feiyuya   


    Tsh4d0wT 发表于 2025-4-15 00:13
    我自用的python版本是3.8.10,blutter是当时装的最新的
    你可能是没有执行blutter的初始化环境脚本
    也可 ...

    有可能是我没有git和cmake导致的
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部