精华内容
下载资源
问答
  • 二进制漏洞实例分析

    2017-03-15 17:20:21
    Windows和Linux下的漏洞涵盖方方面面,本文将从Windows和Linux下漏洞入手,拣选一些易搭建,易分析的软件,挑选不同类型的二进制漏洞进行分析,讲解常见漏洞类型的分析方法。希望通过本文的训练,学员能够掌握基本...
  • CBTracer:不断构建二进制漏洞和漏洞利用研究的数据集
  • 本部分将对常见的二进制漏洞做系统分析,方便在漏洞挖掘过程中定位识别是什么类型漏洞,工欲善其事,必先利其器。0x01栈溢出漏洞原理栈溢出漏洞属于缓冲区漏洞的一种,实例如下:编译后使用windbg运行直接运行到了...

    本部分将对常见的二进制漏洞做系统分析,方便在漏洞挖掘过程中定位识别是什么类型漏洞,工欲善其事,必先利其器。

    0x01栈溢出漏洞原理

    栈溢出漏洞属于缓冲区漏洞的一种,实例如下:

    编译后使用windbg运行

    直接运行到了地址0x41414141,这个就是字符串AAAA,就是变量str里面的字符串通过strcpy拷贝到栈空间时,没有对字符串长度做限制,导致了栈溢出,最后覆盖到了返回地址,造成程序崩溃。

    溢出后的栈空间布局如下:

    栈溢出原理图

    0x02 堆溢出漏洞原理

    使用以下代码演示堆溢出漏洞

    由于调试堆和常态堆的结构不同,在演示代码中加入getchar函数,用于暂停进程,方便运行heapoverflowexe后用调试器附加进程。debug版本和Release版本实际运行的进程中各个内存结构和分配过程也不同,因此测试的时候应该编译成release版本。

    运行程序,使用windbg附加调试(一定要附加调试),g运行后程序崩溃

    上面的ecx已经被AAAA字符串覆盖掉了,最后在引用该地址的时候导致崩溃,通过前面的栈回溯定位到了main函数入口,找到复制字符串的函数下断点

    此时堆块已经分配完毕,对应的分配地址位于0x007104a0,0x007104a0是堆块数据的起始地址,并非堆头信息的起始地址,对于已经分配的堆块,开头有8字节的HEAP_ENTRY结构,因此heap的HEAP_ENTRY结构位于0x007104a0-8=0x710498。

    在windbg上查看两个堆块的信息,这两个堆块目前处于占用状态,共有0x10大小空间

    在windbg中,使用!heap查看HeapCreate创建的整个堆块信息,可以发现堆块heap后面还有一个空闲堆块0x007104c0:

    在复制字符串的时候,原本只有0x10大小的堆块,填充过多的字符串的时候就会覆盖到下方的空闲堆块007104c0,在复制前007104c0空闲堆块的HEAP_FREE_ENTRY结构数据如下:

    覆盖后0x007104c0空闲块的HEAP_FREE_ENTRY结构数据如下:

    整个空闲堆头信息都被覆盖了,包括最后的空闲链表中的前后向指针都被成了0x41414141,后面调用HeapFree释放堆块的时候,就会将buf2和后面的空闲堆块0x007104c0合并,修改两个空闲堆块的前后向指针就会引用0x41414141,最后造成崩溃。

    如果把上面释放堆块的操作换成分配堆块HeapAlloc(),也会导致崩溃,因为在分配堆块的时候会去遍历空闲链表指针,会造成地址引用异常,当内存中已经分配多个堆块的时候,可能覆盖到的就是已经分配到的堆块,此时可能就是覆盖HEAP_ENTRY结构,而不是HEAP_FREE_ENTRY结构。

    堆溢出原理图

    0x03 堆调试技巧

    微软提供了一些调试选项用于辅助堆调试,可以通过windbg提供的gflag.exe或者!gflag命令来设置:

    对heapoverflow.exe添加堆尾检查和页堆,去掉堆标志:

    堆尾检查

    主要是在每个堆块的尾部,用户数据之后添加8字节,通常是连续的2个0xabababab,该数据段被破坏就可能发生了溢出。

    对heapoverflow.exe开启hpc和htc,用windbg加载对heapoverflow程序,附加进程无法在堆尾添加额外标志,使用以下命令开启堆尾检查和堆参数检查:

    执行命令g后,按下回车键程序会断下来

    上面一句调试输出信息的意思是,在大小为0x10的堆块0x001E0498的0x001E04B0覆盖破坏了,0x10大小的空间加上堆头的8字节一共0x18字节,0x001E04B0-0x001E0498=0x18,也就是说0x001E04B0位于堆块数据的最后一个字节上,基于上面的信息,可以分析出程序主要是因为向0x10的堆块中复制过多数据导致的堆溢出。

    页堆

    在调试漏洞的时候,经常需要定位导致漏洞的代码和函数,比如导致堆溢出的字节复制指令rep movsz等,前面的堆尾检查方式主要是堆被破坏的场景,不利于定位导致漏洞的代码。为此。引入了页堆的概念,开启后,会在堆块中增加不可访问的栅栏页,溢出覆盖到栅栏页就会触发异常。

    开启页堆:

    用windbg加载heapoverflow,运行!gflag命令开启了页堆,然后g运行后在cmd按下回车键断下

    可以发现程序在复制A字符串的时候触发了异常,程序复制到0x11字节的时候被断下,此时异常还未破坏到堆块,直接定位导致溢出的复制指令rep movs

    根据栈回溯,调用rep movs的上一层函数位于image00400000+0x1084的上一条指令,也就是00401322,此处调用了00401000函数,很容易发现这是主入口函数:

    0x04整数溢出漏洞原理

    整数分为有符号和无符号两类,有符号数以最高位作为符号位,正整数最高位为1,负整数最高位为0,不同类型的整数在内存中有不同的取值范围,unsigned int = 4字节,int = 4字节,当存储的数值超过该类型整数的最大值就会发生溢出。

    在一些有符号和无符号转换的过程中最有可能发生整数溢出漏洞。

    基于栈的整数溢出

    基于栈的整数溢出的例子:

    代码中size变量是无符号短整型,取值范围是0~65535,输入的值大于65535就会发生溢出,最后得到size为4,这样会通过边界检查,但是用memcpy复制数据的时候,使用的是int类型的参数i,这个值是输入的65540,就会发生栈溢出:

    基于堆的整数溢出

    基于堆的整数溢出的例子:

    代码中的size是unsigned short int类型,当输入小于5,size减去5会得到负数,但由于unsigned short int取值范围的限制无法识别负数,得到正数65533,最后分配得到过大的堆块,溢出覆盖了后面的堆管理结构:

    0x05 格式化字符串漏洞原理

    格式化漏洞产生的原因主要是对用户输入的内容没有做过滤,有些输入数据都是作为参数传递给某些执行格式化操作的函数的,比如:printf,fprintf,vprintf,sprintf。

    恶意用户可以使用%s和%x等格式符,从堆栈和其他内存位置输出数据,也可以使用格式符%n向任意地址写入数据,配合printf()函数就可以向任意地址写入被格式化的字节数,可能导致任意代码执行,或者读取敏感数据。

    以下面的代码为例讲解格式化字符串漏洞原理:

    可以发现当输入数据包含%s和%x格式符的时候,会意外输出其他数据:

    用ollydbg附加调试程序,执行前需要先设置命令行参数,调试-参数-命令行:test-%x

    在运行程序后,传递给printf的参数只有test-%x,但是他把输入参数test-%x之后的另一个栈上数据当做参数传给了printf函数,因为printf基本类型是:

    传递给printf的参数只有一个,但是程序默认将栈上的下一个数据作为参数传递给了printf函数,刚好下一个数据是strcpy()函数的目标地址,就是buff变量,buff刚好指向test-%x的地址0x0019fec4,所以程序会输出0x0019fec4,如果后面再加一个%x就会将src参数的值也输出了,这样就可以遍历整个栈上数据了。

    除了利用%x读取栈上数据,还可以用%n写入数据修改返回地址来实现漏洞利用。

    0x06 双重释放漏洞原理

    Double Free漏洞是由于对同一块内存进行二次释放导致的,利用漏洞可以执行任意代码,编译成release示例代码如下:

    在二次释放p2的时候就会发生程序崩溃,但是并不是每次出现Double Free都会发生崩溃,要有堆块合并的动作发生才会发生崩溃

    双重释放原理图

    在释放过程中,邻近的已经释放的堆块存在合并操作,这会改变原有堆头信息,之后再对其地址引用释放就会发生访问异常。

    0x07释放后重引用漏洞原理

    通过以下代码理解UAF漏洞原理:

    buf2 “占坑”了buf1 的内存位置,经过UAF后,buf2被成功篡改了

    程序通过分配和buf1大小相同的堆块buf2实现占坑,似的buf2分配到已经释放的buf1内存位置,但由于buf1指针依然有效,并且指向的内存数据是不可预测的,可能被堆管理器回收,也可能被其他数据占用填充,buf1指针称为悬挂指针,借助悬挂指针buf1将内存赋值为hack,导致buf2也被篡改为hack。

    如果原有的漏洞程序引用到悬挂指针指向的数据用于执行指令,就会导致任意代码执行。

    在通常的浏览器UAF漏洞中,都是某个C++对象被释放后重引用,假设程序存在UAF的漏洞,有个悬挂指针指向test对象,要实现漏洞利用,通过占坑方式覆盖test对象的虚表指针,虚表指针指向虚函数存放地址,现在让其指向恶意构造的shellcode,当程序再次引用到test对象就会导致任意代码执行。

    UAF漏洞利用原理图

    0x08 数组越界访问漏洞

    先区分一下数组越界漏洞和溢出漏洞:数组越界访问包含读写类型,溢出属于数据写入;部分溢出漏洞本质确实就是数组越界漏洞。

    数组越界就像是倒水的时候倒错了杯子,溢出就像是水从杯子里溢出来。

    下面代码为例分析数组越界访问漏洞:

    执行生成的程序,然后分别输入12345,输出结果如上,当输入的数组下标分别是12的时候,会得到正常数值,但是从索引3开始就超出了原来的数组array的范围,比如输入5,就会数组越界访问array数组,导致读取不在程序控制范围内的数值。

    使用ollydbg调试发现array[5]就是从array开始的第六个数据0x4012A9,已经读取到了array之外的数据,如果越界访问距离过大,就会访问到不可访问的内存空间,导致程序崩溃。

    0x09类型混淆漏洞原理

    类型混淆漏洞(Type Confusion)一般是将数据类型A当做数据类型B来解析引用,这就可能导致非法访问数据从而执行任意代码,比如将Unit转成了String,将类对象转成数据结构。

    类型混淆漏洞是现在浏览器漏洞挖掘的主流漏洞,这类漏洞在java,js等弱类型语言中非常常见。

    下面的代码,A类被混淆成B类,就可能导致私有域被外部访问到:

    class A {

    private int value;

    };

    class B {

    public int value;

    };

    B attack = AcastB(var); //将A类型混淆转成B类型

    attack.value = 1; //导致可以访问私有域

    以IE/Edge类型混淆漏洞(CVE-2017-0037)为例讲解,漏洞原因是函数处理时,没有对对象类型进行严格检查,导致类型混淆。

    PoC如下:在PoC中定义了一个table,标签中定义了表id为th1,在boom()中引用,然后是setInterval设定事件。

    运行PoC,用Windbg附加并加载运行出现崩溃

    从崩溃点可以看到eax作为指针,引用了一个无效地址,导致崩溃,而上一条指令是一个call,这个无效的返回值来自这个call,在这个call处下断点,ecx作为参数,存放的对象是一个Layout::FlowItem::`vftable虚表

    这里读取虚表中+4的值,为0时this指针赋值v1,随后v1+16后返回,因此,Layout::FlowItem::`vftable所属指针的这个情况是正常的,函数会正常返回进入后续处理逻辑

    让程序继续运行,会再次调用该函数,此时ecx并不是一个虚表对象,而是一个int Array对象,这里我们可以通过条件断点来跟踪两个对象的创建过程,重点关注两个对象创建的函数,一个是FlowItem::`vftable对应的虚表对象,另一个是引发崩溃的int Array对象。这两个函数的返回值,也就是eax寄存器中存放的指向这两个创建对象的指针。

    通过跟踪可以看到第一次调用Readable函数时ecx是一个正常的FlowItem对象,而第二次调用的时候ecx是一个int Array Object。Layout::Patchable >::Readable函数是处理虚表对象的函数,由于boom()函数中引用th1.align导致Readable函数得到第二次引用,由于没有进行对象属性检查,导致第二次调用时将table对象传入,最终发生类型混淆崩溃。

    0x10竞争条件漏洞原理

    竞争条件(Race Condition)是由于多个线程/对象/进程同时操作同一资源,导致系统执行违背原有逻辑设定的行为,这类漏洞在linux,内核层面非常多见,在windows和web层面也存在。

    互斥锁的出现就是为了解决此类漏洞问题,保证某一对象在特定资源访问时,其他对象不能操作此资源。

    比如如下代码:

    执行结果如下:

    按照我们的预想,结果应该都是10,但是发现结果可能存在非预期解,原因就在于我们没有对变量COUNT做同步制约,导致可能Thread-7在读COUNT,还没来得及更改COUNT,Thread-8抢夺资源,也来读COUNT,并且将COUNT修改为它读的结果+1,由此出现非预期。

    展开全文
  • 二进制漏洞:传统的缓冲区溢出、UAF(Use-After-Free)等涉及二进制编码的漏洞统称为二进制漏洞 根据缓冲区所处的不同内存空间以及分配方式的不同,缓冲区溢出可以分为栈溢出和堆溢出 栈溢出原理: 栈是一种基本的...

    二进制漏洞:传统的缓冲区溢出、UAF(Use-After-Free)等涉及二进制编码的漏洞统称为二进制漏洞
    根据缓冲区所处的不同内存空间以及分配方式的不同,缓冲区溢出可以分为栈溢出和堆溢出

    栈溢出原理:

    栈是一种基本的数据结构,是由编译器自动进行分配、释放的。栈遵循先进后出的规则,生长方向为从高向低生长,也就是地址由高到低
    栈中存放的数据:对于要调用的子函数来说,存放子函数的参数、返回地址、局部变量等重要信息。函数可以通过栈来方便的对局部变量进行读取。
    在调用一个子函数的时候,我们要做的工作有以下几步:
    1:将参数push到栈上
    2:将当前eip的值push到栈上进行保存,当作函数调用之后的返回地址
    3:执行子函数的函数体
    在这里插入图片描述
    程序在调用子函数的时候,(可以通过OD等工具看汇编代码)会先将ebp压栈调整栈结构,之后会在栈上开辟一段空间,这段空间就是子函数的空间。在实际的函数调用过程中,我们的局部变量是不断变化的,而且需要注意的是 :栈是向低地址的方向生长的,但是局部变量是向高地址生长的
    在这里插入图片描述
    也就是说,变量是向下 “吞噬” 的,这样的话就会产生一个问题:当变量向下“吞噬”的时候,"一不小心"把下边ebp和eip的空间也”吞噬“了,会发生什么事情呢?这个时候就会产生我们说的栈溢出现象,我们知道eip的值是用来函数执行完之后返回用的,但是在被”吞吃“之后,显然栈空间的值已经发生改变,这个时候就会返回错误。这个时候我们也就控制了程序的EIP,可以为所欲为了。那么什么是造成栈溢出的主要原因呢?
    代码编写过程的不规范,举个例子:在执行数据复制的操作的时候,不检查源数据的长度直接进行复制,很有可能会因为目的空间不够而导致这种情况的产生,如何避免这种情况呢?最简单的办法就是在执行前增加长度限制判断。

    整型溢出漏洞
    整形溢出漏洞主要是指在计算机中的数据类型问题导致的溢出漏洞,整型作为一种数据类型,不仅仅存在正负的问题还存在数据范围的问题甚至还包括不同整型数据之间的赋值和转换问题,而在这其中容易产生一系列不严谨的算法,可能会导致溢出漏洞的产生。
    因此,综上所述,整型漏洞通常分为以下几种:
    1:存储溢出漏洞
    2:运算溢出漏洞
    3:符号问题导致的漏洞

    一:存储溢出漏洞
    存储溢出相对比较简单易懂,就是由于使用不同的数据类型来存储整型数造成的。
    举个例子:
    下边这个代码段,在实际的赋值过程中:作者想要达到的目的可能是将a的值赋给b,但是在实际的输出过程中,我们可以看到,实际的输出结果并不是a的值,输出结果如下:

    int a=0x10000;
    short b=a;
    printf("%d",b);
    

    输出结果:
    在这里插入图片描述
    这是因为,不管哪一种数据类型都是由存储范围的限制的,int型是32位长度的,但是short是16位的数据长度,因此在进行赋值操作的时候,short型的数据没办法将int型数据的所有位数全盘接受,这样就会导致我们之前说的存储溢出漏洞。

    二:运算溢出漏洞
    相对于存储溢出来说,运算溢出相对来说更加容易理解,顾名思义,运算溢出就是在整型数据在运算过程中导致的溢出,我们都知道,数据类型的范围长度以及范围是有限的,当然基于这一点我们在定义数据类型的时候大多时候还是会谨慎的使用各类数据类型,防止超出数据类型的范围,但是需要注意的是,我们往往将注意力放在赋值以及定义一个变量的时候,但是在定义之后,对该数据的各类运算是不是会超出数据类型的范畴我们通常是不会给太多关注的,因为我们大多情况下关注的都是运算的结果。这样也就导致了,很多漏洞都是通过这类型的溢出导致的。

    三:符号问题导致的漏洞
    整型数据分为无符号整型数据和有符号整型数据,如果在编写程序代码的时候,忽略符号问题造成的影响,那么就极有可能造成安全事故。举个著名的例子:
    Apache Web Server分块溢出漏洞

    UAF漏洞
    UAF漏洞全称Use-After-Free,字面意思,也就是说在释放之后使用。具体过程如下图解:
    在这里插入图片描述
    UAF漏洞是因为指针的使用不当造成的,在释放空间或指针之后,又重新被后续的函数调用,因此编写程序的时候为了避免这一漏洞的产生,需要在实际运用的过程中,对指针的有效性进行判断。

    展开全文
  • 二进制漏洞挖掘之Fuzz_代涛.pptx
  • 本文为看雪论坛优秀文章看雪论坛作者ID:微笑明天本文介绍了如何通过IDA...尽管有很多文章介绍了用IDAPython来简化基本的逆向任务,但很少有提及使用IDAPython来审计二进制漏洞的方法。因为这不是一个新的方法(Halva...
    a067319ec86e06eca527464320f48673.png本文为看雪论坛优秀文章看雪论坛作者ID:微笑明天本文介绍了如何通过IDA Python脚本来实现对栈溢出漏洞的检测,并以ascii_easy一道PWN基础题为例来实战。介绍IDAPython是一个用于复杂逆向工程任务的强大的自动化工具。尽管有很多文章介绍了用IDAPython来简化基本的逆向任务,但很少有提及使用IDAPython来审计二进制漏洞的方法。因为这不是一个新的方法(Halvar Flake在2001年做过关于IDA脚本自动分析漏洞的研究),但令人惊讶的是,这个话题没有被更多的说明。这可能是因为在现代操作系统上想要利用漏洞日渐复杂困难。然而,这对于自动化部分的漏洞研究还是有价值的。 在这篇文章中,我们将介绍使用基本的IDAPython来检测程序中出现的能导致栈溢出问题的地方。在这篇文章中,我会用自动化探测方法实战pwnable.kr中的ascii_easy二进制题目。尽管这个二进制文件小到我们可以手动整个去分析它,它仍然是一个很好的学习案例以便我们使用相同的IDAPython技术取分析更大更复杂的二进制文件。开始在我们写任何IDAPython脚本前,我们先要决定我们想让我们的脚本做什么。这里,我们选择了简单漏洞中的一个栈溢出漏洞,其可由strcpy函数导致将用户可以控制的字符串拷贝到栈缓存区中。既然我们知道了我们要寻找什么,我们可以开始考虑如何取自动化寻找这种漏洞。我们分为两步:1. 定位可能导致栈溢出的函数(这里我们选取strcpy函数)2. 分析这些函数的调用来决定这个这个调用我们是否感兴趣(即是否可能导致漏洞)定位函数调用为了寻找任何调用strcpy的地方,我们需要首先定位strcpy这个函数本身。使用IDAPython API很容易做到这一点。使用如下的代码片段来打印二进制文件中所有的函数名:
    for functionAddr in Functions():print(GetFunctionName(functionAddr))
    (注:可以在IDA底部的python命令控制窗口中输入命令)77b3a778b306fe442c2c386d2784c80e.png我们可以看到,所有的函数名都被输出了。2dafdb0714ce156423435fb43b556ded.png然后,我们要添加过滤取寻找我们感兴趣的strcpy函数。简单的字符串比较我们就能达到效果。但因为我们常常需要处理一些函数名相似但仍有区别的情况(例如_strcpy,这取决于导入函数的命名),我们最好检查子字符串。在前面的基础上,我们有下面的代码:
    for functionAddr in Functions():if “strcpy” in GetFunctionName(functionAddr):print hex(functionAddr)
    既然我们获得了我们感兴趣的函数,我们需要获取所有调用它的地方。这需要很多步骤。首先我们要获取strcpy交叉引用的地方,然后我们要检查这其中的每个地方是否真正调用了strcpy函数。总结一下就有下面的代码:
    for functionAddr in Functions():# Check each function to look for strcpyif "strcpy" in GetFunctionName(functionAddr):
            xrefs = CodeRefsTo(functionAddr, False)# Iterate over each cross-referencefor xref in xrefs:# Check to see if this cross-reference is a function callif GetMnem(xref).lower() == "call":print hex(xref)
    ascii_easy二进制文件运行此脚本后我们得到如下结果:52a1fc70e5d4ab9053aaf5a1afd71553.png即我们找到了0x451b2c目标地址。 分析函数调用现在,通过上面的代码,我们知道了如何获取所有程序中调用strcpy的地方。而ascii_easy恰好就只有这一个调用strcpy的地方(也恰好可利用),很多程序有很多调用strcpy的地方(很多都不可被利用),因此我们需要一些方法取分析对strcpy的调用来根据可利用的可能性进行排序。 一个缓存区溢出漏洞的常见特点是它们往往涉及栈上的缓冲区。尽管在堆上或者别的地方的缓存区溢出也是有可能的,栈溢出是一个更简单的利用方式。 这涉及一些对strcpy函数的目的地参数(destination)的分析,我们知道目的地参数是strcpy函数的第一个参数,而且我们可以通过浏览函数的反汇编得到这个参数。这个调用strcpy函数的反汇编如下:8a292554e5ddeb71bb72e6e2b605db64.png分析上面的代码,有两种可以找到_strcpy函数的目的地参数。第一种方法是依赖IDA自动分析后对已知函数的注释。上图中,IDA已经自动检测到了_strcpy函数的dest参数并标记。 另一个方法是从函数调用前开始寻找push指令。每当我们找到一个这样的指令,我们可以自增一个计数器直到我们定位到了参数的索引。这里,既然我们要找的dest参数是第一个参数,这个方法将会在先前一个push指令停下。 在这些情况中,当我们遍历这些代码时,我们要注意某些可以打破函数执行流的指令。例如retjmp这样改变执行流的指令会使精确分析参数变得困难。另外,我们需要确保我们不遍历那些当前所处函数之前的地方。现在,我们将只是在搜索参数时识别非顺序执行代码流的地方。如果找到任何非顺序代码流实例,则停止搜索。 我们将使用第二种查找参数的方法(查找被推送到堆栈的参数)。为了帮助我们以这种方式查找参数,我们创建一个helper函数。此函数将从函数调用的地址向向前查找,跟踪push到堆栈的参数并返回我们指定参数对应的操作数。 对于上面的例子,helper函数将返回eax寄存器的值,因为eax寄存器保存了strcpy的目标参数dest。结合一些基本的python与IDAPython API,我们可以构建一个如下函数:
    def find_arg(addr, arg_num):# Get the start address of the function that we are in
       function_head = GetFunctionAttr(addr, idc.FUNCATTR_START)
       steps = 0
       arg_count = 0# It is unlikely the arguments are 100 instructions away, include this as a safety checkwhile steps < 100:
           steps = steps + 1# Get the previous instruction
           addr = idc.PrevHead(addr)# Get the name of the previous instruction
           op = GetMnem(addr).lower()# Check to ensure that we haven’t reached anything that breaks sequential code flowif op in ("ret", "retn", "jmp", "b") or addr < function_head:returnif op == "push":
               arg_count = arg_count + 1if arg_count == arg_num:# Return the operand that was pushed to the stackreturn GetOpnd(addr, 0)
    为了判断eax是否指向在栈中的缓存区buffer,当它被push入栈时,我们应该继续跟踪eax从哪来。因此,我们使用的和之前类似的搜索循环:
    # Assume _addr is the address of the call to _strcpy# Assume opnd is “eax”# Find the start address of the function that we are searching in
    function_head = GetFunctionAttr(_addr, idc.FUNCATTR_START)
    addr = _addrwhile True:
       _addr = idc.PrevHead(_addr)
       _op = GetMnem(_addr).lower()if _op in ("ret", "retn", "jmp", "b") or _addr < function_head:breakelif _op == "lea" and GetOpnd(_addr, 0) == opnd:# We found the destination buffer, check to see if it is in the stackif is_stack_buffer(_addr, 1):print "STACK BUFFER STRCOPY FOUND at 0x%X" % addrbreak# If we detect that the register that we are trying to locate comes from some other register# then we update our loop to begin looking for the source of the data in that other registerelif _op == "mov" and GetOpnd(_addr, 0) == opnd:
           op_type = GetOpType(_addr, 1)if op_type == o_reg:
               opnd = GetOpnd(_addr, 1)
               addr = _addrelse:break
    上面的代码展示了我们搜索汇编指令以找到保存dest buffer的过程。它也展示了很多检查,例如确保我们没有没有搜索到函数开头之前的地址或任何可能改变执行流的指令。它也尝试追踪其他寄存器。例如,此代码尝试解释下面演示的情况。
    ...
    lea ebx [ebp-0x24]
    ...
    mov eax, ebx
    ...
    push eax
    ...
    另外,在上面的代码中,我们使用了函数is_stack_buffer,这个函数是这个脚本的最后一部分,有些东西没有在IDA API中定义。这个函数的目的很简单,给定指令的地址和操作数索引,检查这个变量是否是一个栈上的buffer。虽然IDA API没有给我们直接提供这样的函数,但我们可以通过其他方法。通过是同get_stkvar函数并检查返回值是None还是一个object,我们可以有效检查操作数是否是一个栈上变量。函数实现如下:
    def is_stack_buffer(addr, idx):
       inst = DecodeInstruction(addr)return get_stkvar(inst[idx], inst[idx].addr) != None
    注意这个函数和IDA7 API并不兼容。在下篇文章中我们会提到检测栈上buffer的新的方法并保持和最近IDA API的兼容。 我们现在可以把他们组合成一个脚本来寻找strcpy导致的栈溢出漏洞了。通过上述技巧我们可以拓展到不止支持strcpy,还可以是strcat、sprintf等函数(可以参考Microsoft Banned Functions List) 完整的脚本:
    def is_stack_buffer(addr, idx):
       inst = DecodeInstruction(addr)return get_stkvar(inst[idx], inst[idx].addr) != Nonedef find_arg(addr, arg_num):# Get the start address of the function that we are in
       function_head = GetFunctionAttr(addr, idc.FUNCATTR_START)
       steps = 0
       arg_count = 0# It is unlikely the arguments are 100 instructions away, include this as a safety checkwhile steps < 100:
           steps = steps + 1# Get the previous instruction
           addr = idc.PrevHead(addr)# Get the name of the previous instruction
           op = GetMnem(addr).lower()# Check to ensure that we havent reached anything that breaks sequential code flowif op in ("ret", "retn", "jmp", "b") or addr < function_head:returnif op == "push":
               arg_count = arg_count + 1if arg_count == arg_num:#Return the operand that was pushed to the stackreturn GetOpnd(addr, 0)for functionAddr in Functions():# Check each function to look for strcpyif "strcpy" in GetFunctionName(functionAddr):
           xrefs = CodeRefsTo(functionAddr, False)# Iterate over each cross-referencefor xref in xrefs:# Check to see if this cross-reference is a function callif GetMnem(xref).lower() == "call":# Since the dest is the first argument of strcpy
                   opnd = find_arg(xref, 1)
                   function_head = GetFunctionAttr(xref, idc.FUNCATTR_START)
                   addr = xref
                   _addr = xrefwhile True:
                       _addr = idc.PrevHead(_addr)
                       _op = GetMnem(_addr).lower()if _op in ("ret", "retn", "jmp", "b") or _addr < function_head:breakelif _op == "lea" and GetOpnd(_addr, 0) == opnd:# We found the destination buffer, check to see if it is in the stackif is_stack_buffer(_addr, 1):print "STACK BUFFER STRCOPY FOUND at 0x%X" % addr break# If we detect that the register that we are trying to locate comes from some other register# then we update our loop to begin looking for the source of the data in that other registerelif _op == "mov" and GetOpnd(_addr, 0) == opnd:
                           op_type = GetOpType(_addr, 1)if op_type == o_reg:
                               opnd = GetOpnd(_addr, 1)
                               addr = _addrelse:break
    可以见: https://github.com/Somerset-Recon/blog/blob/master/into_vr_script.py 运行结果25f9e8276d9d6fdfa863b299a5b8d96f.png这样,我们就找到了存在问题的函数地址0x8048528。2fc07c674de9ea18fcabfe379bc5ac4c.gif- End -6807ea6189c1a41b4011b6a49db2ef23.png

    看雪ID:微笑明天

    https://bbs.pediy.com/user-821225.htm 

    *本文由看雪论坛 微笑明天 原创,转载请注明来自看雪社区进阶安全圈,不得不读的一本书2991dfcfec9e93498f2b5be547074a83.pngbd89a53f1eb78811a5fc03e90f4729a8.png5e8d2ad8c394f8244a219bdfdc91dcdb.gif
    展开全文
  • 本文介绍了如何通过IDA Python...尽管有很多文章介绍了用IDAPython来简化基本的逆向任务,但很少有提及使用IDAPython来审计二进制漏洞的方法。因为这不是一个新的方法(Halvar Flake在2001年做过关于IDA脚本自动分析漏...

    本文介绍了如何通过IDA Python脚本来实现对栈溢出漏洞的检测,并以ascii_easy一道PWN基础题为例来实战。
    原文

    介绍

    IDAPython是一个用于复杂逆向工程任务的强大的自动化工具。尽管有很多文章介绍了用IDAPython来简化基本的逆向任务,但很少有提及使用IDAPython来审计二进制漏洞的方法。
    因为这不是一个新的方法(Halvar Flake在2001年做过关于IDA脚本自动分析漏洞的研究),但令人惊讶的是,这个话题没有被更多的说明。这可能是因为在现代操作系统上想要利用漏洞日渐复杂困难。然而,这对于自动化部分的漏洞研究还是有价值的。

    在这篇文章中,我们将介绍使用基本的IDAPython来检测程序中出现的能导致栈溢出问题的地方。在这篇文章中,我会用自动化探测方法实战pwnable.kr中的ascii_easy二进制题目。尽管这个二进制文件小到我们可以手动整个去分析它,它仍然是一个很好的学习案例以便我们使用相同的IDAPython技术取分析更大更复杂的二进制文件。

    开始

    在我们写任何IDAPython脚本前,我们先要决定我们想让我们的脚本做什么。
    这里,我们选择了简单漏洞中的一个栈溢出漏洞,其可由strcpy函数导致将用户可以控制的字符串拷贝到栈缓存区中。既然我们知道了我们要寻找什么,我们可以开始考虑如何取自动化寻找这种漏洞。

    我们分为两步:

    1. 定位可能导致栈溢出的函数(这里我们选取strcpy函数)
    2. 分析这些函数的调用来决定这个这个调用我们是否感兴趣(即是否可能导致漏洞)

    定位函数调用

    为了寻找任何调用strcpy的地方,我们需要首先定位strcpy这个函数本身。
    使用IDAPython API很容易做到这一点。使用如下的代码片段来打印二进制文件中所有的函数名:

    for functionAddr in Functions(): print(GetFunctionName(functionAddr))

    (注:可以在ida底部的python命令控制窗口中输入命令)

    0c231f3dc7bed98f81bbc270a653b80e.png


    我们可以看到,所有的函数名都被输出了。

    ad48ec5f7965a873548ffb07d49a17f5.png

    然后,我们要添加过滤取寻找我们感兴趣的strcpy函数。简单的字符串比较我们就能达到效果。但因为我们常常需要处理一些函数名相似但仍有区别的情况(例如_strcpy,这取决于导入函数的命名),我们最好检查子字符串。

    在前面的基础上,我们有下面的代码

    for functionAddr in Functions(): if “strcpy” in GetFunctionName(functionAddr): print hex(functionAddr)

    既然我们获得了我们感兴趣的函数,我们需要获取所有调用它的地方。这需要很多步骤。
    首先我们要获取strcpy交叉引用的地方,然后我们要检查这其中的每个地方是否真正调用了strcpy函数。总结一下就有下面的代码:

    for functionAddr in Functions(): # Check each function to look for strcpy if "strcpy" in GetFunctionName(functionAddr):xrefs = CodeRefsTo(functionAddr, False) # Iterate over each cross-referencefor xref in xrefs: # Check to see if this cross-reference is a function call if GetMnem(xref).lower() == "call": print hex(xref)

    对ascii_easy二进制文件运行此脚本后我们得到如下结果:

    68b7718a15deb920f6b9ad1a7bd64ace.png

    即我们找到了0x451b2c目标地址。

    分析函数调用

    现在,通过上面的代码,我们知道了如何获取所有程序中调用strcpy的地方。而ascii_easy恰好就只有这一个调用strcpy的地方(也恰好可利用),很多程序有很多调用strcpy的地方(很多都不可被利用),因此我们需要一些方法取分析对strcpy的调用来根据可利用的可能性进行排序。

    一个缓存区溢出漏洞的常见特点是它们往往涉及栈上的缓冲区。尽管在堆上或者别的地方的缓存区溢出也是有可能的,栈溢出是一个更简单的利用方式。

    这涉及一些对strcpy函数的目的地参数(destination)的分析,我们知道目的地参数是strcpy函数的第一个参数,而且我们可以通过浏览函数的反汇编得到这个参数。这个调用strcpy函数的反汇编如下:

    f3869d82b163817b1ab364d195f68d29.png

    分析上面的代码,有两种可以找到_strcpy函数的目的地参数。
    第一种方法是依赖IDA自动分析后对已知函数的注释。上图中,IDA已经自动检测到了_strcpy函数的dest参数并标记。

    另一个方法是从函数调用前开始寻找push指令。每当我们找到一个这样的指令,我们可以自增一个计数器直到我们定位到了参数的索引。这里,既然我们要找的dest参数是第一个参数,这个方法将会在先前一个push指令停下。

    在这些情况中,当我们遍历这些代码时,我们要注意某些可以打破函数执行流的指令。例如retjmp这样改变执行流的指令会使精确分析参数变得困难。另外,我们需要确保我们不遍历那些当前所处函数之前的地方。现在,我们将只是在搜索参数时识别非顺序执行代码流的地方。如果找到任何非顺序代码流实例,则停止搜索。

    我们将使用第二种查找参数的方法(查找被推送到堆栈的参数)。为了帮助我们以这种方式查找参数,我们创建一个helper函数。此函数将从函数调用的地址向向前查找,跟踪push到堆栈的参数并返回我们指定参数对应的操作数。

    对于上面的例子,helper函数将返回eax寄存器的值,因为eax寄存器保存了strcpy的目标参数dest。结合一些基本的python与IDAPython API,我们可以构建一个如下函数:

    def find_arg(addr, arg_num):# Get the start address of the function that we are infunction_head = GetFunctionAttr(addr, idc.FUNCATTR_START) steps = 0arg_count = 0# It is unlikely the arguments are 100 instructions away, include this as a safety checkwhile steps < 100: steps = steps + 1# Get the previous instructionaddr = idc.PrevHead(addr) # Get the name of the previous instructionop = GetMnem(addr).lower() # Check to ensure that we haven’t reached anything that breaks sequential code flow if op in ("ret", "retn", "jmp", "b") or addr < function_head:returnif op == "push":arg_count = arg_count + 1if arg_count == arg_num:# Return the operand that was pushed to the stackreturn GetOpnd(addr, 0)

    为了判断eax是否指向在栈中的缓存区buffer,当它被push入栈时,我们应该继续跟踪eax从哪来。因此,我们使用的和之前类似的搜索循环:

    # Assume _addr is the address of the call to _strcpy# Assume opnd is “eax”# Find the start address of the function that we are searching infunction_head = GetFunctionAttr(_addr, idc.FUNCATTR_START)addr = _addrwhile True:_addr = idc.PrevHead(_addr)_op = GetMnem(_addr).lower() if _op in ("ret", "retn", "jmp", "b") or _addr < function_head:breakelif _op == "lea" and GetOpnd(_addr, 0) == opnd:# We found the destination buffer, check to see if it is in the stackif is_stack_buffer(_addr, 1):print "STACK BUFFER STRCOPY FOUND at 0x%X" % addrbreak# If we detect that the register that we are trying to locate comes from some other register# then we update our loop to begin looking for the source of the data in that other registerelif _op == "mov" and GetOpnd(_addr, 0) == opnd:op_type = GetOpType(_addr, 1)if op_type == o_reg:opnd = GetOpnd(_addr, 1)addr = _addrelse:break

    上面的代码展示了我们搜索汇编指令以找到保存dest buffer的过程。它也展示了很多检查,例如确保我们没有没有搜索到函数开头之前的地址或任何可能改变执行流的指令。它也尝试追踪其他寄存器。例如,此代码尝试解释下面演示的情况。

    ...lea ebx [ebp-0x24]...mov eax, ebx...push eax...

    另外,在上面的代码中,我们使用了函数is_stack_buffer,这个函数是这个脚本的最后一部分,有些东西没有在IDA API中定义。这个函数的目的很简单,给定指令的地址和操作数索引,检查这个变量是否是一个栈上的buffer。虽然IDA API没有给我们直接提供这样的函数,但我们可以通过其他方法。通过是同get_stkvar函数并检查返回值是None还是一个object,我们可以有效检查操作数是否是一个栈上变量。函数实现如下:

    def is_stack_buffer(addr, idx):inst = DecodeInstruction(addr)return get_stkvar(inst[idx], inst[idx].addr) != None

    注意这个函数和IDA7 API并不兼容。在下篇文章中我们会提到检测栈上buffer的新的方法并保持和最近IDA API的兼容。

    我们现在可以把他们组合成一个脚本来寻找strcpy导致的栈溢出漏洞了。通过上述技巧我们可以拓展到不止支持strcpy,还可以是strcat,sprintf等函数(可以参考Microsoft Banned Functions List)

    完整的脚本

    def is_stack_buffer(addr, idx):inst = DecodeInstruction(addr)return get_stkvar(inst[idx], inst[idx].addr) != Nonedef find_arg(addr, arg_num):# Get the start address of the function that we are infunction_head = GetFunctionAttr(addr, idc.FUNCATTR_START) steps = 0arg_count = 0# It is unlikely the arguments are 100 instructions away, include this as a safety checkwhile steps < 100: steps = steps + 1# Get the previous instructionaddr = idc.PrevHead(addr) # Get the name of the previous instruction op = GetMnem(addr).lower()# Check to ensure that we havent reached anything that breaks sequential code flow if op in ("ret", "retn", "jmp", "b") or addr < function_head: returnif op == "push":arg_count = arg_count + 1if arg_count == arg_num:#Return the operand that was pushed to the stackreturn GetOpnd(addr, 0)for functionAddr in Functions():# Check each function to look for strcpyif "strcpy" in GetFunctionName(functionAddr):xrefs = CodeRefsTo(functionAddr, False)# Iterate over each cross-referencefor xref in xrefs:# Check to see if this cross-reference is a function callif GetMnem(xref).lower() == "call":# Since the dest is the first argument of strcpyopnd = find_arg(xref, 1)function_head = GetFunctionAttr(xref, idc.FUNCATTR_START)addr = xref_addr = xref while True:_addr = idc.PrevHead(_addr)_op = GetMnem(_addr).lower() if _op in ("ret", "retn", "jmp", "b") or _addr < function_head:breakelif _op == "lea" and GetOpnd(_addr, 0) == opnd:# We found the destination buffer, check to see if it is in the stackif is_stack_buffer(_addr, 1):print "STACK BUFFER STRCOPY FOUND at 0x%X" % addr break# If we detect that the register that we are trying to locate comes from some other register# then we update our loop to begin looking for the source of the data in that other registerelif _op == "mov" and GetOpnd(_addr, 0) == opnd:op_type = GetOpType(_addr, 1)if op_type == o_reg:opnd = GetOpnd(_addr, 1)addr = _addrelse:break

    可以见 https://github.com/Somerset-Recon/blog/blob/master/into_vr_script.py

    运行结果

    7e929c753543c0dd434a8bd03c2db28c.png

    这样,我们就找到了存在问题的函数地址0x8048528

    展开全文
  • 介绍二进制漏洞挖掘的一个方法思路,用IDA解析二进制代码,取得程序的函数,栈帧,数据流,控制流;取得程序的界面资源,取得资源对应的函数代码,综合分析程序的堆溢、栈溢、同步问题、线程安全问题、 逻辑漏洞分析...
  • 本文首发于“合天智汇”公众号 作者: 萌新0本文是本系列的第二篇,将对面向二进制程序的静态漏洞挖掘技术进行介绍与分析。面向二进制程序的静态漏洞的挖掘技术由于缺少源代码中的结构化信息,面临着值集分析(vaule-...
  • windows二进制漏洞入门包含了。https://www.kanxue.com/book-42.htm1.简单栈溢出利用2.gs保护绕过利用。包含:攻击seh绕过gs;攻击虚函数表绕过gs3.aslr保护绕过利用。包含:部分覆盖绕过aslr;heap spy 绕过aslr。4...
  • 二进制漏洞——栈溢出复现 栈布局如下: 可以看出,要构造栈溢出需要将函数内部成员长度突破EBP并覆盖到返回地址处即可得到执行。构造代码如下: #include <stdio.h> #include <windows.h> __...
  • 0×0二进制漏洞 二进制漏洞是可执行文件(PE、ELF文件等)因编码时考虑不周,造成的软件执行了非预期的功能。二进制漏洞早期主要以栈溢出为主,那时候操作系统和软件厂商还没有相应的安全意识,漏洞利用在...
  • 亲爱的,关注我吧8/31文章共计3165个词今天的内容有一些图,流量用户注意哦和我一起阅读吧0本文是本系列的第二篇,将对面向二进制程序的静态漏洞挖掘技术进行介绍与分析。面向二进制程序的静态漏洞的挖掘技术由于缺少...
  • 二进制漏洞利用程序位于/bin-sploits/目录中。 这是一个官方的仓库,一个主办。 我们的存储库是: 漏洞和Shellcodes: : 二进制漏洞利用: : 论文: : 漏洞利用数据库是公共漏洞和相应的易受攻击软件的档案库...
  • 二进制漏洞与利用学习

    千次阅读 2017-07-24 10:40:14
    二进制漏洞与利用学习 一、主要术语 Binary:由编译类语言生成的可执行程序 Vulnerability:Binary中包含的可被利用的Bug Exploit:借助Vulnerability来劫持Binary控制流的、经过特殊构造的数据 0day:可被exploit...
  • 本文首发于“合天智汇”公众号 作者:萌新0本文是本系列的第二篇,将对面向二进制程序的静态漏洞挖掘技术进行介绍与分析。面向二进制程序的静态漏洞的挖掘技术由于缺少源代码中的结构化信息,面临着值集分析(vaule-...
  • 0x00 目录本部分将对常见的二进制漏洞做系统分析,方便在漏洞挖掘过程中定位识别是什么类型漏洞,工欲善其事,必先利其器。0x01栈溢出漏洞原理0x02堆溢出漏洞原理0x03堆调试技巧堆尾检查页堆0x04整数溢出漏洞原理...
  • 列表不定期更新,有遗漏的地方还望各位留帖补充:Plast update: 2019.04.05 从漏洞、漏洞利用、Fuzz、利用... 『二进制漏洞分析』学习资源整理https://bbs.pediy.com/thread-221851.htm 基础知识: Win...
  • 二进制漏洞挖掘之栈溢出-开启RELRO

    千次阅读 2019-10-18 15:11:53
    二进制漏洞-栈溢出 github地址:https://github.com/ylcangel/exploits/tree/master/stack_overflow 你可以用于学习,但不能用于商用(如出书、以盈利为目的的课程、文章等),转载需标明出处。 测试平台 系统:...
  • 二进制漏洞挖掘之栈溢出-开启FORTIFY

    千次阅读 2019-10-18 15:23:49
    二进制漏洞-栈溢出 github地址:https://github.com/ylcangel/exploits/tree/master/stack_overflow 你可以用于学习,但不能用于商用(如出书、以盈利为目的的课程、文章等),转载需标明出处。 测试平台 系统:...
  • 二进制漏洞挖掘之栈溢出-开启PIE

    千次阅读 2019-10-18 14:56:57
    二进制漏洞-栈溢出 github地址:https://github.com/ylcangel/exploits/tree/master/stack_overflow 你可以用于学习,但不能用于商用(如出书、以盈利为目的的课程、文章等),转载需标明出处。 测试平台 系统:...
  • 二进制漏洞-栈溢出 github地址:https://github.com/ylcangel/exploits/tree/master/stack_overflow 你可以用于学习,但不能用于商用(如出书、以盈利为目的的课程、文章等),转载需标明出处。 测试平台 系统:...
  • 55-AA · 2015/05/28 14:420x00 前言本文是二进制漏洞相关的系列文章。printf有一些鲜为人知的特性,在为编码提供便利的同时,也引入了安全的问题。本文重点描述printf在漏洞利用中的一些用法,在正常的编程中不建议...
  • ‍rop-tool v2.4.2一种帮助您编写二进制漏洞利用的工具选项rop-tool v2.4.2Help you make binary exploits.Usage: rop-tool [OPTIONS]Commands : gadget Search gadgets patch Patch the binary info ...
  • 不知道从什么时候开始,学习编程成为一件让人头疼的事情。总有那么几个深夜,我抬头望着明月...如果你想更了解汇编语言,以及二进制漏洞,那么的看雪优质课程不要错过——《汇编语言详解与二进制漏洞初阶》这门学生...
  • 二进制漏洞-栈溢出 github地址:https://github.com/ylcangel/exploits/tree/master/stack_overflow 你可以用于学习,但不能用于商用(如出书、以盈利为目的的课程、文章等),转载需标明出处。 测试平台 系统:...
  • 二进制漏洞-栈溢出 github地址:https://github.com/ylcangel/exploits/tree/master/stack_overflow 你可以用于学习,但不能用于商用(如出书、以盈利为目的的课程、文章等),转载需标明出处。 测试平台 系统:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,084
精华内容 433
关键字:

二进制漏洞