一、什么是 Protocol Witness Table?

  • 我们都知道 C 函数调用是静态派发,简单来说可以理解为是用汇编命令 call $address 来实现,这种方式效率最高,但是灵活性不够。
  • OC 的方法调用完全是基于动态派发,总是调用 objc_msgSend 实现,这种方式非常灵活,允许各种 Hook 黑科技,但是流程最长,效率最低。
  • 在 Swift 中,协议方法的调用,使用协议方法表的方式完成,也就是 Protocol Witness Table,下文简称 PWT。
  • 现有如下代码:
protocol Drawable {func draw() -> Int
}struct Point: Drawable {var x, y: Intfunc draw() -> Int {return x + y}
}struct Line: Drawable {var length: Intfunc draw() -> Int {return length}
}func foo() -> Int {let p: Drawable = xxxreturn p.draw()
}
  • 在 foo 函数中,变量 p 并没有明确的类型,只知道它遵守 Drawable 协议,实现了 draw 方法。但是编译时并不能知道,调用的是结构体 Line 还是 Point 的 draw 方法。
  • 因此,PWT 的实现方式是:每个类都会有一个方法表(通过数组来实现),里面保存了它用于实现协议的函数的地址,只要知道一个类的信息和函数信息,就可以实现函数调用,这个方法表,就是 PWT。

二、PWT 的汇编实现

  • 除了从理论上了解 PWT 的概念,还可以从汇编角度来实际感受一下。现有如下代码:
protocol Drawable {func draw() -> Int
}struct Point: Drawable {var x, y: Intfunc draw() -> Int {return x + y}
}func foo() -> Int {let p: Drawable = Point(x: 1, y: 2)return p.draw()
}
  • Debug 模式下的汇编代码:
swift-ui-test`foo():0x10518a860 <+0>:   push   rbp0x10518a861 <+1>:   mov    rbp, rsp0x10518a864 <+4>:   push   r130x10518a866 <+6>:   sub    rsp, 0x480x10518a86a <+10>:  mov    edi, 0x10x10518a86f <+15>:  mov    esi, 0x20x10518a874 <+20>:  call   0x10518ae50               ; swift_ui_test.Point.init(x: Swift.Int, y: Swift.Int) -> swift_ui_test.Point at ContentView.swift:270x10518a879 <+25>:  lea    rcx, [rip + 0x1948]       ; type metadata for swift_ui_test.Point0x10518a880 <+32>:  mov    qword ptr [rbp - 0x18], rcx0x10518a884 <+36>:  lea    rcx, [rip + 0x189d]       ; protocol witness table for swift_ui_test.Point : swift_ui_test.Drawable in swift_ui_test0x10518a88b <+43>:  mov    qword ptr [rbp - 0x10], rcx0x10518a88f <+47>:  mov    qword ptr [rbp - 0x30], rax0x10518a893 <+51>:  mov    qword ptr [rbp - 0x28], rdx0x10518a897 <+55>:  mov    rax, qword ptr [rbp - 0x18]0x10518a89b <+59>:  mov    rcx, qword ptr [rbp - 0x10]0x10518a89f <+63>:  lea    rdx, [rbp - 0x30]0x10518a8a3 <+67>:  mov    rdi, rdx0x10518a8a6 <+70>:  mov    rsi, rax0x10518a8a9 <+73>:  mov    qword ptr [rbp - 0x38], rax0x10518a8ad <+77>:  mov    qword ptr [rbp - 0x40], rcx0x10518a8b1 <+81>:  mov    qword ptr [rbp - 0x48], rdx0x10518a8b5 <+85>:  call   0x10518ae60               ; __swift_project_boxed_opaque_existential_1 at <compiler-generated>0x10518a8ba <+90>:  mov    rcx, qword ptr [rbp - 0x40]0x10518a8be <+94>:  mov    rdx, qword ptr [rcx + 0x8]0x10518a8c2 <+98>:  mov    r13, rax0x10518a8c5 <+101>: mov    rdi, qword ptr [rbp - 0x38]0x10518a8c9 <+105>: mov    rsi, rcx0x10518a8cc <+108>: call   rdx
->  0x10518a8ce <+110>: mov    rdi, qword ptr [rbp - 0x48]0x10518a8d2 <+114>: mov    qword ptr [rbp - 0x50], rax0x10518a8d6 <+118>: call   0x10518aec0               ; __swift_destroy_boxed_opaque_existential_1 at <compiler-generated>0x10518a8db <+123>: mov    rax, qword ptr [rbp - 0x50]0x10518a8df <+127>: add    rsp, 0x480x10518a8e3 <+131>: pop    r130x10518a8e5 <+133>: pop    rbp0x10518a8e6 <+134>: ret
  • 首先按照函数调用来分割下,这里实现了结构体的初始化工作:
0x10518a86a <+10>:  mov    edi, 0x1
0x10518a86f <+15>:  mov    esi, 0x2
0x10518a874 <+20>:  call   0x10518ae50               ; swift_ui_test.Point.init(x: Swift.Int, y: Swift.Int) -> swift_ui_test.Point at ContentView.swift:27
  • 根据结构体的调用惯例,可以知道返回值是通过 rax 和 rdx 两个寄存器返回的,当然也可以看下这个函数的内部实现来验证下,可以看出,Debug 模式下对于理解汇编代码和进行反汇编都是非常友好的,非常耿直的用一个函数调用告诉我们这里实在创建结构体实例;如果是 Release 模式,大概率是直接对 rax 和 rdx 赋值。
  • 接下来分别把 metadata 和 Point 类的 PWT 表取出,存到栈上,注意到下一个 call 的函数是 __swift_project_boxed_opaque_existential_1 at ,它的存在是由于我们的这种写法导致:
let p: Drawable = Point(x: 1, y: 2)
  • 这里的 p 就是一个 existential 对象,Drawble 协议是一个 existential type,简单说结论,这个函数调用以后,入参寄存器 rdi 的内容会被赋值给 rax 寄存器来当做返回值。注意这个函数的入参 rdi 寄存器,是由下面几个关键路径构成的:
0x10518a89f <+63>:  lea    rdx, [rbp - 0x30]
0x10518a8a3 <+67>:  mov    rdi, rdx
  • 因此返回值 rax ,其实就是栈基址 rbp 减掉 0x30,这个地址内存贮的值,是结构体的第一个成员变量 x = 1,顺便说一下,这个地址向上(高地址方向)偏移 8 字节,存储的是第二个成员变量 y = 2。
  • 下一个关键操作是 call rdx,它的取值来源是:
0x1073be8be <+94>:  mov    rdx, qword ptr [rcx + 0x8]
  • 这里的 rcx 经过几次存储和取出,可以跟踪到它最初的源头,就是:
0x1073be884 <+36>:  lea    rcx, [rip + 0x189d]       ; protocol witness table for swift_ui_test.Point : swift_ui_test.Drawable in swift_ui_test
  • 从逻辑上看,调用了 PWT 内存地址 + 0x8 位置的函数,具体如下:

  • 分析一下:
    • 首先看下 [rip + 0x189d] 的值是多少,在执行这行命令时,rip 的值是下一行命令的地址,即 0x1073be88b,相加后得到 0x000000010518c128;
    • 由于 Hopper、MachoView 等工具只能显示相对便宜,因此要先减去当前程序在内存中的偏移,可以用 image list swift-ui-test 来查看;
    • 得到结果是 0x4128。
  • 因此 0x4128 就是 Point 结构体的 PWT 的位置,可以在 Hopper 中验证下:

  • 这里其实是一个指针数组,第一个指针是 0x100003998,内容如下,暂时没有深入研究其中存储内容的含义,但是可以看出名字是:protocol conformance descriptor for swift_ui_test.Point : swift_ui_test.Drawable in swift_ui_test:

  • 第二个指针是 0x100002ff0,跳转过去看下:

  • 从 demangle 后的结果也能看出来,这是一个遵守了协议的证明(Protocol Witness),遵守的协议函数是:Drawable.draw() -> Swift.Int,结构体是 Point,协议名是 Drawable,因此 call rdx 实际上就是调用 call 0x100002ff0。
  • 再来对比下入参和参数,rax 被作为 r13 传入,函数内部分别把 r13 和 r13 + 8 的位置读出来,放入 rdi 和 rsi 寄存器。正如前文所述,r13/rax 这个地址上,存储的是 x 的值,+0x8 则存储了 y 的值,因此可以理解为把结构体 p 传入。
  • 最后调用 $s13swift_ui_test5PointV4drawSiyF 这个函数符号,内部逻辑有点啰嗦,猜测是 Debug 环境导致,但本质上就是一个加法运算。

三、结论

  • PWT 是为了解决协议方法调用在编译时无法确定地址,而引入的中间层;
  • 每个遵守了协议的类,都会有自己的 PWT。遵守的协议中函数越多,PWT 中存储的函数地址就越多;
  • 准确来说,PWT 是指针数组,但是第一个指针并不是函数指针,而是 protocol conformance descriptor,从第二个开始才是函数指针;
  • 对协议方法的调用,首先会调用一个 PWT address + offset 这个函数,这个函数被叫做 protocol witness,它的内部会做一些参数处理,最后再调用真实的函数;
  • 对于实际被调用的来说,只看它的内部实现,无法和其它函数做出区分,但是可以观察它的 caller,如果是一个 protocol witness 就可以说明。

iOS逆向之Protocol Witness Table的汇编实现原理相关推荐

  1. IOS逆向--使用IDA的Patch更改汇编或二进制码并写入项目和deb重新打包

    本次遇到一个项目,因为是逆向 不能拿到项目的源码,需要改的也是很简单的判断语句,但是也把我这个菜逼弄懵逼了.还好有个如师长的逆向大佬指导,我才去了解这个Patch的功能.接下来我以一个小案例来实现. ...

  2. iOS逆向一:数字签名苹果应用双重签名原理应用重签名

    hash hash(哈希)算法是指将任意长度的文本,通过一个算法后得到一个固定长度的文本(也可能是二进制数据),哈希其实是一种思想,所有符合这种思想的算法都可以称之为哈希算法(不如MD5,sha1,s ...

  3. iOS 逆向之ARM汇编

    最近对iOS逆向工程很感兴趣. 目前iOS逆向的书籍有: <Hacking and Securing IOS Applications>, <iOS Hacker's Handboo ...

  4. IOS逆向学习-Tweak

    IOS逆向学习-Tweak 1. theos指令及可能遇到问题 2. thes的实战练习 2.1 将桌面的更新数字去掉 2.2. 给微信发现界面增加两行功能 2.2.1 hook代码语法知识 2.2. ...

  5. android微信逆向工程,iOS逆向 - 微信自动添加好友

    相关源码:Github地址 一.前言 本篇主要实现在微信上自动添加好友,从而熟悉 iOS 逆向分析的过程. 二. 工具 2.1 MacBook 软件 制作 Tweak 的工具 端口转发,可以让我们通过 ...

  6. iOS逆向-微信自动添加好友

    前言 上次完成了 macOS 版微信小助手,现在终于有(xian)时(de)间(huang)来说说 iOS 逆向了.本篇主要实现在微信上自动添加好友(即自动验证新的朋友申请),从而熟悉 iOS 逆向分 ...

  7. 2020年 IOS 逆向 反编译 注入修改游戏或APP的调用参数新手系列教程——使用theos tweak 注入hook修改游戏执行代码上传动态头像

    2020年 IOS 逆向 反编译 注入修改游戏或APP的调用参数新手系列教程--使用theos tweak 注入hook修改游戏执行代码上传动态头像 开篇 需求&最终效果 环境要求与即将使用的 ...

  8. IOS逆向笔记之HOOK实现(非越狱)

    HOOK是越狱的最终目标,目的是给应用添加功能如插件或者是更改应用的某个功能来满足我们的需求,如微信中添加抢红包插件.本文将以最近比较火的"快看"漫画为例子去除付费漫画中的收费弹窗 ...

  9. iOS逆向实战与工具使用(微信添加好友自动确认)

    iOS逆向实战与工具使用(微信添加好友自动确认) 原文链接 源码地址 WeChatPlugin-iOS Mac OS 版微信小助手(远程控制.消息防撤回.自动回复.微信多开) 一.前言 本篇主要实现在 ...

最新文章

  1. (转)java DecimalFormat用法
  2. 红顶商人 —— 胡雪岩
  3. 一篇文章学会mysql_一篇文章帮你搞定所有MySQL命令!
  4. 循环神经网络 递归神经网络_了解递归神经网络中的注意力
  5. 成功的换心手术——Windows Phone 8 发布
  6. 动手学深度学习Pytorch Task06
  7. C# 使用Log4Net记录程序日志
  8. JavaScript基础知识指南-思维导图
  9. MySQL详细教程 这一篇就够啦!
  10. android矢量地图画法_Android 我们的矢量地图,放大不失真
  11. linux命令行听歌,谁说linux命令行不能听歌?今天就给你们介绍一款神器
  12. 一个超厉害的在线画图工具
  13. matlab求解mtsp多配送中心路径优化问题(附代码)
  14. Writeup for 0CTF2017 web
  15. 计算机管理储存u盘无法使用,U盘无法识别的三种常见情况
  16. 广西艺术学院2012年本科招生专业考试通知
  17. vscode中文配置中文插件
  18. 基于 python 的语音识别 API 调用
  19. 里奥机器人控制app_Cruzr(机器人控制软件)
  20. 金砖国家智慧城市建设案例

热门文章

  1. 自己也遇到了-db_recovery_file_dest_size 修改大一点
  2. linux 设置中文版man手册
  3. 每次都能遇到的莫名其妙问题,谨记,速查手册
  4. 通过IFeatureClass 接口查询 IWorkspace, 查询通配符
  5. cmd.exe_参数_启动参数
  6. 7-4 找到共同的选修课-hebust (10 分)
  7. mysql maxtmptables_mysql的tmp_table_size和max_heap_table_size
  8. typescript 怎么表示当前时间减一个月_TypeScript 入门知识点总结
  9. android如何监听按钮,Android – 两个onClick监听器和一个按钮
  10. android 查看gpio状态_GPIO子系统重要概念