之前一直在考虑,不同的内核源码编译出来的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文件。

可以看到内核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 p 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 p headers:          40 (bytes)

Number of p 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 p groups in this file.

There are no program headers in this file.

Relocation p '.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 p '.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 p'.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 ps 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结构网上资料很多,这里就不赘述了。

可以看到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 p .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,把它改成这样。

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

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

Insmod hello.ko

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

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

 只读常量段

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

Start of p headers:          904 (bytes into file)

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

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

可以继续翻上去看readelf的输出,p 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格式里,每个p header是40字节,就从光标所在位置往下数,仅保留需要的段部分,其他的全都删除。

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

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

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

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

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

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

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

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

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

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

insmod hello.ko

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

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

内核ko模块加载的时候一定会调用外部的函数,比如printk函数,这个printk函数的汇编代码在内核的某个位置,执行期加载到了内存的某个位置。我的模块怎么找到这个函数的真实汇编调用呢?内核在加载ko模块的时候,会读取Relocation p段,你需要什么函数,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 p '.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 p段,可以看到00000010  00001e01,00000018  00000102,这个开头跟最后48字节的开头完全一致,既然exit段都没了,可以删!

Relocation p '.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 p段已经没用了,也可以删。

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

我这里使用了能正常使用的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模块了。

1.人工智能:嵌入式技术的机遇与挑战

2.厉害了!用6个芯片打造复古经典计算机

3.国产操作系统这盘棋不简单

4.做嵌入式必须知道的国产CPU之路,里面有良机!

5.Linux是否能在8位MCU上运行?

6.为了适合你的项目,rt-thread有时候需要裁剪!

不同的内核源码编译出来的ko文件,区别到底是什么?相关推荐

  1. Android 内核源码编译记录

    注:此处内容总结自google官网:AOSP 编译内核.编译完成后刷机部分参考自其他大佬的文章.文中末尾提供了上传至CSDN的msm内核和Aarch64gcc工具 的下载链接,不想从官网下载的可以直接 ...

  2. 【linux内核-源码编译之centos7】

    linux内核-源码编译之centos7 一. 为什么要编译内核 二.疑难杂症 三.演示环境 四.下载源码 4.1.两者源码区别 4.2.将获取到的源码放在/usr/src/kernels/ 下 五. ...

  3. 编译linux源码报错,记录一次Linux内核源码编译实验

    记录一次Linux内核源码编译实验 文章目录 记录一次Linux内核源码编译实验 0. 实验环境 1. 选择.下载内核源码 2. 安装必要的依赖软件以及性能要求 3. 解压.配置和编译内核源码 3.1 ...

  4. Linux下imx6dl开发板从镜像的烧写、内核源码编译到第一个驱动运行的详细步骤

    文章目录 前言 一.对开发板烧写镜像 1.镜像烧写 2.串口测试 二.搭建交叉编译环境 1.Ubuntu下搭建交叉编译环境 2.WSL下搭建交叉编译环境 三.编译Linux内核源码 1.Ubuntu下 ...

  5. Linux源码编译(一):从头文件说起

    Linux源码编译(一):从头文件说起 2013-04-10 14:37:24 分类: LINUX 在Linux体系结构章节中,主要让大家对Linux结构层次有一定的了解,没有过多的长篇大论,力求简结 ...

  6. Jetson Agx Xavier USB驱动裁剪+can时钟修改+内核源码编译流程(jetpack4.6.1)

    自己做了一块Xavier载板,硬件裁剪了一些功能,导致官方镜像usb无法使用,我使用的是jetpack4.6.1(R32.7.1),所以进行驱动修改,重新编译内核. 一.虚拟机环境 在Windows下 ...

  7. Petalinux 使用外部ADI的内核源码编译记录-附oe_runmake解决办法

    最近在使用zynq+ad9361,需要使用ADI提供的内核源码.按照UG1144的,Using External Kernel and U-Boot with PetaLinux.配置工程petali ...

  8. xilinx linux内核,Xilinx-Zynq Linux内核源码编译过程

    本文内容依据http://www.wiki.xilinx.com网址编写,编译所用操作系统为ubuntu 14 1.交叉编译环境的安装配置 2.uboot的编译 1)下载uboot源代码 下载uboo ...

  9. meego内核源码编译

    需要的工具:SDK(主要是那个镜像文件),mic2 内核源码:kernel-2.6.35.3-12.3.src.rpm 由 http://repo.meego.com/MeeGo/tools/repo ...

最新文章

  1. 【Python培训基础】一篇文件教你py文件打包成exe
  2. adb 命令 (01)
  3. JavaScript(循环)
  4. svd 分解详细证明
  5. gdc2011一些“其他”
  6. AppVerifier的功能和原理
  7. 京东白条要上征信了!你用还是不用
  8. sonar扫描普通JAVA执行,SonarQube扫描源代码的方法
  9. 微模式重金招聘高级图像算法工程师
  10. 超火AI变脸特效来袭!马云蔡徐坤一起《吗咿呀嘿》 网友:摇不能停
  11. 科创板开市暴涨,详解25家企业的“造富”能力
  12. 自定义View:悬浮球与加速球
  13. 微型计算机系统评课,微机课评课稿.doc
  14. 墨刀原型图设计大全(转)
  15. linux+qq+输入法下载官网,续:Linux下安装输入法和QQ软件
  16. 【WiFi】hostapd 配置80M频宽某些信道启动失败问题分析及解决
  17. 前序遍历、中序遍历、后序遍历
  18. linux 磁盘分区的原理,linux磁盘—分区原理
  19. centos7安装harbor详细教程
  20. 【科创人独家】知道创宇杨冀龙:技术人的商业思维都是锤出来的,真实需求长在客户的KPI上

热门文章

  1. Win11右键菜单不折叠的设置方法
  2. android mvp 代码范例,Android MVP开发模式有案例和源码,反正我能看懂的MVP
  3. PHP:PDO->fetch()和fetchAll()遍历,session进行会话跟踪,用户退出清除session
  4. 【最佳实践】双因子认证在行云管家中的应用实践
  5. 一些常用的AHK操作
  6. 猿辅导学员入选国家队,竞赛老师成为“最强辅助”
  7. angular5中使用全局变量
  8. dbfs和dbm的换算_【清晰易懂】dBFS、dBm、dBV、dBW、0dB、-3dB概念解析
  9. ToG产品_产品培训问卷框架_2019_007
  10. MSVC ++ Version