看雪论坛
发新帖
5

[翻译]一篇安全学习笔记

lumou 2017-8-19 11:48 2374

我就我看到的安全视频做了一些笔记(作为一种快速回忆的方法)。

这些笔记对于初学者来说可能更有用。

这些笔记并非以难易程度排序,而是以时间顺序(也就是说,最近的在前面)。

 

许可证

本文全部文字在“知识共享署名-非商业性使用-相同共享方式4.0国际”(CC-BY-NC-SA)条款执行提供。

 

笔记


CTF中逆向和PWN的分析

写于 2017 / 7 / 2

有感于 @p4n74  @h3rcul35  InfoSecIITR #bin 的讨论。我们讨论的是让新手觉得很困惑的如何着手处理比较大的二进制文件,尤其是精简过的(stripped)。

 

不管是要逆向还是要pwn它,为能有效进行漏洞利用,你都要先分析拿到的二进制文件。由于该二进制文件可能被精简过(显示为file),所以要我们得知道从哪开始分析。

 

在寻找二进制文件里的漏洞的过程中,有几种不同的分析方式(就我收集到,不同的CTF战队有不同的风格):

 

1.静态分析

1.1 将代码全部转换(Transpiling)成C

这种分析方式比较少见,但是对于小型的二进制文件来说挺有用的。这种方法就是将整个文件都拖进逆向工具中。在IDA中打开每一个函数(用decomplier view,反编译视图),然后重命名(快捷键:n,重新编辑(快捷键:y)这些函数使反编译代码更具可读性。然后,将这些代码复制/导出成单独的.c文件,这些C文件可以经过编译也能得到和最初那个文件差不多的二进制文件。至此,对源文件的分析就算做好了,可以用来查找漏洞或者其他有用信息了。只要找到漏洞点,利用IDA处理好的汇编和伪码(用Tab实现两者的快速切换,用空格切换函数的图形视图和文本视图)就可以进行exploit的编写了。

 

1.2最小化对反编译的分析

 

我们常常得做这件事,因为二进制文件里的大部分是相对没用的(从攻击者的角度)。你只需要分析可疑函数或者能帮你找到漏

洞的函数。为达此目的,可以从以下几个方面着手:

 

1.2.1 main函数开始

 

现在,很多精简过的二进制文件(stripped binary),对main函数并没有标记(虽然IDA 6.9 以后的版本已经帮你标记了),但是,时间久了你也得自己学会如何从进入点(也就是 IDA默认打开的地方)到达main,并从那里开始分析。

 

1.2.2 查找相关的字符串

 

有的时候,你知道程序会输出某些有用的字符串(比如说,逆向中的“恭喜, flag%s”).你可以跳转到到字符串视图(快捷键:Shift+F12),找到该字符串,用XRefs进行逆向操作(快捷键:x.XRefs能帮你找到该字符串的函数路径,对路径中的函数使用XRefs,直到你找到main(或者你知道的其他点)。

 

1.2.3 从一些随机函数开始

 

有的时候,没有特别有用的字符串,然后你又不想从main开始。这个时候,你可以快速浏览整个函数表,查找看起来比较可疑的函数(比如有很多常量,很多xor等等)或者调用了重要函数(对mallocfree等等进行XRefs),然后你就可以从那里开始了,向前(顺着调用它的函数)或者向后(XRef该函数)均可。

 

1.3 纯汇编分析

 

有时,你无法使用反编译视图(原因可能是:奇怪的架构,或者反汇编技术,或者手动汇编,或者反汇编看起来过于复杂)。这种情况下,只看反汇编视图是最有效的法子了。对于未知的架构,打开自动备注功能是一个非常有用的方法,因为它可以给每一条指令进行注释。另外,节点着色(node colorization)和群节点功能(group nodes functionalities)也超有用。即使你都没有用到这些,只是简单的给汇编语言加注释就能帮到很多。就我个人而言,我更喜欢写python形式的注释,这样我稍后就可以手动将它们转换成python了(对不得不用Z3库的逆向尤其有用)。

 

1.4 使用类似BAP的平台

 

这种分析是(半)自动的,而且对于大多数软件来说通常是有用的,但是很少能直接用在CTF中。

 

2.Fuzzing 模糊测试

 

Fuzzing是获取漏洞很有效的技术。你都不需要完全了解它,只需使用漏洞检查工具(Fuzzer)就可以得到很多触手可及的漏洞,之后再对它们进行分析分类得到真正的漏洞。更多信息参看我的笔记 basics of fuzzing  genetic fuzzing

 

3.动态分析

动态分析可以在使用静态分析找到漏洞之后进行,以便更快的写出exploit。当然,也可以用动态分析来发现漏洞。一般是这样,在调试器中运行可执行程序,尝试着走一遍代码以期找到触发bug的地方。在合适的地方设置断点,然后分析寄存器//栈的状态,我们就可以知道发生了什么。我们也可以用调试器快速确定重要函数。可以这么做:比如,在最开始的时候对所有函数都暂时插入断点,然后分两步走:一步走所有不重要的函数,一步只走重要的函数。第一步经过所有不重要的函数然后取消端点,最后只留下第二步中的重要函数还有断点。

 

我个人的分析方式是,先用静态分析,通常从main(或者,对于不是基于console的应用程序,从字符串)开始,快速找到可疑函数。然后从这里开始向前向后扩展,通常会写注释,重命名,重编辑变量以便反编译。和很多人一样,对于可能有用但现在还未知的函数/变量等我会用 AppleBanana Carrot等这些名称,为便于分析(对我来说,跟踪func_123456这种风格的名字有点难)。我也会用IDA中的结构体视图来定义结构体(和枚举)来使反编译的效果更好。一旦我找到漏洞,我会用pwntools(并用它来调用gdb.attach() )来写一个脚本。这样我对于发生了什么就有更多的控制权。在gdb中,我通常使用gdb的简单版本,虽然我有添加peda命令,如果需要就可以即时加载peda

 

我的风格也一直在变化,因为对于我的工具我用得越来越顺手了,再加上自己写的工具。非常乐意听到其他的分析方式, 以助我更快分析。如果您对本文有任何评论/批评/赞扬,请移步我的Twitter @jay_f0xtr0t.

 

 

Return Oriented Programming面向返回编程

写于2017 / 6 / 4

有感于Gynvael Coldwind这个超赞的视频。在这个视频中他谈及ROP的基础,并提了一些技巧。

 

面向返回编程(Return Oriented ProgrammingROP)是最典型的漏洞利用技术之一,用来避开NXnon executable memory, 不可执行内存)保护。微软的NXDEPdata execution prevention, 数据执行保护)。Linux也有NX,这就意味着,在这种保护机制下,你不再可以将shellcode放到堆栈中并使之得以执行。现在,要执行代码,你得进入之前已经存在的代码区(main binary 主库,或者它的库——Linuxlibcldd等;Windowskernel32ntdll等)。ROP是通过复用已经存在的代码片段产生的,并指出如何将你想要执行的代码融入到这些代码片段中。

 

起初,ROP是从ret2libc开始的,然后通过可以使用更多的小代码块而逐步发展。有人说,ROP已“死”,因为新增的保护技术。但是在很多场合中依然有用(而且在很多CTF中是必须的)。

 

ROP最重要的部分是gadgets(指令片段)。Gadget是“可用于ROP的代码块”。通常是以ret结尾(其他类型的gadget可能也会有用,比如以pop  eax; jmp eax等结尾的)。我们将这些gadget串在一起形成exploit,成为ROP链。

 

ROP最重要的前提是你对栈有控制权(也就是说,栈指针指向你能控制的缓冲区)。如果这个条件不成立,你需要使用一些技巧(比如,stack pivoting栈转)来获取控制。

 

你要如何提取gadget?使用可以下载的工具(比如, ropgadget)或者在线工具(比如, ropshell)或者你自己写(对一些比较难的题目,这个可能更有用,因为你可以有针对性的设计)。最基本的,我们需要可以跳转到这些gadget的地址。这里就有一个ASLR(地址空间配置随机加载)的问题,在这种情况下,在真正可以执行ROP之前你会遭遇地址泄露。

 

现在是,我们要如何利用这些gadget形成rop链?我们首先寻找“基础gadget”。这类gadget可以帮我执行一些简单的任务(例如,pop ecx; ret,通过放置这个gadget可以将某个值加载到ecx中,在加载完该值后,就返回到链尾)最有用的基础gadget通常是“指向寄存器”,“将寄存器的值放置在指向某个寄存器的地址中”。

 

我们可以从这些最基础的函数开始逐步获取高级功能(类似下面的文章,exploitation abctionstra)。例如,利用“指向寄存器”,和“在某个地址存值”这个gadget,我们可以构造函数实现将特定的值传入特定的地址。用这个函数,我们可以将任意的字符串传到内存的任意位置。有了这么一个函数我们就差不多大功告成了。因为我们可以在内存中构造任意数据结构,也可以用任意的参数调用任意的函数(因我没有可以指向寄存器,将值保存到栈中)。

 

从基础函数开始而不是从可以实现复杂功能的函数开始的一个重要原因是,减少出错的几率(这在ROP中很常见)。

 

还有很多关于ROP的想法,工具,技巧,但这可能是另一篇笔记的内容了,下次吧: )

 

PS: Gyn的博文Return-Oriented Exploitation值得一读。

 

 

 

遗传模糊

写于2017 / 3 / 27,增加于2017 / 3 / 29

有感于Gynvael Coldwind这个视频。在视频中他谈论了遗传模糊背后的理论,并开始组建一个简易的遗传模糊测试工具。之后,他在这个视频中完成了其实现。

 

“高级的”模糊测试(相较于我在 "Basics of Fuzzing" 提到的盲测试器,blind fuzzer)也修改/变异字节,但是比起盲测,它做得更智能一些。

 

我们为什么需要一个遗传模糊测试器呢?

 

对于盲测试器来说,有些程序可能会比较难搞,因为有的漏洞可能需要几个条件同时满足才会触发。在盲测试器中,发生这种事的可能性非常低,因为它不知道它是否有些许进化。举个具体的例子,如果有这么一段代码:if a: if b: if c: if d: crash!(让我们称之为CRASHER),这段代码表示需要满足四个条件才能crash该程序。然而,盲测试器可能就无法通过条件a, 因为四个变异a,b,c,d同时发生的可能性太低了。事实上,即使它做了a进化了,下一个变异可能有回到!a,因为它对程序一无所知。

稍等,那么什么时候这种情况才会出现?

 

举个例子,这在文件格式解析器中就很常见啊。为获取某些特定的代码路径,必须同时检查好几项“这个必须是这个值,那个必须是那个值,其他的也必须是规定好的”等等。还有,没有一款现实世界的软件是“简单的”,大多数软件有很多很多可能的路径,有些路径只有同时满足几个条件才能到达。因此,很多这类程序路径对于盲测试器来说是不可达的。另外,有些路径是真的不可达,因为没有完成足够的变异。如果这些路径由bug,盲测试器将无法将其找出。

 

那我们可以怎样可以比盲测试器做得更好?

 

考虑上面那段CRASHER代码的流程图。如果某个盲测试器机缘之下满足a,那它也不会意识到自己到达了新的节点,所以它会将其忽略。另一方面,AFL(和其他遗传或“smart”模糊测试器)会做的是,它们能意识到这是新的信息(“一条全新的路径”)然后将这个样本作文一个新的点保存到资料库中。这就意味着,现在这个测试器可以从a开始。当然,从a开始有时也会返回到!a,但更多时候,它不会,而是更可能到达b。这样又到达了一个新的节点,将其加入资料库中。继续这个操作,将会有越来越多的路径得到检查,最后到达crash!。

 

为什么要这样做?

 

将变异的样本保存到资料库中,就能到达之前未能到达的区域,也就能对这些区域进行模糊测试。那既然我们可以对这些区域进行测试,自然能找到这些区域的bug

 

为什么称之为遗传模糊?

 

这种“smart”模糊测试类似于遗传算法。样本的交叉、变异能产生新的样本。我们保留能适应我们测试条件的样本。在这个例子中,条件是“它能到达流程图中的多少个节点?”。保存能到达更多节点的。跟遗传算法不是很像,但是它的一个变体(因为我们保存了所有到达未探查领域的节点,并且没有做交叉)。基本上就是,从已经存在的样本中选择,进行变异,之后进行适应性测试(看它是否能到达新领域),再重复。

 

等一下,所以我们只是记录未到达节点?

 

不,并非如此。AFL记录的是图中的边,而不是点。还有,它不仅仅是说“边被遍历过或没有”,它记录该边遍历了多少次。如果一条边遍历了0124816,…次,它就可以当作是一条新的路径并添加到资料库中。这么做是因为,查看边比查看点更能区分应用程序的状态,用指数级增长来记录遍历的次数能够给到给多的信息(一条边被遍历一次很遍历两次有很大的不同,但是遍历10次和遍历11次的区别就没有那么大了)。

 

那么,在遗传模糊测试器中你要做什么?

 

我们需要两样东西。第一部分叫做追踪器tracer(或者,追踪设备)。它主要是告诉你执行了哪条指令。AFL用一种很简单的方法做到这一点。它跳到编译阶段,在形成汇编语言之后,编译之前,它会寻找基础代码块(通过找结束点,检查跳转/分支指令),并添加代码到每一个代码块以标志该代码已经执行(可能会进入shadow memory影子内存或其他)如果我们没有源代码,我们用其他技术来进行追踪(比如:pin, 调试器等)。事实证明,即使是ASAN也能给出覆盖率信息(具体看查看相关文档)。

 

第二部分是,我们利用追踪器提供的覆盖率信息去追踪刚出现的新路径,并将这些刚形成的样本增加到资料库中,以便将来进行随机选择。

 

追踪器有多种机制,有基于软件的,也有基于硬件的。基于硬件的,拿Intel CPU来说,其内存中有一个缓冲区,它记录了所有进入该缓冲区的基本代码块。这属于内核特征,所以内核不得不支持它并提供APILinux是这样的)。基于软件的,我们通过增加代码,或是用调试器(用临时断点,或者通过单步),或者用内存检测工具(Address SanitizerASAN)的追踪功能,或者用钩子,或者虚拟机,或者其他的方法来达到。

 

区分这两种机制的其他方法是要么通过黑盒追踪(这样你只能用未修改过的二进制文件)要么通过软件白盒追踪(利用这种方法,你能够接触到源代码,并修改源码增加追踪的代码)。

 

AFL是在编译的过程中用软件指令来追踪(或者通过QEMU虚拟机)。Honggfuzz支持软硬件两种追踪方式。其他的smart fuzzer或许不一样。Gyn开发的那一款用的是ASAN提供的追踪/覆盖率信息。

 

其他的fuzzer会利用forkserver或者其他方法来加速。以后再研究:)


Fuzzing基础

写于2017420

有感于Gynvael Coldwind这个直播,直播中他谈论了什么是fuzzing,并从头建立起一个基础fuzzer

 

首先,什么是fuzzer?为什么我们要使用它?

 

假设我们有一个程序库/程序需要输入数据。输入数据可能以某种格式构建(比如PDFPNGXML;但不需要是任何“标准”格式)。从安全的角度看,如果在输入数据和进程/程序库/程序之间有一个安全边界,我们可以提交一些“特殊的输入数据”来引起超出边界的行为。Fuzzer就是这么做的。它通过“改变”输入数据的值(有可能会恶化数据),得到一个正常的执行结果(包括安全的可控错误)或得到一个crash。这可能是因为边界逻辑没有处理好造成的。

 

Crashing是最简单的错误情况。可能会有其他的情况,比如,使用ASANaddress sanitizer,地址消毒剂)等会导致同时检测到更多的错误,这可能是安全问题。比如,一个缓冲区发生单字节溢出,可能不会引起crash,但是使用ASAN后,fuzzer可能会检测到这个错误。

 

Fuzzer的另一个用途是,fuzzing一个程序所产生的输入可以在其他的程序库/程序中使用,并观察是否有区别。比如,一些高精度的数学程序库错误被发现,但不会引起安全问题,我们就不投入精力来研究这些问题。

 

Fuzzer是怎么工作的?

 

Fuzzer基于一个变化的重复执行的循环,测试应用程序的状态空间并试着“随机地”发现crash或安全隐患。

 

Fuzzer的输出是什么?

 

fuzzer中,debugger有时会附加到应用程序上来获得crash的回应,之后分析它是一个安全隐患还是一个良性(但可能很重要)的crash

 

怎样确定程序的哪部分适合最先测试?

 

fuzzing时,我们总是希望专注于程序的一个简单块或一个小组块,这样可以减少要执行程序的数量。通常,我们只关注解析和处理。此外,安全边界会影响我们决定哪部分更重要。

Fuzzer的种类?

 

Fuzzer的输入样例称为corpus。在旧版本的fuzzer中(又叫“bind”或“dumbfuzzer)需要大量的corpus。新版本的fuzzer(又叫“geneticfuzzer,例如AFL)不再需要大量的corpus,因为它会自己探索状态。

 

Fuzzer有用吗?

 

Fuzzer主要用于“可轻易实现的目标”,它不能找到复杂的逻辑漏洞,但它可以找到容易找到的错误(通常在人工分析时容易漏掉)。虽然我通篇都在说输入数据,并经常提到输入文件,但不是仅限于此。Fuzzer可以处理标准输入、文件输入、网络套接字和很多其他输入类型。不失一般性,我们在这里可以认为输入是一个文件。

 

怎样写一个(基础的)fuzzer

 

再次说明,它只是一个变化的重复执行的循环。我们需要经常调用目标(subprocess.Popen)。还要将输入数据(比如文件)传递到程序并检测crashSIGSEGV等会产生异常且能被捕捉到)。现在我们只需要写一个对输入文件的修改器,并在变化的文件中持续调用目标。

 

Mutators?是什么?

 

有很多可能的mutator。简单(也就是实现简单)的可以变化二进制、变化字节或变化成特殊值。为了提高捕捉到crash的几率,不只变化一位或一种,可以做多个变化(可能根据其参数化的百分比?)。还可以(不是随机变化)将字节//双字等变化成特殊值。特殊值可以是00xff0xffff0xffffffff0x8000000032INT_MIN)、0x7fffffff32INT_MAX)等。基本上,选择常会引起安全问题的变化(因为它们可能会触发一些边界问题)。如果知道程序的更多信息,我们可以写一些智能mutator(例如,针对字符串形式的整数,可以将字符串转化成“65536”或“-1”等)。基于块的mutator会移动整块数据(基本上会重组输入数据)。附加mutator也是有用的(例如将较多的输入放入缓冲区)。截取mutator也是有用的(例如,截取掉一些不能处理的结束符)。基本上,所有的变化都可以尝试。对程序越有经验(和一般的开发),越有可能写出有用的mutator

 

但是什么是“geneticfuzzing

可能之后会讨论。这有两个新版fuzzer(开源)的链接

AFL  http://lcamtuf.coredump.cx/afl/

honggfuzzhttps://github.com/google/honggfuzz

 

Exploitation 抽象化

写于201747

有感于一个挑战赛PicoCTF 2017的影响(保留的竞赛名字,因为比赛还在进行)。

 

注意:这部分可能对于一些读者来说很简单,但必须要写,因为我也是最近才弄明白这个层次。

编程时我们都会用抽象的概念,包括类、对象、函数、元函数、多态、monad、函子等诸如此类的东西。然而,开发时真的存在这些东西吗?显然,我们可以利用这些抽象概念形成的错误,但是在这,我要说些不一样的东西。

 

在之前的多个CTF比赛中,我写的漏洞利用,都是删除某个shell的特殊脚本。我使用pwntools做框架(用来连接服务器、转换、DynELF等)。每个漏洞利用都希望成为执行任意代码的专用方式,但是这很有挑战,就像我在之前的"Advanced" Format String Exploitation部分写的那样,这让我认识到可以用一致的方式将漏洞利用分层,通过不同的抽象层来完成目标

比如,假设漏洞是一个逻辑错误,可以让我们在缓冲区后的一个小范围内写或读4字节数据。我们想一直利用它执行代码,并最终夺旗。

 

这种情况下,我们抽象它为short-distance-write-anything原语。显然,我们可以利用它做很多事。不过我只写了一个小的Python函数vuln(offset, val)。然而,因为在缓冲区后可能有一些有用的数据或元数据,我们可以利用它建立read-anywhere原语和write-anything-anywhere原语。也就是说,我写了一些Python函数来调用vuln()函数。get_mem(addr)set_mem(addr, val)函数都使用vuln()函数覆盖一个指针(在这个例子中),这个指针会在缓冲区中调用。

有了get_mem(addr)set_mem(addr, val)抽象函数后,我创建了一个反ASLR的抽象,通过get_mem(addr)GOT方法泄露出2个地址并与一个c运行库比较(感谢niklasb构建这个数据库)。这些偏移地址让我实现了libc_base函数,可以用函数库中的函数覆盖GOT中的任何函数。

 

这基本上让我控制了EIP(在我需要的时候“触发”这些函数中的某个)。现在,我只需要用正确的参数调用触发器。所以我设置参数为一个单独的抽象,然后调用trigger()在系统中访问shell

 

太长不看:可以创建小的开发原语(没有太大作用),分层组合它们来建立一个强壮的原语,我们就可以实现完整的执行。

 

“高级的”标准字符串开发

写于201746

有感于Gynvael Coldwind这个直播,他在直播中讲了标准字符串开发)

 

简单的标准字符串开发:

 

你可以用%p来查看栈的内容。如果标准字符串就在栈中,可以把一个地址(称为foo)放入栈中,然后用位置说明符n$寻找它(比如,如果找栈的第7位,AAAA %7$p会返回AAAA 0x41414141)。然后用格式说明符%s建立一个read-where原语(比如,AAAA %7$s会返回0x41414141地址中的值,继续前面的例子)。还可以用格式说明符%n将它放入一个write-what-where原语。通常相反,我们用%hhn(一个扩展c运行库,如果我没记错的话)来一次写一字节数据。

 

用以上原语实现反ASLR(如果有的话),然后重写GOT入口(命名为exit()fflush()或……)将它写入arbitrary-eip-control原语,让我们可以执行任意代码。

 

可能的困难(让其“高级”开发):

 

如果有局部ASLR,我们仍可以用标准字符串攻击它,但当我们只有一次性的漏洞利用时就会很困难(比如,我们的漏洞利用需要立即运行,但每次运行的地址是随机的)。解决的方法是部分重写内存中已有的地址(因为ASLR只影响高位)。这样我们可以在执行中获得可靠性。

 

如果我们有一个只读GOT部分,“高级”攻击的重写GOT就没有作用了。这是,我们寻找可选择的区域来重写(最好是函数指针)。这些区域有:__malloc_hook(查看同一man页)、标准输入的写或刷新的表指针等。在这种情况下,可以访问c运行库源是很有用的。关于重写__malloc_hook,即使应用没有调用malloc也会重写,只要调用了printf(或其他相同的操作)。在内存中,如果我们传一个比64K大的说明符(称为%70000c),就会调用malloc,这样不管地址是什么都会在指定在全局变量__malloc_hook中。

 

如果标准字符串缓冲区不在栈中,仍然可以获得一个write-what-where原语,虽然有一点复杂。首先,停止使用位置说明符n$,因为如果使用它,printf会复制那个在之后我们会修改的栈。现在,我们找到两个指向栈本身的指针,并用他们重写指向栈的指针低端字节,这样它们将指向x+0x+4 ,x是栈顶指针。利用这两个重写,我们可以控制x的四个字节,而这就成为我们的where原语。现在我们只要在到达该点之前都忽略标准字符串的其他位置,那我们就能能到一个write – what – where 原语。


 

竞争条件及其利用

写于2017 / 4 / 1

有感于 Gynvael Coldwind这个关于竞争条件的视频。

 

如果一个内存区域(或文件或其他资源)在预想中就算被访问两次也应该得到一样的结果,却由于线程切换导致结果变化,我们就需要竞争条件了。

 

大多数情况是TOCTTOUTime – of – check to Time – of - use)错误,这种错误表现为要使用一个变量得先检查其值,如果满足特定的条件才可以使用它。在这种情况下,我们可以开两个线程来进行攻击,一个线程持续用各种值来进行检查,另一个程序持续看能否使用,这样因为随机性,没准我们就能进入“机遇之窗”,也就是检查和使用之间的一小段时间。

 

通常这段时间非常短。我们要用多种技巧来延长这个时间,例如将因素扩大到3倍甚至百倍。要怎么扩大呢?我们主要是通过控制该值的如何进入cachepage来实现的。如果某个值(假设它是long int类型的)没有对其cache line,那就需要两个cache line,这样在相同的指令被执行时就会造成一定的延迟。或者,破坏页对齐(也就是,把它放到页边界)能造成更长的延迟。也就有更大的几率触发竞争条件。

 

还有更好的方法来提升这种竞争条件(例如清理TLB,但有时也没必要这么做)。

 

在极端情况下,竞争条件也可以用来执行ring0代码(ring0root权限更高,因为是内核模式执行)。

 

也可以在architecture emulator之上写工具/插件“自动”查找竞争条件。更多细节:http://vexillium.org/pub/005.html

 

堆利用类型

写于 2017 / 3 / 31

有感于Gynval Coldwind这个关于堆利用的视频。

 

Use-after-free(先释放后使用):

 

假设有很多指针指向堆中的地址,而堆在不确定所有指针是否已经更新的情况就被释放了。那么可能会留下一些指向这个被释放过的堆的悬挂指针。这样就可以利用了,通过申请指向这部分内存的其他类型的指针,你就可以控制不同的区域,接着你就可以执行任意代码了。

 

Double free(双重释放):

释放一个内存区域,然后再释放一次。如果你可以做到双重释放,那你可以用malloc来控制其内部结构。相比于use – after –free ,这可能会更复杂一些,所以用你能做到的那种方式。

堆溢出:

 

如果你能在已经分配的内存外面进行写操作,那么你可以向已分配内存的下一个内存块的内部结构进行写,而且通过控制重写内部值,就能满足 read – what –where原语,这个条件通常用来获取高级权限(通常是,通过 GOT PLT _fini_array 执行任意代码)。


原文链接:https://github.com/jaybosamiya/security-notes

本文有看雪翻译小组 lumou 编译



本主题帖已收到 0 次赞赏,累计¥0.00
最新回复 (0)
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 域名 加速乐 保护 | SSL证书 又拍云 提供 | 微信公众号:ikanxue
Time: 0.015, SQL: 8 / 京ICP备10040895号-17