iOS逆向学习(arm64汇编入门)

iOS汇编

iOS汇编语音有很多钟。常见的有8086汇编、arm汇编、x86汇编等等。

arm汇编

iOS的架构从最初的armv6发展到后来的armv7和armv7s,最后发展到现在的arm64,不管是armv6还是后来的armv7,以及arm64都是arm处理器的指令集。armv7和armv7s是真机32位处理器使用的架构,而arm64是真机64位处理器使用的架构。 iPhone 5C是最后一款arm32位版本的iPhone,在iPhone5s之后,所有的iPhone设备都采用arm64架构。arm64汇编在真机上使用,如下:

TestFont`-[ViewController test]:
    0x10286e574 <+0>:  sub    sp, sp, #0x20             ; =0x20 
    0x10286e578 <+4>:  mov    w8, #0x14
    0x10286e57c <+8>:  mov    w9, #0xa
    0x10286e580 <+12>: str    x0, [sp, #0x18]
    0x10286e584 <+16>: str    x1, [sp, #0x10]
->  0x10286e588 <+20>: str    w9, [sp, #0xc]
    0x10286e58c <+24>: str    w8, [sp, #0x8]
    0x10286e590 <+28>: add    sp, sp, #0x20             ; =0x20 
    0x10286e594 <+32>: ret  

x86汇编

x86汇编是模拟器使用的汇编语言,它的指令和arm64汇编的语法不同,如下

TestFont`-[ViewController test]:
    0x10b089520 <+0>:  pushq  %rbp
    0x10b089521 <+1>:  movq   %rsp, %rbp
    0x10b089524 <+4>:  movq   %rdi, -0x8(%rbp)
    0x10b089528 <+8>:  movq   %rsi, -0x10(%rbp)
->  0x10b08952c <+12>: movl   $0xa, -0x14(%rbp)
    0x10b089533 <+19>: movl   $0x14, -0x18(%rbp)
    0x10b08953a <+26>: popq   %rbp
    0x10b08953b <+27>: retq   

为什么要学习arm64汇编?

代码调试

在平常开发中,在调试程序的时候,如果程序crash,通常会定位到具体的崩溃代码。但是有时候也会遇到一些比较诡异的crash,比如说崩溃在了系统库中,这个时候定位到具体的crash原因会非常困难。如果利用汇编调试技巧来进行调试,可能会让我们事半功倍。

逆向调试

在逆向别人App过程中,我们可以通过LLDB对内存地址进行断点操作,但是当执行到断点时,LLDB展现给我们的是汇编代码,而不是OC代码,所以想要逆向并且动态调试别人的App,就需要学习汇编的知识。

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这有个iOS交流群:642363427,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术,iOS开发者一起交流学习成长!

arm64汇编入门

想要学习arm64汇编,需要从以下三个方面入手,寄存器、指令和堆栈。

寄存器

arm64中有34个寄存器,如下

通用寄存器

  • 64 bit的通用寄存器有29个,分别是x0 ~ x28
  • 32 bit的也有29个,分别是w0 ~ w28(属于x0 ~ x28的低32位)
  • 其中x0 ~ x7通常拿来存放函数的参数,如果参数更多,则采用堆栈来进行传递
  • x0中通常存放函数的返回值
也会有人将x0 ~ x30叫做通用寄存器,但是在实际使用中x29和x30并没有对应的低32位的寄存器w29、w30,而且x29和x30寄存器有着特殊的用途,所以在此我只讲x0 ~ x28记为通用寄存器

程序计数器

pc (Program Counter)寄存器,它记录着当前CPU正在执行的指令的地址,通过register read pc查看寄存器中存储的值

(lldb) register read pc
      pc = 0x000000010286e588  TestFont`-[ViewController test] + 20 at ViewController.m:28
(lldb) 

堆栈指针

  • sp (Stack Pointer)
  • fp (Frame Pointer),也就是之前所说的x29

链接寄存器

lr (Link Register)寄存器,也就是之前所说的x30寄存器,它存储着函数的返回地址

程序状态寄存器

arm体系中包含一个当前程序状态寄存器cpsr (Current Program Status Register)和五个备份的程序状态寄存器spsr (Saved Program Status Registe),备份的程序状态寄存器用来进行异常处理。

  • 程序状态寄存器的每一位都有特定的用途,此处只介绍几种常用的标志位
  • 其中N、Z、C、V均为条件码标志位,他们的内容可被算数或者逻辑运算的结果所改变,并且可以决定某条指令是否被执行。条件码标志各位的具体含义如下

指令

mov指令

mov指令可以将另一个寄存器、被移位的寄存器或者将一个立即数加载到目的寄存器

mov指令在arm64汇编中的实际使用

  • 在xcode中新建test.s文件,在test.s文件中添加以下代码
; 此处.text表示此代码放在text段中
.text
; .global表示将后面跟随的方法给暴露出去,不然外部无法调用,方法名以_开头
.global _test

; 此处为_test方法
_test:
; mov指令,将立即数4加载到x0寄存器中
mov x0, #0x4
mov x1, x0
; 汇编指令中,ret表示函数的终止
ret

  • 在xcode中新建test.h头文件,将test.s中的_test方法暴露出来
#ifndef test_h
#define test_h

void test(void);

#endif /* test_h */

  • 在viewDidLoad中调用test()函数,然后在LLDB中使用register read x0 读取寄存器中存放的值
(lldb) register read x0
      x0 = 0x000000010320c980
(lldb) si
(lldb) register read x0
      x0 = 0x0000000000000004
(lldb) register read x1
      x1 = 0x00000001e60f3bc7  "viewDidLoad"
(lldb) si
(lldb) register read x1
      x1 = 0x0000000000000004

通过对汇编指令增加断点,一步一步调试可以看出,在执行完mov指令后,x0和x1寄存器的值都被修改了

ret

ret指令表示函数的返回,而且它还有一个非常重要的作用,就是将lr(x30)寄存器的值赋值给pc寄存器

  • 在viewDidLoad中调用test()函数,在test()函数上打上断点,执行程序如下
  • 使用register read 查看lr和pc寄存器的值
(lldb) register read lr
      lr = 0x00000001021965a4  TestFont`-[ViewController viewDidLoad] + 68 at ViewController.m:23
(lldb) register read pc
      pc = 0x00000001021965a4  TestFont`-[ViewController viewDidLoad] + 68 at ViewController.m:23
(lldb) 

此时,lr寄存器和pc寄存器的值都是test()函数起始地址

  • 使用si指令跳转到test()函数中
  • 再次查看lr和pc寄存器的值,发现lr的值变成了test()函数的下一条指令的地址,也就是test()函数执行完成之后,主程序需要执行的下一条指令。pc寄存器保存了当前即将执行的指令的地址,如下
(lldb) register read lr
      lr = 0x00000001021965a8  TestFont`-[ViewController viewDidLoad] + 72 at ViewController.m:24
(lldb) register read pc
      pc = 0x0000000102196abc  TestFont`test

  • 执行完test()函数,发现程序跳转到了lr寄存器所保存的指令地址,也就是0x00000001021965a8,此时再次查看lr和pc寄存器的值,发现pc寄存器存放的地址已经变成了lr寄存器存放的地址
(lldb) register read lr
      lr = 0x00000001021965a8  TestFont`-[ViewController viewDidLoad] + 72 at ViewController.m:24
(lldb) register read pc
      pc = 0x00000001021965a8  TestFont`-[ViewController viewDidLoad] + 72 at ViewController.m:24
(lldb) 

add指令

add指令是将两个操作数相加,并将结果存放到目标寄存器中。具体说明如下

在arm64汇编中,相应的就是操作x0~x28,执行如下汇编代码

.text
.global _test

_test:

mov x0, #0x4
mov x1, #0x3

add x0, x1, x0

ret

执行完test()函数,通过register read查询x0的值,最后可以看到x0存放的值为7,如下

(lldb) register read x0
      x0 = 0x0000000000000004
(lldb) si
(lldb) register read x1
      x1 = 0x0000000000000003
(lldb) si
(lldb) register read x0
      x0 = 0x0000000000000007

sub指令

sub指令是将操作数1减去操作数2,再减去cpsr中的C条件标志位的反码,并将结果存放到目标寄存器中

cmp指令

cmp指令是把一个寄存器的内容和另一个寄存器的内容或者立即数做比较,同时会更新CPSR寄存器中条件标志位的值

  • 执行如下汇编代码
.text
.global _test

_test:

mov x0, #0x4
mov x1, #0x3

cmp x0, x1

ret

  • 在执行cmp代码之前和之后各打印一次CPSR寄存器的值如下
(lldb) register read cpsr
    cpsr = 0x60000000
(lldb) si
(lldb) si
(lldb) si
(lldb) register read cpsr
    cpsr = 0x20000000
(lldb) 

可以发现,在执行cmp操作之后,cpsr寄存器的值变成了0x20000000,转换成16进制后,得到32位标志位如下

可以发现第31位,也就是N位的值为0,同时第30位,也就是Z位的值也为0,这就表示,x0和x1寄存器相比较之后的值为非零非负,而使用x0 - x1得到的结果是1,符合非零非负的条件。

  • 修改汇编代码,调换x0和x1寄存器的位置,如下
_test:

mov x0, #0x4
mov x1, #0x3

cmp x1, x0

ret

  • 再次在cmp代码执行前后读取CPSR寄存器的值
(lldb) register read cpsr
    cpsr = 0x60000000
(lldb) s
(lldb) register read cpsr
    cpsr = 0x80000000
(lldb) 

这个时候,cpsr寄存器的值变成了0x80000000,转换成16进制后,如下

可以看出,第31位N位的值变成了1,第30位Z位的值为0,这表示,x0和x1寄存器相比较之后的值为非零负数,使用x1-x0得到的结果是-1,符合非零负数的条件

B指令

B指令是最简单的跳转指令,一旦遇到B指令,程序会无条件跳转到B之后所指定的目标地址处执行。

BL指令

BL指令是另一个跳转指令,但是在跳转之前,它会先将当前标记位的下一条指令存储在寄存器lr(x30)中,然后跳转到标记处开始执行代码,当遇到ret时,会将lr(x30)中存储的地址重新加载到PC寄存器中,使得程序能返回标记位的下一条指令继续执行。

  • 首先执行以下汇编代码
.text
.global _test

label:
mov x0, #0x1
mov x1, #0x8
ret

_test:
mov x0, #0x4
bl label
mov x1, #0x3
cmp x1, x0
ret

  • 断点到bl label指令时,读取lr寄存器和PC寄存器的值
  • 执行bl label指令,跳转到label标记处,再次读取lr(x30)寄存器和PC寄存器的值,这个时候会发现lr(x30)寄存器存放的地址已经变成mov x1, #0x3这条指令的内存地址
  • 执行完label标记中的所有代码,发现程序再次回到lr寄存器所存储的地址,也就是mov x1, #0x3这句指令继续执行,并且此时pc寄存器所存储的地址也变成了mov x1, #0x3这句指令的地址。

想了解更多逆向相关知识不妨动动小手,添加一下咱们的交流群642363427来为你的技术多添一份光彩。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容