catalogue

. 引言
. 内核ko timer定时器,检测sys_call_table adress变动
. 通过/dev/kmem获取IDT adress
. 比较原始的系统调用地址和当前内核态中的系统调用地址发现是否有sys_call_table hook行为

0. 引言

内核rookit通常以系统调用为攻击目标,主要出于两个原因

. 在内核态劫持系统调用能以较小的代价控制整个系统,不必修太多东西
. 应用层大多数函数是一个或多个系统调用不同形式的封装,更改系统调用意味着其上层所有的函数都会被欺骗

当前的系统调用地址保存在系统调用表中,位于操作系统为内核保留的内存空间(虚拟地址最高1GB),系统调用入口地址的存放顺序同/usr/include/asm/unistd.h中的排列顺序,按系统调用号递增9

Relevant Link:

http://www.blackhat.com/presentations/bh-europe-09/Lineberry/BlackHat-Europe-2009-Lineberry-code-injection-via-dev-mem-slides.pdf

1. 内核ko timer定时器,检测sys_call_table adress变动

. The module does a copy of the Syscall Table to save all syscalls pointers
. After this first step, the module uses the kernel timer to check every X secondes the diff between the Syscall Table and the copy.
. If a diff is found, the module creates a workqueue to execute the python script and restore the good syscall pointer.

The python script is executed with root creds and the syscall number which is hooked, is passed as the first argument of script (sys.argv[1]).

0x1: hook_detection.c

/*
** Copyright (C) 2013 - Jonathan Salwan - http://twitter.com/JonathanSalwan
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
**
** For more information about this module,
** see : http://shell-storm.org/blog/Simple-Hook-detection-Linux-module/
**
*/ #include <asm/uaccess.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/syscalls.h>
#include <linux/timer.h>
#include <linux/workqueue.h> #define PATH_PYTHON "/usr/bin/python2.7"
#define PATH_SCRIPT "/opt/scripts/hook_detected.py" #define TIME_SLEEP 30000 /* in msec */ static struct timer_list timer_s;
static struct workqueue_struct *wq;
static unsigned int syscall_table_size;
static unsigned long *addr_syscall_table;
static unsigned long *dump_syscall_table; static int exec_python_script(unsigned int sys_num)
{
char s_num[];
char *argv[] = {PATH_PYTHON, PATH_SCRIPT, s_num, NULL};
static char *envp[] = {"HOME=/", "TERM=linux", "PATH=/sbin:/bin:/usr/sbin:/usr/bin", NULL};
struct subprocess_info *sub_info; sprintf(s_num, "%d", sys_num);
sub_info = call_usermodehelper_setup(argv[], argv, envp, GFP_ATOMIC);
if (sub_info == NULL)
return -ENOMEM;
call_usermodehelper_exec(sub_info, UMH_WAIT_PROC);
return ;
} static unsigned long *get_syscalls_table(void)
{
unsigned long *start; /* hack :/ */
for (start = (unsigned long *)0xc0000000; start < (unsigned long *)0xffffffff; start++)
if (start[__NR_close] == (unsigned long)sys_close){
return start;
}
return NULL;
} static unsigned int get_size_syscalls_table(void)
{
unsigned int size = ; while (addr_syscall_table[size++]);
return size * sizeof(unsigned long *);
} static void check_diff_handler(struct work_struct *w)
{
unsigned int sys_num = ; while (addr_syscall_table[sys_num]){
if (addr_syscall_table[sys_num] != dump_syscall_table[sys_num]){
printk(KERN_INFO "hook_detection: Hook detected ! (syscall %d)\n", sys_num);
write_cr0(read_cr0() & (~0x10000));
addr_syscall_table[sys_num] = dump_syscall_table[sys_num];
write_cr0(read_cr0() | 0x10000);
exec_python_script(sys_num);
printk(KERN_INFO "hook_detection: syscall %d is restored\n", sys_num);
}
sys_num++;
}
}
static DECLARE_DELAYED_WORK(check_diff, check_diff_handler); static void timer_handler(unsigned long data)
{
unsigned long onesec; onesec = msecs_to_jiffies();
queue_delayed_work(wq, &check_diff, onesec);
if (mod_timer(&timer_s, jiffies + msecs_to_jiffies(TIME_SLEEP)))
printk(KERN_INFO "hook_detection: Failed to set timer\n");
} static int __init hook_detection_init(void)
{
addr_syscall_table = get_syscalls_table();
if (!addr_syscall_table){
printk(KERN_INFO "hook_detection: Failed - Address of syscalls table not found\n");
return -ECANCELED;
} syscall_table_size = get_size_syscalls_table();
dump_syscall_table = kmalloc(syscall_table_size, GFP_KERNEL);
if (!dump_syscall_table){
printk(KERN_INFO "hook_detection: Failed - Not enough memory\n");
return -ENOMEM;
}
memcpy(dump_syscall_table, addr_syscall_table, syscall_table_size); wq = create_singlethread_workqueue("hook_detection_wq"); setup_timer(&timer_s, timer_handler, );
if (mod_timer(&timer_s, jiffies + msecs_to_jiffies(TIME_SLEEP))){
printk(KERN_INFO "hook_detection: Failed to set timer\n");
return -ECANCELED;
} printk(KERN_INFO "hook_detection: Init OK\n");
return ;
} static void __exit hook_detection_exit(void)
{
if (wq)
destroy_workqueue(wq);
kfree(dump_syscall_table);
del_timer(&timer_s);
printk(KERN_INFO "hook_detection: Exit\n");
} module_init(hook_detection_init);
module_exit(hook_detection_exit); MODULE_AUTHOR("Jonathan Salwan");
MODULE_DESCRIPTION("Hook Detection");
MODULE_LICENSE("GPL");

Relevant Link:

http://shell-storm.org/blog/Simple-Hook-detection-Linux-module/hook_detection.c
http://shell-storm.org/blog/Simple-Hook-detection-Linux-module/

2. 通过/dev/kmem获取IDT adress

核心逻辑是通过idt获取80中断的地址,获取了sys_call_table之后,进行一次拷贝,之后就可以进行diff对比

#include < stdio.h >
#include < sys/types.h >
#include < fcntl.h >
#include < stdlib.h > int kfd; struct
{
unsigned short limit;
unsigned int base;
} __attribute__ ((packed)) idtr; struct
{
unsigned short off1;
unsigned short sel;
unsigned char none, flags;
unsigned short off2;
} __attribute__ ((packed)) idt; int readkmem (unsigned char *mem,
unsigned off,
int bytes)
{
if (lseek64 (kfd, (unsigned long long) off,
SEEK_SET) != off)
{
return -;
} if (read (kfd, mem, bytes) != bytes)
{
return -;
} } int main (void)
{
unsigned long sct_off;
unsigned long sct;
unsigned char *p, code[];
int i; /* request IDT and fill struct */ asm ("sidt %0":"=m" (idtr)); if ((kfd = open ("/dev/kmem", O_RDONLY)) == -)
{
perror("open");
exit(-);
} if (readkmem ((unsigned char *)&idt,
idtr.base + * 0x80, sizeof (idt)) == -)
{
printf("Failed to read from /dev/kmem\n");
exit(-);
} sct_off = (idt.off2 < < ) | idt.off1; if (readkmem (code, sct_off, 0x100) == -)
{
printf("Failed to read from /dev/kmem\n");
exit(-);
} /* find the code sequence that calls SCT */ sct = ;
for (i = ; i < ; i++)
{
if (code[i] == 0xff && code[i+] == 0x14 &&
code[i+] == 0x85)
sct = code[i+] + (code[i+] < < ) +
(code[i+] < < ) + (code[i+] < < );
}
if (sct)
printf ("sys_call_table: 0x%x\n", sct);
close (kfd);
}

Relevant Link:

http://www.rootkitanalytics.com/kernelland/IDT-dev-kmem-method.php
http://www.phpweblog.net/GaRY/archive/2007/06/17/get_sys_call_table_address.html

3. 比较原始的系统调用地址和当前内核态中的系统调用地址发现是否有sys_call_table hook行为

原始的系统调用地址在内核编译阶段被指定,不会更改,通过比较原始的系统调用地址和当前内核态中的系统调用地址我们就可以发现系统调用有没有被更改。原始的系统调用地址在编译阶段被写入两个文件

. System.map: 该文件包含所有的符号地址,系统调用也包含在内
. vmlinux-2.4.x: 系统初始化时首先被读入内存的内核映像文件

vmlinux-2.4.x文件通常以压缩的格式存放在/boot目录下,所以在比较之前必须解压这个文件,另一个问题是: 我们的比较的前提是假设system.map及vmlinuz image都没有被入侵者更改,所以更安全的做法是在系统干净时已经创建这两个文件的可信任的拷贝,并创建文件的md5 hash

在大多数被装载内核后门情况中,内核在系统初始化之后才被更改,更改发生在加载了rootkit的module或者被植入直接读写/dev/kmem的on-the-fly kernel patch之后。而通常情况下rootkit并不更改vmlinuz和system.map这两个文件,所以打印这两个文件中的符号地址就可以知道系统原始的系统调用地址,系统当前运行中的系统调用地址(可能被更改)可以同过/proc下的kcore文件得到,比较两者就知道结果

0x1: 获取原始内核系统调用函数地址

/boot/System.map-3.10.-.el7.x86_64

0x2: 获取当前内核系统调用地址

cat /proc/kallsyms 

0x3: 对比区别

从里面枚举sys_call_table的function point地址
/boot/System.map-3.10.-.el7.x86_64 和
cat /proc/kallsyms | grep sys_fork
进行diff对比 cat System.map-4.4.--generic | grep sys_fork

这里遇到一个问题,ubuntu对sys_socket相关的系统调用会进行内核地址重定位,因此需要对检测结果进行一个误报过滤,看是否所有的函数的gap都相同,如果相同,则说明是系统自己的function address relocation行为

def hex2dec(string_num):
return str(int(string_num.upper(), )) def check_rookit(diff_func_table):
last_func_address_gap =
cur_func_address_gap =
for item in diff_func_table:
cur_func_address_gap = abs(int(hex2dec(item['cur_funcaddress'])) - int(hex2dec(item['ori_funcaddress'])))
if last_func_address_gap != and cur_func_address_gap != last_func_address_gap:
return True
else:
last_func_address_gap = cur_func_address_gap
return False

Relevant Link:

http://www.xfocus.net/articles/200411/754.html
http://www.magicsite.cn/blog/Linux-Unix/Linux/Linux41576.html
http://blog.csdn.net/tommy_wxie/article/details/8039695

Copyright (c) 2017 LittleHann All rights reserved

Linux sys_call_table变动检测的更多相关文章

  1. Linux IO时事检测工具iostat

    Linux IO时事检测工具iostat iostat命令用于检测linux系统io设备的负载情况,运行iostat将显示自上次运行该命令以后的统计信息.用户可以通过指定统计的次数和时间来获得所需的统 ...

  2. 安全运维之:Linux后门入侵检测工具的使用

    安全运维之:Linux后门入侵检测工具的使用 https://blog.csdn.net/exitgogo/article/details/39547113

  3. linux编写脚本检测本机链接指定IP段是否畅通

    linux编写脚本检测本机链接指定IP段是否畅通,通过ping命令检测指定IP,检测命令执行结果,若为0表示畅通,若为1表示不通,以此判断网络是否畅通,但是指定机器禁用ping命令除外.代码如下: # ...

  4. Linux中系统检测工具top命令

    Linux中系统检测工具top命令 本文转自:https://www.cnblogs.com/zhoug2020/p/6336453.html 首先介绍top中一些字段的含义: VIRT:virtua ...

  5. Linux内核死锁检测机制【转】

    转自:http://www.oenhan.com/kernel-deadlock-check 死锁就是多个进程(线程)因为等待别的进程已占有的自己所需要的资源而陷入阻塞的一种状态,死锁状态一旦形成,进 ...

  6. Linux硬盘性能检测

    对于现在的计算机来讲,整个计算机的性能主要受磁盘IO速度的影响,内存.CPU包括主板总线的速度已经很快了. 基础检测方法 1.dd命令 dd命令功能很简单,就是从一个源读取数据以bit级的形式写到一个 ...

  7. [LINUX]警告:检测到时钟错误。您的创建可能是不完整的。

    [LINUX]警告:检测到时钟错误.您的创建可能是不完整的.   原因:     如果上一次编译时为20071001,你把系统时间改成20070901后再编译就会报这样的错误. 解决:     把时间 ...

  8. Linux主机入侵检测

    检查系统信息.用户账号信息 ● 操作系统信息 cat /proc/version 用户信息 用户信息文件 /etc/passwd root:x:0:0:root:/root:/bin/bash 用户名 ...

  9. Linux后门入侵检测

    蛋疼啊,服务器被入侵成肉鸡了,发出大量SYN请求到某个网站!(瞬间有种被OOXX(强)(奸)的赶脚) 泪奔ING... 源起: Linux服务器日常检查,#ps aux 发现大量httpd进程,和往常 ...

随机推荐

  1. PLsql链接oracle配置

    在Oracle的安装文件下查找tnsnames.ora文件 如果真的找不到路径,建议大家在Oracle安装位置全文搜索tnsnames.ora 配置格式 个人配置 下载并安装PL/SQL,成功安装后配 ...

  2. Codeforces Round #475 Div. 1

    B:当n是偶数时无解,因为此时树中有奇数条边,而我们每次都只能删除偶数条.当n是奇数时一定有解,因为此时不可能所有点度数都为奇数,只要找到一个度数为偶数的点,满足将它删掉后,各连通块大小都为奇数就可以 ...

  3. Balanced Number HDU - 3709 数位dp

    题意: 给出范围 算出 满足  选取一个数中任一一个 树作为支点  两边的数分别乘以到中心的距离和 左和等于右和   的数有多少个 数位DP题 状态转移方程为dp[pos][x][state]=dp[ ...

  4. 洛谷P2320鬼谷子的钱袋.

    题目 这个题考察二进制分解. \(Code\) #include <bits/stdc++.h> #pragma GCC optimize(2) #pragma GCC optimize( ...

  5. 爬虫_电影天堂 热映电影(xpath)

    写了一天才写了不到100行.不过总归是按自己的思路完成了 import requests from lxml import etree import time BASE = 'http://www.d ...

  6. 【HDU 4343】Interval query(倍增)

    BUPT2017 wintertraining(15) #8D 题意 给你x轴上的N个线段,M次查询,每次问你[l,r]区间里最多有多少个不相交的线段.(0<N, M<=100000) 限 ...

  7. Centos Install Keepalived

    Keepalived简介Keepalived 的作用是检测 web 服务器的状态,如果有一台 web 服务器死机,或工作出现故障,Keepalived 将检测到,并将有故障的 web 服务器从系统中剔 ...

  8. 【WC2018】即时战略(动态点分治,替罪羊树)

    [WC2018]即时战略(动态点分治,替罪羊树) 题面 UOJ 题解 其实这题我也不知道应该怎么确定他到底用了啥.只是想法很类似就写上了QwQ. 首先链的部分都告诉你要特殊处理那就没有办法只能特殊处理 ...

  9. cf1000F One Occurrence (线段树)

    这题我是离线做的 设i位置的数上次出现的位置是pre[i](如果第一次出现那就是0) 可以想到,用线段树维护一个区间的pre的最小值,如果它小于区间左端点,那这个数就是一个合法的答案 但直接这样做是错 ...

  10. centos7添加并挂载新硬盘

    环境目标: 配置一台centos7,主硬盘40G装系统:副硬盘200G作为数据盘(格式:XFS)挂载到根目录:/data/ 说明:XFS是高性能文件系统,SGI为他们的 IRIX平台而设计: 自从20 ...