swift 混淆原理探究

swift 混淆原理探究

前提

由于swift是一门静态语言, 其派发机制与OC不同,两者在构建时其MachO文件是有区别的.适用于OC代码层面的混淆方案在swift中并不适用.要探究swift的混淆方案,我们要搞清楚swift在构建时和OC的区别.对于面向对象的编程来说MachO文件可以简单看做是保存了所有类信息的文件,我们需要搞清楚这些类的信息是如何保存的.

MachO是如何存储OC中Class信息的

首先看一下MachO文件的整体结构, 我们将从__objc_classlist入手,探究OC类的存储方式.MachO文件结构如下

__objc_classlist 中存储的类表,其实就是一系列地址,每个地址对应一个类.

其中红色0xd408 指向_objc_data分区中的某个地址,结构如下

arduino 复制代码
struct class64
{
    unsigned long long isa;
    unsigned long long superClass;
    unsigned long long cache;
    unsigned long long vtable;
    unsigned long long data;
};

其中红色0xc048为data指针 指向_objc_data分区中的某个地址,结构如下

arduino 复制代码
struct class64Info
{
    unsigned int flags;//objc-runtime-new.h line:379~460
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved;
    unsigned long long  instanceVarLayout;
    unsigned long long  name;
    unsigned long long  baseMethods;
    unsigned long long  baseProtocols;
    unsigned long long  instanceVariables;
    unsigned long long  weakInstanceVariables;
    unsigned long long  baseProperties;
};

根据0xc048往下数32个字节能找到name地址0x7368, 可以在__objc_classname中找到该类名为ViewController. \x56\x69\x65\x77\x43\x6f\x6e\x74\x72\x6f\x6c\x6c\x65\x72是其Unicode编码

同理可知 0x6420 为其方法列表地址,其结构如下,首先是method64_list_t随后跟着是relative_method_t

arduino 复制代码
struct method64_list_t
{
  unsigned int entsize; //hopper显示为flag
  unsigned int count;
};
​
struct relative_method_t {
    int32_t nameOffset;   // SEL*
    int32_t typesOffset;  // const char *
    int32_t impOffset;    // IMP
};
​

0x6f78表示方法名偏移.0x0fb0为方法类型偏移. 0xfffffc60为Imp偏移.

0x6428 + 0x6f78 = 0xd3a0,0xd3a0_objc_selrefs分区

里面存放一个字符串的指针 再找到0x6500位于 __objct_methodname分区

可以看到方法名是viewDidLoad. 同样的方式可以找到方法实现 00006430 + fffffc60 = 100006090为Imp地址.

以上就是OC中Class的大致存储方式.总结一下

  • __objc_classlist存放着Class指针指向__objc_data中的地址
  • _objc_data存放着class64结构体数据其中data指向了class64Info的地址
  • class64Infoname指向了__objc_classname中的字符串,表示类名
  • class64InfobaseMethods指向了__objc_methodlist中的结构体method64_list_t,其中存放着方法信息
  • method64_list_tcount表明方法数量,在其随后的地址中以relative_method_t表示
  • relative_method_tnameOffset可以计算得出方法名的地址, 这一地址位于__objc_methodname分区.
  • relative_method_timpOffset可以计算得出方法对应的函数地址,位于__text分区.

类名和方法名都已字符串的方式存储在对应的分区__objc_classname__objc_methodname中了. 这和我们的直觉是一致的,OC作为一门动态语言方法名一定会以某种形式保存在MachO文件中.

基于这个原因对OC代码的混淆方案中会对方法名和类名进行替换从而达到混淆的效果. 但是我们知道swift作为一门静态语言,其方法的派发机制和OC是不同的. 那么问题来了, 对swift进行方法名称的替换有一样有效吗? 下文将探究该问题.

Swift的派发机制

swift拥有三种不同的派发机制

  • 静态派发(直接派发)

  • 函数表派发

  • 动态派发

    我们需要搞清楚,这些派发方式是否有方法名的参与.

    他们的区别总结如下

sql 复制代码
这张图出自[Static vs Dynamic Dispatch in Swift: A decisive choice](https://medium.com/@bakshioye/static-vs-dynamic-dispatch-in-swift-a-decisive-choice-cece1e872d) 这篇文章, 不过在实际的测试中,有一些细微的差别. Release模式下编译器尽可能的将函数表派发优化为静态派发甚至直接优化为内联函数, 因此为了观察派发的具体指令需要在Debug下进行.
  • 静态派发会在编译或者静态链接时直接确定函数的地址,肯定没有符号的参与.
  • 动态派发就是oc的消息机制, 需要方法名的参与,不赘述了.

下面探寻一下 函数表派发的细节

测试代码

其中 bl , blr 为跳转指令. 其后是地址或者寄存器. 这里iOS加载MachO时存在随机地址偏移(ASLR), 我们需要对地址进行转换得到其在MachO文件中的地址,

image list 打印内存中所有image , 找到SwiftConfuse这个Image,查看其地址 0x0000000102058000, 我们计算地址要减去这个数,来得到文件的偏移地址进行分析.

下面逐行分析这些指令做了啥

bl 0x10205db54: 还原地址 0x10205db54 - 0x012058000 = 0x5b54, Hopper跳转到地址0x5b54,

这是一个读取swift metadata的程序, 获取Foo的matedata的地址并返回.

mov x20 ,x0: 将x0复制到x20, 前一条指令返回的地址保存到x0中,

bl 0x10205d664 : 还原地址 0x10205d664 - 0x012058000 = 0x5664 , Hopper跳转查看,该程序用来初始化Foo对象并返回对象地址.

可以通过x0地址来验证 成功调用了Foo对象的bar方法.

mov x20 ,x0: 将x0复制到x20

str x20, [sp, #0x10] : 把寄存器x20的值保存到栈内存[sp + #0x10], 对象的赋值操作,我们不关心.

ldr x8 [x20] : 这里读取的是x20地址开始的8个字节的数据. x20中存储的是Foo对象的地址. 根据Swift对象地址布局. 对象的第一个8字节存储的就是对象metadata的地址, 对的,其实就是isa指针😏. 所以这里获取就是Foo的metadata信息

metadata 地址为 0x1020720c8. 还原地址 0x1020720c8 - 0x012058000 = 0x1a0c8

这个结构看着是不是很熟悉, 其实就是上文探究OC类存储时的 class64, 说明了OC和swift类的存储方式是一致的. 也可以解释为啥OC类的结构体中有vtable.

ldr x8 [x8, #0x50] : 读取x8偏移50字节后地址的8个直接的数据 即 0x1a0c8 + 50 = 0x1a118 中的值,

这里得到的就是0x10000053f4 就是bar方法的地址,通过hopper可以验证.

blr x8: 执行bar方法.

注意: 这里有2次获取matedata, 第一次是通过matedata accessor获取的, 第二次是通过对象地址获取的. 这是因为在存在对象的情况下,通过对象获取效率更高.

由上述流程可以看出Swift函数表派发寻址原理, swift class matedata 中存储了vtable, 函数表派发会先找到swift类的matedata, 再通过偏移量找到vtable中的地址. 这一过程并没有也不需要函数名的参与.

同OC根据matedata也可以迅速定位出swift类名的位置, matedata->data->name

当然这个和OC有点区别, OC是保存在 __objc_classname分区的, swift是保存在__cstring分区的.

结论

对于swift而言

  • 由于其大部分的方法是静态派发或者函数表派发,machO文件中不保存方法名称.对方法名进行混淆只对@objc dynamic修饰的方法起作用, 大部分情况下是无用功, 市面上一些对swift进行方法名替换混淆的工具从原理上来说就是行不通的.
  • 纯swift类 类名和属性名方法名, 包括方法名的字符串不都储存. 继承OC的类的类名和存储属性的字符串存储在 Text段的 __cstring 分区, @objc修饰的方法名 和 继承自OC类的方法名会储存
相关推荐
有事没事实验室18 分钟前
CSS 浮动与定位以及定位中z-index的堆叠问题
前端·css·开源
2501_9153738841 分钟前
Vue路由深度解析:Vue Router与导航守卫
前端·javascript·vue.js
小妖6661 小时前
前端表格滑动滚动条太费事,做个浮动滑动插件
前端
读心悦1 小时前
5000 字总结CSS 中的过渡、动画和变换详解
前端·css·tensorflow
__BMGT()1 小时前
C++ QT 打开图片
前端·c++·qt
仍然探索未知中2 小时前
前端扫盲HTML
前端·html
Brilliant Nemo2 小时前
Vue2项目中使用videojs播放mp4视频
开发语言·前端·javascript
酷爱码2 小时前
Linux实现临时RAM登录的方法汇总
linux·前端·javascript
LuckyLay3 小时前
Vue百日学习计划Day16-18天详细计划-Gemini版
前端·vue.js·学习
想要飞翔的pig3 小时前
uniapp+vue3页面滚动加载数据
前端·vue.js·uni-app