mirror of
https://github.com/valitydev/osquery-1.git
synced 2024-11-07 18:08:53 +00:00
Merge pull request #528 from facebook/linux-camb
Initial linux kernel instrumentation bits
This commit is contained in:
commit
c54a568af3
6
kernel/linux/.gitignore
vendored
Normal file
6
kernel/linux/.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
Module.symvers
|
||||
modules.order
|
||||
.tmp_versions*
|
||||
*.cmd
|
||||
*.mod.c
|
||||
*.ko
|
47
kernel/linux/Makefile
Normal file
47
kernel/linux/Makefile
Normal file
@ -0,0 +1,47 @@
|
||||
obj-m += camb.o
|
||||
camb-objs += main.o sysfs.o hash.o
|
||||
|
||||
# We need headers to build against a specific kernel version
|
||||
ifndef KDIR
|
||||
KDIR = /lib/modules/$(shell uname -r)/build
|
||||
# @echo "Using default kernel directory: ${KDIR}"
|
||||
endif
|
||||
|
||||
# If user specifies a System.map, get addresses from there
|
||||
ifdef SMAP
|
||||
OPTS += -DTEXT_SEGMENT_START="0x$(shell grep '\s\+T\s\+_stext\b' ${SMAP} | cut -f1 -d' ')"
|
||||
OPTS += -DTEXT_SEGMENT_END="0x$(shell grep '\s\+T\s\+_etext\b' ${SMAP} | cut -f1 -d' ')"
|
||||
OPTS += -DSYSCALL_BASE_ADDR="0x$(shell grep '\s\+R\s\+sys_call_table\b' ${SMAP} | cut -f1 -d' ')"
|
||||
|
||||
# Otherwise, they must be present on the build line
|
||||
else
|
||||
OPTS += -DTEXT_SEGMENT_START="${TEXT_SEGMENT_START}"
|
||||
OPTS += -DTEXT_SEGMENT_END="${TEXT_SEGMENT_END}"
|
||||
OPTS += -DSYSCALL_BASE_ADDR="${SYSCALL_BASE_ADDR}"
|
||||
endif
|
||||
|
||||
ifdef HIDE_ME
|
||||
OPTS += -DHIDE_ME
|
||||
camb-objs += hide.o
|
||||
endif
|
||||
|
||||
all:
|
||||
|
||||
ifndef SMAP
|
||||
ifndef TEXT_SEGMENT_START
|
||||
@echo "Missing parameter: TEXT_SEGMENT_START"
|
||||
@exit 1
|
||||
endif
|
||||
|
||||
ifndef TEXT_SEGMENT_END
|
||||
@echo "Missing parameter: TEXT_SEGMENT_END"
|
||||
@exit 1
|
||||
endif
|
||||
|
||||
ifndef SYSCALL_BASE_ADDR
|
||||
@echo "Missing parameter: SYSCALL_BASE_ADDR"
|
||||
@exit 1
|
||||
endif
|
||||
endif
|
||||
|
||||
$(MAKE) -C $(KDIR) M=$(shell pwd) EXTRA_CFLAGS="${OPTS}" modules
|
91
kernel/linux/hash.c
Normal file
91
kernel/linux/hash.c
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
/* Crypto */
|
||||
#include <linux/crypto.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <crypto/sha.h>
|
||||
|
||||
#include "hash.h"
|
||||
|
||||
unsigned char *kernel_text_hash(void) {
|
||||
return (unsigned char *) hash_data((void *) TEXT_SEGMENT_START,
|
||||
TEXT_SEGMENT_END - TEXT_SEGMENT_START);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generic function for performing a SHA-1 hash of a memory range
|
||||
*
|
||||
* @param data - Beginning memory address to perform hash
|
||||
* @param len - size in bytes of the address range to hash
|
||||
*
|
||||
* @return allocated buffer containing the hash string; or NULL upon error.
|
||||
*/
|
||||
unsigned char *hash_data(const void *data, size_t len) {
|
||||
struct scatterlist sg;
|
||||
struct hash_desc desc;
|
||||
size_t out_len = SHA1_DIGEST_SIZE * 2 + 1;
|
||||
unsigned char hashtext[SHA1_DIGEST_SIZE];
|
||||
unsigned char *hashtext_out = kmalloc(out_len, GFP_KERNEL);
|
||||
|
||||
if (!hashtext_out) {
|
||||
printk(KERN_INFO "Could not allocate space for hash\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sg_init_one(&sg, data, len);
|
||||
desc.flags = 0;
|
||||
desc.tfm = crypto_alloc_hash("sha1", 0, CRYPTO_ALG_ASYNC);
|
||||
|
||||
crypto_hash_init(&desc);
|
||||
crypto_hash_update(&desc, &sg, sg.length);
|
||||
crypto_hash_final(&desc, hashtext);
|
||||
|
||||
snprintf(hashtext_out,
|
||||
out_len,
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x"
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
hashtext[0], hashtext[1], hashtext[2], hashtext[3],
|
||||
hashtext[4], hashtext[5], hashtext[6], hashtext[7],
|
||||
hashtext[8], hashtext[9], hashtext[10], hashtext[11],
|
||||
hashtext[12], hashtext[13], hashtext[14], hashtext[15],
|
||||
hashtext[16], hashtext[17], hashtext[18], hashtext[19]
|
||||
);
|
||||
|
||||
if (desc.tfm) {
|
||||
crypto_free_hash(desc.tfm);
|
||||
}
|
||||
|
||||
return hashtext_out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Callback for the sysfs object read. This happens when a file is
|
||||
* read(2) (or equivalent) from within sysfs. E.g. cat /sys/foo/bar will
|
||||
* call bar's *_show callback method.
|
||||
*
|
||||
* @param obj - reference to a kernel object within the sysfs filesystem
|
||||
* @param attr - attribute of said kernel object
|
||||
* @param buf - buffer that will be allocated and filled with the hash
|
||||
*
|
||||
* @return size in bytes of the hash string; or -1 upon error.
|
||||
*/
|
||||
ssize_t text_segment_hash_show(struct kobject *obj,
|
||||
struct attribute *attr,
|
||||
char *buf) {
|
||||
ssize_t ret;
|
||||
char *hash = kernel_text_hash();
|
||||
|
||||
if (hash) {
|
||||
ret = scnprintf(buf, PAGE_SIZE, "%s\n", hash);
|
||||
kfree(hash);
|
||||
} else {
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
4
kernel/linux/hash.h
Normal file
4
kernel/linux/hash.h
Normal file
@ -0,0 +1,4 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
unsigned char *kernel_text_hash(void);
|
||||
unsigned char *hash_data(const void *, size_t);
|
26
kernel/linux/hide.c
Normal file
26
kernel/linux/hide.c
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include <linux/module.h>
|
||||
|
||||
#include "hide.h"
|
||||
|
||||
extern char *module_str;
|
||||
|
||||
void rm_mod_from_list(void) {
|
||||
THIS_MODULE->list.next->prev = THIS_MODULE->list.prev;
|
||||
THIS_MODULE->list.prev->next = THIS_MODULE->list.next;
|
||||
}
|
||||
|
||||
void rm_mod_from_sysfs(void) {
|
||||
kobject_del(THIS_MODULE->holders_dir->parent);
|
||||
}
|
||||
|
||||
void rm_mod_from_ddebug_tables(void) {
|
||||
ddebug_remove_module(module_str);
|
||||
}
|
||||
|
||||
void hide_me(void) {
|
||||
rm_mod_from_list();
|
||||
rm_mod_from_sysfs();
|
||||
rm_mod_from_ddebug_tables();
|
||||
}
|
6
kernel/linux/hide.h
Normal file
6
kernel/linux/hide.h
Normal file
@ -0,0 +1,6 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
void rm_mod_from_list(void);
|
||||
void rm_mod_from_sysfs(void);
|
||||
void rm_mod_from_ddebug_tables(void);
|
||||
void hide_me(void);
|
96
kernel/linux/main.c
Normal file
96
kernel/linux/main.c
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/fdtable.h>
|
||||
#include <linux/dcache.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/fcntl.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/dirent.h>
|
||||
#include <linux/reboot.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <asm/syscall.h>
|
||||
|
||||
#include "sysfs.h"
|
||||
#include "hash.h"
|
||||
#ifdef HIDE_ME
|
||||
#include "hide.h"
|
||||
#endif
|
||||
|
||||
extern struct kobject *camb_kobj;
|
||||
char *module_str = "camb";
|
||||
|
||||
static unsigned long **syscall_table = (unsigned long **) SYSCALL_BASE_ADDR;
|
||||
static unsigned long *syscall_table_copy[NR_syscalls];
|
||||
|
||||
/* Allow writes to executable memory pages */
|
||||
void en_mem_wr(void) {
|
||||
write_cr0(read_cr0() & (~0x10000));
|
||||
}
|
||||
|
||||
/* Disallow writes to executable memory pages */
|
||||
void dis_mem_wr(void) {
|
||||
write_cr0(read_cr0() | 0x10000);
|
||||
}
|
||||
|
||||
int syscall_addr_modified_show(struct kobject *obj,
|
||||
struct attribute *attr,
|
||||
char *buf) {
|
||||
unsigned int i = -1, mod = 0, ret;
|
||||
|
||||
while(++i < NR_syscalls)
|
||||
if (syscall_table[i] != syscall_table_copy[i])
|
||||
mod = 1;
|
||||
ret = scnprintf(buf, PAGE_SIZE, "%d\n", mod);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Copy the system call pointer table */
|
||||
void grab_syscall_table(void) {
|
||||
unsigned int i;
|
||||
for (i = 0; i < NR_syscalls; i++)
|
||||
syscall_table_copy[i] = syscall_table[i];
|
||||
}
|
||||
|
||||
static int __init camb_init(void) {
|
||||
printk(KERN_INFO "[%s] init\n", module_str);
|
||||
|
||||
if (expose_sysfs()) {
|
||||
printk(KERN_ERR "Cannot expose self to sysfs\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Hide the fact that we're monitoring the system for tampering */
|
||||
#ifdef HIDE_ME
|
||||
hide_me();
|
||||
#endif
|
||||
|
||||
grab_syscall_table();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit camb_exit(void) {
|
||||
printk(KERN_INFO "[%s] exit\n", module_str);
|
||||
|
||||
if (camb_kobj) {
|
||||
kobject_put(camb_kobj);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module_init(camb_init);
|
||||
module_exit(camb_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("@unixist");
|
||||
MODULE_DESCRIPTION("Detect kernel tampering");
|
49
kernel/linux/sysfs.c
Normal file
49
kernel/linux/sysfs.c
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include <linux/sysfs.h>
|
||||
#include <linux/kobject.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "hash.h"
|
||||
#include "sysfs.h"
|
||||
|
||||
struct kobject *camb_kobj;
|
||||
|
||||
extern ssize_t syscall_addr_modified_show(struct kobject *obj,
|
||||
struct attribute *attr,
|
||||
char *buf);
|
||||
extern ssize_t text_segment_hash_show(struct kobject *obj,
|
||||
struct attribute *attr,
|
||||
char *buf);
|
||||
|
||||
struct kobj_attribute attr_syscall_addr_modified =
|
||||
__ATTR(syscall_addr_modified, 0444, syscall_addr_modified_show, NULL);
|
||||
|
||||
struct kobj_attribute attr_text_segment_hash =
|
||||
__ATTR(text_segment_hash, 0444, text_segment_hash_show, NULL);
|
||||
|
||||
struct attribute *camb_attrs[] = {
|
||||
&attr_text_segment_hash.attr,
|
||||
&attr_syscall_addr_modified.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
struct attribute_group attr_group = {
|
||||
.attrs = camb_attrs
|
||||
};
|
||||
|
||||
int expose_sysfs(void) {
|
||||
int err = 0;
|
||||
camb_kobj = kobject_create_and_add("camb", kernel_kobj);
|
||||
if (camb_kobj) {
|
||||
if ((err = sysfs_create_group(camb_kobj, &attr_group)) != 0) {
|
||||
kobject_put(camb_kobj);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("@unixist");
|
||||
MODULE_DESCRIPTION("Detect kernel tampering");
|
3
kernel/linux/sysfs.h
Normal file
3
kernel/linux/sysfs.h
Normal file
@ -0,0 +1,3 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
int expose_sysfs(void);
|
@ -54,6 +54,7 @@ else()
|
||||
networking/linux/socket_inode.cpp
|
||||
networking/linux/port_inode.cpp
|
||||
networking/linux/arp_cache.cpp
|
||||
system/linux/kernel_integrity.cpp
|
||||
system/linux/kernel_modules.cpp
|
||||
system/linux/processes.cpp
|
||||
system/linux/users.cpp
|
||||
|
6
osquery/tables/specs/linux/kernel_integrity.table
Normal file
6
osquery/tables/specs/linux/kernel_integrity.table
Normal file
@ -0,0 +1,6 @@
|
||||
table_name("kernel_integrity")
|
||||
schema([
|
||||
Column("sycall_addr_modified", INTEGER),
|
||||
Column("text_segment_hash", TEXT),
|
||||
])
|
||||
implementation("kernel_integrity@genKernelIntegrity")
|
55
osquery/tables/system/linux/kernel_integrity.cpp
Normal file
55
osquery/tables/system/linux/kernel_integrity.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
#include <fstream>
|
||||
#include <boost/algorithm/string/trim.hpp>
|
||||
|
||||
#include <boost/lexical_cast.hpp>
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include <osquery/core.h>
|
||||
#include <osquery/tables.h>
|
||||
#include <osquery/database.h>
|
||||
#include <osquery/filesystem.h>
|
||||
|
||||
namespace osquery {
|
||||
namespace tables {
|
||||
|
||||
const std::string kKernelSyscallAddrModifiedPath = "/sys/kernel/camb/syscall_addr_modified";
|
||||
const std::string kKernelTextHashPath = "/sys/kernel/camb/text_segment_hash";
|
||||
|
||||
QueryData genKernelIntegrity(QueryContext &context) {
|
||||
QueryData results;
|
||||
Row r;
|
||||
std::string content;
|
||||
std::string text_segment_hash;
|
||||
std::string syscall_addr_modified;
|
||||
|
||||
// Get an integral value, 0 or 1, for whether a syscall table pointer is modified.
|
||||
auto f1 = osquery::readFile(kKernelSyscallAddrModifiedPath, content);
|
||||
if (f1.ok()) {
|
||||
boost::trim(content);
|
||||
syscall_addr_modified = content;
|
||||
} else {
|
||||
VLOG(1) << "Cannot read file: " << kKernelSyscallAddrModifiedPath;
|
||||
return results;
|
||||
}
|
||||
|
||||
// Get the hash value for the kernel's .text memory segment
|
||||
auto f2 = osquery::readFile(kKernelTextHashPath, content);
|
||||
if (f2.ok()) {
|
||||
boost::trim(content);
|
||||
text_segment_hash = content;
|
||||
} else {
|
||||
VLOG(1) << "Cannot read file: " << kKernelTextHashPath;
|
||||
return results;
|
||||
}
|
||||
|
||||
r["sycall_addr_modified"] = syscall_addr_modified;
|
||||
r["text_segment_hash"] = text_segment_hash;
|
||||
results.push_back(r);
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user