MotoMAGX weakness of kernel modules hashes
Contents |
Security of kernel modules
Motorola uses SHA1 hash of kernel module matching to prevent "untrusted" modules to be loaded. Using the GPL'ed source code of linux kernel (actually kernel/module.c) we can study how this hash is generated. Generally, all sections of ELF file which has ALLOC flag will be used to produce a hash of module. But there are some exceptions - symbol tables are treated as ALLOCed (for kallsyms feature), .modinfo treated as non-ALLOCed, and .gnu.linkonce.this_module section is not used in hash computation. After studying this, we can speak of a weakness of this system.
Weakness
If we'll see, which sections of ELF file doesn't affect module hash - we can find that very important section - a section with relocations - doesn't have ALLOC flag, so we can easily edit it, not affecting the hash. This actually leads us to editing the module text or data in some very limited way. But that is enough - since we have .gnu.linkonce.this_module section which is ALLOCed, we can relocate some branches in kernel text to this section, where we can place our code. That's it - we can finally modify module's logic for our own needs. Actually, it is not guaranteed that we'll find suitable branch instruction in kernel text, but it seems that is not a problem in existing MAGX phones (tested on Z6, U9, E8).
Example of usage
I had chosen motsecurity_sysfs.ko module, which contains suitable branch instruction (funny thing - module with motsecurity in his name helps to break security of the phone), and I didn't find better code, than a code that disables module's hash checking in runtime.
% arm-linux-gnueabi-objdump -rd motsecurity_sysfs.ko ... 140: e2855001 add r5, r5, #1 ; 0x1 144: ea000008 b 16c <init_module-0xf0> 148: e5113030 ldr r3, [r1, #-48] ...
As we can see, on 0x144 there is a unconditional branch to +0x28. If we'll analyze logic module's text, we can find out that this branch happens when module finds '\n' in text written to /sys/security/allowed_mounts. Let's adjust the code with relocation
% arm-linux-gnueabi-objdump -rd motsecurity_sysfs_new.ko
...
140: e2855001 add r5, r5, #1 ; 0x1
144: ea000008 b 28 <init_module-0x234>
144: R_ARM_JUMP24 __this_module
148: e5113030 ldr r3, [r1, #-48]
...
__this_module is a symbol that can mean "start of .gnu.linkonce.this_module section". So, now this instruction can be interpreted as "unconditionally branch to code, which is placed in .gnu.linkonce.this_module+0x28". 0x28 is a great choice, because it points to space which is reserved to null-terminated kernel name string. We have enough space to execute following code
ptr: .word 0xc005f914
old: .word 0xebfffdb1
new: .word 0xe3a00000
sub r7, pc, #0x14
ldmia r7, {r4, r5, r6}
ldr r7, [r4]
cmp r5, r7
streq r6, [r4]
mvn r0, #0x1
sub sp, fp, #0x20
ldmia sp, {r4, r5, r6, r7, r8, fp, sp, pc}
which safely changes a word at 0xc005f914 from 0xebfffdb1 (call hash cheking subroutine) to 0xe3a00000 (put 0 to r0, successful "checking") and returns from orignal sysfs write handler. Let's put it in ELF section
% arm-linux-gnueabi-objdump -rD motsecurity_sysfs_new.ko
...
1c: c005def4 strgtd sp, [r5], -r4
20: ebfffdb1 bl fffff6ec <cleanup_module+0xfffff3ec>
24: e3a00000 mov r0, #0 ; 0x0
28: e24f7014 sub r7, pc, #20 ; 0x14
2c: e8970070 ldmia r7, {r4, r5, r6}
30: e5947000 ldr r7, [r4]
34: e1550007 cmp r5, r7
38: 05846000 streq r6, [r4]
3c: e3e00001 mvn r0, #1 ; 0x1
40: e24bd020 sub sp, fp, #32 ; 0x20
44: e89da9f0 ldmia sp, {r4, r5, r6, r7, r8, fp, sp, pc}
...
First three instruction is actually our data, our entry point of this code is 0x28. In current example the string with a name should be shortened - to "motsecurity_sys" for example.
You can get working example for 44R Z6 firmware here. Please ensure that you have 6ccb945a8bd6726df807cdba7992c5c63eb4d246 listed in trusted hashes (/etc/modules.hash), and that your kernel's md5 is 34517c12b2f6c9cb7a8898345ddc1b1f (you can calculate it on /dev/mtdblock/kern).
Better way to patch a kernel
As we can obtain an addresses of kernel symbols using kallsyms, we can can make this exploit to be more convinient. It would be hard (or even impossible) to use kallsyms lookup subroutines from our kernelspace code, so it is possible to write userspace helper, that looks address of module_hash_verify in /proc/kallsyms, modifies module's code to use that address and inserts module. It should be fairly simple. The kernelmode code itself should replace first to words of module_hash_verify with
e3a00000 mov r0, #0 e12fff1e bx lr
If we'd assume that module_hash_verify's code consists of at least two instructions, it would be fairly safe to do such substitution. Thanks for WyrM for idea. Simple and dirty implementation is here.