Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hooks, ftrace, reflective symbols extractor, etc. #23

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ modules.order
Module.symvers
Mkfile.old
dkms.conf
.idea/
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ however adding this snapshot module will still be a small improvement.

## Usage

Load it using `./load.sh`, unload it using `./unload.sh`.
!WARNING! This LKM is in alpha testing state. DO NOT LOAD IT ON YOU'RE REAL MACHINE WITHOUT TESTING!!!
!DANGER!! It can crash the kernel and you will lose all you're unsaved data (open tabs, notes, etc.)

At the moment it builds and run ok on at least:
Debian buster Linux stand 4.19.160 #2 SMP Mon Dec 28 11:58:39 EET 2020 x86_64 GNU/Linux
Debian bullseye Linux l0c4lh05t 5.10.24 #8 SMP Sun Jun 13 01:31:09 EEST 2021 x86_64 GNU/Linux
Both on real hardware and under qemu vm.

`./load.sh` will compile the module for you, you need also python3.

While the module is loaded, [AFL++](https://github.com/AFLplusplus/AFLplusplus)
will detect it and automatically switch from fork() to snapshot mode.
Expand Down Expand Up @@ -93,5 +98,16 @@ Remove the snapshot, you can not call `afl_snapshot_take` in another program poi

+ support for multithreaded applications
+ file descriptors state restore (lseek)
+ switch from kprobe to ftrace for hooking (faster)

+ switch from ftrace to jmp for hooking (faster)
+ add support of tasks snapshot control from other process (can be achived via find_vpid(pid))

### Chandgelog

v1.1.0:
+ Add ftrace support
+ Add reflective symbols extractor (work on 5.10+)
+ Fix horrible bug which fault on do_exit_group() because of invalid return size (long/int) -- try make it universal (reflective)
+ Minimal security fixes like do NOT trying to insert LKM after building...

v1.0.0:
+ Initial release
2 changes: 1 addition & 1 deletion include/libaflsnapshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ int afl_snapshot_init();
void afl_snapshot_exclude_vmrange(void *start, void *end);
void afl_snapshot_include_vmrange(void *start, void *end);
int afl_snapshot_do(void);
int afl_snapshot_take(int config);
int afl_snapshot_take(int pid, unsigned long config);
void afl_snapshot_restore(void);
void afl_snapshot_clean(void);

Expand Down
6 changes: 4 additions & 2 deletions lib/libaflsnapshot.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@ void afl_snapshot_include_vmrange(void *start, void *end) {

}

int afl_snapshot_take(int config) {
int afl_snapshot_take(int pid, unsigned long config) {

return ioctl(dev_fd, AFL_SNAPSHOT_IOCTL_TAKE, config);
/* high half of config can't be used! */
if(config >> 0x20) return -1;
return ioctl(dev_fd, AFL_SNAPSHOT_IOCTL_TAKE, (((long)pid) << 0x20) | config);

}

Expand Down
3 changes: 2 additions & 1 deletion load.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ cd src/

rmmod afl_snapshot
make
insmod afl_snapshot.ko && echo Successfully loaded the snapshot module
echo "DO NOT INSERT THIS LKM IN YOU'RE REAL MACHINE WITHOUT TESTING! YOU CAN LOSE YOU'RE RUNTIME DATA!!!"
#insmod afl_snapshot.ko && echo Successfully loaded the snapshot module
35 changes: 12 additions & 23 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,46 +9,35 @@ else
ARCH := ia32
endif

MAP = /boot/System.map-$(shell uname -r)
SYMS = /proc/kallsyms

ifndef LINUX_SYSTEM_MAP
ifdef KALLSYMS
override LINUX_SYSTEM_MAP = /proc/kallsyms
else
ifeq "$(shell test -r $(MAP) && echo 1)" "1"
override LINUX_SYSTEM_MAP = $(MAP)
else
ifeq "$(shell test -r $(SYMS) && echo 1)" "1"
override LINUX_SYSTEM_MAP = $(SYMS)
else
$(error no readable $(MAP) and no readable $(SYMS) found)
endif
endif
endif
endif
#MAP = /opt/DEBIAN/fs/boot/System.map-4.19.160
#/boot/System.map-$(shell uname -r)
#SYMS = /opt/DEBIAN/fs/proc/kallsyms

obj-m += afl_snapshot.o
afl_snapshot-objs := memory.o files.o threads.o task_data.o snapshot.o hook.o module.o


ccflags-y := \
-ggdb3 \
-std=gnu99 \
-Wframe-larger-than=1000000000 \
-Wframe-larger-than=100000000 \
-I$(M)/../include \
-Wno-declaration-after-statement \
$(CCFLAGS)
-fdata-sections \
$(CCFLAGS) \
-O0 -g3 -ggdb3

ifdef DEBUG
ccflags-y += -DDEBUG
endif

LINUX_DIR ?= /lib/modules/$(shell uname -r)/build
#LINUX_DIR ?= /lib/modules/$(shell uname -r)/build
LINUX_DIR ?= /opt/DEBIAN/stuff/linux-4.19.160
#/lib/modules/$(shell uname -r)/build

.PHONY: all

all:
env ARCH='$(ARCH)' LINUX_SYSTEM_MAP='$(LINUX_SYSTEM_MAP)' python3 lookup_symbols.py
#env ARCH='$(ARCH)' LINUX_SYSTEM_MAP='$(LINUX_SYSTEM_MAP)' python3 lookup_symbols.py

$(MAKE) -C '$(LINUX_DIR)' M='$(M)' modules

Expand Down
2 changes: 1 addition & 1 deletion src/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include <linux/kern_levels.h>

/* Output macros */

//#define DEBUG DEBUG
#define HEXDUMP(type, prefix, ptr, size) \
do { \
\
Expand Down
2 changes: 1 addition & 1 deletion src/files.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ void take_files_snapshot(struct task_data *data) {
struct fdtable * fdt = rcu_dereference_raw(files->fdt);
int size, i;

size = (fdt->max_fds - 1) / BITS_PER_LONG + 1;
size = (fdt->max_fds - 1) / BITS_PER_LONG + 1; // NOLINT(cppcoreguidelines-narrowing-conversions)

if (data->snapshot_open_fds == NULL)
data->snapshot_open_fds =
Expand Down
175 changes: 175 additions & 0 deletions src/ftrace_helper.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@

// (Th3C4t) Switch from ftrace to jmp via prologue patch
// e9 3b c1 ff 7d jmp 0x7dffc140



/*
* Helper library for ftrace hooking kernel functions
* Author: Harvey Phillips ([email protected])
* License: GPL
* */

#include <linux/ftrace.h>
#include <linux/linkage.h>
#include <linux/slab.h>
#include <linux/uaccess.h>

#define HOOK(_name, _hook, _orig) \
{ \
.name = (_name), \
.function = (_hook), \
.original = (_orig), \
}

/* We need to prevent recursive loops when hooking, otherwise the kernel will
* panic and hang. The options are to either detect recursion by looking at
* the function return address, or by jumping over the ftrace call. We use the
* first option, by setting USE_FENTRY_OFFSET = 0, but could use the other by
* setting it to 1. (Oridinarily ftrace provides it's own protections against
* recursion, but it relies on saving return registers in $rip. We will likely
* need the use of the $rip register in our hook, so we have to disable this
* protection and implement our own).
* */
#define USE_FENTRY_OFFSET 0
#if !USE_FENTRY_OFFSET
#pragma GCC optimize("-fno-optimize-sibling-calls")
#endif
/* We pack all the information we need (name, hooking function, original function)
* into this struct. This makes is easier for setting up the hook and just passing
* the entire struct off to fh_install_hook() later on.
* */
struct ftrace_hook {
const char *name;
void *function;
void *original;

unsigned long address;
struct ftrace_ops ops;
};

/* Ftrace needs to know the address of the original function that we
* are going to hook. As before, we just use kallsyms_lookup_name()
* to find the address in kernel memory.
* */
static int fh_resolve_hook_address(struct ftrace_hook *hook)
{
hook->address = (long unsigned)_kallsyms_lookup_name((void*)hook->name);

if (!hook->address)
{
printk(KERN_DEBUG "AFL-kit: unresolved symbol: %s\n", hook->name);
return -ENOENT;
}

#if USE_FENTRY_OFFSET
*((unsigned long*) hook->original) = hook->address + MCOUNT_INSN_SIZE;
#else
*((unsigned long*) hook->original) = /*hook->address;*/ ( *(uint32_t*)hook->address == (uint32_t)0x00441f0f ) ?
(((uint64_t)hook->address) + 0x05) : (uint64_t)hook->address;
#endif

return 0;
}

/* See comment below within fh_install_hook() */
static void notrace fh_ftrace_thunk(unsigned long ip, unsigned long parent_ip, struct ftrace_ops *ops, struct pt_regs *regs)
{
struct ftrace_hook *hook = container_of(ops, struct ftrace_hook, ops);

#if USE_FENTRY_OFFSET
regs->ip = (unsigned long) hook->function;
#else
if(!within_module(parent_ip, THIS_MODULE))
regs->ip = (unsigned long) hook->function;
#endif
}

/* Assuming we've already set hook->name, hook->function and hook->original, we
* can go ahead and install the hook with ftrace. This is done by setting the
* ops field of hook (see the comment below for more details), and then using
* the built-in ftrace_set_filter_ip() and register_ftrace_function() functions
* provided by ftrace.h
* */
int fh_install_hook(struct ftrace_hook *hook)
{
int err;
err = fh_resolve_hook_address(hook);
if(err)
return err;

/* For many of function hooks (especially non-trivial ones), the $rip
* register gets modified, so we have to alert ftrace to this fact. This
* is the reason for the SAVE_REGS and IP_MODIFY flags. However, we also
* need to OR the RECURSION_SAFE flag (effectively turning if OFF) because
* the built-in anti-recursion guard provided by ftrace is useless if
* we're modifying $rip. This is why we have to implement our own checks
* (see USE_FENTRY_OFFSET). */
hook->ops.func = fh_ftrace_thunk;
hook->ops.flags = FTRACE_OPS_FL_SAVE_REGS
| FTRACE_OPS_FL_RECURSION_SAFE
| FTRACE_OPS_FL_IPMODIFY;

err = ftrace_set_filter_ip(&hook->ops, hook->address, 0, 0);
if(err){
printk(KERN_DEBUG "AFL-kit: ftrace_set_filter_ip() failed: %d\n", err);
return err;
}

err = register_ftrace_function(&hook->ops);
if(err){
printk(KERN_DEBUG "AFL-kit: register_ftrace_function() failed: %d\n", err);
return err;
}

return 0;
}

/* Disabling our function hook is just a simple matter of calling the built-in
* unregister_ftrace_function() and ftrace_set_filter_ip() functions (note the
* opposite order to that in fh_install_hook()).
* */
void fh_remove_hook(struct ftrace_hook *hook)
{
int err;
*((unsigned long*) hook->original) = hook->address;
err = unregister_ftrace_function(&hook->ops);
if(err)
printk(KERN_DEBUG "AFL-kit: unregister_ftrace_function() failed: %d\n", err);

err = ftrace_set_filter_ip(&hook->ops, hook->address, 1, 0);
if(err)
printk(KERN_DEBUG "AFL-kit: ftrace_set_filter_ip() failed: %d\n", err);
}

/* To make it easier to hook multiple functions in one module, this provides
* a simple loop over an array of ftrace_hook struct
* */
int fh_install_hooks(struct ftrace_hook *hooks, size_t count)
{
int err;
size_t i;

for (i = 0 ; i < count ; i++)
{
err = fh_install_hook(&hooks[i]);
if(err)
goto error;
}
return 0;

error:
while (i != 0)
{
fh_remove_hook(&hooks[--i]);
}
return err;
}

void fh_remove_hooks(struct ftrace_hook *hooks, size_t count)
{
size_t i;

for (i = 0 ; i < count ; i++)
fh_remove_hook(&hooks[i]);
}
8 changes: 4 additions & 4 deletions src/hook.c
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
#ifdef USE_KPROBES
#include <linux/kernel.h>
#include <linux/kprobes.h>
#include <linux/module.h>
#include <linux/slab.h>

// TODO(andrea) switch from Kprobes to Ftrace

struct hook {

struct kprobe kp;
Expand All @@ -15,8 +14,8 @@ struct hook {
LIST_HEAD(hooks);

int try_hook(const char *func_name, void *handler) {

struct hook *hook = kmalloc(sizeof(struct hook), GFP_KERNEL | __GFP_ZERO);
/* Sometimes non-atomic allocations can fall, when calling from userspace (init) context */
struct hook *hook = kmalloc(sizeof(struct hook), GFP_ATOMIC | __GFP_ZERO);
INIT_LIST_HEAD(&hook->l);
hook->kp.symbol_name = func_name;
hook->kp.pre_handler = handler;
Expand Down Expand Up @@ -53,3 +52,4 @@ void unhook_all(void) {
}

}
#endif
Loading