stack smash
造成原因
当进行栈溢出时,触发了__stack_chk_fail,从而拦截了该栈溢出,使程序崩溃
利用原理
我们首先了解一下__stack_chk_fail函数的构成
__
__发现调用了__fortify_fail函数,那我们再看下这个函数
发现有两个参数,一个是msg(信息,也就是前边的"stack smashing detected"),另一个是__libc_argv[0],这其实是主函数里的一个参数,保存了我们的程序名
那么我们的方法就是,将__libc_argv[0]的地址替换为flag所在的地址,这样在进行检测报错后就可以直接将flag给打印出来(一般而言都是有open函数打开了一个flag文件然后读入)
使用方式
在动态调试中,往gets/read函数处(注意函数里边的参数是什么)下断点后进行栈溢出,退出输入函数后使用stack计算程序名读入的地址与rsp指向的地址之间的偏移量,将发送等同于偏移量大小的垃圾数据后进行地址跳转即可
形式如
1 | payload = offset * b'a' + p64(flag_addr) |
局限性
在glibc-2.23及之前的版本可用,但在glibc-2.27及之后的版本,因为stack_chk_fail或fortify_fail源代码的中一些参数的变化,导致了stack smash这样的方法无法通过检测,再也行不通
有且仅有在flag文件内置于程序内可用
多进程爆破(多用于爆破Canary)
原理
fork函数会在父进程中创建子进程并完全复制父进程的地址空间的内容,如果成功,fork函数会在子程序返回0,若出错则会返回负值。因为完全复制的特性,所以子进程和父进程的Canary是一样的,如果在循环中调用了fork函数,可以根据函数特性爆破Canary的值
利用方式
Canary默认的最低字节是’\x00’,因此我们只用爆破前七个字节就够了。然后构造payload
基础形式
1 | canary = '\x00' #默认最低字节已经是\x00 |
像p.recvuntil和string_in_father其实是可变的,甚至可以说是不一定有的,要根据具体情况具体分析,这只是个大致模板,真要做还得慢慢分析
局限性
爆破很慢,因为循环次数多,而且每次都要判断,导致爆破出canary的过程极其漫长,有这时间都可以去做别的题目了
条件苛刻,首先得有fork函数,其次还要循环调用,缺一不可
stack pivot(栈迁移)
原理
构造一条恶意的ROP链,将rbp,rsp迁移到这含有这条恶意的ROP链的栈(通常是bss区上,且有可执行权限)上,控制执行流
这其中很重要的就是leave指令的应用
1 | leave -> mov rsp,rbp |
还有ret,ret返回的地址是栈顶数据,而栈顶由rsp决定,我们只要控制了rsp,我们也就控制了ret返回的栈顶数据
利用方式
在read/gets函数溢出后,如果有canary就接收canary,而后接收栈地址,然后构造第一次的payload
以puts函数泄露libc_base为例(攻击的基础样式)
1 | elf = ELF('该程序的绝对路径/相对路径') |
通过泄露出的libc_base可以通过对应版本的libc.so文件调用任意函数,将system函数和bin/sh迁移到stack上,再将rsp迁移到stack上即可。但是在第二次进入main函数的时候,栈会被抬高,因此我们需要计算抬高的栈距离我们第一次泄露的栈的距离,在read/gets函数里能看到buf的地址,从而算出offset的值,就可以构造第二次的payload的了
1 | stack = stack - offset - 8 #第二次时的栈的地址 |
优点
所需字节数较少,适用于read读入字节不够的情况,当然,前提还是要能进行栈溢出
条件
仍需要栈溢出
存在可控制内容的内存,并且需要能够泄露出基地址
SROP(早期应该不怎么会见得到这玩意)
原理
这个玩意我觉得我解释不清,直接甩佬的链接就好了
利用方式
在Linux i386下调用sigreturn的代码存放在vdso中(暂不研究,主讲x86_64)
在Linux x86_64下,系统调用号为15的syscall为sigreturn,题目里如果在汇编代码里有设置rax为15的话(mov rax,15),很大可能就是要利用SROP进行攻击了
常见模板
1 | frame = SigreturnFrame() #pwntools集成了这种攻击方式 |
优点
可以直接getshell
可以orw
可以执行mprotect进而利用shellcode来orw
局限性
需要有足够大的空间来塞下整个sigreturn frame
需要有可写入的空间,如bss,data,栈等等