学习,得向基础看齐。之前面试,别人问了我栈溢出,又问了堆溢出,索性今天就栈溢出基础弄个总结,并结合IDA。希望自己可以有新收获。
1)我们先回顾一下call和ret指令,可以认为是函数调用栈。
call的操作是,调用一个过程,指挥处理器从新的内存地址开始执行。过程使用ret来将处理器转回到该过程被调用的程序点上。
从物理的角度来解释,call指令将其返回地址压入堆栈,再把被调用过程的地址复制到指令指针寄存器。当过程准备返回时,它的ret指令从堆栈把返回地址弹回到指令指针寄存器。
函数调用栈在内存中从高地址向低地址生长,所以栈顶对应的内存地址在压栈时变小,退栈时变大。
2)函数调用背景。
函数状态主要涉及三个寄存器--esp,ebp,eip。esp 用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。ebp 用来存储当前函数状态的基地址,在函数运行时不变,可以用来索引确定函数参数或局部变量的位置。eip 用来存储即将执行的程序指令的地址。
a.将被调用函数的参数压入栈内
b.将被调用函数的返回地址压入栈内
c.将调用函数的基地址(ebp)压入栈内,并将当前栈顶地址传到 ebp 寄存器内
d.将被调用函数的局部变量压入栈内
e.将被调用函数的局部变量弹出栈外
f.将调用函数(caller)的基地址(ebp)弹出栈外,并存到 ebp 寄存器内
g.将被调用函数的返回地址弹出栈外,并存到 eip 寄存器内
3)当函数正在执行内部指令的过程中我们无法拿到程序的控制权,只有在发生函数调用或者结束函数调用时,程序的控制权会在函数状态之间发生跳转,这时才可以通过修改函数状态来实现攻击。
我们只需要让溢出数据用攻击指令的地址来覆盖返回地址就可以了。我们可以在溢出数据内包含一段攻击指令,也可以在内存其他位置寻找可用的攻击指令。
自此,栈溢出的基础理论讲完。
与此相关的技术,有下面这些。
修改返回地址,让其指向溢出数据中的一段指令(shellcode)
修改返回地址,让其指向内存中已有的某个函数(return2libc)
修改返回地址,让其指向内存中已有的一段指令(ROP)
修改某个被调用函数的地址,让其指向另一个函数(hijack GOT)
1)实验目的:证明存在栈溢出漏洞
2)实验环境:IDA,OD,小程序。
3)要求(个人要求):个人突破,熟练使用IDA和OD。
准备代码如下:
看到代码后,怎样来利用了?步骤是怎样的?
1)运行程序,观察情况,特别需要注意字符串和一些特殊的函数。
2)在上一步的基础上,我们在关键点上设下断点。
3)控制台交互,观察栈的运行情况,从而发现破绽。
1),导入OD,或者IDA,都可以。当然导入OD,较为简单。这里我选择用IDA,执行本地调试。先执行win32_remote.exe,然后在IDA界面,点击debugger,具体的环境设置,网上很全,当然如果有问题,也可以和我交流。按F9开始执行,看到“please input password:”。这个字符串,很重要,将是我们的突破口。
,
(其实用IDA来调试,查看栈的情况,也可以,但是没有OD方便。)
2)通过上一步的字符串,来定位到该位置。alt+T,可以全局搜索该字符串。在00C717D6这里。
搜索到后,选中这一代码段的标记,sub_C717B0,快捷键N,实现改名。
为了进一步确定,我们可以通过查看伪代码,具体的情况,如图。
这里注意到一个关键的函数,sub_C712AD,我们双击进入,看到返回的是另一个函数sub_C71640,我们继续点击,看到了这个代码段。
这里可以确认,密码是“1234567”。有些时候,某些程序的密码就在程序的内部,也有的是一个密码生成器,自动生成,需要逆向算法。j_strcmp是一个比较的接口,j_strcpy是将输入的密码,复制到&Dest上。可以确定这个&Dest就是栈的开头。下图是该栈的情况。
这里有个8很明显。记住。
我们进入OD,Ctrl+G,搜索这个,00C717D6,并F2,下断点。
F9运行程序,可以看到正好停在这里。
这时,我输入“12345678”,这个比正确的密码多了一个8,结果竟然成功了。
这不是重点,重点是观察寄存器的变化。
3)上一步,我们已经设置了断点,输入了8个数,就成功的通过了。我们来单步执行,重点观察一下寄存器。
通过查阅资料,得到了下面的一些启发。
程序的关键地方是strcpy(buffer,password);
栈内的依次存放的是authenticated、局部变量char buffer[8]。
当我们输入的密码,超过buffer的长度,正好覆盖掉authenticated。而在源代码中authenticated是否等于0,用来说明当前密码正确与否的状态,。转换到汇编代码中,[ebp-4]是否等于0,用来判断密码是否正确。
所以,只需要输入任意八位数字,填满buffer,利用字符截断符NULL来填充[ebp-0x4]的值,就可以绕过密码验证。
当我们输入更长的数字后,会发生什么呢?
首先authenticated会修改,同时后面的EBP和返回地址也会修改,查阅资料上解释的是,程序崩溃,出现异常。但是我觉得不是的。因为当我故意夸大输入的长度时,才会出现奔溃。当我仅仅输入10个数字的时候,是没有出现崩溃的。
至于它的崩溃点是那里,这里可以通过pattern字符来验证,我不做深究。
从逻辑上,可以认为是,当我们输入的长度过长,覆盖了ret,会导致栈无法正常返回,从而堆栈不平衡,导致崩溃。
如果大家对此,有什么好的看法,请私信告诉我。下一篇,堆溢出。
using namespace std;
int
verify_password(char
*
password)
{
int
authenticated;
char
buffer
[
8
];
/
/
add local
buffer
to be overflowed
authenticated
=
strcmp(password, PASSWORD);
/
/
compare the two values
strcpy(
buffer
, password);
/
/
over flowed here!
return
authenticated;
}
int
main(){
int
valid_flag
=
0
;
char password[
1024
];
while
(
1
)
{
printf(
"please input password: "
);
scanf(
"%s"
, password);
valid_flag
=
verify_password(password);
if
(valid_flag)
{
printf(
"incorrect password!\n\n"
);
}
else
{
printf(
"Congratulation! You have passed the verification!\n"
);
break
;
}
}
return
0
;
}
}
}
}
using namespace std;
int
verify_password(char
*
password)
{
int
authenticated;
char
buffer
[
8
];
/
/
add local
buffer
to be overflowed
authenticated
=
strcmp(password, PASSWORD);
/
/
compare the two values
strcpy(
buffer
, password);
/
/
over flowed here!
return
authenticated;
}
int
main(){
int
valid_flag
=
0
;
char password[
1024
];
while
(
1
)
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-3-8 19:52
被奋进的小杨编辑
,原因: