GCC栈溢出保护
逆向过elf程序都知道,GCC的canary,x86_64下从fs:0x28偏移处获取,32位下从gs:0x14偏移处获取。但知道canary如何产生,为什么在这里取的人比较少。
下面以x86_64平台为例,通过glibc源码分析一下。
看第一个问题:为什么从%fs:0x28处取。%fs寄存器被glibc定义为存放tls信息,查看tls结构:
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,0x28偏移 */
uintptr_t pointer_guard;
……
} tcbhead_t;
可以看到%fs:0x28实际取的是当前线程控制块的stack_guard变量,这个变量在线程创建时已经固定。下面看第二个问题,stack_guard如何赋值的。
Linux加载器完成elf加载后,会将入口设置为_start,并在栈上为_start提供入参。_start的代码在sysdeps/x86_64/start.S文件中。
_start从栈上取参数,然后调用__libc_start_main()函数,这个函数也是在main()函数之前执行:
58 ENTRY (_start)
59 /* Clearing frame pointer is insufficient, use CFI. */
60 cfi_undefined (rip)
61 /* Clear the frame pointer. The ABI suggests this be done, to mark
62 the outermost frame obviously. */
63 xorl %ebp, %ebp
64
65 /* Extract the arguments as encoded on the stack and set up
66 the arguments for __libc_start_main (int (*main) (int, char **, char **),
67 int argc, char *argv,
68 void (*init) (void), void (*fini) (void),
69 void (*rtld_fini) (void), void *stack_end).
70 The arguments are passed via registers and on the stack:
71 main: %rdi
72 argc: %rsi
73 argv: %rdx
74 init: %rcx
75 fini: %r8
76 rtld_fini: %r9
77 stack_end: stack. */
__libc_start_main()首先以_dl_random这个全局变量为入参,生成canary,然后通过THREAD_SET_STACK_GUARD宏将canary赋值给tls的stack_guard变量。
198 /* Set up the stack checker's canary. */
199 uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
200 # ifdef THREAD_SET_STACK_GUARD
201 THREAD_SET_STACK_GUARD (stack_chk_guard);
202 # else
203 __stack_chk_guard = stack_chk_guard;
204 # endif
看下_dl_random哪里来的,在glibc源码中,有2处但实现大致相同:
126 /* Random data provided by the kernel. */
127 void *_dl_random;
288 case AT_RANDOM:
289 _dl_random = (void *) av->a_un.a_val;
注意av这个变量,逆向跟踪发现其最终来自__libc_start_main()的argv参数。也就是_dl_random是由加载器提供的。而AT_RANDOM表示内核提供了接口,支持canary的随机数生成。可以使用下面命令查看:
kiiim@ubuntu :~/glibc-2.22$ LD_SHOW_AUXV=1 /bin/true grep AT_RANDOM
AT_RANDOM: 0x7fffdaf776e9
看下实际代码中,这个内核接口指的是什么,canary值又如何取。
rand_size = CONFIG_SECURITY_AUXV_RANDOM_SIZE * sizeof(unsigned long);
u_rand_bytes = NULL;
if (rand_size) {
unsigned char k_rand_bytes[CONFIG_SECURITY_AUXV_RANDOM_SIZE * sizeof(unsigned long)];
get_random_bytes(k_rand_bytes, rand_size);
u_rand_bytes = (elf_addr_t __user *)STACK_ALLOC(p, rand_size);
if (__copy_to_user(u_rand_bytes, k_rand_bytes, rand_size))
return -EFAULT;
}
发现在内核中通过get_random_bytes()接口产生,并copy_to_user()到用户空间。而内核中的安全随机数,也推荐使用get_random_bytes()生成。下面看下实现:
http://lxr.free-electrons.com/source/drivers/char/random.c
void get_random_bytes(void *buf, int nbytes)
{
#if DEBUG_RANDOM_BOOT > 0
if (unlikely(nonblocking_pool.initialized == 0))
printk(KERN_NOTICE "random: %pF get_random_bytes called "
"with %d bits of entropy available\n",
(void *) _RET_IP_,
nonblocking_pool.entropy_total);
#endif
trace_get_random_bytes(nbytes, _RET_IP_);
extract_entropy(&nonblocking_pool, buf, nbytes, 0, 0);
}
EXPORT_SYMBOL(get_random_bytes);
而看一下read /dev/urandom的内核实现:
static ssize_t
urandom_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
int ret;
if (unlikely(nonblocking_pool.initialized == 0))
printk_once(KERN_NOTICE "random: %s urandom read "
"with %d bits of entropy available\n",
current->comm, nonblocking_pool.entropy_total);
nbytes = min_t(size_t, nbytes, INT_MAX >> (ENTROPY_SHIFT + 3));
ret = extract_entropy_user(&nonblocking_pool, buf, nbytes);
trace_urandom_read(8 * nbytes, ENTROPY_BITS(&nonblocking_pool),
ENTROPY_BITS(&input_pool));
return ret;
}
可以看到get_random_bytes()与read /dev/urandom实现是相同的,都是通过extract_entropy*从"entropy pool"中取的随机数。只不过一个在内核空间用,将结果返回到一块内核buffer,一个在用户空间使用,将结果返回到一块用户buffer。
下面再来看下,程序中如何使用这个canary。分析a()函数:
void a() {
int a = 3;
char str[16];
}
x86_64平台汇编如下:
(gdb) disass a
Dump of assembler code for function a:
0x000000000040055d <+0>: push %rbp
0x000000000040055e <+1>: mov %rsp,%rbp
0x0000000000400561 <+4>: sub $0x30,%rsp
0x0000000000400565 <+8>: mov %fs:0x28,%rax
0x000000000040056e <+17>: mov %rax,-0x8(%rbp)
0x0000000000400572 <+21>: xor %eax,%eax
0x0000000000400574 <+23>: movl $0x3,-0x24(%rbp) ;变量重排,a的地址低于str地址
0x000000000040057b <+30>: mov -0x8(%rbp),%rax
0x000000000040057f <+34>: xor %fs:0x28,%rax
0x0000000000400588 <+43>: je 0x40058f <a+50>
0x000000000040058a <+45>: callq 0x400440 <__stack_chk_fail@plt>
0x000000000040058f <+50>: leaveq
0x0000000000400590 <+51>: retq
End of assembler dump.
可以看到,GCC的栈保护还实现了变量重排。但与微软实现不同,GCC取出canary后并没有与ebp异或,直接放到栈上。也就是说,同一线程中,所有的canary值都是相同的,通过调试验证也中如此:
Breakpoint 1, 0x000000000040056e in a () at 1.c:4
4 void a() {
(gdb) p/x $rax
$1 = 0xc609d364696f6000
(gdb) c
Continuing.
Breakpoint 2, 0x00000000004005a2 in b () at 1.c:9
9 void b() {
(gdb) p/x $rax
$2 = 0xc609d364696f6000
(gdb) c
Continuing.
Breakpoint 1, 0x000000000040056e in a () at 1.c:4
4 void a() {
(gdb) p/x $rax
$3 = 0xc609d364696f6000
(gdb)
GCC栈溢出保护的更多相关文章
- gcc栈溢出保护机制:stack-protector
关键词:stack-protector.stack-protector-strong.stack-protector-all等等. 1. gcc栈保护机制stack-protector简介 gcc提供 ...
- 逆向工程学习第四天--Windows栈溢出保护机制(GS)原理及绕过测试
GS简介: Windows的缓冲区安全监测机制(GS)可以有效的阻止经典的BOF攻击,因为GS会在函数调用前往函数栈帧内压入一个随机数(canary),然后等函数返回前,会对canary进行核查,判断 ...
- GCC 中的编译器堆栈保护技术
GCC 中的编译器堆栈保护技术 前几天看到的觉得不错得博客于是转发了,但这里我补充一下一些点. GCC通过栈保护选项-fstack-protector-all编译时额外添加两个符号,__stack_c ...
- [转] GCC 中的编译器堆栈保护技术
以堆栈溢出为代表的缓冲区溢出已成为最为普遍的安全漏洞.由此引发的安全问题比比皆是.早在 1988 年,美国康奈尔大学的计算机科学系研究生莫里斯 (Morris) 利用 UNIX fingered 程序 ...
- linux程序的常用保护机制
操作系统提供了许多安全机制来尝试降低或阻止缓冲区溢出攻击带来的安全风险,包括DEP.ASLR等.在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了DEP(Linux下对应NX).ASLR(Lin ...
- Linux下基本栈溢出攻击【转】
转自:http://blog.csdn.net/wangxiaolong_china/article/details/6844415 版权声明:本文为博主原创文章,未经博主允许不得转载. 目录(?)[ ...
- Linux中的保护机制
Linux中的保护机制 在编写漏洞利用代码的时候,需要特别注意目标进程是否开启了NX.PIE等机制,例如存在NX的话就不能直接执行栈上的数据,存在PIE 的话各个系统调用的地址就是随机化的. 一:ca ...
- Linux保护机制和绕过方式
Linux保护机制和绕过方式 CANNARY(栈保护) 栈溢出保护是一种缓冲区溢出攻击缓解手段,当函数存在缓冲区溢出攻击漏洞时,攻击者可以覆盖栈上的返回地址来让shellcode能够得到执行.用C ...
- C++栈溢出
先看一段代码 #include<iostream> using namespace std; #define n 510 void sum(int a,int b) { cout<& ...
随机推荐
- 如何使a标签打开新页面并阻止刷新当前页面
错误: HTML中,使用href属性时,当前页面和新页面均跳转到URL指定的页面,即当前页面也刷新: <li id='goToBack'><a href='**.action' ta ...
- python assert使用说明
python assert断言的作用 python assert断言是声明其布尔值必须为真的判定,如果发生异常就说明表达示为假. assert断言语句的语法格式 判断a与1.b是否一致,msg类似备注 ...
- CentOS下安装mysql5.7和mysql8.x
5.7和8.15版本亲测.centos版本为:CentOS-7-x86_64-Minimal-1810. 1.下载mysql57-community-release-el7-9.noarch.rpm. ...
- MySql多个count查询
现有一个student表结构数据如下: id hight sex age 1 160 0 16 2 170 1 16 3 180 1 17 4 160 1 16 5 170 ...
- object遍历删除空值
export function deleteObjEmpty(search = {}) { for (let i in search) { search[i] == undef ...
- TCP_NODELAY算法使用事项
当有一个TCP数据段不足MSS,比如要发送700Byte数据,MSS为1460Byte的情况.nagle算法会延迟这个数据段的发送,等待,直到有足够的数据填充成一个完整数据段.也许有人会问,这有什么影 ...
- unity中鼠标按下加速漫游,鼠标抬起减速漫游。
private bool IsMouseUpOrDown=true; //一开始默认是鼠标抬起状态 if (Input.GetMouseButtonDown(1)) //鼠标按下的瞬间状态 { IsM ...
- ural1517
题解: 后缀数组 求一下最长公共字串 代码: #include<cstdio> #include<cmath> #include<algorithm> #inclu ...
- [POJ2985]The k-th Largest Group
Problem 刚开始,每个数一个块. 有两个操作:0 x y 合并x,y所在的块 1 x 查询第x大的块 Solution 用并查集合并时,把原来的大小删去,加上两个块的大小和. Notice 非旋 ...
- day27 网络通信协议 tcp/udp区别
今日主要内容: 一.网络通信协议 二.tcp udp协议下的socket 一.网络通信协议 1.1互联网的本质就是一系列的网络协议 本机IP地址('127.0.0.1',xxxx) 互联网连接的电脑互 ...