序幕
正如您知道的那样,ARM为我们周围的各种低功耗设备供电,包括但不限于电话,路由器,物联网设备等。因此,深入研究这种体系结构并理解它与x86和x64体系结构有何不同之处是合理的。对于这篇文章,我们将重点介绍目前最常用的64位ARM CPU。我们的设备包括ARM Cortex-A53 CPU的Ubuntu 16.04,它支持32位和64位指令集。
在之前的文章中,我们逆向了x64Linux和Windows中的C++二进制文件。在这篇文章中,我们将会使用同样的程序,但用c语言重写。
编译程序:
$ gcc crack_me.c -o crack_me
####二进制信息:
反编译
现在让我们用启动GDB二进制并开始分析。请注意,我使用GEF(https://github.com/hugsy/gef)和GDB,所以我的提示符看起来像gef
>而不是gdb
>。我们先分解一下主要功能。
$ gdb ./crack_me
gef> disas main
我们的注意力直接转向<main + 64>
处的<check_pass>
函数,但在之前,您可能需要花点时间并理解这些指令的含义。您可以在ARM的文档(https://developer.arm.com/docs/100069/latest/a64-general-instructions)上阅读更多关于这些内容的信息。
以下是对我们的分析很重要的一些说明。
b -分支到标签,类似于jmp语句
bl -分支到链接到标签,类似于调用语句
b.ne -分支到标签,如果不相等,类似于jne语句
b.eq -分支到标签,如果相等,类似于je声明
让我们深入汇编代码。编号是指在gdb反汇编输出中突出显示的部分。
1.在地址0x4007bc
, <main+4>
,堆栈指针(SP)寄存器是MOV “ED
寄存器X29
。然后我们注意到从x29
寄存器访问的主要函数参数。请注意,x29
寄存器的偏移量28包含argc
,而偏移量16包含argv
(这是我们的输入密码)。在比较argc
值时,如果它等于0x2
,我们将(b.eq – branch if equal)
分支到<main + 52>
。
2.接下来的三行<main + 52>
,<main + 56>
和<main + 60>
将argv
字符串的大小从16扩展到24(16 + 0x8 = 24),并由x0寄存器引用。
3.然后我们调用(bl – branch with link)
到<check_pass>
函数。
我们来拆开<check_pass>
函数。
gef> disas check_pass
4.在地址 0x400738
,<check_pass+8>
,新的argv
字符串从x0寄存器复制到x29
寄存器。偏移量为24。然后,我们看到一些堆栈的canary操作,从<check_pass+12>
到<check_pass+24>
,有一些被储存在x29寄存器中,地址是0x411048
,然后在函数的末尾,从<check_pass + 96>
开始,直到<check_pass + 124>
。
5.回到<check_pass>
函数的主体,我们看到从<check_pass + 32>
开始,有些东西被访问从0x4008d0
,并被储存到x29
寄存器中,有偏移0x28(40)
,可能是秘密密码?
6.然后从<check_pass + 60>
开始,x1
寄存器指向从0x4008d0
和x0
寄存器中新复制的数据到x29
寄存器中的argv
字符串,偏移量为24,然后调用strcmp (x0 & x1)
函数。strcmp
函数的返回值存储在16位通用w0
寄存器中。如果字符串相等,则w0
设置为0x0
,否则设置为0x1
。
回到<main>
功能…
7.<check_pass>
函数的返回值存储在w0寄存器中,该值被复制到偏移量为44的x29寄存器中。然后在<main + 76>
处比较w0寄存器的值以查看它是否等于0x1。如果不是,我们跳转(b.ne – branch if not equal)
到<main + 100>
,这将导致我们获得成功消息,最后退出程序。
现在我们将用错误的密码启动该程序。但在此之前,我们必须在<main + 76>
的比较语句中添加断点。
gef> break *0x400804
gef> run pass123
我们在0x400804
,<main+76>
的比较语句中击中了断点另外,请注意x0寄存器的值是0x1。因为,x0指针只是w0寄存器+ 32位额外位,x0包含<check_pass>
函数的返回值。从源代码中,我们知道程序将检查check_pass函数的返回值是否为1,以显示“错误密码”消息。因此,该值应该是除了0x1之外的任何值,以便程序向我们显示成功消息。
让我们改变它的价值…
gef> set $x0=0x0
现在让我们继续执行。
gef> continue
结语
原来我们的假设是正确的。将x0的值从0x1更改为0x0的技巧。这意味着它会一直检查w0是否设置为0x1来显示不正确的消息,我们从程序的源代码中知道这一点。因此,回到<check_pass>
函数,我们注意到从地址0x4008d0
复制了一些东西。我们来检查一下。
这看起来不像任何有效的汇编指令,但53的重复是可疑的,41也是十六进制的'A
‘。这绝对看起来像一个常量字符串。让我们看看更深。从我们的地址0x4008d0
转储10多行…
查看0x4008d0
和0x4008d4
,我们可以看出它是little-endian 8
位字符串。让我们尝试解码它…
这里我们有原始密码“ PASSWORD ”
。
这只是使用gdb分析二进制文件在不同的体系结构中的一个例子。展望未来,我们将处理更复杂的程序,不常见的架构和更奇怪的二进制文件。
原文地址:https://scriptdotsh.com/index.php/2018/04/26/ground-zero-part-3-reverse-engineering-basics-linux-on-arm64/