茉莉薰衣草 發表於 2020-2-10 10:58:02

iOS汇编入门教程之ARM64汇编基础教程

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>前言</li><li>一个例子</li><li>入门攻略</li><li>栈</li><li>深入</li><li>总结</li><li>参考资料</li></ul></div><p class="maodian"></p><h2><strong>前言</strong></h2>
<p>对于应用层开发人员而言,仅仅掌握Objective-C和系统框架即可较好的完成开发,但在涉及到应用加固、逆向分析等内容时仅有应用层开发技能就会显得非常的无力,因此掌握汇编对于突破iOS开发水平的瓶颈十分有效。</p>
<p class="maodian"></p><h2>一个例子</h2>
<p>以反调试为例,我们知道,通过调用ptrace函数可以阻止调试器依附。</p>
<div class="jb51code">
<pre class="brush:plain;">
ptrace(31, 0, 0, 0)</pre>
</div>
<p>这种方式能够被函数hook轻易破解,例如使用facebook的fishhook。为了防止函数被hook,我们可以将函数调用转为通过汇编发起系统调用,即使用下面的代码。</p>
<div class="jb51code">
<pre class="brush:plain;">
mov x0, #31
mov x1, #0
mov x2, #0
mov x3, #0
mov x16, #26
svc #0x80</pre>
</div>
<p>其中x0-x3存储的为函数入参,x16存储的为函数编号,通过Apple提供的System Call Table 可以查出ptrace的编号为26,最后一句指令发起了系统调用。通过使用_asm_指令能够将汇编代码嵌入我们的函数中,构成反调试方法。</p>
<div class="jb51code">
<pre class="brush:plain;">
// 使用inline方式将函数在调用处强制展开,防止被hook和追踪符号
static __attribute__((always_inline)) void anti_debug() {
// 判断是否是ARM64处理器指令集
#ifdef __arm64__
// volatile修饰符能够防止汇编指令被编译器忽略
__asm__ __volatile__(
"mov x0, #31\n"
"mov x1, #0\n"
"mov x2, #0\n"
"mov x3, #0\n"
"mov x16, #26\n"
"svc #0x80\n"
);
#endif
}</pre>
</div>
<p>虽然上面的反调试机制并不完善,但是比直接调用ptrace要好上很多倍,从这一点来看,掌握汇编技能对于iOS应用安全和底层研究非常有利。</p>
<p class="maodian"></p><h2><strong>入门攻略</strong></h2>
<p>iOS设备主要使用的为ARM64汇编,因此本文主要介绍ARM64汇编的入门技巧。汇编入门最难的地方在于对栈的理解,汇编的所有指令操作都是围绕栈实现的,在汇编中,没有变量的概念,只有寄存器和内存。</p>
<p class="maodian"></p><h2>栈</h2>
<p>汇编中的栈是由高地址向低地址生长的数据结构,sp指针永远指向栈顶,需要记住的是,在某位置进行存储时,是向高地址进行的,下面以一个简单的例子讲解汇编的栈操作。我们以一段简单的C代码为例。</p>
<div class="jb51code">
<pre class="brush:plain;">
// hello.c
#include &lt;stdio.h&gt;

int test(int a, int b) {
int res = a + b;
return res;
}

int main() {
int res = test(1, 2);
return 0;
}
</pre>
</div>
<p>使用clang可以将其编译为特定指令集的汇编代码,这里我们将其编译为ARM64指令集的汇编代码。</p>
<div class="jb51code">
<pre class="brush:plain;">
clang -S -arch arm64 -isysroot `xcrun --sdk iphoneos --show-sdk-path` hello.c</pre>
</div>
<p>完整的汇编代码如下。</p>
<p></p>
<div class="jb51code">
<pre class="brush:plain;">
.section        __TEXT,__text,regular,pure_instructions
        .ios_version_min 11, 2
        .globl        _test
        .p2align        2
_test:   ; @test
; BB#0:
        sub        sp, sp, #16 ; =16
        str        w0,
        str        w1,
        ldr        w0,
        ldr        w1,
        add                w0, w0, w1
        str        w0,
        ldr        w0,
        add        sp, sp, #16 ; =16
        ret

        .globl        _main
        .p2align        2
_main:   ; @main
; BB#0:
        sub        sp, sp, #32 ; =32
        stp        x29, x30, ; 8-byte Folded Spill
        add        x29, sp, #16 ; =16
        orr        w0, wzr, #0x1
        orr        w1, wzr, #0x2
        stur        wzr,
        bl        _test
        mov        w1, #0
        str        w0,
        mov       x0, x1
        ldp        x29, x30, ; 8-byte Folded Reload
        add        sp, sp, #32 ; =32
        ret
.subsections_via_symbols</pre>
</div>
<p>本节我们只讨论栈操作,因此忽略main函数和printf调用部分,我们只看对test函数的调用,节选这一段汇编代码如下。</p>
<p></p>
<div class="jb51code">
<pre class="brush:plain;">
sub        sp, sp, #16 ; =16
        str        w0,
        str        w1,
        ldr        w0,
        ldr        w1,
        add w0, w0, w1
        str        w0,
        ldr        w0,
        add        sp, sp, #16 ; =16
        ret</pre>
</div>
<p>首先介绍一下基本指令和指令的学习方式,要查询某个指令如何使用,最好的方式是去查询ARM公司提供的官方文档,在官方文档页面可以直接搜索指令并查看用法和例程,本文会简单讲解上面的汇编代码中出现的指令。</p>
<p>sub用于对寄存器实施减法, suba,b,c等价于 a=b-c,在ARM汇编中,目的操作数一般出现最前方,例如 mov ra,rb 代表将rb寄存器的值复制到ra寄存器。add和sub同理,只是将减法变成了加法。</p>
<p>str和ldr是一对指令,str的全称是store register,即将寄存器的值存储到内存中,ldr的全称是load register,即将内存中的值读到寄存器,因此他们的第一个参数都是寄存器,第二个参数都是内存地址。 代表 sp+12 这个地址,同理 代表 sp-12 这个地址。注意这里的数字都是以字节为单位的偏移量,以 str w0, 为例,w是4字节的寄存器,这个指令代表将w0寄存器的值存储在sp+12这个地址上,由于w0有4个字节,所以存储后会占据 sp+12~sp+16这个内存区域。</p>
<p>下面将分段讲解这段汇编代码,在编译器生成汇编时,首先会计算需要的栈空间大小,并利用sp指针向低地址开辟相应的空间,我们再来看一下test函数。</p>
<div class="jb51code">
<pre class="brush:plain;">
int test(int a, int b) {
int res = a + b;
return res;
}
</pre>
</div>
<p>这里涉及了3个int变量,分别是a、b、res,int变量占据4个字节,因此一共需要12个字节,但ARM64汇编为了提高访问效率要求按照16字节进行对齐,因此需要16byte的空间,也就是需要在栈上开辟16字节的空间,我们来看汇编的第一句,正是将sp指针下移16字节。</p>
<div class="jb51code">
<pre class="brush:plain;">
sub        sp, sp, #16</pre>
</div>
<p style="text-align: center"><img alt="" src="https://img.jbzj.com/file_images/article/202002/2020210105911619.jpg?2020110105953" /></p>
<p>sp下移16后,留下了4个4字节的内存空格,共计16字节,我们继续看下面的句子。</p>
<p>这两句的含义是将w0存储在sp+12的格子中,w1存储在sp+8的格子中,上面的例子中提到 x0, x1等寄存器将顺序存放函数的入参,x0和w0是同一个寄存器的不同大小体现,x0为8字节,w0为x0的前4个字节,因此w0是函数的第一个入参a,w1是函数的第二个入参b,由于存储是从低地址到高地址的,所以a将占据 sp+12~sp+16,同理b将占据 sp+8~sp+12,则栈的结构变为下图。</p>
<p style="text-align: center"><img alt="" src="https://img.jbzj.com/file_images/article/202002/2020210110014490.jpg?202011011030" /></p>
<div class="jb51code">
<pre class="brush:plain;">
str        w0,
str        w1, </pre>
</div>
<p style="text-align: center"></p>
<p>按照“上帝视角”,接下来test函数应该将a和b相加,需要注意的是,只有寄存器才能参与运算,因此接下来的汇编代码又将变量的值从内存中读出,进行相加运算。</p>
<p></p>
<div class="jb51code">
<pre class="brush:plain;">
ldr        w0,
        ldr        w1,
        add w0, w0, w1</pre>
</div>
<p>由此可见先存储再读取后运算其实是多余的,这是没有进行编译优化的结果,学习不进行编译优化的汇编更能让我们理解其工作机制。</p>
<p style="text-align: center"><img alt="" src="https://img.jbzj.com/file_images/article/202002/2020210110108832.jpg?202011011123" /></p>
<p>接下来的代码将w0存入了sp+4,也就是res变量的内存区域。</p>
<div class="jb51code">
<pre class="brush:plain;">
str        w0, </pre>
</div>
<p>接下来就要进行返回了,在例子中我们提到,函数的返回值一般存储在x0寄存器中返回,因此我们需要将res的值载入x0寄存器。</p>
<div class="jb51code">
<pre class="brush:plain;">
ldr        w0, </pre>
</div>
<p>这里之所以使用w寄存器,是因为int为4字节,这也就是类型转换时带来信息丢失的原因,例如从long到int的转换就类似于将x寄存器的值以w的形式进行存储。最后的代码为将栈还原,并返回到函数调用处继续向下执行。</p>
<div class="jb51code">
<pre class="brush:plain;">
add        sp, sp, #16
ret</pre>
</div>
<p>显然,经过这样的操作,栈被完全还原到了函数调用以前的样子,需要注意的细节是,栈空间中的内存单元并未被清空,这也就导致下一次使用低地址的栈时,未初始化单元的值是不确定的,这也就是局部变量不初始化值随机的根本原因。</p>
<p>通过上面的例子,我们对栈有了基本的认识,汇编的操作基本都是对栈进行的,只要理解了栈机制,只需要学习各种指令,即可掌握足够使用的汇编技能。</p>
<p class="maodian"></p><h2>深入</h2>
<p>在了解了栈以后,就可以看一些较为复杂的汇编片段来进行学习了,初级阶段可以尝试看着函数写汇编代码,高级阶段要求能够看着汇编还原成函数逻辑,本文仅仅介绍入门基础,下面推荐一些大牛的博客供大家深入学习汇编技能。</p>
<p>1.知兵的知乎专栏</p>
<p>2.刘坤的汇编入门文章</p>
<p class="maodian"></p><h2>总结</h2>
<p>掌握ARM汇编能够帮助开发者更好地了解编译器和CPU的工作原理,除了能够指导编码外,还能够扩宽视野,通过反编译分析一些闭源代码的逻辑或是进行一些安全加固,因此在汇编上付出时间是十分值得的。</p>
<p class="maodian"></p><h2>参考资料</h2>
<p>1.知兵. iOS调试进阶 https://zhuanlan.zhihu.com/c_142064221</p>
<p>2.ARM官方文档 http://infocenter.arm.com/help/index.jsp&#63;topic=/com.arm.doc.dui0802a/STUR_fpsimd.html</p>
<p>3.反调试和绕过 http://jmpews.github.io/2017/08/09/darwin/反调试及绕过</p>
<p>以上所述是小编给大家介绍的iOS汇编入门教程之ARM64汇编基础,希望对大家有所帮助!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>MySQL 5.7.22 二进制包安装及免安装版Windows配置方法</li><li>Ubuntu16.04安装mysql5.7.22的图文教程</li><li>win10 下安装mysql服务器社区版本mysql 5.7.22 winx64的图文教程</li><li>Mac 下 MySQL5.7.22的安装过程</li><li>ARM64架构下安装mysql5.7.22的全过程</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: iOS汇编入门教程之ARM64汇编基础教程