|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
0x0 介绍1 b4 ], [" ~4 V
- Y% q! |; _( ]本文记录软件安全课程一项实验内容,为”分析一款编译器的安全特性”,偷懒选了Linux下的gcc,网上有很多相关资料,这里做一实验总结,主要是测试该特性在当前版本Linux平台下是否工作,顺便比较和Windows平台的异同.
N9 E( p( ~* N& ?5 S! R
8 i, d- ~4 N- I另:有更多关于Windows平台下的安全保护机制,但由于windows平台编译器众多(特别是vs开头的),十分依赖编译器和操作系统的配合(虽然在linux平台的实验表明这种程度的安全措施只能依靠编译器和OS的配合,甚至于CPU提供相关的指令集)
, {: A' w& U! O
" j3 s4 [. q7 P9 R本文分析和实验内容如下:
) ]! @7 J( D& @6 k$ q/ l. b6 H$ r; K
+ |4 Q2 p. ?& D, `! k3 Ba) 分析gcc编译器对安全特性的支持。2 n+ M9 V0 q; T x2 S& t
7 \# L7 g' I# D" Z' |
b) 写程序观察生成可执行代码的差异。
- T3 h2 Q5 O* F2 p8 _1 w8 P% m
, L! D0 b8 N7 J& X" dc) 分析这些安全特性和程序编写者、编译器、操作系统的关联。
3 ?& s( O/ [1 \& O6 o' f1 X
9 ^/ z8 K! P+ p. H& B实验环境: ubuntu 18.04 + gcc 7.3
; v; r9 c/ O* g6 r3 Q' k F3 q0 y2 L8 I$ {
经学习研究,Linux操作系统中gcc相关的安全保护机制有:栈Canaries保护、PIE机制、NX、fortity、relro机制。
, L8 @4 w" ]3 k2 i/ e( J/ U% r# J1 ^: ^# C0 _' U
0x1 Canaries& u8 L7 J+ C4 M% X1 _/ U2 g
; z' J1 d8 B" |* ]
在windows操作系统中,这一机制被称为gs机制。实现方式是类似的,即在函数的返回地址前加一个cookie检查,在linux中被称为Canaries。gcc在4.2版本中增加了-fstack-protector来使用这种保护方式,若要禁用栈保护,则应使用-fno-stack-protector。对栈空间的保护在gcc中是默认开启的。. X6 c( l3 b7 l! t1 G/ o
+ x- Y" Q4 l, q9 WCanaries的产生方式是随机的,即从fs寄存器的0x28偏移处获取,而fs寄存器是被glibc定义存放tls信息的。如图是glibc定义的fs寄存器的数据结构:# p* K; X3 J( c' G
) \' M5 x5 c6 V
/ z; j; I T3 h' Z: Z/ H
! P" f' D' f* c2 K
图1 fs寄存器的数据结构& ^8 ?5 Y+ {; c! [ [# A
6 Q2 i( e3 \8 G: e& h: f e
可以发现,0x28处存放的即是stark_guard,canaries值。这个值是进程载入时初始化的,生成随机数的代码如下:
& n* C( B- ]' Z& h/ G- E9 K K! j, n- w k; I; X
1 _! Z! i f8 H' \. ~. o
. t X: C; E7 ~8 u) w; K! L图2 canaries的值初始化方式* k/ ~# }1 I1 b0 d' q9 Z
% \$ a& F' q0 Y8 Z2 j
可以看到,_dl_random是一个随机数,它由_dl_sysdep_start函数从内核获取的。_dl_setup_stack_chk_guard函数负责生成canary值,THREAD_SET_STACK_GUARD宏将canary设置到%fs:0x28位置。# f& q7 V' {+ b5 i' y8 @7 Y0 C
# K, h2 ^/ t: g; [在实验中,我们编写了代码,使用调试程序查看其栈结构和机器码。下图是开启了栈保护的栈结构和机器码:
8 ]9 o* m1 T8 A: ~ R3 S* O& x1 W' {
& \, H4 q m. g. g8 m, c4 u
+ {; o! s, A5 u+ v6 J
图3 栈结构
4 l: y$ N1 w9 S$ t- C6 a; x; k: A9 Z4 u
* `% @4 j# n; w3 X; o3 }* I3 ]! |! f- Y& G r' [9 F' s
图4 开启栈保护函数的执行代码0 P* I: c a8 i4 Y9 ]* ?4 p
2 s; u* b. ?" P0 }6 I) ^' h
可以看到,在初始化函数时。除了push rbp之外,在申请栈空间后又将fs[0x28]压入了栈中,即rbp-0x8。在函数结束前,又检查该处数值是否和fs[0x28]的大小相同,以此保证函数栈未被溢出。该检查如下图所示:1 s9 S4 D! ^+ X. Y7 J
: ]$ ^9 w2 b& l* w2 {- k/ b8 {! F, v- O1 {; O
2 p8 ?7 T2 p2 | |
|