|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
0x0 介绍/ @) h# M' u; ^9 i$ b
1 n& W6 W9 j {) S# R/ V本文记录软件安全课程一项实验内容,为”分析一款编译器的安全特性”,偷懒选了Linux下的gcc,网上有很多相关资料,这里做一实验总结,主要是测试该特性在当前版本Linux平台下是否工作,顺便比较和Windows平台的异同.# c3 A+ P: `% a' @5 X) D" Q
* H }) _; o+ F' u+ O
另:有更多关于Windows平台下的安全保护机制,但由于windows平台编译器众多(特别是vs开头的),十分依赖编译器和操作系统的配合(虽然在linux平台的实验表明这种程度的安全措施只能依靠编译器和OS的配合,甚至于CPU提供相关的指令集)
1 c T# m& b. |& ]) S! T) }
% p. R. L. s1 y8 \4 S! B p本文分析和实验内容如下:
4 M5 B' I$ n$ t) D$ L1 D
w" I- d8 E: z9 Fa) 分析gcc编译器对安全特性的支持。% b0 D$ ]& D9 b; Y) v3 r) B
4 i1 E" V" g* Q6 j
b) 写程序观察生成可执行代码的差异。
* B* r) A( P( N
* U' G! T5 Y' ?c) 分析这些安全特性和程序编写者、编译器、操作系统的关联。9 N/ _8 Q v |. {( A
O& M0 K# w! R/ H5 s$ U. p) |实验环境: ubuntu 18.04 + gcc 7.3
. H6 {/ i( d2 r: p7 l& }* S
' o! t9 K1 ~* M9 v/ i经学习研究,Linux操作系统中gcc相关的安全保护机制有:栈Canaries保护、PIE机制、NX、fortity、relro机制。
: n. s- Y: `* y7 [, O+ R. Y! l: q7 D5 s# P- A) B1 N* l
0x1 Canaries3 T" M. H |9 P4 Z- f$ g
# ]1 A+ _# }' s! R1 B! z Q6 W在windows操作系统中,这一机制被称为gs机制。实现方式是类似的,即在函数的返回地址前加一个cookie检查,在linux中被称为Canaries。gcc在4.2版本中增加了-fstack-protector来使用这种保护方式,若要禁用栈保护,则应使用-fno-stack-protector。对栈空间的保护在gcc中是默认开启的。+ E6 p8 P0 w v/ S/ U
1 M; R9 Q7 c7 ACanaries的产生方式是随机的,即从fs寄存器的0x28偏移处获取,而fs寄存器是被glibc定义存放tls信息的。如图是glibc定义的fs寄存器的数据结构:
, ^, m/ H0 d2 g" {
1 G$ i: Y. j3 T2 h/ S
! \7 ~2 l# h4 q. y3 {( S6 D! W8 B0 Q8 `
图1 fs寄存器的数据结构+ q! {/ X0 X4 N5 Z" W# H
- C5 G) }7 Q% O3 X! k2 t( `; C
可以发现,0x28处存放的即是stark_guard,canaries值。这个值是进程载入时初始化的,生成随机数的代码如下:# R1 L$ l4 ?& r" q, T
$ f7 n1 d8 t: [% @8 q
+ v) b( j" `8 z. J5 K- N% ]' ?4 n
图2 canaries的值初始化方式
" n6 z6 J4 n" s0 x* w+ V' l9 K, m) x' h7 K
可以看到,_dl_random是一个随机数,它由_dl_sysdep_start函数从内核获取的。_dl_setup_stack_chk_guard函数负责生成canary值,THREAD_SET_STACK_GUARD宏将canary设置到%fs:0x28位置。$ E# t/ j2 b5 a) m! p8 D c
- P9 {, P$ m6 b: y1 r1 H' t+ j# {在实验中,我们编写了代码,使用调试程序查看其栈结构和机器码。下图是开启了栈保护的栈结构和机器码:
8 O! u+ m$ Z# P) G1 r, ]8 c, z! F: V0 t/ v
6 b4 i+ P& M$ L6 r; e8 Z: s7 g. r ?" t8 a
图3 栈结构
6 _5 w- W& E! s4 K, S" H
: v& n8 ]& d, j+ Q& Y: B
0 X$ V0 D/ @# X: I6 S1 C( k: K9 d
图4 开启栈保护函数的执行代码$ Q0 `' _' \6 q1 @
. t" f! E, q! O2 s$ z可以看到,在初始化函数时。除了push rbp之外,在申请栈空间后又将fs[0x28]压入了栈中,即rbp-0x8。在函数结束前,又检查该处数值是否和fs[0x28]的大小相同,以此保证函数栈未被溢出。该检查如下图所示:
3 \- Q5 X. C* W. L
3 y& |$ H* \) r; C1 ~
; \3 h" j: \1 J7 ?% s0 d; \
2 g: u9 X5 V8 e |
|