吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 4246|回复: 22
上一主题 下一主题
收起左侧

[MacOS逆向] Reveal逆向破解:从验证废除到凭证植入

  [复制链接]
跳转到指定楼层
楼主
sunny77 发表于 2025-9-20 22:26 回帖奖励
本帖最后由 sunny77 于 2025-9-23 00:25 编辑

文章目录

  • 免责声明
  • 授权机制
  • 代码签名
  • 验证废除
  • 指纹生成
  • 凭证植入
  • 网络钩子
  • 惊喜彩蛋

免责声明

本文所有内容仅用于学术研究、技术探讨及教育学习的目的,旨在帮助读者加深对软件安全及逆向工程的理解。文中描述的技术与方法并非鼓励或支持任何未经授权的行为,亦不应将本文内容用于任何未经授权的目的。软件破解及相关技术的使用需遵守各国法律法规,任何个人或机构在实践时应对自身行为负责,作者不承担因不当使用本文内容所引发的任何责任。

授权机制

启动应用,订阅弹窗映入眼帘。点击「Enter License Key」,我们进入解锁界面,任意输入一个无效的许可Key点击激活,应用弹出「This license does not exist」。


Alright,那就返回上一步,点击「Start Trial」输入邮箱地址来申请试用,很快就会收到一封包含试用许可Key的邮件,Key值如下。

RVL-HSQXA-AJASK-BLGER-PICEG-VAXKW

再次来到解锁界面输入此Key。这一次,打开抓包工具,观察网络请求与响应。先是客户端应用发起的请求中包含了刚才的许可Key和一个设备指纹。

POST /api/v1/application/license/verify HTTP/1.1

{
  "key":"RVL-HSQXA-AJASK-BLGER-PICEG-VAXKW",
  "fingerprint":"U0hBMjU2IGRpZ2VzdDogMjliMjUzNWNjMGM1ZTFlNzczYWEwNWFkMDQ1ZmI3MDNmZmJjNmU0OWU2ZWQwYTY2MWUxYzZhZGU3NDMwNzRiMw=="
}

随即服务器端返回了200状态码,且返回的响应正文中有个名为access_token的关键字段。仔细观察这个由点分隔的三段式结构,没错,正是JWT的典型特征。

HTTP/1.1 200 OK

{
  "refresh_token": "e8991018349asc3c12dd4812d2345650fdfdaad585ed0cdc1eb9010397684ec6",
  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NTgxODY4NjEuMDYwNjQ5OSwiZXhwIjoxNzU4MTg3MTYxLjA2MDY1LCJlbWFpbCI6InN2bm55NzdAZ21haWwuY29tIiwibGljZW5zZV9wYXRoIjoiXC9saWNlbnNlc1wvMjREQjBCM0ItNDhCQi0xNEE2LTkxQ0QtMDI4OTA4NDkyQ0UzIiwiY2xpZW50X2V4cGlyZXNfYXQiOjE3NTg0NDYzNjEuMDYwNjUsImxpY2Vuc2Vfc3R5bGUiOjAsIm9yZ2FuaXphdGlvbl9zdHlsZSI6MCwiaWRlbnRpZmllciI6IjRpR0R4bFBjaE1kMmsrK3lzOWVpTzE4eFJOYUdyV0xGWjRBeWRCaUJyOT0iLCJpc19zdWJzY3JpcHRpb25fdXNhYmxlIjpmYWxzZSwic3ViIjoiRDcxOEU1RDctOUE2OC0xNEY0LUNFRDEtNENFMkUyNEQwNTkwIiwib3JnYW5pemF0aW9uX25hbWUiOiJzdm5ueTc3QGdtYWlsLmNvbSIsImZpbmdlcnByaW50IjoiVTBoQk1qVTJJR1JwWjJWemREb2dNamxpTWpVek5XTmpNR00xWlRGbE56Y3pZV0V3TldGa01EUTFabUkzTURObVptSmpObVUwT1dVMlpXUXdZVFkyTVdVeFl6WmhaR1UzTkRNd056UmlNdz09IiwibGljZW5zZV9leHBpcnkiOjE3NTkzOTY0MDkuMDExOTU3MiwiaXNzIjoiUmV2ZWFsIGJ5IE1hbnRlbCJ9.OPReNgGoxsJora0jvmLlq37rbOqNmQiHNtgFLwFZ0RBD5wiLz6xkXpPHWlk6Ot5Y8wisld_-F6muEwEeqPyPcGKWNuD_Ot4bnG8uYx4rUx-CeiW33saDDl-mMX1GCF50917wWJH00zYzfZtZMIZMwupsUutUswDm9S6DFwUyArvXNBagUc9Rvi6-X1yBm2BHBLvOkp8p726mMQphk8MJ1GcxiwbuAIcq3Wg2FxZronT234XG2gWOAzYkubKF4GbTP6kjlpt3jSGoEk0qNKwsa6mK7WNicIVARy4-6_FzGjPvzNx9O9qsHPy8LE-fad6YaG1JmikA-XQw9QsdITttjK",
  "token_type": "bearer",
  "expires_in": 300
}

JWT三段内容分别是Header、Payload和Signature,我们先Base64解码第一部分,得到算法声明——RS256,一种基于RSA非对称加密的签名算法,这将意味着会涉及到一对公私钥。

{
  "alg": "RS256",
  "typ": "JWT"
}

继续解第二部分,Payload中包含了所有与授权相关的关键信息,如许可类型(license_style)、许可过期时间(license_expiry)、设备指纹(fingerprint),以及一个决定性的标志位——is_subscription_usable,介于是试用许可Key,当前值为false。

{
  "iat": 1758186861.0606499,
  "exp": 1758187161.06065,
  "email": "svnny77@gmail.com",
  "license_path": "/licenses/24DB0B3B-48BB-14A6-91CD-028908492CE3",
  "client_expires_at": 1758446361.06065,
  "license_style": 0,
  "organization_style": 0,
  "identifier": "4iGDxlPchMd2k++ys9eiO18xRNaGrWLFZ4AydBiBr9=",
  "is_subscription_usable": false,
  "sub": "D718E5D7-9A68-14F4-CED1-4CE2E24D0590",
  "organization_name": "svnny77@gmail.com",
  "fingerprint": "U0hBMjU2IGRpZ2VzdDogMjliMjUzNWNjMGM1ZTFlNzczYWEwNWFkMDQ1ZmI3MDNmZmJjNmU0OWU2ZWQwYTY2MWUxYzZhZGU3NDMwNzRiMw==",
  "license_expiry": 1759396409.0119572,
  "iss": "Reveal by Mantel"
}

至此,Reveal应用的授权机制已然清晰明了,完整流程如下。

  1. 客户端上报:当用户输入许可Key后,应用会附上一个设备指纹,一同发送至服务端。
  2. 服务端签发:服务端验证Key的有效性,并将授权信息与客户端上报的设备指纹打包成一个JWT Payload。接着,动用其私钥对整个Payload进行RS256签名,生成access_token返回至客户端。
  3. 客户端验证:客户端收到access_token后,将使用一份内置于应用之中的公钥来验证其签名。只有签名验证通过,才可表明这份授权凭证的确由官方签发且未被篡改。

而我们在没有私钥的情况下,就无法伪造出能通过客户端公钥验证的合法签名。这意味着,任何对Payload内容的篡改,如将is_subscription_usable改为true或是延长license_expiry,都会导致在客户端验证签名时失败,从而导致应用无法激活。

代码签名

既然伪造服务端凭证的破解方式已被非对称加密所封锁,那便只能在客户端做手脚。下面计划通过动态调试与分析,定位客户端签名校验的核心逻辑。在此之前,先对应用重签名,为后续LLDB调试做准备。

$ sudo codesign -f -s - --all-architectures --deep Reveal.app
Reveal.app: replacing existing signature

然而,应用却打不开。正好,不妨直接转入LLDB中,运行触发报错以捕获完整调用栈。

$ lldb Reveal.app
(lldb) target create "Reveal.app"
Current executable set to 'Reveal.app' (arm64).
(lldb) r
Process 21916 launched: 'Reveal.app/Contents/MacOS/Reveal' (arm64)
Reveal/ApplicationBuildTimestamp.swift:54: Fatal error: 'try!' expression unexpectedly raised an error: Reveal.(unknown context at $100e03154).ApplicationBuildTimestampError.noTimestamp
Process 21916 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = Fatal error: 'try!' expression unexpectedly raised an error: Reveal.(unknown context at $100e03154).ApplicationBuildTimestampError.noTimestamp
    frame #0: 0x00000001b185796c libswiftCore.dylib`_swift_runtime_on_report
libswiftCore.dylib`:
->  0x1b185796c <+0>: ret    
libswiftCore.dylib`:
    0x1b1857970 <+0>: b      0x1b185796c   ; _swift_runtime_on_report
libswiftCore.dylib`:
    0x1b1857974 <+0>: adrp   x8, 301844
    0x1b1857978 <+4>: ldrb   w0, [x8, #0x6fc]
Target 0: (Reveal) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = Fatal error: 'try!' expression unexpectedly raised an error: Reveal.(unknown context at $100e03154).ApplicationBuildTimestampError.noTimestamp
  * frame #0: 0x00000001b185796c libswiftCore.dylib`_swift_runtime_on_report
    frame #1: 0x00000001b19009f8 libswiftCore.dylib`_swift_stdlib_reportFatalErrorInFile + 208
    frame #2: 0x00000001b14f6330 libswiftCore.dylib`closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in closure #1 (Swift.UnsafeBufferPointer<Swift.UInt8>) -> () in Swift._assertionFailure(_: Swift.StaticString, _: Swift.String, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 104
    frame #3: 0x00000001b14f5428 libswiftCore.dylib`Swift._assertionFailure(_: Swift.StaticString, _: Swift.String, file: Swift.StaticString, line: Swift.UInt, flags: Swift.UInt32) -> Swift.Never + 260
    frame #4: 0x00000001b155c390 libswiftCore.dylib`swift_unexpectedError + 496
    frame #5: 0x000000010012d454 Reveal`___lldb_unnamed_symbol12335 + 1204
    frame #6: 0x000000010003f604 Reveal`___lldb_unnamed_symbol6634 + 196
    frame #7: 0x000000010003f920 Reveal`___lldb_unnamed_symbol6635 + 112
    ....

根据以上信息,转至frame #5 0x10012d454。由于调用栈中显示的地址是BL指令执行后的返回点,存在+4的偏移,所以实际的函数调用位于此地址之上的0x10012d454-4,其处于sub_10012CFA0中的loc_10012D438分支。

__text:0x110012D438 loc_10012D438    ; CODE XREF: sub_10012CFA0+444↑j
__text:0x110012D438                  ; sub_10012CFA0+454↑j ...
__text:0x110012D438     ADRL         X1, aRevealApplicat_0 ; "Reveal/ApplicationBuildTimestamp.swift"
__text:0x110012D440     MOV          X0, X19
__text:0x110012D444     MOV          W2, #0x26 ; '&'
__text:0x110012D448     MOV          W3, #1
__text:0x110012D44C     MOV          W4, #0x36 ; '6'
__text:0x110012D450     BL           _swift_unexpectedError
__text:0x110012D454     BRK          #1

下面是相应汇编片段的伪码。

  if ( (*(unsigned int (__fastcall **)(char *, __int64, __int64))(v5 + 48))(v3, 1LL, v4) == 1 )
  {
    v47 = sub_10012D49C(v3);
    v48 = sub_10012D4DC(v47);
    v46 = swift_allocError(&type metadata for ApplicationBuildTimestampError, v48, 0LL, 0LL);
    swift_willThrow();
    goto LABEL_12;
  }

LABEL_12:
    result = swift_unexpectedError(v46, "Reveal/ApplicationBuildTimestamp.swift", 38LL, 1LL, 54LL);
    __break(1u);
    return result;

if代码框中对应的是分支loc_10012D408。

__text:0x10012D408 loc_10012D408    ; CODE XREF: sub_10012CFA0+1D4↑j
__text:0x10012D408     MOV          X0, X28
__text:0x10012D40C     BL           sub_10012D49C
__text:0x10012D410     BL           sub_10012D4DC
__text:0x10012D414     MOV          X1, X0
__text:0x10012D418     ADRL         X0, $s6Reveal30ApplicationBuildTimestampErrorON ; type metadata for ApplicationBuildTimestampError
__text:0x10012D420     MOV          X2, #0
__text:0x10012D424     MOV          W3, #0
__text:0x10012D428     BL           _swift_allocError
__text:0x10012D42C     MOV          X19, X0
__text:0x10012D430     MOV          X21, X0
__text:0x10012D434     BL           _swift_willThrow

此分支在地址sub_10012CFA0+1D4有被引用,以下是引用处的汇编片段。开头调用了sub_100208AA4和sub_1002080A0,它们内部又有对_SecCodeCopySigningInformation与_kSecCodeInfoTimestamp的调用,这两函数分别负责提取应用的代码签名信息和签名时间戳。因而可得,该分支判断背后实际建立在签名校验结果的基础之上。

__text:0x10012D12C     BL       sub_100208AA4
__text:0x10012D130     CBNZ     X21, loc_10012D3F8
__text:0x10012D134     MOV      X25, X0
__text:0x10012D138     STUR     X21, [X29,#var_80]
__text:0x10012D13C     STUR     X23, [X29,#var_60]
__text:0x10012D140     MOV      X0, X20             ; id
__text:0x10012D144     BL       _objc_release
__text:0x10012D148     MOV      X8, X28
__text:0x10012D14C     MOV      X0, X25
__text:0x10012D150     BL       sub_1002080A0
__text:0x10012D154     MOV      X0, X25
__text:0x10012D158     BL       _swift_bridgeObjectRelease
__text:0x10012D15C     LDR      X8, [X26,#0x30]
__text:0x10012D160     MOV      X0, X28
__text:0x10012D164     MOV      W1, #1
__text:0x10012D168     MOV      X2, X19
__text:0x10012D16C     BLR      X8
__text:0x10012D170     CMP      W0, #1
__text:0x10012D174     B.EQ     loc_10012D408

之后在地址0x10012D170,程序将寄存器W0的值与1进行比较,随即分支判断随之生效,若W0的值等于1,则跳转至loc_10012D408。从该路径继续执行至loc_10012D438,最终触发ApplicationBuildTimestampError.noTimestamp错误。

因此,要规避该异常,只需将原本与1的比较改为与0进行比较,即可阻断报错分支。

__text:0x10012D170     CMP    W0, #0

验证废除

成功绕过启动时的代码签名校验后,应用现已能够正常运行。下面正式分析JWT签名验证机制,并尝试将其彻底废除。

根据开头的分析,服务器端对返回的JWT Token采用了RS256签名算法。而对于Apple应用,执行此类非对称加密签名验证的标准常用途径,就是调用Security框架中的SecKeyVerifySignature函数。所以,SecKeyVerifySignature自然成为本次验证废除的关键。

__stubs:0x100C4AECC ; Boolean __cdecl SecKeyVerifySignature(SecKeyRef key, SecKeyAlgorithm algorithm, CFDataRef signedData, CFDataRef signature, CFErrorRef *error)
__stubs:0x100C4AECC _SecKeyVerifySignature  ; CODE XREF: sub_1009521EC+50↑p
__stubs:0x100C4AECC                         ; sub_1009524D4+4C↑p
__stubs:0x100C4AECC     ADRP    X16, #_SecKeyVerifySignature_ptr@PAGE
__stubs:0x100C4AED0     LDR     X16, [X16,#_SecKeyVerifySignature_ptr@PAGEOFF]
__stubs:0x100C4AED4     BR      X16 ; __imp__SecKeyVerifySignature

为精准定位哪些地址上调用了它,我们直接在LLDB中对SecKeyVerifySignature设置断点,然后运行应用。

(lldb) br s -n SecKeyVerifySignature
Breakpoint 1: where = Security`SecKeyVerifySignature, address = 0x00000001a502e15c
(lldb) r
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001a502e15c Security`SecKeyVerifySignature
Security`SecKeyVerifySignature:
->  0x1a502e15c <+0>:  pacibsp 
Target 0: (Reveal) stopped.

执行finish命令至调用SecKeyVerifySignature的返回点。

(lldb) finish
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
    frame #0: 0x00000001002369b4 Reveal`___lldb_unnamed_symbol18939 + 80
Reveal`___lldb_unnamed_symbol18939:
->  0x1002369b4 <+80>: mov    x20, x0
Target 0: (Reveal) stopped.

查看相应调用栈。

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
  * frame #0: 0x00000001002369b4 Reveal`___lldb_unnamed_symbol18939 + 80
    frame #1: 0x00000001002340fc Reveal`___lldb_unnamed_symbol18827 + 216
    frame #2: 0x000000010025c0b0 Reveal`___lldb_unnamed_symbol19999 + 376
    frame #3: 0x000000010025a1f8 Reveal`___lldb_unnamed_symbol19980 + 244
    frame #4: 0x000000010025060c Reveal`___lldb_unnamed_symbol19496 + 764
    frame #5: 0x000000010024c718 Reveal`___lldb_unnamed_symbol19457 + 1248
    frame #6: 0x000000010012d2c4 Reveal`___lldb_unnamed_symbol12335 + 804
    frame #7: 0x000000010003f604 Reveal`___lldb_unnamed_symbol6634 + 196
    frame #8: 0x000000010003f920 Reveal`___lldb_unnamed_symbol6635 + 112
    ....

通过dis展示反汇编指令,可确定0x1002369b4-4地址正是调用SecKeyVerifySignature之处。

(lldb) dis
Reveal`___lldb_unnamed_symbol18939:
    ; ...
    0x1002369b0 <+76>:  bl     0x100c6a60c    ; symbol stub for: SecKeyVerifySignature
->  0x1002369b4 <+80>:  mov    x20, x0
    0x1002369b8 <+84>:  mov    x0, x23
    0x1002369bc <+88>:  bl     0x100c6b0b0    ; symbol stub for: objc_release
    0x1002369c0 <+92>:  mov    x0, x19
    0x1002369c4 <+96>:  bl     0x100c6b0b0    ; symbol stub for: objc_release
    0x1002369c8 <+100>: cmp    w20, #0x0
    0x1002369cc <+104>: cset   w0, ne
    0x1002369d0 <+108>: ldp    x29, x30, [sp, #0x30]
    0x1002369d4 <+112>: ldp    x20, x19, [sp, #0x20]
    0x1002369d8 <+116>: ldp    x22, x21, [sp, #0x10]
    0x1002369dc <+120>: ldp    x24, x23, [sp], #0x40
    0x1002369e0 <+124>: ret

于是我们再在地址0x1002369b0处设置断点,重启应用观察传入SecKeyVerifySignature的实参值。

(lldb) br s -a 0x1002369b0
Breakpoint 2: where = Reveal`___lldb_unnamed_symbol18939 + 76, address = 0x00000001002369b0
(lldb) r
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x00000001002369b0 Reveal`___lldb_unnamed_symbol18939 + 76
Reveal`___lldb_unnamed_symbol18939:
->  0x1002369b0 <+76>: bl     0x100c6a60c  ; symbol stub for: SecKeyVerifySignature
Target 0: (Reveal) stopped.

待中断触发,打印出传入实参值,结果如下。

  • x0:2048位RSA公钥。
  • x1:RS256签名算法。
  • x2:JWT Header.Payload部分。
  • x3:JWT Signature部分。
(lldb) register read x0 x1 x2 x3
      x0 = 0x0000600000c20900
      x1 = 0x00000001ff6d3030  @"algid:sign:RSA:message-PKCS1v15:SHA256"
      x2 = 0x0000600000c14ed0
      x3 = 0x0000600000c15c20

(lldb) expr -l objc -- @import CoreFoundation;
(lldb) expr -l objc -- @import Security;
(lldb) expr -l objc -O -- CFBridgingRelease(SecKeyCopyAttributes((SecKeyRef)$x0))
{
    esiz = 2048;
    priv = 0;
    ...
}
(lldb) expr -l objc -O -- [(NSData *)CFBridgingRelease(SecKeyCopyExternalRepresentation((SecKeyRef)$x0, NULL)) base64EncodedStringWithOptions:0]
MIIBCgKCAQEAnD9x77cVGbT+9+4odDK5+vqH4Z0JMJXPZ3eATw+aPPHQKj5GhHTJ2pUBUoazri0IIZWFYy8O3UfNF9awDmBc+4kfz1qBTpmYWGhKHKKDGGadIAGiUQxVu81lrqRqPbDUQFl7oryQIAoC7RosL2XlUM6pfNvUwMAlX6Eiuc0N/pba3cBbTdFNSPO+BG6hs7yVSH4q6DAHCMUxOzYE0YFS/f7XcY2bGWbWiKwvpqzNbuYX+JvAALBrS0/Ggr7FX/45c3aIiDlFrDEeFMlDwupjZqViUyU9GiPxa92j6VV1y2FJ+yepdKXVDdL7/zIft2TbskVilRBJ27sC2S6k2amdiwIDAQAB

(lldb) po (NSString *)$x1
algid:sign:RSA:message-PKCS1v15:SHA256

(lldb) expr -l objc -O -- [[NSString alloc] initWithData:(NSData *)$x2 encoding:4]
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NTgxODY4NjEuMDYwNjQ5OSwiZXhwIjoxNzU4MTg3MTYxLjA2MDY1LCJlbWFpbCI6InN2bm55NzdAZ21haWwuY29tIiwibGljZW5zZV9wYXRoIjoiXC9saWNlbnNlc1wvMjREQjBCM0ItNDhCQi0xNEE2LTkxQ0QtMDI4OTA4NDkyQ0UzIiwiY2xpZW50X2V4cGlyZXNfYXQiOjE3NTg0NDYzNjEuMDYwNjUsImxpY2Vuc2Vfc3R5bGUiOjAsIm9yZ2FuaXphdGlvbl9zdHlsZSI6MCwiaWRlbnRpZmllciI6IjRpR0R4bFBjaE1kMmsrK3lzOWVpTzE4eFJOYUdyV0xGWjRBeWRCaUJyOT0iLCJpc19zdWJzY3JpcHRpb25fdXNhYmxlIjpmYWxzZSwic3ViIjoiRDcxOEU1RDctOUE2OC0xNEY0LUNFRDEtNENFMkUyNEQwNTkwIiwib3JnYW5pemF0aW9uX25hbWUiOiJzdm5ueTc3QGdtYWlsLmNvbSIsImZpbmdlcnByaW50IjoiVTBoQk1qVTJJR1JwWjJWemREb2dNamxpTWpVek5XTmpNR00xWlRGbE56Y3pZV0V3TldGa01EUTFabUkzTURObVptSmpObVUwT1dVMlpXUXdZVFkyTVdVeFl6WmhaR1UzTkRNd056UmlNdz09IiwibGljZW5zZV9leHBpcnkiOjE3NTkzOTY0MDkuMDExOTU3MiwiaXNzIjoiUmV2ZWFsIGJ5IE1hbnRlbCJ9

(lldb) expr -l objc -O -- [(NSData *)$x3 base64EncodedStringWithOptions:0]
OPReNgGoxsJora0jvmLlq37rbOqNmQiHNtgFLwFZ0RBD5wiLz6xkXpPHWlk6Ot5Y8wisld_-F6muEwEeqPyPcGKWNuD_Ot4bnG8uYx4rUx-CeiW33saDDl-mMX1GCF50917wWJH00zYzfZtZMIZMwupsUutUswDm9S6DFwUyArvXNBagUc9Rvi6-X1yBm2BHBLvOkp8p726mMQphk8MJ1GcxiwbuAIcq3Wg2FxZronT234XG2gWOAzYkubKF4GbTP6kjlpt3jSGoEk0qNKwsa6mK7WNicIVARy4-6_FzGjPvzNx9O9qsHPy8LE-fad6YaG1JmikA-XQw9QsdITttjK

ni单步执行,进入SecKeyVerifySignature后finish至调用返回地址0x1002369b4,此时查看SecKeyVerifySignature的返回值。

->  0x1002369b0 <+76>: bl     0x100c6a60c  ; symbol stub for: SecKeyVerifySignature
(lldb) ni
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001a502e15c Security`SecKeyVerifySignature
Security`SecKeyVerifySignature:
->  0x1a502e15c <+0>:  pacibsp 
(lldb) finish
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
    frame #0: 0x00000001002369b4 Reveal`___lldb_unnamed_symbol18939 + 80
Reveal`___lldb_unnamed_symbol18939:
->  0x1002369b4 <+80>: mov    x20, x0
(lldb) register read x0
      x0 = 0x0000000000000001

因为并未篡改JWT Payload数据,SecKeyVerifySignature自然是返回1以表签名验证成功。

__text:0x1002369B0     BL      _SecKeyVerifySignature
__text:0x1002369B4     MOV     X20, X0
; ...
__text:0x1002369C8     CMP     W20, #0
__text:0x1002369CC     CSET    W0, NE

而基于后续逻辑,若要废除JWT签名验证机制,可操作的手法并不局限于一种。

->  0x1002369b4 <+80>: mov    x20, x0
(lldb) register read w20 x20
     w20 = 0xff6d3030
     x20 = 0x00000001ff6d3030  @"algid:sign:RSA:message-PKCS1v15:SHA256"
(lldb) ni
->  0x1002369b8 <+84>: mov    x0, x23
(lldb) register read w20 x20
     w20 = 0x00000000
     x20 = 0x0000000000000000

既可直接在MOV赋值之时,改为恒赋1;也可在CMP比较之时,改为与1比较;还可在最后CSET条件设置之时,将NE改为EQ。以下三选一即可。

__text:0x1002369B4     MOV     X20, #1
__text:0x1002369C8     CMP     W20, #1
__text:0x1002369CC     CSET    W0, EQ

Patch完后,别忘了对应用重签名。

指纹生成

虽然JWT签名验证已被废除,但在授权信息中还有一个与设备唯一绑定的fingerprint指纹字段,若设备指纹与本机不符,应用仍会拒绝授权。因此,我们还需还原出指纹的生成算法。

首先,从请求流量中抓取到fingerprint字段值,注意这只是本机值,即便抓到也不能用于其他设备。对该值Base64解码,发现它并非随机字节,而是可读文本,固定前缀为“SHA256 digest: ”,后面紧随一段64字符的十六进制串,明显像是一个32字节的SHA256摘要的十六进制表示。

$ echo -n 'U0hBMjU2IGRpZ2VzdDogMjliMjUzNWNjMGM1ZTFlNzczYWEwNWFkMDQ1ZmI3MDNmZmJjNmU0OWU2ZWQwYTY2MWUxYzZhZGU3NDMwNzRiMw==' | base64 -d
SHA256 digest: 29b2535cc0c5e1e773aa05ad045fb703ffbc6e49e6ed0a661e1c6ade743074b3

由此,初步推断fingerprint是由:输入数据 → SHA256 → 十六进制 → 拼接前缀 → Base64编码,最终构成。而在IDA Pro Imports表中也看到了CryptoKit框架,并引入了与流式哈希相关接口。

CryptoKit.framework
 Versions
  A
   CryptoKit
    0x101414A10        dispatch thunk of HashFunction.init()
    0x101414A00        dispatch thunk of HashFunction.update(bufferPointer:)
    0x101414A08        dispatch thunk of HashFunction.finalize()

那么,为验证如上推断,我们在LLDB中采取下面两条追踪策略。

  1. 对base64EncodedString设置断点,以定位fingerprint的最终生成点并回溯堆栈。
  2. 对HashFunction.update与HashFunction.finalize设置断点,捕获被哈希的数据与最终摘要。

按照以上第一点,先对base64EncodedString设置断点,重启应用。

(lldb) br s -n base64EncodedString
Breakpoint 3: where = Foundation`Foundation.Data.base64EncodedString(options: __C.NSDataBase64EncodingOptions) -> Swift.String, address = 0x00000001a3ba3958
(lldb) r
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
    frame #0: 0x00000001a3ba3958 Foundation`Foundation.Data.base64EncodedString(options: __C.NSDataBase64EncodingOptions) -> Swift.String
Foundation`Foundation.Data.base64EncodedString(options: __C.NSDataBase64EncodingOptions) -> Swift.String:
->  0x1a3ba3958 <+0>:  pacibsp 
Target 0: (Reveal) stopped.

随后中断在base64EncodedString入口地址,finish至调用该函数的返回地址0x100257730。

(lldb) finish
Process 22085 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
    frame #0: 0x0000000100257730 Reveal`___lldb_unnamed_symbol19843 + 892
Reveal`___lldb_unnamed_symbol19843:
->  0x100257730 <+892>: mov    x19, x0
Target 0: (Reveal) stopped

不妨将base64EncodedString的返回值打印出来,可发现与前面的fingerprint一摸一样。

(lldb) po $x0
108
(lldb) po $x1
U0hBMjU2IGRpZ2VzdDogMjliMjUzNWNjMGM1ZTFlNzczYWEwNWFkMDQ1ZmI3MDNmZmJjNmU0OWU2ZWQwYTY2MWUxYzZhZGU3NDMwNzRiMw==

bt查看此时的调用栈,看到上游的调用链与后续应关注的函数调用地址。

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
  * frame #0: 0x0000000100257730 Reveal`___lldb_unnamed_symbol19843 + 892
    frame #1: 0x00000001000a1a58 Reveal`___lldb_unnamed_symbol9050 + 472

逐帧退栈,追踪至frame #1(地址0x1000A1A58-4)所调函数,即sub_1002573B4。

__text:0x1000A1A54     BL    sub_1002573B4

在sub_1002573B4当中果然见到有关SHA256哈希的符号调用。

__text:0x1002573EC     BL    _$s9CryptoKit6SHA256VMa ; type metadata accessor for SHA256

__text:0x100257428     BL    _$s9CryptoKit12SHA256DigestVMa ; type metadata accessor for SHA256Digest

; ...

我们先从sub_1002573B4前面部分开始看起,开头调用了下面几个函数。

__text:0x10025745C     BL    sub_1002577A4

__text:0x100257468     BL    _getuid

__text:0x100257484     BL    _$ss23CustomStringConvertibleP11descriptionSSvgTj ; dispatch thunk of CustomStringConvertible.description.getter

点进sub_1002577A4中,似乎是个获取网卡MAC地址的函数;至于getuid,顾名思义,大概用于获取当前用户UID。那就在LLDB中一验究竟,对地址0x10025745C设置断点,并重启。

(lldb) br s -a 0x10025745C
Breakpoint 4: where = Reveal`___lldb_unnamed_symbol19843 + 168, address = 0x000000010025745c
(lldb) r
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
    frame #0: 0x000000010025745c Reveal`___lldb_unnamed_symbol19843 + 168
Reveal`___lldb_unnamed_symbol19843:
->  0x10025745c <+168>: bl     0x1002577a4     ; ___lldb_unnamed_symbol19844
Target 0: (Reveal) stopped.

ni单步往下执行,过程中打印出sub_1002577A4返回值,为0x00001f88c300c968,与实际MAC地址68:c9:00:c3:88:1f相比,顺序是相反着的,这并非值错误,而是因为大小端存储方式差异导致的。

->  0x10025745c <+168>: bl     0x1002577a4     ; ___lldb_unnamed_symbol19844
(lldb) ni
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
    frame #0: 0x0000000100257460 Reveal`___lldb_unnamed_symbol19843 + 172
Reveal`___lldb_unnamed_symbol19843:
->  0x100257460 <+172>: mov    x27, x0
    0x100257464 <+176>: mov    x28, x1
    0x100257468 <+180>: bl     0x100c6ad50     ; symbol stub for: getuid
Target 0: (Reveal) stopped.
(lldb) register read x0
      x0 = 0x00001f88c300c968
$ ifconfig en0 | grep ether | awk '{print $2}'
68:c9:00:c3:88:1f

再看getuid函数,其返回值是0x00000000000001f5,正是当前用户UID的十六进制。

....
(lldb) ni
->  0x100257468 <+180>: bl     0x100c6ad50     ; symbol stub for: getuid
(lldb) ni
->  0x10025746c <+184>: stur   w0, [x29, #-0x90]
(lldb) register read x0
      x0 = 0x00000000000001f5
$ id -u
501
$ printf "0x%x" 501
0x1f5

往下接着运行,直至地址0x100257484,此处调用了Swift.CustomStringConvertible.description,单步运行并查看返回值,得到0x0000000000313035。

(lldb) ni
->  0x100257484 <+208>: bl     0x100c6940c  ; symbol stub for: dispatch thunk of Swift.CustomStringConvertible.description.getter : Swift.String
(lldb) ni
->  0x100257488 <+212>: bl     0x100256bd8  ; ___lldb_unnamed_symbol19838
(lldb) register read x0
      x0 = 0x0000000000313035

将该十六进制值按ASCII解码后,结果为105。结合前面提及的大小端存储差异,该函数作用是返回UID的字符串化结果。

$ echo 0x313035 | xxd -r -p
105

接下来我们将目光转向HashFunction.update,由于这是Swift的一个协议方法,没法像C或Objective-C函数那样直接按名下断点,所以需把断点设至该方法的跳转存根地址0x100C66C40。

__stubs:0x100C66C40 ; __int64 __fastcall dispatch thunk of HashFunction.update(bufferPointer:)(_QWORD, _QWORD, _QWORD, _QWORD)
__stubs:0x100C66C40 _$s9CryptoKit12HashFunctionP6update13bufferPointerySW_tFTj
__stubs:0x100C66C40             ; CODE XREF: sub_1002570B4+D4↑p
__stubs:0x100C66C40             ; sub_1002570B4+1B4↑p ...
__stubs:0x100C66C40     ADRP    X16, #_$s9CryptoKit12HashFunctionP6update13bufferPointerySW_tFTj_ptr@PAGE
__stubs:0x100C66C44     LDR     X16, [X16,#_$s9CryptoKit12HashFunctionP6update13bufferPointerySW_tFTj_ptr@PAGEOFF]
__stubs:0x100C66C48     BR      X16 ; __imp__$s9CryptoKit12HashFunctionP6update13bufferPointerySW_tFTj
(lldb) br dis 
All breakpoints disabled. (5 breakpoints)
(lldb) br s -a 0x100C66C40
Breakpoint 6: where = Reveal`symbol stub for: dispatch thunk of CryptoKit.HashFunction.update(bufferPointer: Swift.UnsafeRawBufferPointer) -> (), address = 0x0000000100c66c40
(lldb) r
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1
    frame #0: 0x0000000100c66c40 Reveal`dispatch thunk of CryptoKit.HashFunction.update(bufferPointer: Swift.UnsafeRawBufferPointer) -> ()
Reveal`dispatch thunk of CryptoKit.HashFunction.update(bufferPointer: Swift.UnsafeRawBufferPointer) -> ():
->  0x100c66c40 <+0>: adrp   x16, 902

Reveal`dispatch thunk of CryptoKit.HashFunction.finalize() -> τ_0_0.Digest:
    0x100c66c4c <+0>: adrp   x16, 902
Target 0: (Reveal) stopped.

待触发中断,打印出HashFunction.update的输入buffer,发现寄存器x0指向的内存地址0x16fdfdff8,之中的内容正是MAC地址与UID的拼接。

(lldb) register read x0
      x0 = 0x000000016fdfdff8
(lldb) memory read $x0
0x16fdfdff8: 68 c9 00 c3 88 1f 35 30 31 00 00 00 00 00 00 00
0x16fdfe008: 44 00 6f a3 ac a0 10 ad 50 e0 df 6f 01 00 00 00

查看此时调用栈,与base64EncodedString调用栈对比,发现分水岭在于frame #3(0x00000001000a1a58),即base64EncodedString调用栈中的frame #1,这也证明了前面对该处所调函数sub_1002573B4进行分析是正确的。

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1
  * frame #0: 0x0000000100c66c40 Reveal`dispatch thunk of CryptoKit.HashFunction.update(bufferPointer: Swift.UnsafeRawBufferPointer) -> ()
    frame #1: 0x000000010025718c Reveal`___lldb_unnamed_symbol19839 + 216
    frame #2: 0x00000001002576bc Reveal`___lldb_unnamed_symbol19843 + 776
    frame #3: 0x00000001000a1a58 Reveal`___lldb_unnamed_symbol9050 + 472
    ....

最后,对HashFunction.finalize设置断点,同样设置在其跳转存根地址,是0x100C66C4C。重启应用,待抵达其入口地址,finish执行完至调用返回点。

(lldb) br dis
All breakpoints disabled. (6 breakpoints)
(lldb) br s -a 0x100C66C4C
Breakpoint 7: where = Reveal`symbol stub for: dispatch thunk of CryptoKit.HashFunction.finalize() -> τ_0_0.Digest, address = 0x0000000100c66c4c
(lldb) r
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 7.1
    frame #0: 0x0000000100c66c4c Reveal`dispatch thunk of CryptoKit.HashFunction.finalize() -> τ_0_0.Digest
Reveal`dispatch thunk of CryptoKit.HashFunction.finalize() -> τ_0_0.Digest:
->  0x100c66c4c <+0>: adrp   x16, 902

Reveal`dispatch thunk of CryptoKit.HashFunction.init() -> τ_0_0:
    0x100c66c58 <+0>: adrp   x16, 902
Target 0: (Reveal) stopped.

(lldb) finish
* thread #1, queue = 'com.apple.main-thread', stop reason = step out
    frame #0: 0x00000001002576dc Reveal`___lldb_unnamed_symbol19843 + 808
Reveal`___lldb_unnamed_symbol19843:
->  0x1002576dc <+808>: ldur   x8, [x29, #-0xd0]
Target 0: (Reveal) stopped.

finalize()的返回值被写入栈上的临时缓冲区,读取当前栈指针sp所指向的内存,即可看到该缓冲区的内容,正是此前Base64解码后得到的那32字节SHA256摘要。

(lldb) memory read $sp
0x16fdfe050: 29 b2 53 5c c0 c5 e1 e7 73 aa 05 ad 04 5f b7 03
0x16fdfe060: ff bc 6e 49 e6 ed 0a 66 1e 1c 6a de 74 30 74 b3

经过动态追踪与内存比对,我们证实了先前对指纹生成算法的猜想:将MAC地址与当前用户UID的字符串形式直接拼接,形成一个9字节的二进制数据块;然后对该数据块进行SHA256哈希,得到32字节摘要;再将摘要转换为十六进制字符串,并添加“SHA256 digest: ”前缀;最后对整段文本Base64编码,生成最终的fingerprint。

凭证植入



在前面LLDB调试运行应用的过程中,相信已经见到过上面的Keychain弹窗,这一现象表明应用通过网络获取到授权凭证信息后,将其存储至Keychain当中了。

使用security结合find-generic-password子命令可从Keychain中一窥凭证究竟,如下password中的access_token就是来自网络响应中的access_token。

$ sudo security find-generic-password -s "com.ittybittyapps.Reveal2" -a "credential" -g
keychain: "/Users/$USER/Library/Keychains/login.keychain-db"
version: 512
class: "genp"
attributes:
    0x00000007 <blob>="com.ittybittyapps.Reveal2"
    0x00000008 <blob>=<NULL>
    "acct"<blob>="credential"
    "cdat"<timedate>=0x32303235303931393038333331335A00  "20250919083313Z\000"
    "crtr"<uint32>=<NULL>
    "cusi"<sint32>=<NULL>
    "desc"<blob>=<NULL>
    "gena"<blob>=<NULL>
    "icmt"<blob>=<NULL>
    "invi"<sint32>=<NULL>
    "mdat"<timedate>=0x32303235303931393038333332325A00  "20250919083322Z\000"
    "nega"<sint32>=<NULL>
    "prot"<blob>=<NULL>
    "scrp"<sint32>=<NULL>
    "svce"<blob>="com.ittybittyapps.Reveal2"
    "type"<uint32>=<NULL>
password: "{"state":"present","content":{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3NTgxODY4NjEuMDYwNjQ5OSwiZXhwIjoxNzU4MTg3MTYxLjA2MDY1LCJlbWFpbCI6InN2bm55NzdAZ21haWwuY29tIiwibGljZW5zZV9wYXRoIjoiXC9saWNlbnNlc1wvMjREQjBCM0ItNDhCQi0xNEE2LTkxQ0QtMDI4OTA4NDkyQ0UzIiwiY2xpZW50X2V4cGlyZXNfYXQiOjE3NTg0NDYzNjEuMDYwNjUsImxpY2Vuc2Vfc3R5bGUiOjAsIm9yZ2FuaXphdGlvbl9zdHlsZSI6MCwiaWRlbnRpZmllciI6IjRpR0R4bFBjaE1kMmsrK3lzOWVpTzE4eFJOYUdyV0xGWjRBeWRCaUJyOT0iLCJpc19zdWJzY3JpcHRpb25fdXNhYmxlIjpmYWxzZSwic3ViIjoiRDcxOEU1RDctOUE2OC0xNEY0LUNFRDEtNENFMkUyNEQwNTkwIiwib3JnYW5pemF0aW9uX25hbWUiOiJzdm5ueTc3QGdtYWlsLmNvbSIsImZpbmdlcnByaW50IjoiVTBoQk1qVTJJR1JwWjJWemREb2dNamxpTWpVek5XTmpNR00xWlRGbE56Y3pZV0V3TldGa01EUTFabUkzTURObVptSmpObVUwT1dVMlpXUXdZVFkyTVdVeFl6WmhaR1UzTkRNd056UmlNdz09IiwibGljZW5zZV9leHBpcnkiOjE3NTkzOTY0MDkuMDExOTU3MiwiaXNzIjoiUmV2ZWFsIGJ5IE1hbnRlbCJ9.OPReNgGoxsJora0jvmLlq37rbOqNmQiHNtgFLwFZ0RBD5wiLz6xkXpPHWlk6Ot5Y8wisld_-F6muEwEeqPyPcGKWNuD_Ot4bnG8uYx4rUx-CeiW33saDDl-mMX1GCF50917wWJH00zYzfZtZMIZMwupsUutUswDm9S6DFwUyArvXNBagUc9Rvi6-X1yBm2BHBLvOkp8p726mMQphk8MJ1GcxiwbuAIcq3Wg2FxZronT234XG2gWOAzYkubKF4GbTP6kjlpt3jSGoEk0qNKwsa6mK7WNicIVARy4-6_FzGjPvzNx9O9qsHPy8LE-fad6YaG1JmikA-XQw9QsdITttjK","refresh_token":"e8991018349asc3c12dd4812d2345650fdfdaad585ed0cdc1eb9010397684ec6"}}"

既然能够通过security命令查看应用的凭证信息,那必然也可以通过security来植入凭证,用到的子命令是add-generic-password。不过,需先构造出access_token字段值,基于前一节对指纹生成算法的分析,先把本机fingerprint生成出来。

$ ifconfig en0 | grep ether | awk '{print $2}'
68:c9:00:c3:88:1f

$ printf "%d" `id -u` | xxd -p
353031

$ printf '\x68\xc9\x00\xc3\x88\x1f\x35\x30\x31' | shasum -a 256
29b2535cc0c5e1e773aa05ad045fb703ffbc6e49e6ed0a661e1c6ade743074b3  -

$ echo -n 'SHA256 digest: 29b2535cc0c5e1e773aa05ad045fb703ffbc6e49e6ed0a661e1c6ade743074b3' | base64
U0hBMjU2IGRpZ2VzdDogMjliMjUzNWNjMGM1ZTFlNzczYWEwNWFkMDQ1ZmI3MDNmZmJjNmU0OWU2ZWQwYTY2MWUxYzZhZGU3NDMwNzRiMw==

然后在原有的试用授权信息基础之上修改关键字段值,无非是将0改为1、false改为true、小值改大。

{
  "is_subscription_usable": true,
  "fingerprint": "U0hBMjU2IGRpZ2VzdDogMjliMjUzNWNjMGM1ZTFlNzczYWEwNWFkMDQ1ZmI3MDNmZmJjNmU0OWU2ZWQwYTY2MWUxYzZhZGU3NDMwNzRiMw==",
  "exp": 253392455349,
  "license_path": "/licenses/24DB0B3B-48BB-14A6-91CD-028908492CE3",
  "iss": "Reveal by Mantel",
  "organization_style": 1,
  "license_expiry": 253392455349,
  "sub": "D718E5D7-9A68-14F4-CED1-4CE2E24D0590",
  "organization_name": "F",
  "client_expires_at": 253392455349,
  "iat": 1757680656,
  "license_style": 1,
  "identifier": "4iGDxlPchMd2k++ys9eiO18xRNaGrWLFZ4AydBiBr9=",
  "email": "K'ed_by_0xf"
}

中间构造access_token的其他操作省略,最终的凭证植入命令如下。

$ sudo security add-generic-password -s "com.ittybittyapps.Reveal2" -a "credential" -U -w '{"state": "present", "content": {"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc19zdWJzY3JpcHRpb25fdXNhYmxlIjp0cnVlLCJmaW5nZXJwcmludCI6IlUwaEJNalUySUdScFoyVnpkRG9nTWpsaU1qVXpOV05qTUdNMVpURmxOemN6WVdFd05XRmtNRFExWm1JM01ETm1abUpqTm1VME9XVTJaV1F3WVRZMk1XVXhZelpoWkdVM05ETXdOelJpTXc9PSIsImV4cCI6MjUzMzkyNDU1MzQ5LCJsaWNlbnNlX3BhdGgiOiIvbGljZW5zZXMvMjREQjBCM0ItNDhCQi0xNEE2LTkxQ0QtMDI4OTA4NDkyQ0UzIiwiaXNzIjoiUmV2ZWFsIGJ5IE1hbnRlbCIsIm9yZ2FuaXphdGlvbl9zdHlsZSI6MSwibGljZW5zZV9leHBpcnkiOjI1MzM5MjQ1NTM0OSwic3ViIjoiRDcxOEU1RDctOUE2OC0xNEY0LUNFRDEtNENFMkUyNEQwNTkwIiwib3JnYW5pemF0aW9uX25hbWUiOiJGIiwiY2xpZW50X2V4cGlyZXNfYXQiOjI1MzM5MjQ1NTM0OSwiaWF0IjoxNzU3NjgwNjU2LCJsaWNlbnNlX3N0eWxlIjoxLCJpZGVudGlmaWVyIjoiNGlHRHhsUGNoTWQyaysreXM5ZWlPMTh4Uk5hR3JXTEZaNEF5ZEJpQnI5PSIsImVtYWlsIjoiSydlZF9ieV8weGYifQ==.dummy-signature", "refresh_token": "6"}}'

由于应用每次启动之初,都会携带access_token请求activation/verify接口来进行激活验证。因此,还需屏蔽其网络通信,确保应用只依赖植入的本地凭证。

网络钩子

回顾前面所有,蓦然回首,又想到一种相对简单、优雅的破解方式,即最常见的中间人劫持,网络Hook。通过Hook -[NSURLSession dataTaskWithRequest:completionHandler:]方法,掌控应用与服务端全部的网络通信,并实现以下两点。

  1. 对于首次激活:从请求中提取出fingerprint参数值,将原始响应篡改为有效授权信息的响应,以及篡改状态码为200。
  2. 对于后续验证:直接Drop掉激活验证请求,应用在得不到回应的情况下,会默认本地凭证依然有效。

当然,这一切依然需要建立在废除签名验证的基础之上。介于文章篇幅,Hook代码就不放出。

惊喜彩蛋

无论是基于前述大量分析而施行的凭证植入,还是根据上述逻辑,将编写的网络Hook代码编译成Dylib注入应用中,殊途同归,最终目的都是一致的。不过,彩蛋环节却是后者独有的。

现在,打开应用,确保Mac的声音已开启。在弹出的激活窗口中输入任意Key,点击激活的瞬间,一阵欢庆声顿时响起,热闹非凡,用以庆祝破解成功再合适不过了。

免费评分

参与人数 7吾爱币 +9 热心值 +6 收起 理由
PhiFever + 1 + 1 谢谢@Thanks!
IcePlume + 1 + 1 我很赞同!
zwo + 2 + 1 谢谢@Thanks!
Kristenvivio + 1 我很赞同!
uuwatch + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
小朋友呢 + 2 + 1 鼓励转贴优秀软件安全工具和文档!
buluo533 + 1 + 1 用心讨论,共获提升!

查看全部评分

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

推荐
284406022 发表于 2025-10-16 11:39
作为一名windows 逆向开发, 我居然可以看懂, 而且看得津津有味, 学习到了很多:
逆向的思路和windows差不多:  抓包, 栈回溯, API断点。这些过程中,也学到了arm汇编, 比如 BL相当于 x86 的 CALL,还有函数返回值存在X0中, 函数参数存在X0 X1中,
感谢楼主的文章, 希望能和其交流学习, 我近期也有学习macos逆向的方向
沙发
xixicoco 发表于 2025-9-22 00:55
3#
Kristenvivio 发表于 2025-9-25 07:58
4#
songxng10000 发表于 2025-9-26 15:54
太强悍了大佬
5#
H7ang0 发表于 2025-9-28 11:42
深奥的教程编译成dylib完美解决
6#
scorpionslau 发表于 2025-9-28 15:26
受教受教了 感谢分享分析知识
7#
amwquhwqas128 发表于 2025-9-28 23:07
太过深奥不怎么看得懂, 但还是非常感谢文章
8#
zhufuziji 发表于 2025-10-4 13:59
大家的期待
9#
liangtian 发表于 2025-10-4 18:32
大神牛逼,分享的知识很受用。
10#
wang1anfu 发表于 2025-10-5 04:32
完全看不懂。谢谢大佬
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

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

GMT+8, 2026-2-1 16:36

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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