Updates from 八月, 2016 Toggle Comment Threads | 键盘快捷键

  • jinzihao pm6:33 on 2016年8月29日 链接地址 | 回复  

    C语言判断奇偶性:i&1和i%2  

    三个月前的一篇文章中,曾经好奇为什么C语言中同样是判断整数奇偶性,在不开编译优化(gcc -O0)的情况下,

    (1) bool isOdd = i & 1;

    (2) bool isOdd = i % 2;

    略快(但并没有快很多)。

    看过反汇编得到的汇编代码,发现即便没有开编译优化选项,编译器对于(2)也没有使用除法指令idiv,从而避免了(2)比(1)慢得多的情况。编译器由(2)编译出的汇编代码的作用,是保存一个临时变量t,如果需要判断奇偶性的数i是正数,则t取0,否则t取1;“i是否为奇数”则可以表示为((i + t) & 1) – t,这样就会比i & 1稍慢(但不会慢很多)。

    实际上(1)对于i为负数的情况会得到错误的结果,因此(2)的一点点额外的时间开销是完全有必要的;如果将上一篇文章main函数里面的int全部改为unsigned int,即使在指定gcc -O0的情况下,编译器也会对(1)和(2)编译出完全相同的汇编代码,即(2)也会被优化为i & 1。

     

     
  • jinzihao pm2:28 on 2016年8月20日 链接地址 | 回复  

    缓冲区溢出漏洞的利用 

    注:这篇文章本来是一门课程的(大)作业,由于课程报告题目自选,本文其实就是对CTF中几个很基础的二进制的题写了比较详细的write-up,也算记录了一下自己从零基础开始学习的过程吧。

    0. 写在前面

    操作系统这门课程对系统如何管理内存做了详细的介绍,这是计算机安全的一个重要分支——内存安全的背景知识。我对计算机安全比较感兴趣,而内存安全也是其中绕不开的一个领域。在计算机安全中,内存安全相比起Web安全等领域门槛较高,需要站在操作系统的视角看内存,才能发现潜在的内存漏洞。这学期选了操作系统这门课,也希望能起到“扫盲”的作用,借此机会入门一下内存安全。这篇课程报告主要关注内存安全问题中最常规的缓冲区溢出漏洞,总结了缓冲区溢出漏洞的一些入门知识,也算是一个初学者的一点学习笔记。

    1. 什么是缓冲区溢出漏洞

    这是一个很难一言以蔽之的概念,维基百科的描述如下:

    缓冲区溢出(buffer overflow),是针对程序设计缺陷,向程序输入缓冲区写入使之溢出的内容(通常是超过缓冲区能保存的最大数据量的数据),从而破坏程序运行、并获取程序乃至系统的控制权。[1]

    而缓冲区溢出漏洞的利用,则是通过精心构造的输入数据,向内存的特定位置写入特定信息,进而使程序运行发生异常。注意,这里的异常是对于程序开发者而言的,对于攻击者而言则是预期结果。

    利用缓冲区溢出漏洞的一般流程如下:

    (1) 使用反编译工具(如IDA)对程序进行反编译

    (2) 分析反编译得到的C语言代码和汇编代码,设计攻击方法

    (3) 按照设计好的攻击方法设计输入数据

    (4) 把设计好的输入数据传入到程序中,获得系统权限

    下面结合几个实例,对缓冲区溢出漏洞做些简要介绍。

    2. 实例1——基本思路

    (本题出自Bluelotus-Exercise题集中的shellcode题目[2])

    使用IDA反编译得到:

    全局变量:

    int (*_frame_dummy_init_array_entry[2])() = { &frame_dummy, &_do_global_dtors_aux }; // weak
    int (*_do_global_dtors_aux_fini_array_entry)() = &_do_global_dtors_aux; // weak
    FILE *stderr; // idb
    FILE *stdin; // idb
    FILE *stdout; // idb
    char completed_6591; // weak
    _UNKNOWN shellcode; // weak
    

    main函数:

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
    setbuf(stdin, 0);
    setbuf(stdout, 0);
    setbuf(stderr, 0);
    read(0, &shellcode, 0x3FFu);
    return ((int (*)(void))shellcode)();
    }
    

    这段程序的功能是,从stdin(控制台输入)读入1023个字节,存入shellcode变量,然后通过强制类型转换把shellcode变量作为一个无参数、返回int的函数,调用shellcode变量。由于程序指令本质上也是数据,因此只要将合适的数据写入到shellcode变量,便可以使这段程序执行这段数据对应的程序指令。

    shellcode本质上是一段机器码,直接编写难度很大。可以先按照需求编写C语言程序,然后编译,将编译得到的二进制文件用十六进制编辑器打开,便可以得到这段C语言程序对应的机器码。另外,网络上也有很多现成的shellcode,例如本题中可使用这样一段来自EggRun主页的shellcode [3]:

    6a 0b 58 99 52 68 6e 2f 73 68 68 2f 2f 62 69 89 e3 52 53 89 e1 cd 80

    这段用16进制数表示的机器码对应的C语言程序为execve(/bin/sh),在包括Linux在内的类Unix系统中可以启动一个shell,从而可以执行任意命令。

    另外,shell-storm也提供了大量shellcode,可以根据不同的运行环境和预期效果选择不同的shellcode。[4]

    3. 实例2——利用函数调用栈

    (本题出自Bluelotus-Exercise题集中的simple_stackoverflow题目[5])

    使用IDA反编译得到:

    全局变量:

    int (*_frame_dummy_init_array_entry[2])() = { &frame_dummy, &_do_global_dtors_aux }; // weak
    int (*_do_global_dtors_aux_fini_array_entry)() = &_do_global_dtors_aux; // weak
    FILE *stderr; // idb
    FILE *stdin; // idb
    FILE *stdout; // idb
    char completed_6591; // weak
    _UNKNOWN buf; // weak
    

    main函数和overflow函数:

    //----- (080484BD) --------------------------------------------------------
    int overflow()
    {
    char buf; // [sp+18h] [bp-20h]@1
    read(0, &::buf, 0x3FFu);
    read(0, &buf, 0x3FFu);
    return 0;
    }
    //----- (08048501) --------------------------------------------------------
    int __cdecl main(int argc, const char **argv, const char **envp)
    {
    setbuf(stdin, 0);
    setbuf(stdout, 0);
    setbuf(stderr, 0);
    return overflow();
    }
    

    本题中main函数返回了一个overflow(),也就是main函数在返回时调用了overflow函数,除此之外main函数并没有进行特别的操作。在overflow函数中,先定义了一个局部变量buf,然后向全局变量buf(::buf)中读入1023个字节,再向局部变量buf(buf)中读入1023个字节。只是读入数据,并没有进行函数调用,这时如何执行shellcode呢?

    这里需要对函数调用栈有一些了解,函数调用栈的示意图如下 [6]:

    http://images.cnitblog.com/i/569008/201405/271644419475745.jpg

    main函数调用了overflow函数,从图中可以读出,main函数的调用栈在较高地址,而overflow函数的调用栈在较低地址。在overflow函数调用栈内部,局部变量buf处在局部变量区域,且数据的填充是从低地址到高地址。由于overflow函数向局部变量buf读入了1023个字节,远远超过局部变量buf的实际大小——1字节,因此可以通过向上溢出到main函数的返回地址,从而控制程序执行流,使得main函数返回时跳到我们指定的内存位置执行指令。

    更具体的思路,是在全局变量buf中写入shellcode,在局部变量buf中通过溢出到main函数的返回地址,使main函数的返回地址变为全局变量buf的地址,从而在main函数返回时执行shellcode。按照这个思路构造输入数据:

    首先是23个字节的shellcode,和实例1相同:

    6a 0b 58 99 52 68 6e 2f 73 68 68 2f 2f 62 69 89 e3 52 53 89 e1 cd 80

    接下来是00 * 1000(1000个字节的0),用来填满第一次读入的1023个字节。

    接下来是00 * 36(36个字节的0),这是从局部变量buf到main函数返回地址的距离。可以通过gdb调试运行存在漏洞的程序,使用gdb查看内存指定位置的功能(x命令)确定这个距离[7]。

    最后是80 a0 04 08,表示全局变量buf的内存地址0x0804a080(每四字节一组,由高位向低位读)。这个地址是固定的,可在IDA中查看。

    使用这样一段共1063个字节的输入数据,便可以执行/bin/sh,获取到系统的shell。

    4. 实例3——利用程序自身代码

    (本题出自Bluelotus-Exercise题集中的simple_stackoverflow2题目[8])

    使用IDA反编译得到:

    全局变量:

    int (*_frame_dummy_init_array_entry[2])() = { &frame_dummy, &_do_global_dtors_aux }; // weak
    int (*_do_global_dtors_aux_fini_array_entry)() = &_do_global_dtors_aux; // weak
    FILE *stderr; // idb
    FILE *stdin; // idb
    FILE *stdout; // idb
    char completed_6591; // weak
    

    main函数和overflow函数:

    //----- (080484BD) --------------------------------------------------------
    int overflow()
    {
    char buf; // [sp+18h] [bp-20h]@1
    read(0, &buf, 0x3FFu);
    return 0;
    }
    //----- (080484E5) --------------------------------------------------------
    int __cdecl main(int argc, const char **argv, const char **envp)
    {
    setbuf(stdin, 0);
    setbuf(stdout, 0);
    setbuf(stderr, 0);
    return overflow();
    }
    

    本题和实例2的程序非常相似,唯一的区别在于去除了全局变量buf。由于局部变量buf最多可写入1023字节,远远超出一段shellcode的长度,最直接的思路是将shellcode写入局部变量buf。但实际上,局部变量buf的地址和函数调用栈基地址有关,而Linux系统的ASLR(Address Space Layout Randomization)保护机制[9],使得函数调用栈基地址随机分配,从而无法返回到buf的地址(因为地址不确定)。另外NX位的设置,也会使得栈上的指令无法被执行。

    我们需要的是把shellcode写入内存中一个确定的、可执行的位置,但目前我们只能写入函数调用栈,而函数调用栈位置不确定,且不可执行。但通过操作函数调用栈,我们可以调用任意函数,只要需调用的函数地址已知。可否通过调用其他函数来实现我们的目的呢?使用IDA反编译可以查看程序的PLT(Procedure Linkage Table),这张表记录了程序使用到的动态链接库函数的地址,表的内容在运行时由动态链接器填写。在程序的PLT中有read函数对应的表项(因为程序中用到了read函数),返回到read函数对应的表项便可返回到read函数。read函数的参数也可以通过修改函数调用栈来传入。这样我们就可以在main函数返回时调用read函数。

    到这里,我们已经可以通过调用read函数,向内存中的指定地址写入指定数据了。那么应该向什么地址写入什么数据呢?写入的数据仍然是一段shellcode,和前两个实例相同。写入的位置是.bss,这是程序为未初始化的变量预留的一段内存[10],位置是确定的(可在IDA中读出),且是可执行的。在本程序中.bss长度为40字节,足以写入shellcode。按照这个思路构造输入数据:

    首先是36个字节的0,这是从局部变量buf到main函数返回地址的距离,和实例2相同。

    接下来是一段函数调用栈数据:

    90 83 04 08 40 a0 04 08 00 00 00 00 40 a0 04 08 28 00 00 00

    前4个字节表示PLT中read函数的地址,从高位向低位读,为0x08048390。接下来四个字节为read函数的返回地址,也就是.bss的地址,这里为0x0804a040,用来使read函数调用完成后跳转到shellcode所在的内存继续执行。接下来四个字节为read函数的第一个参数,这里取0x0,表示stdin。接下来四个字节为read函数的第二个参数,表示读入到的内存地址,这里为0x0804a040,为.bss的地址。接下来四个字节为read函数的第三个参数,表示读入的长度,这里取0x28,即.bss的长度40字节。

    接下来用967个字节的0填充第一次read。

    最后,由于我们调用了read函数,因此程序会再读入40字节的数据。这时我们把shellcode输入进去:

    6a 0b 58 99 52 68 6e 2f 73 68 68 2f 2f 62 69 89 e3 52 53 89 e1 cd 80

    到这里,我们执行了shellcode ,从而获取到系统的shell。

    5. 写在最后

    缓冲区溢出漏洞的实际上种类繁多,上面只是列举了几种比较基本的情况。限于知识水平,很难完整地总结所有类型的缓冲区溢出漏洞。另外,随着防御手段的不断升级,新的攻击方法也在不断被发现。最早的操作系统和处理器在安全上考虑不足,而后诸如DEP机制(对应处理器的NX位)、地址随机化(ASLR)相继出现。另外早期的编程语言(如C语言)设计之初也对安全考虑不足,很多C语言内置函数都存在内存安全隐患。但随着C语言标准的进化,很多内置函数增加了一个“安全”(_s)版本,修补了漏洞。而攻击手段也在逐渐演进,包括return-to-libc等攻击手段[11],以及针对地址随机化的诸多绕过技巧的出现[12],让我们看到这场猫鼠游戏在短期之内并没有止境。但无论如何,对可能出现的漏洞了解的越多,写出的程序出现漏洞的概率就越低。另外,程序开发者在这场攻防中占据主动地位,只要有意识地避免了所有已知的漏洞,仍然可以写出(目前)无法被攻破的程序的。

    参考文献:

    [1] https://zh.wikipedia.org/wiki/%E7%BC%93%E5%86%B2%E5%8C%BA%E6%BA%A2%E5%87%BA

    [2] http://learn.blue-lotus.net/challenges#shellcode

    [3] http://www.secdev.org/projects/eggrun/

    [4] http://shell-storm.org/shellcode/

    [5] http://learn.blue-lotus.net/challenges#simple_stackoverflow

    [6] http://www.cnblogs.com/clover-toeic/p/3755401.html

    [7] http://eminzhang.blog.51cto.com/5292425/1256022

    [8] http://learn.blue-lotus.net/challenges#simple_stackoverflow2

    [9] http://drops.wooyun.org/tips/6597

    [10] https://en.wikipedia.org/wiki/.bss

    [11] http://blog.csdn.net/linyt/article/details/43643499

    [12] http://lieanu.github.io/2014/12/08/SCTF-pwn200.html

     
  • jinzihao pm10:34 on 2016年8月15日 链接地址 | 回复  

    Ubuntu on Windows (Bash on Windows) 初体验 

    如何安装?

    1. 在开始菜单搜索“针对开发人员”:
    bash6

    2. 选择开发人员模式,等待下载安装一些程序包;
    bash7

    3. 打开cmd,输入bash,等待下载安装另一些程序包:
    bash1

    效果:
    支持搭建AMP(Apache + MySQL + PHP)(已经说不清是WAMP还是LAMP了):
    bash2

    grep、awk、wget等Linux下常用工具都已经预装了,和Ubuntu一样:
    bash3

    最好的是有apt-get,虽然软件不一定非常全:
    bash4

    lynx这样界面较复杂的控制台应用程序也可以正确显示:
    bash5

     
c
写新的
j
下一篇文章/下一个回复
k
前一篇文章/以前的回复
r
回复
e
编辑
o
显示/隐藏 回复
t
回到顶部
l
go to login
h
show/hide help
shift + esc
取消