2019 3月所学 栈溢出入门级方法

一、操作系统

操作系统是管理计算机硬件资源,控制其他程序运行并为用户提供交互操作界面的系统软件的集合。操作系统是计算机系统的关键组成部分,负责管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本任务。操作系统的种类很多,各种设备安装的操作系统可从简单到复杂,可从手机的嵌入式操作系统到超级计算机的大型操作系统。目前流行的现代操作系统主要有Android、BSD、iOS、Linux、Mac OS X、Windows、Windows Phone和z/OS等,除了Windows和z/OS等少数操作系统,大部分操作系统都为类Unix操作系统。

操作系统可以将函数调用栈的起始地址设为随机化(这种技术被称为内存布局随机化,即Address Space Layout Randomization (ASLR) ),这样程序每次运行时函数返回地址会随机变化。反之如果操作系统关闭了上述的随机化(这是技术可以生效的前提),那么程序每次运行时函数返回地址会是相同的,这样我们可以通过输入无效的溢出数据来生成core文件,再通过调试工具在core文件中找到返回地址的位置,从而确定 shellcode 的起始地址。

操作系统打开了 ASLR,程序每次运行时动态库的起始地址都会变化,也就无从确定库内函数的绝对地址。在 ASLR 被关闭的前提下,我们可以通过调试工具在运行程序过程中直接查看 system() 的地址,也可以查看动态库在内存的起始地址,再在动态库内查看函数的相对偏移位置,通过计算得到函数的绝对地址。

现代操作系统内存通常是以分段的形式存放不同类型的信息的。

二、缓存区

1.栈,由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。

  2.堆,由new分配的内存块,编译器不能控制其释放,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

  3.自由存储区,由malloc等分配的内存块,和堆是十分相似,不过它是用free来结束自己的生命的。

  4.全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

  5.常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(可通过非正当手段修改)

三、寄存器

通用寄存器和特殊寄存器

通用寄存器:

包括一般寄存器(eax、ebx、ecx、edx),索引寄存器(esi、edi),以及堆栈指针寄存器(esp、ebp)。

一般寄存器用来存储运行时数据,是指令最常用到的寄存器,除了存放一般性的数据,每个一般寄存器都有自己较为固定的独特用途。eax 被称为累加寄存器(Accumulator),用以进行算数运算和返回函数结果等。ebx 被称为基址寄存器(Base),在内存寻址时(比如数组运算)用以存放基地址。ecx 被称为记数寄存器(Counter),用以在循环过程中记数。edx 被称为数据寄存器(Data),常配合 eax 一起存放运算结果等数据。

索引寄存器通常用于字符串操作中,esi 指向要处理的数据地址(Source Index),edi 指向存放处理结果的数据地址(Destination Index)。

堆栈指针寄存器(esp、ebp)用于保存函数在调用栈中的状态。

Intel 格式,寄存器名称和数值前无符号:

AT&T 格式,寄存器名称前加“%”,数值前加“$”:

特殊寄存器:

包括段地址寄存器(ss、cs、ds、es、fs、gs),标志位寄存器(EFLAGS),以及指令指针寄存器(eip)。

四、内存

内存分段还包括堆(Heap Segment)、数据段(Data Segment),BSS段,以及代码段(Code Segment)。代码段存储可执行代码和只读常量(如常量字符串),属性可读可执行,但通常不可写。数据段存储已经初始化且初值不为0的全局变量和静态局部变量,BSS段存储未初始化或初值为0的全局变量和静态局部变量,这两段数据都有可写的属性。堆用于存放程序运行中动态分配的内存,例如C语言中的 malloc() 和 free() 函数就是在堆上分配和释放内存。

五、栈

EIP寄存器里存储的是CPU下次要执行的指令的地址

EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址

ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶

函数调用时,调用函数(caller)的状态被保存在栈内,被调用函数(callee)的状态被压入调用栈的栈顶;在函数调用结束时,栈顶的函数(callee)状态被弹出,栈顶恢复到调用函数(caller)的状态。函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。

1.被调用函数(callee)的参数按照逆序依次压入栈内(保存在调用函数(caller)的函数状态内,之后压入栈内的数据都会作为被调用函数(callee)的函数状态来保存)

2.调用函数(caller)进行调用之后的下一条指令地址作为返回地址压入栈内

3.将被调用函数的返回地址压入栈内,并将 ebp 寄存器的值更新为当前栈顶的地址(调用函数(caller)的 ebp(基地址)信息得以保存。同时,ebp 被更新为被调用函数(callee)的基地址。)

4.将调用函数的基地址(ebp)压入栈内,并将当前栈顶地址传到 ebp 寄存器内

5.将被调用函数(callee)的局部变量等数据压入栈内

6.将被调用函数的局部变量弹出栈外

7.将调用函数(caller)的基地址(ebp)弹出栈外,并存到 ebp 寄存器内

8.将被调用函数(callee)的返回地址弹出栈外,并存到 eip 寄存器内

四、基础栈溢出

发生条件:1.程序要有向栈内写入数据的行为。2.程序并不限制写入数据的长度。

基本案例:

1.利用栈溢出执行攻击指令

(一)发生原理:当溢出数据中包括攻击指令的内容或地址,并且攻击指令获得程序的控制权时。

(二)控制程序执行指令最关键的寄存器就是 eip,目标就是让 eip 载入攻击指令的地址。让溢出数据用攻击指令的地址来覆盖返回地址。(在溢出数据内包含一段攻击指令,或在内存其他位置寻找可用的攻击指令。)

(三)将原本指定的函数在调用时替换为其他函数。

技术:

修改返回地址,让其指向溢出数据中的一段指令(shellcode

修改返回地址,让其指向内存中已有的某个函数(return2libc

修改返回地址,让其指向内存中已有的一段指令(ROP

修改某个被调用函数的地址,让其指向另一个函数(hijack GOT

第一项技术

Padding (填充)属性定义元素边框与元素内容之间的空间

padding1 + address of shellcode + padding2 + shellcode

(padding1 处的数据可以随意填充(注意如果利用字符串程序输入溢出数据不要包含 “\x00” ,否则向程序传入溢出数据时会造成截断),长度应该刚好覆盖函数的基地址。address of shellcode 是后面 shellcode 起始处的地址,用来覆盖返回地址。padding2 处的数据也可以随意填充,长度可以任意。shellcode 应该为十六进制的机器码格式。)

1.求返回地址之前的填充数据(padding1)长度

查看汇编代码来确定这个距离,也可以在运行程序时用不断增加输入长度的方法来试探

(cyclic 100就是输出100个乱码字符)

2.求shellcode起始地址

在调试工具里查看返回地址的位置(可以查看 ebp 的内容然后再加4(32位机)64位机则加8)

在调试工具里的这个地址和正常运行时并不一致,这是运行时环境变量等因素有所不同造成的。所以这种情况下我们只能得到大致但不确切的 shellcode 起始地址,解决办法是在 padding2 里填充若干长度的 “\x90”(表示NOP)。

3.求 shellcode 所用溢出数据的最终构造

这种方法生效的一个前提是在函数调用栈上的数据(shellcode)要有可执行的权限(另一个前提是上面提到的关闭内存布局随机化)。

第二项技术

padding1 + address of system() + padding2 + address of “/bin/sh”

address of system() 是 system() 在内存中的地址,用来覆盖返回地址。padding2 处的数据长度为4(32位机),对应调用 system() 时的返回地址。因为我们在这里只需要打开 shell 就可以,并不关心从 shell 退出之后的行为,所以 padding2 的内容可以随意填充。address of “/bin/sh” 是字符串 “/bin/sh” 在内存中的地址,作为传给 system() 的参数。

1.求返回地址之前的填充数据(padding1)长度

同上

2.求system() 函数地址

当函数被动态链接至程序中,程序在运行时首先确定动态链接库在内存的起始地址,再加上函数在动态库中的相对偏移量,最终得到函数在内存的绝对地址。

3.求“/bin/sh” 的地址

在动态库里搜索这个字符串,如果存在,就可以按照动态库起始地址+相对偏移来确定其绝对地址。如果在动态库里找不到,可以将这个字符串加到环境变量里,再通过 getenv() 等函数来确定地址。

第三项技术

针对程序栈溢出所要实现的效果,找到若干段以 ret 作为结束的指令片段,按照上述的构造将它们的地址填充到溢出数据中。

padding + address of gadget 1 + address of gadget 2 + ...... + address of gadget n

padding + address of gadget 1 + param for gadget 1 + address of gadget 2 + param for gadget 2 + ...... + address of gadget n + shellcode

1.栈溢出之后要实现什么效果?

ROP 常见的拼凑效果是实现一次系统调用,Linux系统下对应的汇编指令是 int 0x80。执行这条指令时,被调用函数的编号应存入 eax,调用参数应按顺序存入 ebx,ecx,edx,esi,edi 中。

2.如何寻找对应的指令片段?

若干开源工具可以实现搜索以 ret 结尾的指令片段,著名的包括 ROPgadgetrp++ropeme 等,甚至也可以用 grep 等文本匹配工具在汇编指令中搜索 ret 再进一步筛选。

3.如何传入系统调用的参数?

可以用 pop 指令将栈顶数据弹入寄存器。如果在内存中能找到直接可用的数据,也可以用 mov 指令来进行传输(用pop更简单)

注意:

第一,很多时候并不能一次凑齐全部的理想指令片段,这时就要通过数据地址的偏移、寄存器之间的数据传输等方法来“曲线救国”。

第二,第二,要小心 gadget 是否会破坏前面各个 gadget 已经实现的部分,比如可能修改某个已经写入数值的寄存器。另外,要特别小心 gadget 对 ebp 和 esp 的操作,因为它们的变化会改变返回地址的位置,进而使后续的 gadget 无法执行。

第四项技术:

GOT 全称是全局偏移量表(Global Offset Table),用来存储外部函数在内存的确切地址。GOT 存储在数据段(Data Segment)内,可以在程序运行中被修改。PLT 全称是程序链接表(Procedure Linkage Table),用来存储外部函数的入口点(entry),程序总会到 PLT 这里寻找外部函数的地址。PLT 存储在代码段(Code Segment)内,在运行之前就已经确定并且不会被修改, PLT 并不会知道程序运行时动态链接库被加载的确切位置。 PLT 表内存储的入口点就是 GOT 表中对应条目的地址。

1.如何确定函数 A 在 GOT 表中的条目位置?

程序调用函数时是通过 PLT 表跳转到 GOT 表的对应条目,所以可以在函数调用的汇编指令中找到 PLT 表中该函数的入口点位置,从而定位到该函数在 GOT 中的条目。

2.如何确定函数 B 在内存中的地址?

知道了函数 A 的运行时地址(读取 GOT 表内容),也知道函数 A 和函数 B 在动态链接库内的相对位置,就可以推算出函数 B 的运行时地址。

3.如何实现 GOT 表中数据的修改?

找到若干条 gadget,改写 GOT 表中数据,从而实现函数的伪装。

防御措施:

首先,通常情况下程序在默认编译设置下都会取消栈上数据的可执行权限,这样简单的 shellcode 溢出攻击就无法实现了。其次,可以在操作系统内开启内存布局随机化(ASLR),这样可以增大确定堆栈内数据和动态库内函数的内存地址的难度。编译程序时还可以设置某些编译选项,使程序在运行时会在函数栈上的 ebp 地址和返回地址之间生成一个特殊的值,这个值被称为“金丝雀”。这样一旦发生了栈溢出并覆盖了返回地址,这个值就会被改写,从而实现函数栈的越界检查。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,776评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,527评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,361评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,430评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,511评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,544评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,561评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,315评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,763评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,070评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,235评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,911评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,554评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,173评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,424评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,106评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,103评论 2 352

推荐阅读更多精彩内容

  • 原文地址:C语言函数调用栈(一)C语言函数调用栈(二) 0 引言 程序的执行过程可看作连续的函数调用。当一个函数执...
    小猪啊呜阅读 4,603评论 1 19
  • Return-Oriented-Programming(ROP FTW) Author: Saif El-Sher...
    RealSys阅读 3,315评论 0 2
  • 0. 引言 如果你学的第一门程序语言是C语言,那么下面这段程序很可能是你写出来的第一个有完整的 “输入---处理-...
    pandolia阅读 14,032评论 13 27
  • 最近只感觉工作好累,好累,
    经济兔兔阅读 247评论 0 0
  • 世上凡事诸多,你说不清道不楚,甚至看不出,如果人们遇到了烦恼,多半会苦恼、逃避,甚少有执着的人会去面对,这就应征了...
    风人深致阅读 510评论 0 0