你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

不同的内核源码编译出来的ko文件的区别

[复制链接]
gaosmile 发布时间:2020-6-21 14:57
之前一直在考虑,不同的内核源码编译出来的ko文件,区别到底是什么?

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

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

首先分析一个最简单的hello.ko,Makefile就不写了,因为需要尽可能简单,加一行去除调试信息的objcopy -g hello.ko就好。
hello.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
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文件。
微信图片_20200621145507.png
微信图片_20200621145511.png
微信图片_20200621145514.png
微信图片_20200621145518.png

可以看到内核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结构网上资料很多,这里就不赘述了。

微信图片_20200621145521.png
可以看到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 <cleanup_module>:
   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 <cleanup_module+0x18>
  10:      ebfffffe  bl    0 <printk>
  14:      e89da800     ldm sp, {fp, sp, pc}
  18:      00000000     .word     0x00000000
Disassembly of section .init.text:
00000000 <init_module>:
   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 <init_module+0x1c>
  10:      ebfffffe  bl    0 <printk>
  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,把它改成这样。

微信图片_20200621145524.png

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

我们继续尝试,只有一行汇编的ko文件能否成功加载。
Insmod hello.ko
结果什么都没有发生,哪怕是崩溃,都没有,成功加载,一点反应都没有。
看样子不是汇编的问题,这后面紧跟着的是只读常量段,C语言里写的hello exit和hello init也在。这后面一堆零,是预留给堆栈区的空间。具体应该跳过多少呢?
微信图片_20200621145528.png
只读常量段

翻到上面,从上面readelf的输出可以看到,
Start of section headers:          904 (bytes into file)
去掉52个elf头,汇编段大小是852字节,让我们直接跳到目的地。
微信图片_20200621145531.png

可以看到光标所在位置就是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内容变成了现在这样。

微信图片_20200621145534.png

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

微信图片_20200621145538.png

嗯,很好,就剩一行汇编了,干净多了。
让我们再逐渐把无关的东西清理的更干净一些。(逐渐忘记最初的目的,→_→)

微信图片_20200621145541.png

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

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

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

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

微信图片_20200621145547.png

最后,还剩余 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段已经没用了,也可以删。
微信图片_20200621145551.png

终于到了最后时刻,有兴趣的朋友可以测试一下,现在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文件出来,读一下二进制。

微信图片_20200621145554.png

我这里使用了能正常使用的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模块了。
微信图片_20200621145558.png


收藏 评论1 发布时间:2020-6-21 14:57

举报

1个回答
likang1202 回答时间:2020-6-22 08:47:49
谢谢分享

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
微信公众号二维码 微信公众号
手机版二维码 手机版