Canary绕过总结

1.有循环时,32或者64位程序下都可以逐字节爆破绕过。

2.可通过printf字符串漏洞来泄露(%p.%p.%p….)。

3.通过打印栈上数据的打印函数栈溢出连上canary泄露出来。

4.当程序直接读取flag进入内存时,利用函数__stack_chk_fail,加上足够长度的栈溢出覆盖argv[0]为程序中保存flag的地址。这样当__stack_chk_fail运行时就会打印出argv[0]中地址上对应的内容,也就是flag。有时候需要设置远程环境变量LIBC_FATAL_STDERR_=1,将flag打印在本地。

5.由pthread出来的线程函数中如果有足够长度的栈溢出,可以直接覆盖canary来源tcbhead_t结构体中的canary和栈中的canary为同一数值,这样检查仍旧通过。

(64位中为fs:[28h],32位中为gs:[14h])

▲查找方法:

(1)pwndbg>catch syscall 158

img

(2)查看rsi寄存器,里面存的内容就是tcbhead_t结构体的首地址。

img

(3)之后就可以查看canary的值:

img

但是这个方法好像不怎么顶用了,libc2.23及以下都行,但是libc2.27就会出现无法访问的错误,具体的原因好像是libc升级之后添加了什么东西设置了不可访问:

img

32位下没有arch_prctl这个系统调用了,需要看canary的生成函数调用了什么系统调用,方法类似,下断点之后查出来。

★IDA中远程调试也可以查出来,利用程序开头的fs:28,找到地址,然后减去libc基地址就可以得到偏移。但是需要libc一致,偏移才会一致。而且这个偏移并不是在libc数据段上,只是程序初始化时放在后面的,所以不同的程序不同的libc都会导致偏移不一样,需要具体调试。

▲长度一般为rbp+2000左右,不同的Libc版本都不太一样,需要调试才能知道。原因是通过pthread出来的线程函数栈会被安置到与TLS相差约2000字节的距离处:

img img

这里可以看到,第一个是main函数栈,第二个是在main函数中通过pthread进程创建并且调用的函数栈,两者相差将近0x700000000这么远,完全不是正常的函数调用相差的栈距离。同时在该函数中rbp指向的始终是0000(全是),该函数结束后会先跳转到libc中的libpthread来恢复栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#注释头

▲64位的tcbhead_t结构体:
typedef struct
{
void *tcb; /* Pointer to the TCB. Not necessarily the
thread descriptor used by libpthread. */
dtv_t *dtv;
void *self; /* Pointer to the thread descriptor. */
int multiple_threads;
int gscope_flag;
uintptr_t sysinfo;
uintptr_t stack_guard;//即为canary,fs:28h处
uintptr_t pointer_guard;
...
} tcbhead_t;