吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 743|回复: 10
上一主题 下一主题
收起左侧

[MacOS逆向] lldb 调试某数据库客户端 + license 逆向 + python3 keygen(win/mac 通杀)

  [复制链接]
跳转到指定楼层
楼主
Vvvvvoid 发表于 2025-3-14 14:21 回帖奖励
本帖最后由 Vvvvvoid 于 2025-3-14 21:51 编辑

lldb 调试某数据库客户端 + license 逆向 + python3 keygen(win/mac 通杀)

1. 关于

之前做过该软件的分析, 不过似乎作者与我心有灵犀, 之前的 patch 已经过期了;
今天试一下新的方案, 逆向 license 文件的加解密, 并且完成 keygen

2. license 逆向

ida 拖入文件, 从关键字 license 入手, 查找关键字定位到了该字符串  

__cstring:0000000100735D60 aLicenseFileIsI db 'License file is invalid ',0

查看 Ref, 定位到了加载 license 的函数里面

可以看到有些关键地方,我们挨个去分析  

2.1. 调用 fileExistsAtPath: 查看 license 文件是否存在

__text:0000000100144B6D                 mov     rsi, cs:selRef_fileExistsAtPath_ ; SEL
__text:0000000100144B74                 mov     rdi, rbx        ; id
__text:0000000100144B77                 mov     rdx, r13
__text:0000000100144B7A                 call    _objc_msgSend

lldb 加载程序, 在 call fileExistsAtPath 处下断点, r 重启
断下来后, 输出寄存器 r13 的对象, 查看 license 的具体路径  

br s -a 0x100144B7A

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x0000000100144b7a TablePlus`___lldb_unnamed_symbol8433 + 362
TablePlus`___lldb_unnamed_symbol8433:
->  0x100144b7a <+362>: callq  0x1006d7636    ; symbol stub for: objc_msgSend
    0x100144b7f <+367>: movl   %eax, %r12d
    0x100144b82 <+370>: movq   0x692cf7(%rip), %r14 ; (void *)0x00007ff80fb93350: objc_release
    0x100144b89 <+377>: movq   %rbx, %rdi
Target 0: (TablePlus) stopped.
(lldb) po $r13
/Users/voidm/Library/Application Support/com.tinyapp.TablePlus/.licensemac

由此我们得到了 license 绝对路径:
'/Users/voidm/Library/Application Support/com.tinyapp.TablePlus/.licensemac'

2.1. 调用 RNCryptor 相关库函数进行解密

利用上面获取到的 license 文件路径, 我们试着伪造一个 license
不过,首先得了解下该算法, 我问了我大哥(chatgpt), 跟我介绍了下:  

RNCryptor.DecryptorV3 是 RNCryptor 库中的一个解密器类,用于解密符合 RNCryptor v3 格式的加密数据。
RNCryptor 是基于 AES-256-CBC 和 PBKDF2 进行加密和解密的跨平台库,支持 iOS(Objective-C & Swift)、macOS 和其他平台。  

通过查看<该库源码>
发现其底层调用了 macos 下 CommonCryptor 库的相关函数

NAME
     CCCryptorCreate, CCryptorCreateFromData, CCCryptorRelease,
     CCCryptorUpdate, CCCryptorFinal, CCCryptorGetOutputLength,
     CCCryptorReset, CCCrypt -- Common Cryptographic Algorithm Interfaces

LIBRARY
     These functions are found in libSystem.

SYNOPSIS
     #include <CommonCrypto/CommonCryptor.h>

之后查了些<资料>, 找到了该算法生成 license 的基本文件格式, 如下

Byte:     |    0    |    1    |      2-9       |  10-17   | 18-33 | <-      ...     -> | n-32 - n |
Contents: | version | options | encryptionSalt | HMACSalt |  IV   | ... ciphertext ... |   HMAC   |
version (1 byte): Data format version. Currently 3.
options (1 byte): bit 0 - uses password
encryptionSalt (8 bytes): iff option includes "uses password"
HMACSalt (8 bytes): iff options includes "uses password"
IV (16 bytes)
ciphertext (variable) -- Encrypted in CBC mode
HMAC (32 bytes)

看不懂没关系, 我来翻译一下:  

version: 第一个字节,代表算法版本号, 既然是 DecryptorV3 就是 固定 0x3
options: 第二个字节,是否使用 password, 先默认它是把, 我们写成 0x1
encryptionSalt: 八个字节的 encryptionSalt,用于 KEY 派生的 PBKDF2 盐值, 我们随机造一个
01 00 00 00 00 00 00 00
HMACSalt: 八个字节的 HMACSalt,用于 HMAC KEY 派生的 PBKDF2 盐值, 我们随便造一个
02 00 00 00 00 00 00 00
IV: 十六个字节的 IV,AES CBC 初始向量, 我们随便造一个
03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ciphertext: 可变长度的 ciphertext, 经过 AES-256-CBC 加密的实际内容, 可能包含 PKCS7 填充, 也就是 license 的本体了
这个 ciphertext 先随便伪造一些
04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
HMAC: 三十二长度的 HMAC, SHA256 校验码,用于完整性验证
这个 HMAC 也先随便伪造一些
05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

至此, 临时的 license 文件伪造好了, 写入到 licese 路径下  

2.1.1. encryptionSalt 验证,获取 password

该算法第一步会用 password + salt 来生成 key, 这块会用到 CCKeyDerivationPBKDF  函数 , 我们下断  

CCKeyDerivationPBKDF(CCPBKDFAlgorithm algorithm,
                                const char *password, size_t passwordLen,
                                const uint8_t *salt, size_t saltLen,
                                CCPseudoRandomAlgorithm prf, uint rounds,
                                uint8_t *derivedKey, size_t derivedKeyLen)
br s -n CCKeyDerivationPBKDF

断下来后,可以看到 algorithm 算法参数 与 rounds 值
通过观察参数 salt , 看到与我们伪造的一致, 之后输出参数 password , 获取到了该加密用到的密码 (该密码在同一个软件中通常是固定存在的)  

(lldb) br s -n CCKeyDerivationPBKDF
Breakpoint 3: where = libcommonCrypto.dylib`CCKeyDerivationPBKDF, address = 0x00007ff81e077142

之后记录下改函数生成的 key, 参数的地址为 rsp+0x10, 先记录下地址,
然后 finish 执行完函数后, 在获取地址内容,因为该参数是个引用,需要在解一层地址,
最后获取到 由 salt + password 生成的 key 如下:

665e8e6c6bf0befde5c30062f608db27be0443d59f2e410f4171933fb837565f


(lldb) p/x $rsp+0x10
(unsigned long) 0x00007ff7bfefdbc8

(lldb) finish

(lldb) x/8xg 0x00007ff7bfefdbc8
0x7ff7bfefdbc8: 0x0000600001da5c60 0x0000000000000020

(lldb) mem read 0x00600001da5c60 -c 32
0x600001da5c60: 66 5e 8e 6c 6b f0 be fd e5 c3 00 62 f6 08 db 27  f^.lk......b...'
0x600001da5c70: be 04 43 d5 9f 2e 41 0f 41 71 93 3f b8 37 56 5f  ..C...A.Aq.?.7V_

2.1.2. HMACSalt 验证

第二步会用 password + HMACSalt 来生成 hmac_key, 同前面一样, 下断 CCKeyDerivationPBKDF
断下来后, 通过观察参数 HMACSalt , 看到与我们伪造的一致, 然后输出 password , 也与上面得到的 password 一致  

之后用同样的方式记录下生成的 hmac_key
获取到 由 HMAC Salt + password 生成的 hmac_key  如下:

da049e62d7dcd964fe620f3388573cc88f6ff1e2d4516db7fc374b2f6ff69602

(lldb) p/x $rsp+0x10
(unsigned long) 0x00007ff7bfefdbc8
(lldb) x/8xg 0x00007ff7bfefdbc8
0x7ff7bfefdbc8: 0x0000600001da5c60 0x0000000000000020
0x7ff7bfefdbd8: 0x00000001000974b7 0x0000000000000022
0x7ff7bfefdbe8: 0x0000000000000010 0x0000001000000000
0x7ff7bfefdbf8: 0x0000600001da5c40 0x00007ff7bfefdc50
(lldb) finish
Process 61779 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
    frame #0: 0x00000001000974fd TablePlus`___lldb_unnamed_symbol5091 + 189
TablePlus`___lldb_unnamed_symbol5091:
->  0x1000974fd <+189>: addq   $0x20, %rsp
    0x100097501 <+193>: movl   %eax, %r12d
    0x100097504 <+196>: movq   %r14, %rdi
    0x100097507 <+199>: callq  0x1006d782e    ; symbol stub for: swift_bridgeObjectRelease
Target 0: (TablePlus) stopped.
(lldb) mem read 0x00600001da5c60 -c 32
0x600001da5c60: da 04 9e 62 d7 dc d9 64 fe 62 0f 33 88 57 3c c8  ...b...d.b.3.W<.
0x600001da5c70: 8f 6f f1 e2 d4 51 6d b7 fc 37 4b 2f 6f f6 96 02  .o...Qm..7K/o...

2.1.3 生成 ciphertext

接着看代码, license 解密成功后, 会调用 JSON相关函数转换成 TBLicenseModel 对象
由此得知, 我们的 license Ciphertext 解密后是 json 字符串
在 ida 分析 TBLicenseModel 可以得到以下 ivar 属性  

__const:00000001006E8868 _OBJC_IVAR_$__TtC9TablePlus14TBLicenseModel_sign dq 10h
__const:00000001006E8868                                         ; DATA XREF: __objc_const:0000000100827C68↓o
__const:00000001006E8870 _OBJC_IVAR_$__TtC9TablePlus14TBLicenseModel_email dq 20h
__const:00000001006E8870                                         ; DATA XREF: __objc_const:0000000100827C88↓o
__const:00000001006E8878 _OBJC_IVAR_$__TtC9TablePlus14TBLicenseModel_deviceID dq 30h
__const:00000001006E8878                                         ; DATA XREF: __objc_const:0000000100827CA8↓o
__const:00000001006E8880 _OBJC_IVAR_$__TtC9TablePlus14TBLicenseModel_licenseKey dq 40h
__const:00000001006E8880                                         ; DATA XREF: __objc_const:0000000100827CC8↓o
__const:00000001006E8888 _OBJC_IVAR_$__TtC9TablePlus14TBLicenseModel_purchasedAt dq 50h
__const:00000001006E8888                                         ; DATA XREF: __objc_const:0000000100827CE8↓o
__const:00000001006E8890 _OBJC_IVAR_$__TtC9TablePlus14TBLicenseModel_updatesAvailableUntil dq 60h
__const:00000001006E8890                                         ; DATA XREF: __objc_const:0000000100827D08↓o
__const:00000001006E8898 _OBJC_IVAR_$__TtC9TablePlus14TBLicenseModel_nextChargeAt dq 70h

根据 TBLicenseModel 的 ivar 属性来拼接一个字符串
我们可以利用 key + iv 来做一个 aes cbc 加密字符串, 生成 ciphertext  

生成之后,替换我们 license 文件中的 ciphertext
然后 lldb r 重启  

2.1.4 生成 HMAC

HMAC 是用来验证文件的完整性的,用于数据完整性校验,如果 HMAC 不匹配,则数据可能被篡改
算法如下  

header = version + options + encryptionSalt + HMACSalt + IV
hmac_message = header + ciphertext
HMAC = HMAC-SHA256(hmac_key, hmac_message)

取到之后,替换我们 license 文件中的的 hmac
9d79987595a230de47d90f8d7842b7821696a7517a8a2d8ce98be470ce3e2a52
致此, license 伪造如下:

然后 lldb 重启,继续调试

2.2 验证

解密后的 json 字符串会调用 JSONDecoder.decode 转成 LicenseModel 对象, 我们 在 0000000100144F68 下断

br s -a 0x100144F68

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
    frame #0: 0x0000000100144f68 TablePlus`___lldb_unnamed_symbol8433 + 1368
TablePlus`___lldb_unnamed_symbol8433:
->  0x100144f68 <+1368>: callq  0x1006d5c8c    ; symbol stub for: dispatch thunk of Foundation.JSONDecoder.decode<τ_0_0 where τ_0_0: Swift.Decodable>(_: τ_0_0.Type, from: Foundation.Data) throws -> τ_0_0
    0x100144f6d <+1373>: movq   %r14, %rbx
    0x100144f70 <+1376>: testq  %r12, %r12
    0x100144f73 <+1379>: je     0x100144fb7    ; <+1447>
Target 0: (TablePlus) stopped.
(lldb) 

断下来后, 输出前几个参数调试下  ,
可以看到第一个参数是一个 TBLicenseModel 类的模版,
第二个参数未知
第三个参数 Foundation.__DataStorage 看着像是我们要验证的数据

(lldb) po $rdi
TablePlus.TBLicenseModel

(lldb) po $rsi
1060856922112

(lldb) reg r rsi
     rsi = 0x000000f700000000
(lldb) po $rdx
4611791571565053824

(lldb) reg r rdx
     rdx = 0x4000600001468780
(lldb) po 0x600001468780
Foundation.__DataStorage
class __DataStorage {
    var isa: objc_class* // NULL for pure Swift class
    var refCount: UInt64
    var _bytes: UnsafeMutableRawPointer?
    var _length: UInt64
    var _capacity: UInt64
    var _offset: UInt64
    var _deallocator: ((UnsafeMutableRawPointer, Int64) -> Void)?
    var _needToZero: Bool
}

(lldb) x/8xg 0x600001468780
0x600001468780: 0x00007ff852ef01e0 0x0000000000000003
0x600001468790: 0x00007feeedd21c60 0x00000000000000f7
0x6000014687a0: 0x0000000000000140 0x0000000000000000
0x6000014687b0: 0x0000000000000000 0x0000000000000000

__DataStorage 是 swift 的 类, 可以看到有下面几个属性
通过 lldb 命令, 查看指针的一些偏移, 可以看到第三个为 char* 数组, 第四个为长度,  读取该地址内存, 可以看到 我们的 license 成功了解密出来了

mem read 0x00007feeedd21c60 -c 0xf7

至此 license 解密完成

lldb r 重启程序, 然后直接运行, 看效果

3. keygen

利用前面获取到的各种, 我们用 python3 基于 pycryptodomex 库手写一个 keygen, 来方便快捷生成 license 文件

# pip3 install pycryptodomex
class CryptoBase:
    def __init__(self):
        self.schema_version = 3
        self.aes_mode = AES.MODE_CBC
        self.options = 1
        self.hmac_includes_header = True
        self.hmac_includes_padding = False
        self.hmac_algorithm = SHA256
        self.key_len = 32
class Encryptor(CryptoBase):

    def encrypt(self, plaintext, password):
        salt = os.urandom(8)
        hmac_salt = os.urandom(8)
        iv = os.urandom(16)

        key = self.generate_key(salt, password)
        hmac_key = self.generate_key(hmac_salt, password)

        cipher = AES.new(key, AES.MODE_CBC, iv)
        ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))

        # schema + options + salt + hmac_salt + iv
        header = self.assemble_header(salt, hmac_salt, iv)

        print_hex(key, "key")  # schema (1 byte)

        hmac_message = header + ciphertext
        hmac = self.generate_hmac(hmac_message, hmac_key)

        encrypted_data = header + ciphertext + hmac
        return encrypted_data

    def generate_key(self, salt, password):
        return hashlib.pbkdf2_hmac('SHA1', password.encode('utf-8'), salt, 10000, dklen=self.key_len)

    def assemble_header(self, salt, hmac_salt, iv):
        schema = struct.pack('B', self.schema_version)
        options = struct.pack('B', self.options)
        return schema + options + salt + hmac_salt + iv

    def generate_hmac(self, message, key):
        hmac = HMAC.new(key, digestmod=self.hmac_algorithm)
        hmac.update(message)
        return hmac.digest()
if __name__ == '__main__':
    licenseFile = '/Users/voidm/Library/Application Support/com.tinyapp.TablePlus/.licensemac'
    password = "马赛克"
    plaintext = b'{"sign":"12345678901234567890123456789012345678901234567890","email":"marlkiller","deviceID":"ee4f1d1890b4eb49a5a4d7f195ca8b67","licenseKey":"licenseKey","purchasedAt":"2026-12-30","nextChargeAt":521,"updatesAvailableUntil":"2026-12-30"}'

    encryptor = Encryptor()
    encrypted_data = encryptor.encrypt(plaintext, password)
    write_encrypted_data_to_file(encrypted_data, licenseFile)
    print("Mac Encrypt: " + encrypted_data.hex())

4. 后记

项目已经打包 github,可以直接用 xcode 打开 :
https://github.com/marlkiller/dylib_dobby_hook  

git clone https://github.com/marlkiller/dylib_dobby_hook.git
cd script
sudo bash auto_hack.sh
    1. 软件有接口请求验证, 自行断网测试,这里不做讨论   
    1. mac/win password 不一致, 但是算法通杀.
    1. deviceId 的生成规则<参考这里>  

免费评分

参与人数 3吾爱币 +3 热心值 +3 收起 理由
leepolice + 1 + 1 谢谢@Thanks!
nmweizi + 1 + 1 我很赞同!
alderaan + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
A00 发表于 2025-3-14 14:28
这分析学到了谢谢分享,期待更多优秀分享
3#
JacksonMa 发表于 2025-3-14 14:40
4#
fg411 发表于 2025-3-14 15:11
5#
dwk1818 发表于 2025-3-14 17:23
大佬太厉害了
6#
eagleangle 发表于 2025-3-14 18:39
多谢大佬的详细分析分享
7#
anubiz 发表于 2025-3-14 21:29
学到了!感谢大佬的分享
8#
sdieedu 发表于 2025-3-14 21:30
太厉害了
9#
abcd1001 发表于 2025-3-14 21:36
感谢楼主热心分享~!
10#
a15072753 发表于 2025-3-15 13:22
感谢分享
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-3-15 23:02

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表