Merge pull request #528 from facebook/linux-camb

Initial linux kernel instrumentation bits
This commit is contained in:
Sean Williams 2014-12-29 14:20:54 -08:00
commit c54a568af3
12 changed files with 390 additions and 0 deletions

6
kernel/linux/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
Module.symvers
modules.order
.tmp_versions*
*.cmd
*.mod.c
*.ko

47
kernel/linux/Makefile Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,3 @@
// Copyright 2004-present Facebook. All Rights Reserved.
int expose_sysfs(void);

View File

@ -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

View File

@ -0,0 +1,6 @@
table_name("kernel_integrity")
schema([
Column("sycall_addr_modified", INTEGER),
Column("text_segment_hash", TEXT),
])
implementation("kernel_integrity@genKernelIntegrity")

View 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;
}
}
}