逆向方法反汇编(五)

局部变量属于强引用。汇编代码:

#mark-1: 看汇编objc_msgSend 这里结构是

oc中调用: [Animal alloc]

objc_msgSend(Animal.class,@selector(alloc));

根据汇编也就是x0寄存器也类,x1寄存器是sel

#mark-3:

  • x0:animal对象
  • x1:nil  (也就是首页指针指向nil,释放原来的对象)

注意:objc_storeStrong

所有OC中使用Strong修饰的对象 实际调用的函数是objc_storeStrong,代码实现如下


上面的一些是动态分析,如果是在真实的项目中决定是非常的复杂,所有动态分析很麻烦,可以直接用ida工具来分析

查看ida分析和上面动态分析是一样结果,只是它的更加详细 函数名也一起展示了。

问题:看上图 地址都是text:000000010000….    中间都是一个1 (后面8个0),为什么?

0x100000000 是4个G的虚拟size  (也就是text是从0x100000000开始)

问题:ida是怎么知道 这个地址的函数名 类名等?

主要是通过mach0文件分析对应查找, 如图

 

逆向block(四)

  • globalblock (编译时期)
  • stackblock    (编译时期)
  • mallocblock    (运行时期)

汇编:

#mark-a:

x8是通过adrp add 算出地址是0x104b7c080,读内存地址 得到__NSGlobalBlock__,__NSGlobalBlock__是block的isa指针也就是在这附近有一个block的定义.


比如一些稍复杂的block定义:

汇编:

这时直接用动态调试非常麻烦(算地址再去macho查看),可以直接借助 ida工具来

逆向LLDB(三)

LLDB (Low Lever Debug)

常用指令

  • help breakpoint 产看breakpoint指令
  • breakpoint set -n funcRun   (-n是-name,  funcRun是方法名字) (简写 b funcRun)
  • breakpoint set -n “-[ViewController run]” -n “-[ViewController eat:]”   设置多个断点
  • breakpoint set –selector touchesBegan:withEvent:  断点所以得方法和类无光
  • breakpoint set –file ViewController.m  –selector touchesBegan:withEvent:   在指定文件中设置断点
  • breakpoint set -r Run:  遍历满足整个项目中符合Run:这个字符的所以方法
  • breakpoint list 断点的list
  • breakpoint disable 2.1  禁用2.1处断点(也就是funcRun处断点)
  • breakpoint enable 2.1  启用2.1处断点
  • breakpoint delete 2  删除一组断点 (删除一个是禁用,只有删除一组才可以删除)
  • breakpoint delete  全部删除
  • p 就是expression简写
  • poexpression -O
  • expression 断点修改…  (p (Animal *)self.cat    得到$2 然后再去设置$2.run 才能执行) (control+return 多行)
  • memory read 0x32…..  读内存地址 也可以用 x 0x32…
  • register read x1  读寄存器

  • up 查看堆栈上一个调用的函数
  • thread return  回滚堆栈上一个调用的函数 (再c执行就结束了 不会再往下走, 因为return)
  • frame select 5 查看bt后的#序号的函数,也就是直接断点到#序号的函数中
  • frame variable 查看整个方法中的参数

 

  • continue  简写c 继续执行
  • next 简写n  单步运行,一行一行代码走
  • step 简写s  单步运行,遇到子函数会进去


内存断点:(和断点的一些设置一样 delete 、list等)

  • watchpoint set variable cat->name  对cat的name设置内存断点(也就是name改变就断点)
  • watchpoint set expression 0x00000003d…    给内存地址设置断点

断点添加指令:

  • break command add 1              添加指令
  • breakpoint command list 1     查看1处指令
  • breakpoint command delte 1   删除指令


stop-hook

pass和debugView 不会触发,只对breadpoint,watchpoint

  • target stop-hook add -o “p self.cat.name”
  • target stop-hook delete  删除
  • undisplay 1           删除一组
  • target stop-hook list    查看所有

image

  • image lookup -a 0x32323f2    通过地址查看指令
  • image lookup -t Animal  查看类的信息 (包含了头文件)
  • image list

例子:真机上崩溃信息

可以用image lookup -a 0x18564ed8c 这样方式来看崩溃的堆栈信息


总结:

lldb在断点启动的时候会走 $HOME/.lldbinit 文件 (如果没有自己新建),里面可以配置一些指令,一般是用作导入配置文件

逆向Mach-o(二)

Mach-o(Mach Ojbect缩写)文件有11种格式(并不是所有的可执行文件都有Mach-o),是Mac/iOS上用于存储程序,库的标准格式

Mach-o常见格式:

  • 可执行文件
  • object   (a. 其中包括.o 文件   b. .a静态库文件 其实就是N个.o文件集合)
  •   DYLIB:动态库 (a. dylib  b.framwork)
  • 动态连接器
  • DSYM

例子1:

把上面的代码文件.c编程.o执行命令

  • clang -c main.c   (会生成一个main.o) (也可以直接生成多个 clang -c main.c test.c)
  • file main.o  (main.o: Mach-O 64-bit object x86_64)(属于Mach-o 是一个object 不是可执行文件
  • clang main.o (或者生成cusmain clang -o cusmain main.0)(生成a.out 在file a.out (a.out: Mach-O 64-bit executable x86_64) (可执行文件))
  • ./a.out 直接执行 (Hello, World!)

上面那几步也可以直接生成可执行文件 :

例子2:多文件文件合并成一个可执行文件

这个文件类交Test.c  (不需要引入#include..),另一个文件还是上面的main.c (和.c顺序没关系)

执行下面命令合并(会有警告 没关系)

它会生成一个sks可执行文件,./sks 直接执行就行

*find找文件 find /cust/dsk -name “*.a”


动态库共享缓存:

为了提高性能,系统的动态库文件都存在动态库共享缓存里面(其中动态库只有一份)

比如上面的dyld_shared….x86_64h 多个app动态库都放在这,起到公用库的作用(类似单利) (通过动态连接器加载动态库)

动态加载器(动态连接器)(dyld) (用来加载动态库) (是dynamic linker可执行文件)

  • dynamic linker
  • dynamic loadel


二进制文件

其中xcode中默认是Executable可执行文件

  • Executable (默认)
  • Dynamic Library
  • Bundle
  • Static Library
  • Relocatable Object File

xcode,debug下生成app可执行文件是arm64, 在release中是多个架构,这个debug中YES是当前真机的架构

Architectures和有效架构是取交集,所有上面只生成arm64 armv7,在Architectures加一种就可以生成armv7s架构

在分析汇编代码的时候,大多时候值分析一个架构就可以(经常用于静态库):

拆分二进制文件:

合并:


MachO

MachO主要分为3大块:

  • MachO Header (包含整个架构的信息,load Commands数量等)
  • Load Commands (区域位置)
  • Data (代码 全局变量等)

MachO工具

逆向class-dump(一)

逆向原理:

  • 动态调试:通过界面调试Cycript/LLDB
  • 静态分析:通过汇编代码分析app源码
  • 代码注入:就是动态库,注入进去,动态库默认加载,通过HOOK代码来改变程序执行流程
  • 重签名: 安装非越狱手机 (企业/个人证书)

class-dump

class-dump可以导出程序里的所有的头文件(方法所以得定义,和成员变量(包含.m中的))

安装:

注意:

  • /usr/bin 系统预装,系统升级会更新和覆盖
  • /local/bin 本地 放自己的可执行文件

如果这2个都存在一样的,执行哪个? 主要看 PATH里面的配置顺序, (PATH=$PATH:$HOME/bin:/sbin:/usr/bin:/usr/sbin)

  • 如果你使用的是bash,cat ~/.bash_profile (bash默认加载bash_profile)
  • 如果是zsh, cat ~/.zshrc (zsh默认架子啊zshrc)

安装方式几种:

  1. 把class-dump可执行文件直接放进;local/bin 目录
  2. 在当前用户下新建个文件夹(如 mkdir ~/bin),然后chmod +x  ~/bin/class-dump 给他执行权限,然后在bash_profile/zshrc找到export PATH=$HOME/bin/:$PATH   然后source ~/.bash_profile 或source ~/.zshrc

配置好,要退出终端在打开。验证有没有成功,直接在终端执行 class-dump,看到版本就成功了

使用:

这是微信的ipa包 链接:https://pan.baidu.com/s/1C9hLt6oOv6jgdrSaMH6f-Q 密码:j7ad

  1. 可以使用自己的ipa包,解压找到可执行文件
  2. 执行class-dump -H yourexecFile (这样是把所有的头文件(包含第三方的)输出到当前文件夹下,也可以指定路径class-dump -H yourexecFile -o /customDir )

努力改变

  1.  尽最大可能 早起早睡
  2.  一周至少2次去健身
  3.  2周至少给家人打一个电话
  4.  努力做到让家人身心健康 家庭和谐
  5.  少发脾气,对于不喜欢的人要忍 忍 忍, 看不惯的事深虑后决定
  6.  尽量不要做一些后悔 内疚的事
  7.  认识和解释自己的不足
  8.  再交2-3个真正的铁哥们
  9.  制作一份简单计划 尽量要按时完成,不然就尽快花别的时间来补上
  10.  禁止制作复杂、很难完成的计划(偶尔是可以的)
  11.  学习技术要专注一点, 要快速的过完一遍.. 然后通过实用来复习巩固
  12.  说话尽量不要太伤人,深虑后再说
  13.  不要计较太多 尽量做到少说 少比
  14.  对于“叛徒/小人”要尽可能容忍,不可能做成朋友的人也不要扯破关系,把他当普通人、陌生人
  15.  找到2个真正的“偶像” 向他学习,生成动力
  16.  尽量让自己每天快乐点

汇编指针(十二)

指针基础

指针在64位占8个字节(64Bit),(比如int 占4个字节 char 1个 ,没说指针),其中指针不能乘除,只能加减运算

1.先看个简单的指针宽度

2.指针自增

指针的运算是它指向的数据类型的宽度决定,比如上面的a=104是 a+sizeof(int)所得,其中就是去掉一个指针决定,继续看下面

问题:int arr[3] = {1,2,3};  arr++;  ❌

arr本是指首地址,也就是arr=arr+1;系统不允许改动,但是可以用int *a = arr; a++来取值

3.多个指针自增

上面是2个指针,在自增的时候,首页去掉一个指针称(int *)也就是一个指针,sizeof(指针),指针宽度8个字节+100=108

4.指针运算

首页看

  1. a=100,b=200,
  2.   a-b=-100
  3. -100/sizeof(int) = -25

5.指针的比较

6.指针运算溢出

指针运算时根据所指向的数据类型算的,


指针反汇编

分析指针汇编

 

在栈上开辟4个字节的地址给x8,然后寄存器w9=10, w9入栈到栈地址向上8个字节地址。这里相对简单易懂些

注意:栈的写入读取是向高地址执行


指针基本用法

1.取没有初始化的地址

直接crash,EXC_BAD_ACCESS,可以直接调试:

lldb: register read x8

lldb: 0x0000000000…

都是0x0000000… 这个地址并没有初始化分配内存地址,在取x8地址的时候并没这个地址直接报Bad_Access,

2.sp拉伸

汇编:

问题:#mark-a sp为什么要拉伸0x20?

解:sp拉伸看func函数里 局部变量是3个指针,也就是3*sizeof(指针)=24,sp是按照16字节对齐(0x20 32字节够用),

如果在func函数里加入

  • char c;
  • int **p2;

4*8+1 = 33,sp 0x20是不够的,所以 sp拉伸必须是0x30sp是16字节对齐,其他的空着

问题:#mark-b x8为什么[x8, #0x8]偏移8字节?

因为他是指针8字节,如果是int是4自己,char为1

3.指针取值方式

汇编:

其中#mark-a, #mark-b 都是偏移8字节取值的,也就是一样的方式,(内存地址一样,反汇编代码也是一样)

比如: int n = *(p+1);     和int m = p[1]; 也是一样的

4.

解释:p+3:  p 是一个指针也就是8个字节,+3就是偏移3个8字节 x*8=24

*(p +3) +2:  取值后再加3 因为是char类型, 也就是3*sizeof(char) =24+2=26;

参考下汇编:

#mark-a  [x8, #0x18]: ox18是24字节, ox2=2 也就是和上面分析一样共偏移27字节

总结: char ch = *(*(p +3) +2);   就是等于  char ch = p[3][2]

计算指针偏移就是去掉一个*看他的类型宽度

编译器优化

在不同模式(debug,release)下,汇编的代码是不一样的。比如在debug,会取值入栈,再从栈取到寄存器等等

优化主要是指汇编指令变少,跳转变少(其中内存读写耗时)。

其中A11 CPU架构中 读写数据速度 寄存器  > 高速缓存 >  内存 > 磁盘

1.Optimization Level优化

其中Optimization有多种

  • None
  • Fast
  • Faster
  • Fastest
  • 线上release默认 Fastest,Smallest (又快又小)
  • Fastest Aggressive optimizations

比如下面简单代码在不同模式下的汇编代码

在 None模式下:

在Fastest Smallest模式下没用使用代码的情况:他直接ret (去掉一些没用垃圾代码)

在Fastest Smallest模式下,有使用代码(他会在编译的时候把函数换成结果,进而优化)

汇编代码:

其中函数在编译的时候已经知道实现的代码结果(内存中找)

其中app安装在磁盘,打开在内存,然后把内存放入8M的高速缓存中,然后寄存器直接去高速缓存中读取

If和switch哪个效率高?

if在汇编判断主要是通过运算

switch有几种情况:

  • 分支大于3时,有序差值小的情况是通过(x8)查地址表来判断(在编译的时候会生成一个表(跳转表每个地址四个字节))效率比if高很多
  • 分支大于3时,无序差值很大编译器会在效率还是内存进行取舍,是时间换空间 还是空间换时间(如果有序差值稍大,它会跳过的分支地址填充default的地址,拿空间换换时间)
  • 分支小于3和if else一样运行判断

所以说哪个效率高?看判断的分支情况来决定

 

具体的if和switch看下面2篇文章分析

汇编If(九)

汇编Switch(十一)