Stack-Smash
stack smash是用来绕过canary的一种方式
原理
在程序开启canary保护之后,如果我们在栈溢出时覆盖了canary,程序最后发现canary被修改的话,就会执行__stack_chk_fail函数来打印argv[0]指针指向的字符串,通常argv[0]指向的是程序名。代码如下1
2
3
4
5
6
7
8
9
10
11void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n",
msg, __libc_argv[0] ?: "<unknown>");
}
所以如果我们能利用栈溢出覆盖argv[0]为我们想要输出的字符串的地址,那么在__fortify_fail函数中就会输出我们想要的信息
练习题1
32C3 CTF readme
Jarvis OJ复现
保护
64位,开启canary、NX、FORTIFY保护1
2
3
4
5
6
7
8pwndbg> checksec
[*] '/home/ayoung/Desktop/temp/temp/jarvis-OJ/smash'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled
分析

_IO_gets处存在栈溢出
找flag
发现data段上存着我们要的flag,地址0x600D21
然而while(1)这个循环干的事就是其打印出的overwrite flag,循环里单字节读入我们的输入,然后从0x6002d0开始存储我们的输入。同时检测到回车或达到32个字符时退出循环,然后memset((void *)((signed int)v1 + 0x600D20LL), 0, (unsigned int)(32 - v1));把我们输入的字符后面的数据清零,也就是说这里原本存着的flag都没了
- 在 ELF 内存映射时,bss 段会被映射两次,所以我们可以使用另一处的地址来进行输出,可以使用 gdb 的 find 来进行查找
查找flag另一处存储flag的地址
用gef搜索看到原本存储flag的0x600D21处已经被我们覆盖成了我们输入的A和一堆0,而0x400d21处存储着flag
确定偏移

可以确定我们是从rsp指向的栈顶开始输入的,断点下到执行_IO_gets之前,查看rsp的值为0x7fffffffdbb0
另外查看栈的信息,从下图可以看到0x7fffffffe18a指向程序名,这个地址就是argv[0],也就是我们想修改的地方。同时可以看到0x7fffffffddc8处保存着我们要修改的地址,所以我们希望溢出到这里然后把其内容覆盖,使其指向我们要的flag(直接在gdb中直接p & __libc_argv[0]也可以)
exp
1 | from pwn import* |
练习题2
2018网鼎杯的 pwn1-GUESS
BUU复现
分析


把flag读到栈上,三次循环里每次调用fork函数,fork函数是通过系统调用创建一个与原来进程几乎完全相同的进程。本题来说就是给了我们三次输入造成栈溢出并且崩溃结束的机会
知识点:在 Linux 系统中,glibc 的环境指针 environ(environment pointer) 为程序运行时所需要的环境变量表的起始地址,环境表中的指针指向各环境变量字符串。从以下结果可知环境指针 environ 在栈空间的高地址处。因此,可通过 environ 指针泄露栈地址。
由于程序没有开启PIE,environ变量中存放的栈地址的值和栈上flag的距离是不变的,所以可以通过计算偏移得到指向flag的地址
思路:
- 第一次溢出并泄露puts函数真实地址
- 通过puts地址计算出environ变量的值
- 第二次溢出泄露栈的地址
- 计算偏移
- 泄露flag内容
具体计算各种偏移的过程在此省略了就
exp
1 | from pwn import* |








