lkm func угоняет ошибку - PullRequest
       31

lkm func угоняет ошибку

0 голосов
/ 30 августа 2018

Я написал небольшой модуль ядра 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 \n"
        "pushq %rax \n"
        "movq %cr0, %rax \n"
        "andq $0xfffffffffffeffff, %rax \n"
        "movq %rax, %cr0 \n"
        "popq %rax " );
}

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

static inline void rw_disable( register uint64_t val ) {
    asm volatile(
        "movq %0, %%cr0\n"
        "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\n", icounter );
            info( "strlen( debug_buffer ) = %ld\n", strlen( debug_buffer ) );
        }
        r = original_read( fd, buf, count );
        strncat( debug_buffer, buf, 1 );
        if ( strlen( debug_buffer ) > BUFFER_SIZE - 100 )
            debug_buffer[0] = '\0';
        return r;
    }
}

int hacked_read_init( void ) {
    register uint64_t cr0;
    info( "Module was loaded\n" );
    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\n" );
    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. Я использую Kali 2018.1 по умолчанию, установленный на образе hdd.qcow2 [30Gb]. Ядро 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

После этого я набираю разные символы, в основном a и left arrow и delete, как вы можете видеть из syslog: icounter = 44000, так что это 44k символов, которые я набрал до ошибки появляется, иногда больше, иногда меньше ... Чтобы быстрее набрать этот номер, я использую /usr/bin/xset r rate 20 60,

или даже вставьте false в оператор if / else, подобный этому 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-коде нет конфликтов, пока выполняется угон.

Это крошечный, легкий сценарий ... Где ошибка?

Update1:

Похоже, я нашел причину, по которой он начинает падать. Ошибка появляется сразу после команды rmmod hacked_read. Так что module_exit() не так, вероятно, cli и sti у asm недостаточно.

Ответы [ 2 ]

0 голосов
/ 09 сентября 2018

Как упомянул @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

Как я нашел кусок БУГА:

Как я уже сказал, ядро ​​моего гостя было скомпилировано с CONFIG_GDB_SCRIPTS=y. Теперь я присоединяю гостя к gdb хоста :

# 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

На стороне guest я извлекаю адреса:

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

На стороне host добавление модуля для отладки:

(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 ...

После этого я могу увидеть в лог-файле: 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 ) {

Бинго!

Теперь, на стороне guest , я делаю rmmod hacked_read. Через 60+ секунд появляется ошибка:

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: tee & BUG: 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\n", icounter );
85.                        info( "strlen( debug_buffer ) = %ld\n", 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] = '\0';
                return r;
        }
}
0 голосов
/ 05 сентября 2018

При удалении модуля из ядра Linux вся память, используемая модулем (данные и код), освобождается. Функция exit() модуля восстанавливает указатель на исходную функцию. Однако ядро ​​может выполнять код замещающей функции во время удаления модуля. Внезапно, прямо в середине этого, функция исчезает, когда освобождается память, занятая кодом модуля. Отсюда и ошибка.

Очевидно, что вы не можете удалить модуль после восстановления указателя на исходную функцию, пока не будете уверены, что нет потоков ядра, которые (могут) выполнять код функции замены. После восстановления указателя все новые потоки ядра будут выполнять исходную функцию, поэтому вам нужно подождать, пока текущие потоки не завершат выполнение замещающей функции. Как это сделать, это еще одна проблема. Возможно, вам придется использовать некоторые приемы, такие как счетчики ссылок и т. Д.

...