ios应用逆向工程5-6章笔记

on under 二进制
2 minute read

理论篇

1.依据维基百科的定义,tweak指的是对电子系统进行轻微调整来增强其功能的工具;在iOS中,tweak特指那些能够增强基他进程功能的dylib,是越狱iOS的最重要组成部分.这种增强原有工具功能的方法是通过进程注入实现的,就像书籍的知识注入大脑的,让大脑变得更强

2.一般来说,编写tweak会用到C,C++和Objective-C三种语言

3.iOS是个封闭的系统,它暴露给我们的中是冰山一角,有太多太多的功能还有待我们进一步挖掘.每次越狱发布后,都会有人把最新的头文件发布出来,google一下iOS private headers即可轻松找到下载链接,省去了自己class-dump的麻烦

4.定位目标文件

  • 固定位置 我们的逆向目标一般是dylib,bundle或daemon,它们在系统中的位置几乎是固定的:
    • 基于CydiaSubstrate的dylib全部位于/Library/MobileSubstrate/DynamicLibraries/下,几乎不费吹灰之力就可以轻松定位
    • bundle主要分为App和framework两类,其中Appstore App全部位于/var/mobile/Containers/Bundle/Application下,系统App全部位于/Applications下,framework全部位于/System/Library/Frameworks/System/Library/PrivateFrameworks
    • daemon的配置文件均位于/System/Library/LaunchDaemons/,/Library/LaunchDaemons/Library/LaunchAgents/下,是一个plist格式的文件.其中的ProgramArguments字段,即是daemon可执行文件的绝对路径
  • cydia 通过dpkg -i安装的的deb包,内容会被cydia如实记录,可在cydia中的Filesystem Content中找到路径
  • PreferenceBundle PreferenceBundle是寄生在Settings应用里的App,它的功能界定有些模糊,既可以作为单纯的配置文件,由别的进程读取后执行,也可以估计有实际功能,自己来执行一些操作.来自AppStore的第三方PreferenceBundle仅可作为配置文件存在,不会含有实际功能;来自Cydia的也不是问题,可通过上面的定位方式找到路径;但对于iOS自带的PreferenceBundle来说,定位的过程就要复杂一些.PreferenceBundle的界面可以用代码编写,也可以用固定格式的plist文件构造.

5.使用cycript可快速测试函数功能,比theos方便

6.解析函数参数

  • getClassName函数能够把对象的类名表示成一个char*,如object_getClassName(arg1)获取arg1这个对象的的对象类名
  • description函数能够把对象的内容表示成一个NSString,如[arg1 description]获取arg1这个对象的对象内容
%hook SBScreenFlash
- (void)flashColor:(id)arg1 withCompletion:(id)arg2
{
    %orig;
    NSLog(@"iOSRE: flashColor: %s, %@", object_getClassName(arg1),arg1);//[arg1 description]可以直接写成arg1
}
end

上面的示例中打印的结果为iOSRE: flashColor: UICachedDeviceWhiteColor, UIDeviceWhiteColorSpace 1 1,其中UICachedDeviceWhiteColor是对象类名,UIDeviceWhiteColorSpace 1 1是对象内容

7.书籍作者强烈建议大家通览class-dump出的头文件,把那些语义明显,自己感兴趣的函数放到iOS上实测一下,这个过程能极大地增加对iOS底层的熟悉程度

8.ARM处理器中的特殊用途的寄存器

  • R0-R3 传递参数与返回值
  • R7 帧指针,指向母函数与被调用子函数在栈中的交界(相当于ebp?)
  • R9 在ios3.0以前被系统保留
  • R12 内部过程调用寄存器,dynamic linker会用到它
  • R13 SP寄存器(相当于esp?)
  • R14 LR寄存器,保存函数返回地址(居然有一个单独的寄存器用来保存函数返回地址)
  • R15 PC寄存器(相当于eip?)

9.ARM调用规则 在执行一块代码时,其前后栈地址应该是不变的.这个操作是通过被执行代码块的前言和后记完成的.

  • 前言
    • 将LR入栈
    • 将R7入栈(push ebp)
    • R7=SP
    • 将需要保留的寄存器原始值入栈
    • 为本地变量开辟空间
  • 后记
    • 释放本地变量占用的空间
    • 将需要保留的原始值出栈
    • 将R7出栈
    • 将LR出栈,PC=LR

10.函数调用

  • 32位ARM函数调用时,函数的前4个参数存放在R0到R3中,其他参数存放在栈中;返回值放在R0中
  • ARM64汇编语言函数前8个参数使用x0-x7寄存器(或w0-w7寄存器)传递,多于8个的参数均通过堆栈传递,并且返回值通过x0寄存器(或w0寄存器)返回

11.ARM处理器用到的指令集分为ARM和THUMB两种;ARM指令长度均为32bit,THUMB指令长度均为16bit.THUMB指令比ARM指令更节省空间,且在16位数据总线上的传输效率更高,相对于ARM指令,THUMB指令的缺点如下

  • 指令数量减少
  • 没有条件执行
  • 所有指令默认附带”s”
  • 桶式移位无法结合其他指令执行
  • 寄存器使用受限
  • 立即数和第二操作数使用受限
  • 不支持数据写回

12.ARM32位处理器的所有指令可大致分为3类,分别是数据操作指令,内存操作指令分支指令

  • 数据操作指令 所有操作数均为32位,所有的结果均为32位,且只能存放在寄存器中
    • 算术操作
        ADD R0,R1,R2        ;R0=R1+R2
        ADC R0,R1,R2        ;R0=R1+R2+C(arry)
        SUB R0,R1,R2        ;R0=R1-R2
        SBC R0,R1,R2        ;R0=R1-R2-!C
        RSB R0,R1,R2        ;R0=R2-R1
        RSC R0,R1,R2        ;R0=R2-R1-!C
        以"C"(即Carry)结尾的变种代表有进位和借位的加减法,当产生进位或没有借位时,将Carry flag置1
      
    • 逻辑操作
        AND R0,R1,R2        ;R0=R1&R2
        ORR R0,R1,R2        ;R0=R1|R2
        EOR R0,R1,R2        ;R0=R1^R2
        BIC R0,R1,R2        ;R0=R1&~R2
        MOV R0,R2           ;R0=R2
        MVN R0,R2           ;R0=~R2
      
    • 比较操作
        CMP R1,R2           ;执行R1-R2并依据结果设置flag
        CMN R1,R2           ;执行R1+R2并依据结果设置flag
        TST R1,R2           ;执行R1&R2并依据结果设置flag
        TEQ R1,R2           ;执行R1^R2并依据结果设置flag
      
    • 乘法操作
        MUL R4,R3,R2        ;R4=R3*R2
        MLA R4,R3,R2,R1     ;R4=R3*R2+R1
      
  • 内存操作指令 ARM内存操作只有2个基础指令,LDR(load register)将内存中的数据存到寄存器(cpu),STR(store register)将寄存器(cpu)中的数据保存到内存.除了LDR和STR外,还可以通过LDM(load multiple)STM(store multiple)进行块传输,一次性操作多个寄存器.需要特别注意的是,LDM和STM的操作方向与LDR和STR完全相反:LDM是把从Rd开始,地址连续的内存数据存入reglist中,STM是把reglist中的值存入从Rd开始,地址连续的内存中.
    • LDR
        LDR Rt,[Rn{,#offset}]       ;Rt=*(Rn{+offset}),{}代表可选
        LDR Rt,[Rn,#offset]!        ;Rt=*(Rn+offset);Rn=Rn+offset
        LDR Rt,[Rn,#offset]         ;Rt=*Rn;Rn=Rn+offset
      
    • STR
        STR Rt,[Rn{,#offset}]       ;*(Rn{+offset})=Rt
        STR Rt,[Rn,#offset]!        ;*(Rn{+offset})=Rt;Rn=Rn+offset
        STR Rt,[Rn],#offset         ;*Rn=Rt;Rn=Rn+offset
      
  • 分支指令
    • 无条件分支
        B label             ;PC=label
        BL label            ;LR=PC-4;PC=label
        BX Rd               ;PC=Rd并切换指令集
      
    • 条件分支
        EQ                  ;Z=1
        NE                  ;Z=0
        CS                  ;C=1
        HS                  ;C=1
        CC                  ;C=0
        LO                  ;C=0
        MI                  ;N=1
        PL                  ;N=0
        VS                  ;V=1
        VC                  ;V=0
        HI                  ;C=1 & Z=0
        LS                  ;C=0 | Z=1
        GE                  ;N=V
        LT                  ;N!=V
        GT                  ;Z=0 & n=V
        LE                  ;Z=1 | N!=V
      

      例如这里这里

13.函数和UI之间的关联非常紧密,如果能拿到感兴趣的UI对象,就可以找到它所对应的函数,我们称该函数为UI函数.这个过程,一般是利用cycript,结合UIView中的神奇私有函数recursiveDescriptionUIResponder中的nextResponder来实现的.注意,游戏一般不是采用UIKit来构建UI的,recursiveDescription和nextResponder不适用于游戏.

ios, reverse
home
github
archive
category