Chapter 10 漏洞利用
10.0 概念
安全事件与软件漏洞
- 安全事件:攻击者在给定时间段内,利用漏洞或其他攻击手段在攻击对象中注入并触发恶意代码,产生拒绝服务、信息泄露、信息窃取、目标控制等后果的过程。
- 漏洞:系统设计、实现或操作管理中存在的缺陷或弱点,能被利用而违背系统的安全策略。
漏洞分类与标准
按照漏洞威胁可分为:
- 提权漏洞
- 获权漏洞
- 拒绝服务漏洞
- 恶意软件植入漏洞
- 数据丢失或泄露漏洞
按照漏洞成因可分为:
- 输入验证漏洞
- 访问验证漏洞
- 竞态漏洞(竞争条件漏洞)
- 意外情况处理漏洞
- 设计漏洞
- 配置漏洞
- 环境漏洞
按照漏洞的严重性可分为:
- A 类漏洞(高):威胁性最大的漏洞,往往由较差的系统管理或错误设置造成。
- B 类漏洞(中):较为严重的漏洞,例如允许本地用户获得增加的和未授权的访问。
- C 类漏洞(低):严重性不是很大的漏洞,例如允许拒绝服务的漏洞。
按照攻击被利用的方式可分为:
- 本地攻击漏洞
- 远程主动攻击漏洞
- 远程被动攻击漏洞
重要网站:
- CVE:漏洞暴露平台
- CNVD:国家互联网应急中心
- CNNVD:国家信息安全测评中心
漏洞利用对系统的威胁
- 非法获取访问权限(获权)
- 未经授权使用资源,如打印、读取、写、执行文件等
- 权限提升(提权)
- 用户账号由低权限提升到高权限
- 拒绝服务
- 使计算机软件或系统无法正常工作、无法提供正常服务
- 本地拒绝服务:运行在本地的程序无法正常工作;远程拒绝服务:发送特定网络数据报文使提供服务的程序异常或退出
- 恶意软件植入
- 主动植入:利用正常功能或漏洞将恶意代码植入到目标之中,无需用户干预
- 被动植入:将恶意代码植入时需要用户配合操作(木马、病毒等)
- 数据丢失或泄露
- 由于对文件访问权限设置错误导致数据被非法读取。如读取密码
- 没有充分验证用户输入导致数据被非法读取。如Web相关漏洞
- 系统漏洞,如DNS域传送漏洞
漏洞产生原因
逻辑错误、缺陷、社工、策略失误等,可以分为技术因素和非技术因素(大多为人为因素)
- 技术错误:即上文提到的按漏洞成因分类的7种
- 非技术错误:缺乏规范导致维护困难、缺乏测试与维护、开发团队不稳定、缺乏进度控制等
漏洞利用方式
- 本地攻击模式:攻击者进入目标系统具有一定权限后进行的攻击。如内网渗透等
- 远程主动攻击模式:攻击者以网络连接发现目标漏洞后进行攻击
- 远程被动攻击模式:攻击者向目标发送网页、文档等点击后触发漏洞
软件漏洞生命周期
- 漏洞挖掘
- 漏洞重现
- 漏洞诊断
- 漏洞修复
- 补丁测试
- 补丁推送
(攻击者在其中任何一个时候都能够对系统造成损失)
典型软件漏洞
- 缓冲区溢出:在下面会具体说明
- 注入攻击:如SQL注入、系统命令注入、脚本注入等,多为对用户的输入检查不严格导致。
- 跨站漏洞:跨站脚本攻击(XSS),攻击者将恶意脚本代码嵌入到Web页面中,用户打开后执行其中的代码触发漏洞或获取网站权限。(被动攻击)
- 权限漏洞:访问控制机制上存在的漏洞。如Linux Kernel Pwn即利用Linux内核模块实现用户权限到root权限的提升。
10.1 栈缓冲区溢出
一、栈
- 栈是一段连续的地址空间,是一个后进先出的数据结构,由高地址向低地址增长。
- 每一个线程都有一个自己的栈,提供一个暂时存放数据的地方。
- push将esp下压,pop将ebp上抬
- esp指向栈顶,ebp指向栈帧底部。
注意:栈地址空间中保存的是程序执行时使用的局部变量,在main函数或其他函数中定义的变量均保存在栈中。代码中的字符串常量(如printf(“Hello world”))定义于rdata段,初始化的全局变量定义于data段,未初始化的全局变量定义于bss段。
栈中包含:
- 函数参数
- 函数返回地址
- ebp的值
- 一些通用寄存器的值
- 当前执行的函数的局部变量
函数调用过程:
- call指令执行之前,程序首先将函数需要的参数逐一push进栈中。(有几种不同的参数压入顺序)
- 执行call指令跳转eip到函数开头。call指令将eip压入栈中。
- 函数开头一般会执行指令push ebp; mov ebp, esp,保存前一个栈帧的地址。
- 之后esp可能会根据该函数中定义的所有局部变量需要占用的空间进行自减,以保证esp和ebp之间能够有充足的空间保存局部变量。
- 开始执行函数功能,函数调用过程结束。
函数调用类型:(32位)
- __cdecl:C调用规则,在后面的参数首先进入堆栈,参数返回后调用者负责清除堆栈,因此这种调用常会产生较大的可执行程序
- __stdcall:标准调用,在后面的参数首先进入堆栈,被调用的函数返回前自行清理堆栈,生成的代码比cdecl小
- __fastcall:将函数列表前两个参数放入寄存器,其他函数压栈,后面的参数首先进入堆栈,被调用者清理堆栈
- Pascal:基本不用,前面的参数首先进入堆栈,不支持可变参数的函数调用
函数返回过程:
- esp上抬到达ebp的位置,局部变量不作处理全部放弃,也不会将这段内存清零。
- ebp找到父函数栈帧的底部(函数调用时已经保存在ebp指向的内存空间)并上抬(pop ebp)。
- retn返回,返回到父函数call指令之后一条指令。
二、栈溢出
特点:
- 缓冲区在栈中分配
- 拷贝的数据过长且没有进行检查
- 覆盖了栈中的一些重要数据,如ebp、返回地址、某些关键局部变量等
产生原因:
- C语言中有大量没有溢出保护的函数,如*scanf系列、*printf系列、gets函数、strcpy函数、strcat函数等(注:read函数理论上可以读取零字节,因此较strcpy等函数具有更高的危险性)
- 程序员缺乏安全意识
shellcode和payload
- shellcode是能完成一种特殊任务的自包含的二进制代码,根据不同的任务可能是发出一条系统调用或是进行高权限操作。
- payload:shellcode以及实现跳转到shellcode的那部分填充代码的统称。
栈溢出shellcode的NSR模式:
- S——shellcode,首先写入栈的低地址处
- R——return address,返回地址,通常写入jmp esp的地址,在S之上
- N——nops,无效指令,在R之上
注意:NSR模式构造的shellcode很可能会失败,原因多种多样。有时jmp esp后栈中的内容会被修改,导致shellcode执行失败。此时可以利用SEH进行攻击。
什么是SEH?
结构化异常处理(StructuredExceptionHandling,SEH)是Windows操作系统处理程序错误或异常的技术。SEH是Windows操作系统的一种系统机制,与特定的程序设计语言无关。SEH的注册结构体只能作为局部变量存在于当前线程的调用栈,如果一旦结构体的地址不在当前调用栈的范围中,则在进行异常分发时,将不会进入该函数。即我们可以通过栈溢出修改SEH中函数指针的值。SEH是基于线程的一种处理机制,且依赖于栈进行存储和查找,所以被称作是基于栈帧的异常处理机制。有关于其具体实现与功能需要对windows异常处理机制有更加深入的了解,考试不做要求。
fs:[0]指向SEH的初始地址,通过获取该地址以确定溢出的长度,这个长度必须确定以保证能够正确覆盖关键函数指针。将指针覆盖为类似于jmp esp指令的地址,即可执行shellcode。
shellcode的特点:
- 长度受限(不是所有),具体问题具体分析。
- 不能使用特定字符,如’\x00’(read函数是唯一能够接收’\x00’的函数)
- API函数自搜索和重定位能力
- 一定的兼容性(很难,windows和linux的内存空间和内核排布很不一样)
练习题
1. 分析如下代码片段,回答下列问题。(25分)
1 | bool guess(){ |
注:①本函数中先定义的随机变量在栈中的地址在后定义的随机变量之上。②数字a的ASCII码为0x30+a,大写字母的ASCII码为0x40+字母序号(A为0,B为1,…,Z为25),小写字母的ASCII码为0x60+字母序号
(1) 简述该函数的执行流程,说明弹出计算器所需的条件。(4分)
(2) 本函数中存在什么漏洞?(3分)
(3) 当第一个输入为’12345678901234567890abcdefABCDEF\n’时,answer的值为_________(填16进制,2分),返回地址的值为_________(填16进制,2分),那么第二个输入为_________(3分)时可以成功弹出计算器。弹出计算器之后,这个程序很有可能会______________________(2分),原因是_______________________(2分)。
(4) 请写出改进方案,修复这个漏洞使计算器不那么容易被弹出。(7分)
答案:
(1) 本程序产生了一个随机数,之后可以在buffer中写入字符串,然后输入一个数字,如果这个数字与随机数相等,则弹出计算器。弹出计算器的条件是猜的数字与随机数相等。
(2) 栈溢出漏洞
(3) 0x30393837;0x42416665;809056311;崩溃;返回地址可能无效
(4) 将scanf("%s", buffer);
修改为scanf("%16s", buffer);
2. 分析如下代码片段,回答下列问题:(30分)
1 | void vuln(){ |
注:①通过反汇编找到vuln函数的起始地址为0x405678。②本函数中先定义的随机变量在栈中的地址在后定义的随机变量之上。
(1) 简述该函数的执行流程。(4分)
(2) 通过反汇编可以得到该函数的汇编代码,已知前几条语句如下,请填写前两空,第三空填写的数值x一定有______________。(3分)
1 | push ebp |
(3) 当输入长度为________时,程序会______________________________,从而获得system函数在内存中的地址。(4分)
(4) 如果这个函数只被调用了一次,能否仅通过这个函数弹出计算器?_____ 。为什么?__________________________________ 。(4分)
(5) 第一次函数调用时,你输入了第(3)题长度的payload,printf函数只显示了你输入的内容,可能的原因是_____________________________。(4分)在函数下一次调用时,你应该如何获取system函数的地址?__________________________________。(4分)
(6) 通过第一次函数调用的漏洞利用,获取到system函数的地址为0x7FAD8341,在该函数第二次被调用时,能否利用漏洞弹出计算器?若能,写出一个有效的输入并简述函数执行流程;若不能,说明理由。如果system函数的地址为0x7FAD8300呢?(7分)
答案:
(1) 函数首先接收任意长度的输入,然后检查exec函数指针是否为空,若不为空则执行到exec处。
(2) ebp;esp;x>=24
(3) 16;在printf输出时将system函数的地址接在输入后面输出
(4) 不能;无法控制exec的值,system的地址也不知道因此无法修改返回地址
(5) system函数地址最低位为空字节;尝试输入17、18、19字节数据,直至能够输出system函数的高位地址
(6) 能。输入为calc.exe(后接12字节空格)\x41\x83\xAD\x7F;不能,会截断。
10.2 堆缓冲区溢出与格式化字符串漏洞
一、堆
- 堆是存放程序中请求操作系统分配给自己的内存段,其大小不固定,可以进行动态扩大与缩小。操作系统中采用动态链表管理,其内存不一定连续。
- 每一个进程都有一个自己的堆(区别栈是每一个线程有一个)
- 使用malloc系列函数(malloc、calloc、realloc等)/new/mmap等函数分配堆空间;使用free/delete/unmap等函数释放堆空间。(调用的系统函数基本上相同)
注意:两个程序动态申请的堆空间地址完全有可能相同。要搞清楚虚拟地址和物理地址的区别,程序中直接操作的是虚拟内存地址,由操作系统从物理地址中映射而来,并非物理内存地址。但两个程序分配的堆地址的物理地址也有可能相同。两个程序可以交错使用这块内存。
结构:堆表和堆块。
- 堆表:位于堆空间起始位置,用于索引堆区中所有堆块的重要信息。在windows堆管理机制中,将堆表分为两种:快表(Lookaside)和空表(FreeList)。
- 空表:共128项的数组,每个数组是一个双向链表头,除索引0的链表外每个链表中存放相同大小的堆块。其中索引为x的链表存放大小为8x的空闲堆块,因此1~127共可以存放8~1016字节的堆块。(32位)索引为0的链表存放大小大于等于1024的堆块,按照从小到大的顺序排列。在实际分配内存流程中,为防止堆空间的碎片化,会对相邻的均处于空表中的堆块进行合并与重新排列。
- 快表:共128项的数组,采用单向链表存储,其中的堆块不合并且每一个链表最多存放4个堆块。其中索引为1~127的链表存放的堆块大小与空表相同。
- 堆块:一块块分离的,碎片化的地址空间,由块首和块身构成。
- 块首:头部几个字节,用于标识自身信息(大小,使用情况等)
- 块身:数据存储区域,紧跟块首,内存分配得到的地址指向块身的头部。在堆块被释放时,块身的前8个字节用于存放堆表中双向链表的向前指针Flink和向后指针Blink。
- HEAP区总体结构:
- hHeap:HEAP的总体管理结构,句柄
- 双指针区:定位分配内存、释放内存的位置,存放一些成对出现的指针
- 用户分配内存区
堆管理相关寄存器:
- eax:一般代码中用作返回值
- ecx:一般代码中用作计数器
- mov [ecx], eax; mov [eax+4], ecx
堆块关键操作:
- 申请堆块时的脱链操作:即双向链表的脱链操作,汇编指令为mov [ecx], eax; mov [eax+4], ecx
- 释放堆块时的入链操作:根据设计思想,应首先判断前后是否能进行堆块合并,若进行合并则需要进行脱链操作,再将合并后的堆块放入指定的链表中。
二、堆溢出
利用思路1:
- 使用mov [ecx], eax; mov [eax+4], ecx完成对任意地址的控制。
- 使用[esi+0x4C]指向下一个空闲堆块头部结构
- 当有不能处理的异常发生时,系统调用UnhandledExceptionFilter函数,其实就是call [0x77EC044c],即执行0x77EC044c指向的异常处理程序。因此修改此处地址即可。
- 随着防护措施的改进,上述思路在当前windows系统中已基本不可用。
利用思路2:堆喷射
- 向堆中注入大量数据,使数据填满特定内存地址空间,当栈溢出时可以引导EIP到堆的空间。其中填充的数据是大量重复的nop指令,如果之后eip能够指向这段nop指令,就能够一直执行到后面的shellcode。这段nop充当类似“滑梯/滑板”的作用。
- 目前依然流行,常见于解释JavaScript的浏览器和PDF解释器
- 申请大量内存时。堆很有可能覆盖到0x0A0A0A0A(162M)、0x0C0C0C0C(192M)、0x0D0D0D0D(208M)
传统的堆喷射使用JavaScript分配内存,根据堆喷射的思想,都是用同样一个指令覆盖一大片内存地址。在每块分配到的内存最后都附加shellcode。这个指令应相当于Nops的作用,且该指令指向的地址正好应落在覆盖的这片大的内存地址中。
指令0C0C对应汇编代码为or al, 0C,对寄存器影响最小,可以起到Nops的作用。如果将eip指向0x0C0C0C0C这个地址(产生异常时可能可以实现),就会在这片内存中不断执行下去直到shellcode为止。 - 优点:
- 增加缓冲区溢出攻击的成功率
- 覆盖地址可以简单使用类NOP指令覆盖
- 可用于堆栈溢出攻击,用slidecode覆盖堆栈返回地址
- 缺点:
- 会导致被攻击进程内存占用暴增,容易被发现
- 不能用于主动攻击,一般通过栈溢出利用或其他漏洞进行协同攻击
- 如果目的地址被shellcode覆盖,则shellcode执行会失败,因此不能保证一定成功
- 检测与防范:
- 发现应用程序的内存大量增加,检测堆上的数据,看是否包含大量slidecode
- 浏览器的脚本解释器开始重复申请堆的时候,监控模块记录堆的大小、内容和数量,如果重复堆请求到达阈值或者覆盖指定地址则立即阻止脚本执行。
- 对于一些利用脚本进行攻击的情况,可以通过hook脚本引擎,分析脚本代码,根据一些堆喷射常见特征检测是否受到攻击
- 开启DEP
利用思路3:Use After Free
- 当一块堆空间被释放,对这段内存空间进行操作即称为UAF。
- 由于windows内存空间中被释放的堆块的块身中存放有两个指针,通过UAF可以对这两个指针进行修改以破坏堆表的双向链表结构。
三、格式化字符串漏洞
- 适用于printf函数系列。
- 产生原因:
- printf的不定长参数个数,且没有进行检查。
- printf的%n能够对一段地址空间的值进行修改。(其功能是将前面已经打印出的字符个数赋给对应的地址)
- 通过静态扫描容易发现。
模拟题
3. 分析如下代码片段,回答下列问题:(24分)
1 | void vuln(){ |
注:①本函数中先定义的随机变量在栈中的地址在后定义的随机变量之上。②执行函数发现,函数的第一个输出为0x1030000,第二个堆块紧邻第一个堆块且在第一个堆块之上。
(1) 本函数存在2个漏洞,分别是____________________________(2分)。
(2) 本题能否直接在栈上写入shellcode?____ ,原因是__________________________。(4分)
(3) 本题能否直接在buf1中写入shellcode?____ ,原因是__________________________。(4分)
(4) 本题能够直接在buf2中写入shellcode,前面的地址填充无效地址?____ ,原因是___________________________。(4分)
(5) 由第(2)题和第(3)题,你认为第一个输入应该输入__________________,这样可以__________________________。之后第二个输入应为____________________________________,这样可以防止___________________,由此可以_______________________。(10分)
答案:
(1) 栈溢出漏洞、堆溢出漏洞
(2) 不能,不知道jmp esp或栈的地址
(3) 不能,buf1的地址后两字节均为空字节且buf的大小只有0x100。无法将返回地址覆盖为buf1中的任何地址(地址的第二个字节一定为\x00),会导致截断
(4) 不能,这样会破坏buf2堆块的头部结构,在free时会产生错误
(5) 0x100字节的垃圾数据,通过printf获取buf2堆块的块首信息;vuln函数的起始地址;函数异常退出;再次输入并在第二次调用时在buf2中溢出shellcode【出题漏洞:第一次申请的buf2和第二次不一样,块首信息可能不同】
10.3 整数溢出与其他漏洞
一、整数溢出
在程序计算过程中,有时会因为计算数值问题导致计算结果不正确。如计算int整型的0x12345678 × 0x87654321,这会导致溢出。有时这种溢出会导致严重的后果,如输入字符串长度时若不加检查输入负数,会导致过量读或过量写漏洞等。
防范方式:
- 整数安全意识:形成关于特殊数据输入的意识,比如之前先确定最大和最小输入,使用合适的数据类型。
- 避免隐患运算直接操作:尽量避免对两个正数相加或相乘之后,再取结果比较。
- 越界判断:在使用变量申请内存,或者作为数组下标时,注意对越界的监测。
- 代码审计、安全检测
模拟题
4. 分析如下代码片段,回答下列问题:(34分)
1 | void vuln(){ |
(1) buf[i] &= 0xDF
的作用是____________________,这段代码实现的功能是______________________________________________________。(6分)
(2) 本题的漏洞产生的原因是_____________________________,要想执行calc.exe,首先需要解决的问题是____________________________。由于___________(填功能)的执行在___________(填功能)之前,因此可以事先______________________,这样缓冲区在读取到变量alphabet的_________(填’高地址一端’或’低地址一端’)时就能够____________________。从而将______(填一个可打印字符)成功写入system命令字符串的缓冲区buf中(参考ASCII码值:0x2B=‘+’; 0x2E=‘.’; 0x31=‘1’; 0x41=‘A’)(16分)
(3) 除了(2)的漏洞外,如果输入字符的ASCII码大于______的ASCII码,就会造成溢出,读取到alphabet高地址位置的值。如果alphabet的首地址为0x6000,那么想要让buf中存放该函数的返回地址,应该输入ASCII码为_____________________(填4个16进制数)的字符。(6分)
(4) 简述本题代码的漏洞修补方案,写出修改后的代码。(6分)
10.4 漏洞利用与发现
1. 漏洞利用
漏洞研究包含漏洞挖掘、漏洞分析、漏洞利用与漏洞防御四个方面。
漏洞的来源:黑客自行挖掘出来的漏洞,还未修复(被称为零日漏洞,即0-day漏洞)、从公开发布的POC或黑客交换漏洞得到(此类漏洞为0-day漏洞或1-day漏洞)、从已经发布的漏洞公告和漏洞补丁中获取的漏洞(又被称为n-day漏洞)。
漏洞利用的条件:用户没有打补丁或更新安全工具、管理员没有打补丁或更新安全工具、存在脏数据渗透路径
Exploit的结构
- Exploit:利用漏洞实现shellcode植入和触发的过程
- Exploit ≈ Payload + Shellcode
- payload是针对于特定漏洞设计的用于触发漏洞的部分,与漏洞本身紧密相关,而shellcode与漏洞本身无关,只是一段可任意执行的代码。
漏洞利用的目标:
- 修改内存变量 (邻接变量)
- 修改代码逻辑(代码的任意跳转)
- 修改函数的返回地址
- 修改函数指针(C++)[虚函数](虚函数用于C++函数重载)
- 修改异常处理函数指针(SEH,VEH,UEF,TEH)
- 修改线程同步的函数指针
漏洞利用的过程:
- 定位漏洞点:利用静态分析和动态调试确定漏洞机理,如堆溢出、栈溢出、整数溢出的数据结构,影响的范围
- 按照利用要求,编写Shellcode
- 溢出,覆盖代码指针,使得Shellcode获得可执行权
2. Ret2Libc + ROP
这是一种将返回地址修改到dll文件代码段的漏洞利用方式,可以不使用shellcode就能够执行指定功能。特点是向dll借代码而不是自己写shellcode,也可以通过这种利用方式完善payload。其中ROP返回到的地址可能不是函数的起始地址,而是函数中某一个位置的地址。
Ret2Libc -> VirtualProtect
在dll文件中有一个VirtualProtect函数可以用于关闭栈不可执行保护(NX),从而可以在栈上写入shellcode并执行。首先使用Ret2Libc将执行VirtualProtect函数,传入合适的参数。返回后转到jmp esp的地址,即可执行shellcode。
Ret2Libc的防护措施:ASCII armoring
将所有库函数的起始地址都包含一个零字节,使得无法完整溢出。
恶意代码对抗措施:Ret2plt
- Linux ELF文件中大多数有一个.plt节,其中存放一系列jmp指令跳转到所有需要引用的库函数。因此可以将返回地址写到.plt节中跳转到需要的库函数中。.plt节的深入原理与Linux的间接跳转原理有关,不要求掌握。
练习题
5. 分析如下代码片段,回答下列问题:(29分)
1 | char key[10] = {0}; |
1 | (0x1000) |
(1) 要想在全局变量key中保存字符串’calc.exe’,需要满足的条件是________________________________________;要想在全局变量func中保存函数指针system,需要满足的条件是___________________________;(4分)
(2) 本题的栈溢出需要通过_______漏洞触发,在vuln函数输入len为_________,可以绕过检查,从而能够写入很长的字符串。输入字符串的第________(填16进制数)位能够覆盖vuln函数返回地址。(6分)
(3) 设vuln函数的返回地址在栈中保存的位置为0x100,那么如果需要在全局变量key中保存字符串’calc.exe’,需要将这个地方覆盖为_______________,这个函数的第一个参数应该写在__________________(填’0x104’或’0x108’)的位置,原因是____________________________。(6分)
(4) 如果想在全局变量func中保存函数指针system,在调用foo2函数之前需要首先使用0x1000和0x1010的两段代码碎片,这种漏洞利用方式被称为__________。(2分)
(5) 写出read函数执行时你的完整输入,格式:每行按"地址:值"格式书写,如"0x100:0x12345678"。已知函数的返回地址为0x100,foo1函数的起始地址为0x4013AB,foo2函数的起始地址为0x402880,全局变量key的地址为0x602040,func的地址为0x602050,可以写入任意字节(含零字节),提示:你需要首先计算输入的起始地址然后再开始书写。(8分)
(6) 修复这个漏洞最简单的修改方法是______________________。(3分)
答案:
(1) 调用foo1函数且参数从左到右的值依次应为0x12345678、0x87654321、0xdeadbeef;调用foo2函数且调用时eax的值应为0x12345678,ebx的值应为0x87654321
(2) 整型溢出漏洞;负数;0x48~0x4b
(3) foo1函数起始地址(或foo1函数中strcpy语句的起始地址);0x108;0x104应为foo1函数的返回地址
(4) ROP
(5)
- 0xbc~0xff:垃圾数据
- 0x100~0x103:0x1020
- 0x104~0x107:0x1000
- 0x108~0x10b:0x4D9BF99F(0x4D9BF99F XOR 0xcafebabe = 0x87654321)
- 0x10b~0x10f:0x1010
- 0x110~0x113:0x1000
- 0x114~0x117:0x12345678
- 0x118~0x11b:0x402880(foo2函数起始地址)
- 0x11c~0x11f:0x4013AB(foo1函数起始地址)
- 0x120~0x123:0x1030(思考这里将返回地址设为0x1030的作用是什么)
- 0x124~0x127:0x12345678
- 0x128~0x12b:0x87654321
- 0x12c~0x12f:0xdeadbeef
- 0x130~0x133:0x602050
- 0x134~0x137:0xdeadbeef
- 0x138~0x13b:0x602040
(6) if判断条件加上对负数的判断
6. Ret2csu是Linux平台下一种经典的ROP利用方法,它利用了每一个linux系统中可执行文件都存在的函数__libc_csu_init函数。下面是该函数的一个片段,分析如下代码片段,回答下列问题:(20分)(注:考试中不会出64位机器的题目,这里仅做练习,必要信息都会给出)
1 | loc_401A50: ; 下面是每一条指令的地址 |
(1) 经过检查机器码发现,pop r15指令占2字节,其高1字节可表示另一条指令:pop rdi。同样pop r14指令的高1字节可表示pop rsi指令。在上述汇编代码中,将rip改为___________可以执行pop rsi,改为__________可以执行pop rdi。(4分)
(2) 这段代码中有一个比较判断,当使用call指令执行代码之后,如果不再需要call调用,应该跳过这个判断,需要满足_________________。(3分)
(3) 在64位Linux系统中,函数调用的前6个参数分别存放在rdi,rsi,rdx,rcx,r8,r9寄存器中。若要调用某函数,其前三个参数需要是0x12345678,0x87654321,0xdeadbeef,在ROP覆盖返回地址时,可以将返回地址修改为________________,可连续设置多个寄存器的值为自己设定的任意值。(3分)
(4) 设此时某关键函数foo的地址保存在0x602020处,请写出ROP的内容以执行"foo(0x12345678,0x87654321,0xdeadbeef)"(从返回地址开始写,返回地址保存在栈中的地址为0x100000)(10分)
格式示例:
1 | 0x100000: 0x1234567887654321 |
答案:
(1) 0x401A70;0x401A72
(2) r14==rbx+1
(3) 0x401A69
(4)
- 0x100000~0x100007:0x401A69
- 0x100008~0x10000f:0(rbx)
- 0x100010~0x100017:0x12345678(rbp)
- 0x100018~0x10001f:0x602020(r12)
- 0x100020~0x100027:0xdeadbeef(r13)
- 0x100028~0x10002f:1(r14)
- 0x100030~0x100037:0x87654321(r15)
- 0x100038~0x10003f:0x401A50