|
近日,我在写内核模块的时候犯了一个低级错误:
: X% M7 b5 }0 G: E
( D9 @8 h! W( `' x2 |9 `
在内核看来,用户态提供的虚拟地址是不可信的,所以在一旦在内核态访问用户态内存发生缺页中断,处理起来是非常棘手的。 7 N( e; Q3 |+ h" U& w6 R# [9 B
Linux内核的做法是提供了一张 异常处理表 ,使用专有的函数来访问用户态内存。类似 try-catch块一般。具体详情可参见copy_to_user/copy_from_user的实现以及内核文档Documentation/x86/exception-tables.txt的描述。
( B$ [( b2 E( S
本来简单看下这个异常处理表能怎么玩。
8 T; e$ K0 k8 h! c2 }% Y+ R
首先,我们可以写一片代码,将内核的异常处理表dump下来:
% Y$ f% v8 A- [* O4 J* L- // show_extable.c4 ]- Q0 o4 K, } m/ N; {
- #include <linux/module.h>0 X( |; @% m) h, V
- #include <linux/kallsyms.h>/ Z$ k8 T5 L+ z* {% K
- ) s O; n R: w1 ?1 F e; Z6 [ V3 {
- int (*_lookup_symbol_name)(unsigned long, char *);( c: x# z$ }& n. ?, X* w$ Z
- unsigned long (*_get_symbol_pos)(unsigned long, void *, void *);7 W, A2 S2 t6 A+ ^6 s
- unsigned long start_ex, end_ex;- g2 O' h0 z( W9 p7 k
! P& K9 K, L- x" V# A- int init_module(void)4 z; Y" c; W6 _; C
- {
3 i$ G/ Q# m, F+ z. U- E' c+ u - unsigned long i;
+ q& j) M: u7 g$ N6 j - unsigned long orig, fixup, originsn, fixinsn, offset, size;
: B# `/ I! p7 r6 m, a5 P - char name[128], fixname[128];
1 h8 L) W) q8 \! j% o' O - 1 t0 I& Q! V: j; E/ M+ I& K
- _lookup_symbol_name = (void *)kallsyms_lookup_name("lookup_symbol_name");
# w3 e" p, c- C( L$ O. I! R - _get_symbol_pos = (void *)kallsyms_lookup_name("get_symbol_pos");
6 I$ i" v1 H: X5 F - start_ex = (unsigned long)kallsyms_lookup_name("__start___ex_table");' }) v" y4 Q2 z8 ~
- end_ex = (unsigned long)kallsyms_lookup_name("__stop___ex_table");
: l1 l, b* {- C. q - # M, }6 ~) k3 S( e9 g% ~' _
- // 按照exception_table_entry的sizeof从start遍历到end。
4 t+ u8 x$ d- k* n0 \1 ` - for(i = start_ex; i < end_ex; i += 2*sizeof(unsigned long)) {
, x, X, G$ @- y$ z1 @: U9 u - orig = i; // 取出exception_table_entry的insn字段地址。
5 Z# X" f. Y ]$ B - fixup = i + sizeof(unsigned int); // 取出fixup字段地址。# j7 q) I. s Z2 S% u, h2 l- s/ R3 |
- % @7 q: W2 B" k& |3 c
- originsn = orig + *(unsigned int *)orig; // 根据相对偏移字段求出绝对地址
8 V0 V8 u/ L& U - originsn |= 0xffffffff00000000;2 P' ^ H) N% }- n) Z x
- fixinsn = fixup + *(unsigned int *)fixup;
) ~1 ]1 c8 N' D$ w! _ - fixinsn |= 0xffffffff00000000;
6 D( S: M0 J! [) s+ {0 R - _get_symbol_pos(originsn, &size, &offset);/ Z! Z4 U3 p- U5 o: C- T
- _lookup_symbol_name(originsn, name);/ @1 m8 ^. w4 {% ?. V
- _lookup_symbol_name(fixinsn, fixname);, f5 ?) G5 a9 z& ~
- printk("[%lx]%s+0x%lx/0x%lx [%lx]%s\n",
( p; s7 i9 U8 R' B - originsn,
) x' u% s( ]$ | - name,9 g) e; e r. R" Y+ {
- offset,7 T* f9 q& t2 ^) a' T2 ^4 a
- size,
" O7 r/ ^* l% Q1 y1 i! l' ~, J/ g1 B$ ` - fixinsn,& L! |. E: |: q @0 N6 U
- fixname);- M7 L- I5 ]8 H" Y H
- } m l' N' a6 E5 }" y# d5 v
! h, O4 z9 R1 }' c3 J. m( ?4 t- return -1;* Z" E6 @: i: E+ p- j. C5 H
- }
, Y& |7 c$ E* y: `" L" T& M" n - MODULE_LICENSE("GPL");
! @' M* q( p1 j5 B' X$ U; R
复制代码
+ A0 x$ \* u: S# ?; |# t
我们看下输出:
6 o& D$ J* V& d' |' }. Z6 r- # ___sys_recvmsg+0x253位置发生异常,跳转到ffffffff81649396处理异常。, @+ Q% L9 y8 a" P% a, N' E
- [ 7655.267616] [ffffffff8150d7a3]___sys_recvmsg+0x253/0x2b0 [ffffffff81649396]bad_to_user' A+ t) Z0 ^) B
- ...
% _0 f1 u T7 I; r: N3 c - # create_elf_tables+0x3cf位置处如果发生异常,跳转到ffffffff81648a07地址执行异常处理。; b5 q/ q8 U+ Y# j/ u5 E, e
- [ 7655.267727] [ffffffff8163250e]create_elf_tables+0x3cf/0x509 [ffffffff81648a1b]bad_gs
6 X9 _- `: K, R& _6 G- }6 l
复制代码 0 g6 N) L' c+ Y% |
" m; K* s1 B5 o' |4 p! M* ~
一般而言,类似bad_to_user,bad_from_user之类的异常处理函数都是直接返回用户一个错误码,比如Bad address之类,并不是直接用户程序直接段错误,这一点和用户态访问非法地址直接发送SIGSEGV有所不同。比如: - <font color="#001000"><font style="background-color:rgb(255, 255, 255)"><font face="Tahoma">
! e! V8 A4 N# T0 t( m - #include <fcntl.h>" u* u' W* B2 J. {
- int main(int argc, char **argv)
+ T$ ]8 d+ W( ~' L: o' o - {
4 @0 _/ b) ]7 W) S8 V4 _ - int fd;+ ~7 R0 I6 B r5 D: u
- int ret;& o- w" J! g, e$ e/ c6 i* A/ L
- char *buf = (char *)0x56; // 显然是一个非法地址。
' g) N8 |- J7 u) R/ L0 W
* `) Q+ \, q+ c8 D/ i6 j- i3 `" V- fd = open("/proc/sys/net/nf_conntrack_max", O_RDWR | O_CREAT, S_IRWXU);
6 t4 ]3 `$ ]3 s" R( D - perror("open");
4 u3 F5 Q8 ~5 H+ R' @9 d - ret = read(fd, buf, 100);
5 i1 O4 X9 T' H6 Q& a/ R' v$ o - perror("read");+ s" J/ j1 D- U* y9 n$ u6 u$ ?
- }
: b% M* V7 J; v' R) s - </font></font></font>5 [6 B% S$ v$ H, v3 J
复制代码
5 ]" y5 o; ~7 f1 c' y5 O2 Z
执行之:
+ h6 j% Q) x( @ g2 i- [root@localhost test]# ./a.out5 U6 S, O( h8 `. t) s6 Z. u
- open: Success' [6 l. ~0 p' f
- read: Bad address # 没有段错误,只是一个普通错误。
$ ~( j; `! {5 \# d+ y
复制代码! H7 r2 X6 u* d# q
我们能不能将其行为修改成和用户态访问非法地址一致呢?简单,替换掉bad_to_user即可,代码如下: - 2 e9 d, I/ {4 Z
- // fix_ex.c" |3 G6 g1 Q: q! w' a
- #include <linux/module.h>: M$ D# U- D' w, r) d8 W/ l
- #include <linux/sched.h>& Q: w6 t0 F0 N
- #include <linux/kallsyms.h>; {$ O7 R6 v6 P
- p! [( T+ V& V3 ?
- int (*_lookup_symbol_name)(unsigned long, char *);
9 F% s" L5 x$ _* D - unsigned long (*_get_symbol_pos)(unsigned long, void *, void *);
) v3 U2 B4 G( Q o: G4 ]! t - unsigned long start_ex, end_ex;
2 {6 C, V: }( I( q( F) }- `* }/ G - void *_bad_from_user, *_bad_to_user;
0 J: S4 P, a9 t* `% ? - 8 Y$ G6 i$ }+ U- z! G
- void kill_user_from(void)1 k) E3 R+ G6 Q; g6 Z& n
- {
" A; t C9 Q" B8 q1 s - printk("经理!rush tighten beat electric discourse!\n");
2 j( y" \5 A3 m/ h; ^ - force_sig(SIGSEGV, current);) X4 R9 v+ d* c9 r7 i5 n
- }$ C9 ^' Z' T, \8 \* N5 p8 \+ @- N3 q0 R
8 D' s- @# j: v% _0 u' y1 v3 W- void kill_user_to(void)
" M( b* P8 j8 C1 W+ B, Y- S - {8 R" W* g* j8 Z0 x* T. J
- printk("经理!rush tighten beat electric discourse! SB 皮鞋\n");) E, i- o) k$ U
- force_sig(SIGSEGV, current);
) [& c* D1 n! N) I( v& k, B: O - }
; G( V1 o, W$ p* l9 P5 z0 A - ( Q/ {0 }# c4 I# L @, f8 L
- unsigned int old, new;. `, t B, D& P/ T9 n
( o. y) `7 Z) m3 z- int (*_lookup_symbol_name)(unsigned long, char *);
- V5 R2 P3 W- R# A, m e7 b - unsigned long (*_get_symbol_pos)(unsigned long, void *, void *);
5 ^6 L. ]/ Q; M9 L. R9 U
3 n, f3 R2 M/ r: O' h J- int hook_fixup(void *origfunc1, void *origfunc2, void *newfunc1, void *newfunc2)
R: |: W% \$ L5 y1 S - {
I' C! F' g) W( Z - unsigned long i;% S8 l( ^: [, M" ~- r1 M0 L# D: H
- unsigned long fixup, fixinsn;# k- F# U" {2 T) _" @1 ~
- char fixname[128];8 d7 x& ~( q3 z% ^$ I+ u" \9 ]
+ `' ~8 h& U7 j( Z5 k6 Y* D- ; J* v% J V; q6 Y
- for(i = start_ex; i < end_ex; i += 2*sizeof(unsigned long)) {
7 z% I2 j5 D+ \/ g6 |4 R+ e - fixup = i + sizeof(unsigned int);
7 p. H' X- V* o: {, |7 d" G s y5 x - fixinsn = fixup + *(unsigned int *)fixup;
6 F p' S7 q* A# y9 h& ? o - fixinsn |= 0xffffffff00000000;
& o3 ?' a8 l7 U - _lookup_symbol_name(fixinsn, fixname);3 P5 t |' a1 s& ~
- if (!strcmp(fixname, origfunc1) ||$ B7 }8 t( D" l$ R1 R0 f
- !strcmp(fixname, origfunc2)) {5 u4 p: E2 p/ r" f- _" k I
- unsigned long new;
2 R" w9 O! j" u. R - unsigned int newfix;* G; E, ^4 P) |+ S. u
/ L4 I: f8 Y& `1 P- ]- if (!strcmp(fixname, origfunc1)) {: U8 J; w& X% o% f7 h0 U5 u( |( W
- new = (unsigned long)newfunc1;1 _* I9 z6 m$ d$ a
- } else {
7 V- u* \. o% s' |6 z - new = (unsigned long)newfunc2;3 R b) s O, C1 _. q4 |0 C
- }. t( ?$ d# m+ z0 }- W
- new -= fixup;
4 d6 F: }4 `% t2 }0 y+ z M - newfix = (unsigned int)new;5 ]# [1 t+ N4 b& H
- *(unsigned int *)fixup = newfix;# M" F! \( Y* \" m1 n4 G% q. Y" @+ `
- }# M" u7 q+ L! n& @2 C9 [* D6 g" C! l
- }8 X) h" i( v6 q' j; E S$ Z
- 5 Q. z1 U- l* K3 @" W5 f" U+ ~
- return 0;
: ], A+ g5 Y( ^' _6 d% _3 G* @ t+ { - }
4 L A3 @6 O* U* U7 t: B2 h - 1 T: m0 N! O( h4 _6 H, y+ i! ^: \4 i
- int init_module(void)
- {. R' f Y# C# N) J* X - {. Z8 C! Z. n- O% ? Z* r
- _lookup_symbol_name = (void *)kallsyms_lookup_name("lookup_symbol_name");
5 y, i/ [( q0 X2 R. J F - _get_symbol_pos = (void *)kallsyms_lookup_name("get_symbol_pos");$ c4 U& B2 V3 |; a
- _bad_from_user = (void *)kallsyms_lookup_name("bad_from_user");
' ]( w; z. \7 v' z: {- E5 j Q - _bad_to_user = (void *)kallsyms_lookup_name("bad_to_user");
, V9 V7 x: }5 y - start_ex = (unsigned long)kallsyms_lookup_name("__start___ex_table");
. X% x6 L! p) A) P9 e, m9 v0 R4 Z - end_ex = (unsigned long)kallsyms_lookup_name("__stop___ex_table");& O) I9 g2 d. y; S- v* O
- $ H6 S, j" a$ r2 H5 {! V+ x* N
- hook_fixup("bad_from_user", "bad_to_user", kill_user_from, kill_user_to);
; Y4 K* u% L2 a# D5 |1 U - return 0;
1 O# m& F/ f% j+ z7 O) j - }. K ^: P' O1 @$ K1 E" b2 j
- void cleanup_module(void)
" g" ~6 y1 `% D9 f$ a, f - {
+ i% r+ ^& U7 D3 Z( F+ [7 c. i: c - hook_fixup("kill_user_from", "kill_user_to", _bad_from_user, _bad_to_user);! f- d/ {) R+ y5 j
- }2 h$ n% p9 B( z( I1 O; Q5 q& ^( n& n: w
- ; f2 U i# P% k- v4 h
- MODULE_LICENSE("GPL");# L9 Z0 j. _" ?$ Y
复制代码, i Q5 o* X) t0 t0 y: f2 X
编译,加载,重新执行我们的a.out: - ) |8 D. P' W1 {: n( C
- [root@localhost test]# insmod ./fix_ex.ko
3 }% ?0 D7 j1 }. P - [root@localhost test]# ./a.out. [: h4 [! `. x/ \0 s7 _; }
- open: Success
1 x: o8 N; ~; Q2 }% w7 F3 O6 Z: q4 U - 段错误- p) u0 {" N N- {2 \
- [root@localhost test]# dmesg
1 N+ y7 a9 W8 l; F - [ 8686.091738] 经理!rush tighten beat electric discourse! SB 皮鞋6 S+ M- {- r) }8 _* [- }4 C
- [root@localhost test]#
- [( O+ |# s4 H. [& G+ d
复制代码
9 j( Y1 x) j; B. |2 U
' G0 c" `$ I! N8 K% h ^: O
发生了段错误,并且打印出了让经理赶紧打电话的句子。
% s' ^: [' t: G R
其实,我的目的并不是这样的,我真正的意思是,Linux的异常处理链表,又是一个藏污纳垢的好地方,我们可以在上面的hook函数中藏一些代码,比如说inline hook之类的,然后呢?然后静悄悄地等待用户态进程的bug导致异常处理被执行。将代码注入的时间线拉长,从而更难让运维和经理注意到。 9 U8 T) [6 t+ e+ R. c1 E
让代码注入的时间点和模块插入的时间点分开,让事情更加混乱。不过,注意好隐藏模块或者oneshot哦。
( d; |% ~) g$ C
浙江温州皮鞋湿,下雨进水不会胖。
) G# a5 K7 L9 k# J. @ |