linux内核 sp什么意思,不同的内核源码编译出来的ko文件,区别到底是什么?

  • Post author:
  • Post category:linux


之前一直在考虑,不同的内核源码编译出来的ko文件,区别到底是什么?

能不能不编译内核加载内核模块呢?最近逆向分析了linux内核ko模块的结构,事实证明,是可以的。

我在这里给大家分享一些我的心得。

194824146_2_20200707043852880

首先分析一个最简单的hello.ko,Makefile就不写了,因为需要尽可能简单,加一行去除调试信息的objcopy -g hello.ko就好。

hello.c

#include

#include

#include

static int __init hello_init(void){

printk(KERN_EMERG ‘\nhello init.\n’);

return 0;

}

static void __exit hello_exit(void){

printk(KERN_EMERG ‘\nhello exit.\n’);

}

module_init(hello_init);

module_exit(hello_exit);

MODULE_LICENSE(‘GPL’);

可以看到编译后的ko文件只有2.4Kb。

我这里准备好一个基于arm架构板子交叉编译好的linux3.6.33的linux内核,正经的烧进去,后续简称now kernel。

在胡乱修改make menuconfig的模块结构之后,重新在另一个无关的目录编译另外一个linux内核,后续简称fake kernel。

首先我先把基于fake kernel编译的hello.ko拷贝到我的板子上正在使用的now kernel上,然后执行:insmod hello.ko。

结果什么都没有发生,没有报错,没有执行,没有打印???

首先确定了一点,不基于同一套内核源码编译的内核模块是无法直接加载的。那是什么导致了加载失败呢?我们先用二进制编辑器打开刚刚编译的ko文件。

194824146_3_20200707043852911

194824146_4_20200707043853114

194824146_5_20200707043853443

194824146_6_20200707043853927

可以看到内核ko文件就是个标准的elf格式的文件,那么我们用readelf读一下ko文件的结构。

readelf -a hello.ko

ELF Header:

Magic:   7f 45 4c 46 01 01 01 6100 00 00 00 00 00 00 00

Class:                            ELF32

Data:                             2’s complement, little endian

Version:                          1 (current)

OS/ABI:                           ARM

ABIVersion:                       0

Type:                              REL (Relocatable file)

Machine:                          ARM

Version:                          0x1

Entry point address:              0x0

Start of program headers:         0 (bytes into file)

Start of section headers:         904 (bytes into file)

Flags:                            0x600, GNU EABI, software FP, VFP

Size of this header:              52 (bytes)

Size of program headers:          0 (bytes)

Number of program headers:        0

Size of section headers:          40 (bytes)

Number of section headers:        19

Section header string table index: 16

Section Headers:

[Nr] Name              Type            Addr     Off   Size   ES Flg Lk Inf Al

[0]                   NULL            00000000 000000 000000 00      0  0  0

[1] .text             PROGBITS        00000000 000034 000000 00  AX 0   0  1

[2] .exit.text        PROGBITS        00000000 000034 00001c 00  AX 0   0  4

[3] .rel.exit.text    REL             00000000 000904 000010 08     17  2  4

[4] .init.text        PROGBITS        00000000 000050 000020 00  AX 0   0  4

[5] .rel.init.text    REL             00000000 000914 000010 08     17  4  4

[6] .modinfo          PROGBITS        00000000 000070 000060 00   A  0   0  4

[7] .rodata.str1.4    PROGBITS        00000000 0000d0 000028 01 AMS  0  0  4

[8] .data             PROGBITS        00000000 0000f8 000000 00  WA 0   0  1

[9] .gnu.linkonce.thi PROGBITS       00000000 0000f8 000150 00  WA  0  0  4

[10].rel.gnu.linkonce REL            00000000 000924 000010 08    17   9  4

[11] .note.gnu.build-i NOTE           00000000 000248 000024 00   A  0  0  4

[12] .bss              NOBITS          00000000 00026c 000000 00  WA 0   0  1

[13] .comment          PROGBITS        00000000 00026c 000056 00      0  0  1

[14] .note.GNU-stack  PROGBITS        00000000 0002c2000000 00      0   0  1

[15] .ARM.attributes  ARM_ATTRIBUTES  00000000 0002c2000010 00      0   0  1

[16] .shstrtab         STRTAB          00000000 0002d2 0000b6 00      0  0  1

[17] .symtab           SYMTAB          00000000 000680 0001f0 10     18 27  4

[18] .strtab           STRTAB          00000000 000870 000091 00      0  0  1

Key to Flags:

W(write), A (alloc), X (execute), M (merge), S (strings)

I(info), L (link order), G (group), x (unknown)

O(extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.

There are no program headers in this file.

Relocation section ‘.rel.exit.text’ atoffset 0x904 contains 2 entries:

Offset    Info    Type            Sym.Value  Sym. Name

00000010 00001e01 R_ARM_PC24       00000000   printk

00000018 00000102 R_ARM_ABS32      00000000   .rodata.str1.4

Relocation section ‘.rel.init.text’ atoffset 0x914 contains 2 entries:

Offset    Info    Type            Sym.Value  Sym. Name

00000010 00001e01 R_ARM_PC24       00000000   printk

0000001c 00000102 R_ARM_ABS32      00000000   .rodata.str1.4

Relocation section’.rel.gnu.linkonce.this_module’ at offset 0x924 contains 2 entries:

Offset    Info    Type            Sym.Value  Sym. Name

000000d4 00001d02 R_ARM_ABS32      00000000   init_module

00000140 00001c02 R_ARM_ABS32      00000000   cleanup_module

There are no unwind sections in this file.

Symbol table ‘.symtab’ contains 31 entries:

Num:    Value  Size Type   Bind   Vis      Ndx Name

0: 00000000     0 NOTYPE  LOCAL DEFAULT  UND

1:00000000     0 SECTION LOCAL  DEFAULT   7

2: 00000000     0 NOTYPE  LOCAL DEFAULT    2 $a

3: 00000000    28 FUNC    LOCAL DEFAULT    2 hello_exit

4: 00000018     0 NOTYPE  LOCAL DEFAULT    2 $d

5: 00000000     0 NOTYPE  LOCAL DEFAULT    4 $a

6: 00000000    32 FUNC    LOCAL DEFAULT    4 hello_init

7: 0000001c     0 NOTYPE  LOCAL DEFAULT    4 $d

8: 00000000     0 NOTYPE  LOCAL DEFAULT    6 $d

9: 00000000    12 OBJECT  LOCAL DEFAULT    6 __mod_license18

10:00000000     0 NOTYPE  LOCAL DEFAULT    7 $d

11: 0000000c     0 NOTYPE  LOCAL DEFAULT    6 $d

12: 0000000c    35 OBJECT  LOCAL DEFAULT    6 __mod_srcversion23

13: 00000030     9 OBJECT  LOCAL DEFAULT    6 __module_depends

14: 0000003c    34 OBJECT  LOCAL DEFAULT    6 __mod_vermagic5

15: 00000000     0 NOTYPE  LOCAL DEFAULT    9 $d

16: 00000000     0 SECTIONLOCAL  DEFAULT    1

17: 00000000     0 SECTIONLOCAL  DEFAULT    2

18: 00000000     0 SECTIONLOCAL  DEFAULT    4

19: 00000000     0 SECTIONLOCAL  DEFAULT    6

20: 00000000     0 SECTIONLOCAL  DEFAULT    8

21: 00000000     0 SECTIONLOCAL  DEFAULT    9

22: 00000000     0 SECTIONLOCAL  DEFAULT   11

23: 00000000     0 SECTIONLOCAL  DEFAULT   12

24: 00000000     0 SECTIONLOCAL  DEFAULT   13

25: 00000000     0 SECTIONLOCAL  DEFAULT   14

26: 00000000     0 SECTIONLOCAL  DEFAULT   15

27: 00000000   336 OBJECT  GLOBAL DEFAULT    9 __this_module

28: 00000000    28 FUNC    GLOBAL DEFAULT    2 cleanup_module

29: 00000000    32 FUNC    GLOBAL DEFAULT    4 init_module

30: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printk

No version information found in this file.

Notes at offset 0x00000248 with length 0x00000024:

Owner              Data size       Description

GNU         0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)

Attribute Section: aeabi

File Attributes

下面我们读一下这个二进制文件,elf头52个字节,也就是读到0x34。elf结构网上资料很多,这里就不赘述了。

194824146_7_202007070438545

可以看到0x34个字节的elf头之后紧跟着0D C0 A0 E1 00 D8 2D E9  04 B0 4C E2  04 00 9F E5 FE FF FF EB

对照上面readelf的输出,开头零地址对应的是.text段,相对文件的跳转位置是0x34,

接下来使用objdump工具逆向读取一下后面的arm汇编段。

objdump -S hello.ko

00000000 :

0:      e1a0c00d     mov       ip, sp

4:      e92dd800     push       {fp, ip, lr, pc}

8:      e24cb004     sub  fp, ip, #4       ;0x4

c:       e59f0004     ldr   r0, [pc, #4]   ;18

10:      ebfffffe  bl    0

14:      e89da800     ldm sp, {fp, sp, pc}

18:      00000000     .word     0x00000000

Disassembly of section .init.text:

00000000 :

0:      e1a0c00d     mov       ip, sp

4:      e92dd800     push       {fp, ip, lr, pc}

8:      e24cb004     sub  fp, ip, #4       ;0x4

c:       e59f0008     ldr   r0, [pc, #8]   ;1c

10:      ebfffffe  bl    0

14:      e3a00000     mov       r0, #0     ;0x0

18:      e89da800     ldm sp, {fp, sp, pc}

1c:      00000014     .word     0x00000014

可以看到,这个项目的汇编代码其实只有60个字节。开头的 e1a0c00d, e92dd800,跟阅读ko二进制文件的0D C0 A0 E1 00 D8 2D E9  04 B0 4C E2  04 00 9F E5 FE FF FF EB也是一一对应的。开头是 cleanup_module,机器码跟汇编是一一对应的。

那么我们的ko模块加载无效,是不是汇编段导致的问题呢?printk这种代码还是太复杂了,我们把这个汇编段精简一下,只保留一行arm汇编:mov pc, #9

汇编语言跟机器码是一一对应的,查表手算一下,可以知道这行命令对应的机器码是:e3a0f009

让整个程序精简到4个字节,上来就把pc指针置为9,触发内核panic,通过查看内核panic的寄存器状态,看pc指针的值是不是9,判断程序是否执行。

我们直接修改文件二进制,找到init_module对应的汇编代码入口位置e1a0c00d,把它改成这样。

194824146_8_2020070704385452

可以看到,删掉了汇编段所有代码,把汇编段第一条命令改成了09 F0 A0 E3,字节序问题,也就是上面手算的e3a0f009,mov pc, #9指令了。

我们继续尝试,只有一行汇编的ko文件能否成功加载。

Insmod hello.ko

结果什么都没有发生,哪怕是崩溃,都没有,成功加载,一点反应都没有。

看样子不是汇编的问题,这后面紧跟着的是只读常量段,C语言里写的hello exit和hello init也在。这后面一堆零,是预留给堆栈区的空间。具体应该跳过多少呢?

194824146_9_20200707043854146

只读常量段

翻到上面,从上面readelf的输出可以看到,

Start of section headers:          904 (bytes into file)

去掉52个elf头,汇编段大小是852字节,让我们直接跳到目的地。

194824146_10_20200707043854177

可以看到光标所在位置就是elf格式的section header了,前面紧接着的都是些字符串、模块信息之类了。

可以继续翻上去看readelf的输出,section header一共0-18,也就是19个段。一个加载就崩溃的模块,我们不需要exit段,那让我们仅保留下面几个段:

.text,.init.text,.rel.init.text,.modinfo(模块信息,内核会读取识别这个段的数据),.symtab(保存了很多symbol信息,还是有必要留一下的),.shstrtab(段的名字,需要保留一下),.gnu.linkonce.this_module,.rel.gnu.linkonce.this_module这几个段。暴力一点,把其他的段全都删了吧。

在elf格式里,每个section header是40字节,就从光标所在位置往下数,仅保留需要的段部分,其他的全都删除。

重新readelf看一下seciton内容变成了现在这样。

194824146_11_20200707043854505

我们重新逆向一下修改后的汇编代码

194824146_12_20200707043854614

嗯,很好,就剩一行汇编了,干净多了。

让我们再逐渐把无关的东西清理的更干净一些。(逐渐忘记最初的目的,→_→)

194824146_13_20200707043854646

rel.init.text段标注了汇编段指定位置的动态预留地址,因为现在已经没有printk了,删!对应的二进制位置在这里。

.symtab段里有大量的标号,除init_module、cleanup_module、__this_module等一些有symbol的位置信息外,其他不用的,全部删除。

这下面紧接着的是symtab的位置信息,这是真正保存命名的字符串数据,这部分都是寻找字符串命名的,在此就不赘述了。

194824146_14_20200707043854693

这里多截取了一些,但是可以看到,这个ko文件打开二进制,在修改的情况下,基本整个读完了。

194824146_15_20200707043854864

最后,还剩余 48字节没读,也是最关键的48字节了,这里先卖个关子。

让我们把删的面目全非的ko文件扔到内核里加载一下看看,还能用不。。。

insmod hello.ko

嗯,依然没有任何反应,ismod可以看到模块成功加载了,但是如果pc置为9,应该会触发panic才对,但是依然是一行汇编都没有执行的状态。

我们回来继续读这关键的48字节所对应的Relocation section段。首先简单介绍一下这一段是干什么用的,为什么是ko模块对接最关键的段。

内核ko模块加载的时候一定会调用外部的函数,比如printk函数,这个printk函数的汇编代码在内核的某个位置,执行期加载到了内存的某个位置。我的模块怎么找到这个函数的真实汇编调用呢?内核在加载ko模块的时候,会读取Relocation section段,你需要什么函数,symbol名字是什么。内核在动态寻找这个printk函数对应的正在运行的内核的内存位置,然后在加载ko模块的时候,将printk在内核里运行时的真实内存地址覆盖到这个ko模块的指定位置,这样在ko模块执行到调用printk这行ebfffffe汇编的时候,调用的就是内核printk真实的地址了。

首先,exit段被我删了,这里readelf显示的对应位置开始缺失了,请滚动到最上方查看最初的readelf的exit段打印

[3].rel.exit.text    REL             00000000 000904 000010 08     17  2  4

Relocation section ‘.rel.exit.text’ atoffset 0x904 contains 2 entries:

Offset    Info    Type            Sym.Value  Sym. Name

00000010 00001e01 R_ARM_PC24       00000000   printk

00000018 00000102 R_ARM_ABS32      00000000   .rodata.str1.4

0x0940的位置是exit段的Relocation section段,可以看到00000010  00001e01,00000018  00000102,这个开头跟最后48字节的开头完全一致,既然exit段都没了,可以删!

Relocation section ‘.rel.init.text’ atoffset 0x914 contains 2 entries:

Offset    Info    Type       Sym.Value  Sym. Name

00000010 00001e01 R_ARM_PC24       00000000   printk

0000001c 00000102 R_ARM_ABS32      00000000

init段里还有个printk的动态入口?现在就一行汇编,这个Relocation section段已经没用了,也可以删。

194824146_16_20200707043854911

终于到了最后时刻,有兴趣的朋友可以测试一下,现在ko模块依然能正常加载成功,但是依然不执行没有反应。

我们还剩下16字节没有修改,这16字节分为两组,读readelf打印可以看到,分别表示:

000000d4 00001d02 R_ARM_ABS32      00000000   init_module

00000140 00001c02 R_ARM_ABS32      00000000   cleanup_module

因为cleanup_module对应的exit段已经不存在了,我们继续删。好了,还剩余8字节。

明显前4字节表示的是offset位置,后4字节表示的是info信息,但这4字节到底是怎么来的呢?笔者尝试修改后4个字节info信息,发现加载ko的时候开始失败了,提示信息为unknown symbol。这个info是如何算出来的呢,我暂时也没有找到相关信息,希望有知道的好心读者能告知一下。

最后让我们看一下offset这4字节,其实就是内核加载ko模块时候的入口相对地址,我们先从上述now kernle里面找一个正常能用的ko文件出来,读一下二进制。

194824146_17_2020070704385536

我这里使用了能正常使用的ebtables.ko模块,可以看到对应的init_module的offset地址是BC 00 00 00,而我的hello.ko的offset地址是D4 00 00 00。让我们手动把入口地址改为BC 00 00 00。

insmod hello.ko

成功触发kernel panic,查看pc指针值,就是9,终于成功加载了。

已经写的够长了,后面的就不赘述了,因为内核ko模块的地址全部是按照相对地址计算的,除了这一行汇编。类似printk,nf_register_hook,register_sysctl_table等常用的调用测试,均不影响正常使用。

所以当无法完美使用之前内核代码的情况下,编译一个magic code一致的假的fake kernel,只要版本基本一致,头文件没有什么区别,编译出来的ko文件,修改一下.rel.gnu.linkonce.this_module段的offset地址,info信息不用改动,就能在没有编译过的内核上完美正常运行自己编译的内核ko模块了。

194824146_18_20200707043855146