Linux sys_call_table變更檢測

cataloguephp

0. 引言
1. 內核ko timer定時器,檢測sys_call_table adress變更
2. 經過/dev/kmem獲取IDT adress
3. 比較原始的系統調用地址和當前內核態中的系統調用地址發現是否有sys_call_table hook行爲

 

0. 引言html

內核rookit一般以系統調用爲攻擊目標,主要出於兩個緣由python

1. 在內核態劫持系統調用能以較小的代價控制整個系統,沒必要修太多東西 
2. 應用層大多數函數是一個或多個系統調用不一樣形式的封裝,更改系統調用意味着其上層全部的函數都會被欺騙

當前的系統調用地址保存在系統調用表中,位於操做系統爲內核保留的內存空間(虛擬地址最高1GB),系統調用入口地址的存放順序同/usr/include/asm/unistd.h中的排列順序,按系統調用號遞增9linux

Relevant Link:web

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變更redis

1. The module does a copy of the Syscall Table to save all syscalls pointers
2. After this first step, the module uses the kernel timer to check every X secondes the diff between the Syscall Table and the copy.
3. 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]).shell

0x1: hook_detection.cubuntu

/*
**  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[32];
  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[0], argv, envp, GFP_ATOMIC);
  if (sub_info == NULL)
    return -ENOMEM;
  call_usermodehelper_exec(sub_info, UMH_WAIT_PROC);
  return 0;
}

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 = 0;

  while (addr_syscall_table[size++]);
  return size * sizeof(unsigned long *);
}

static void check_diff_handler(struct work_struct *w)
{
  unsigned int sys_num = 0;

  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(1000);
  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, 0);
  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 0;
}

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 adresspython2.7

核心邏輯是經過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 -1;
      }

      if (read (kfd, mem, bytes) != bytes) 
     {
             return -1;
     }

 }
 
 int main (void)
 {
      unsigned long sct_off;
      unsigned long sct;
       unsigned char *p, code[255];
      int i;


/* request IDT and fill struct */



    asm ("sidt %0":"=m" (idtr));

    if ((kfd = open ("/dev/kmem", O_RDONLY)) == -1)
    {
                perror("open");
            exit(-1);
    }
    
    if (readkmem ((unsigned char *)&idt, 
               idtr.base + 8 * 0x80, sizeof (idt)) == -1)
    {
            printf("Failed to read from /dev/kmem\n");
            exit(-1);
    }
    
    sct_off = (idt.off2 < < 16) | idt.off1;

    if (readkmem (code, sct_off, 0x100) == -1)
    {
            printf("Failed to read from /dev/kmem\n");
            exit(-1);
    }

/* find the code sequence that calls SCT */


 sct = 0;
    for (i = 0; i < 255; i++)
    {
            if (code[i] == 0xff && code[i+1] == 0x14 && 
                                        code[i+2] == 0x85)
                    sct = code[i+3] + (code[i+4] < < 8) + 
                        (code[i+5] < < 16) + (code[i+6] < < 24);
    }
    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行爲 

原始的系統調用地址在內核編譯階段被指定,不會更改,經過比較原始的系統調用地址和當前內核態中的系統調用地址咱們就能夠發現系統調用有沒有被更改。原始的系統調用地址在編譯階段被寫入兩個文件

1. System.map: 該文件包含全部的符號地址,系統調用也包含在內
2. 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.0-327.el7.x86_64

0x2: 獲取當前內核系統調用地址

cat /proc/kallsyms 

0x3: 對比區別

從裏面枚舉sys_call_table的function point地址
/boot/System.map-3.10.0-327.el7.x86_64

和
cat /proc/kallsyms | grep sys_fork
進行diff對比 

cat System.map-4.4.0-53-generic | grep sys_fork

這裏遇到一個問題,ubuntu對sys_socket相關的系統調用會進行內核地址重定位,所以須要對檢測結果進行一個誤報過濾,看是否全部的函數的gap都相同,若是相同,則說明是系統本身的function address relocation行爲

def hex2dec(string_num):
    return str(int(string_num.upper(), 16))

def check_rookit(diff_func_table):
    last_func_address_gap = 0
    cur_func_address_gap = 0
    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 != 0 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

相關文章
相關標籤/搜索