Linux的系统调用、网络连接状态、磁盘I/O;可疑行为监控/日志收集、SHELL命令执行流程

目录

  1. 引言
  2. 用户编程接口(API)、系统命令、系统调用、和内核函数的关系
  3. Linux的系统调用
  4. Linux Shell命令执行
  5. Linux系统调用日志收集
  6. Linux网络连接状态日志收集
  7. Linux磁盘I/O事件日志收集
  8. 后记

 

0. 引言

对系统调用、网络连接、磁盘文件操作、恶意rootkit的动态监控,是进行主机入侵恶意行为实时检测的一个很好的思考方向,作为纵深防御的一环,当黑客实际入侵了一台机器之后,我们怎样通过监控机制实时地发现入侵行为,显然,这需要在安全攻防的规律的基础上,结合操作系统的事件监控进行采集维度确定、事件数据采集、黑白名单数据源过滤、数据集中通道上传、基于规则/行为模式的入侵判别/分析。

一般来说,我们可以从服务器上采集的信息维度有:

11. 系统调用 2 1.1 Bash命令 3 黑客在获取了linux服务器的ssh、webshell之后,必然会执行一些bash命令进行提权、建立账户等猥琐操作 4 我们知道,对于Bash命令,大部分情况下是通过"do_execve系统调用(6个exec执行函数的总称)"执行环境变量PATH下的某个应用程序(有少部分的bash命令属于内建命令,不走系统调用流程) 5 关于shell命令执行,我们之后会学习到 6 1.2 网络连接 7 1) 正向连接 8 黑客很可能使用黑下来的主机充当DDOS、暴力破解的肉鸡,这些行为都会建立正向的网络连接,在这个过程中必然会调用到linux系统提供的特定系统调用。我们可以从中发现"以系统调用为数据源的行为轨迹" 9 2) 反向连接 10 反向连接一般被用在黑客建立"网络后门端口"时使用,方便2次入侵或者进行内网渗透。或者当被渗透的目标主机处在内网之中,而内网的边界路由器、防火墙又阻止外部客户端主动连接目标主机,这个时候,黑客就可以利用反 11向连接(例如nc)主动从内网向外部客户端发起连接,从而bypass目标系统边界的防火墙(基于路由规则的防火墙,例如iptables) 12 3) 隧道连接 13 隧道连接和反向连接很像,区别在于隧道连接是一个加密后的反向连接 14 152. 磁盘操作 16通过监控指定敏感目录的磁盘操作行为(例如www、tmp目录),可以防止从系统层面防止黑客对WEB漏洞的攻击和利用 17 1) webshell入侵 18 黑客可以通过cms的注入漏洞进行webshell的入侵 19 1.1) 通过修改update现有文件的getshell 20 1.2) 童鞋写入write新文件的getshell 21 2) 读写敏感文件 22 建立黑名单机制,对系统中关键的敏感路径进行监控,例如: 23 2.1) /etc/passwd 24 2.2) /etc/shadow 25 2.3) /etc/httpd/.. 26 3) 可执行文件 27 3.1) 通过在/tmp/目录上传exp、poc进行渗透提权 28 3.2) 通过上传DDOS、暴力密码破解工具进行对外肉鸡攻击 29对于攻击者来说,不管是上传exp、还是新建webshell后门、异或是读取机密文件,都会涉及到磁盘I/O操作,所以磁盘I/O操作的这块行为分析,也是我们的分析重点 30 31 323. Rootkit 33黑客在获取一台服务器的完全控制权之后,往往会在磁盘上留下rootkit,为了下次2次入侵、或者隐藏自己的入侵痕迹。我们可以通过: 34 1) 基于磁盘文件事件方式监控 35 通过对磁盘的inotify进行注册,每当有文件/文件夹创建的时候就自动进行检测 36 2) 每天定时监控 37 38

 

1. 用户编程接口(API)、系统命令、系统调用、和内核函数的关系

在研究恶意入侵行为检测的时候,我们可能会问自己这样的一个问题,黑客上传的exp使用的可能是函数库中的API、而在Bash中执行的指令又由shell负责解析执行,难道我们要分别对它们进行Hook监控吗?

答案是否定的,为了理解这个原因,我们需要先理清用户编程接口(API)、系统命令、系统调用、和内核函数的关系

1 21. 内核函数 3Linux系统内核由许多的代码模块组成,每个模块都会提供一些相应的的调用接口,我们在进行内核编程的时候,可以把这些接口看成是linux提供的SDK来使用。 4Linux系统中存在许多内核函数,有些是内核文件中自己使用的,有些则是可以export出来供内核其他部分共同使用的。内核公开的内核函数,export出来的可以使用命令ksyms 或 cat /proc/ksyms来查看 5 62. 系统调用 7操作系统(内核态)提供给用户程序(用户态)调用的一组"特殊"接口(属于一种内部中断)。用户程序(ring3)可以通过这组"特殊"接口来获得操作系统内核(ring0)提供的服务 8系统调用是一层用户进入内核的接口,它本身并非内核函数,进入内核后,不同的系统调用会找到对应到各自的内核函数(系统调用服务例程)。实际上针对请求提供服务的是内核函数而非调用接口 9比如系统调用 getpid实际上就是调用内核函数sys_getpid 10asmlinkage long sys_getpid(void) 11{ 12 return current->tpid; 13} 14 15 163. 用户编程接口(API) 17用户编程接口其实是一个函数定义,就是我们常说的C库,说明了如何获得一个给定的服务,比如read( )、malloc( )、free( )、abs( )等。它有可能和系统调用形式上一致,比如read()接口就和read系统调用对应,但这种对应并 18非一一对应,往往会出现: 19 1) 几种不同的API内部用到同一个系统调用,比如malloc( )、free( )内部利用brk( )系统调用来扩大或缩小进程的堆 20 2) 一个API利用了好几个系统调用组合完成服务 21 3) 有些API甚至不需要任何系统调用——因为它并不是必需要使用内核服务,如计算整数绝对值的 abs()接口 22值得注意的是Linux的用户编程接口遵循了在Unix世界中最流行的应用编程界面标准——POSIX标准,这套标准定义了一系列API。在Linux中(Unix也如此),这些API主要是通过C库(libc)实现的,它除了定义的一些标准的C函数外,一个 23很重要的任务就是提供了一套封装例程(wrapper routine)将系统调用在用户空间包装后供用户编程使用 24 254. 系统命令 26系统命令即Bash Shell命令,系统管理员通过bash输入命令,然后由bash通过API调用系统调用,最后通过内核函数完成相应功能。 27例如pwd、uname等系统命令 28

总体来说,它们的先后顺序是这样的:

1BASH系统命令 ->API接口 ->系统调用 ->内核函数 2     ->系统调用 ->内核函数 3//程序员可以选择使用C库进行编程,或者直接调用系统调用进行编程 4

们看个例子来对比一下通过C库调用和直接调用的区别

1#include <syscall.h> 2#include <unistd.h> 3#include <stdio.h> 4#include <sys/types.h> 5 6int main(void) 7{ 8 long ID1, ID2; 9 /*-----------------------------*/ 10 /* 直接系统调用*/ 11 /* SYS_getpid (func no. is 20) */ 12 /*-----------------------------*/ 13 ID1 = syscall(SYS_getpid); 14 printf ("syscall(SYS_getpid)=%ld\n", ID1); 15 16 /*-----------------------------*/ 17 /* 使用"libc"封装的系统调用 */ 18 /* SYS_getpid (Func No. is 20) */ 19 /*-----------------------------*/ 20 ID2 = getpid(); 21 printf ("getpid()=%ld\n", ID2); 22 23 return(0); 24} 25

总结来说,不管是执行程序(exec)、还是bash命令执行,最终都需要通过系统调用来实现,所以我们只需要对do_execve()进行Hook监控就可以实现对命令、程序执行这块的监控

 

2. Linux的系统调用

0x1: 系统调用基本概念

从逻辑上来说,系统调用可被看成是一个内核(ring0)与用户空间(ring3)程序交互的接口,它好比一个中间人,把用户进程的请求传达给内核,待内核把请求处理完毕后再将处理结果送回给用户空间。
系统服务之所以需要通过系统调用来提供给用户空间的根本原因是为了对系统进行"保护",因为我们知道Linux的运行空间分为内核空间(ring0)和用户空间(ring3)

11) 内核空间(ring0) 22) 用户空间(ring3) 3

它们各自运行在不同的级别中,逻辑上相互隔离,所以用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间函数

但是很多情况下,用户进程需要获得系统服务(调用系统内核函数),这时就必须利用系统提供给用户的"特殊接口",即系统调用。
它的特殊性主要在于规定了用户进程进入内核的具体位置,换句话说,用户访问内核的路径是事先规定好的,只能从规定位置进入内核(即陷入点trap),而不准许肆意跳入内核。有了这样的陷入内核的统一访问路径限制才能保证内核安全

0x2: 系统调用实现

系统调用是一个内部陷入(trap)中断,系统调用的中断号是0x80,int 0x80指令的目的是产生一个编号为0x80的编程异常,这个编程异常对应的是中断描述符表IDT中的第128项,也就是对应的系统门描述符。
门描述符中含有一个预设的内核空间地址,它指向了系统调用处理程序:system_call(),很显然,所有的系统调用都会统一地转到这个地址,但Linux一共有2、3百个系统调用都从这里进入内核后又该如何派发到它们到各自的服务程序去呢?
要理解这个问题,我们就必须明白我们在使用系统调用时都要遵循哪些步骤(一般是C库会帮我们处理好系统调用的细节,程序员面对的只是相对简单的C库API)

11. 程序员在编程中的使用的API实现了对系统调用的封装和包装,例如write()就是对sys_write()的包装,每个包装了系统调用的API都对应有一个"系统调用号" 22. 从应用层ring3进入系统调用需要借助linux系统提供的中断机制 33. Linux中实现系统调用利用了0x86体系结构中的软件中断。int 0x80指令的目的是产生一个编号为128的编程异常,这个编程异常对应的是中断描述符表IDT中的第128项,也就是对应的系统门描述符 44. 门描述符中含有一个预设的内核空间地址,它指向了系统调用处理程序: system_call()(注意,这是"所有"系统调用的总入口,别和系统调用服务程序混淆,这个程序在entry.S文件中用汇编语言编写) 55. 首先Linux为每个系统调用都进行了编号(0~NR_syscall),同时在内核中保存了一张系统调用表,该表中保存了系统调用编号和其对应的服务例程 66. 在系统调入通过系统门陷入内核前,需要把系统调用号一并传入内核,在x86上,这个传递动作是通过在执行int 0x80前把调用号装入eax寄存器实现的 77. 这样系统调用处理程序一旦运行,就可以从eax中得到数据,然后再去系统调用表中寻找相应服务例程了。 88. 除了需要传递系统调用号以外,许多系统调用还需要传递一些参数到内核,比如 9sys_write(unsigned int fd, const char * buf, size_t count) 10调用就需要传递文件描述符fd、要写入的内容buf、以及写入字节数count等几个内容到内核。 11碰到这种情况,Linux会有6个寄存器可被用来传递这些参数:eax(存放系统调用号)、 ebx、ecx、edx、esi及edi来存放这些额外的参数(以字母递增的顺序)。具体做法是在system_call( )中使用SAVE_ALL宏把这些寄存器的值保 12存在内核态堆栈中 139. 有始便有终,当服务例程结束时,system_call()从eax获得系统调用的返回值,并把这个返回值存放在曾保存用户态eax寄存器栈单元的那个位置上。然后跳转到ret_from_sys_call(),终止系统调用处理程序的执行 1410. 当进程恢复它在用户态的执行前,RESTORE_ALL宏会恢复用户进入内核前被保留到堆栈中的寄存器值。其中eax返回时会带回系统调用的返回码(负数说明调用错误,0或正数说明正常完成) 15

完成了以上的各个步骤后,我们在写C代码时编写的一个个C库API调用就通过中断机制被系统正确地解析成一个个对应的系统调用,调用相应的系统子程序,完成我们要求的功能。

0x3: 系统调用分类

1系统调用主要分为以下几类: 2 3 41. 控制硬件 5系统调用往往作为硬件资源和用户空间的抽象接口 6 72. 设置系统状态或读取内核数据 8系统调用是用户空间和内核的唯一通讯手段,所以用户设置系统状态,比如开/关某项内核服务(设置某个内核变量),或读取内核数据都必须通过系统调用。比如getpgid、getpriority、setpriority、sethostname 9 2.1 系统控制 10 1) ioctl: I/O总控制函数 11 2) _sysctl: 读/写系统参数 12 3) acct 启用或禁止进程记账 13 4) getrlimit: 获取系统资源上限 14 5) setrlimit: 设置系统资源上限 15 6) getrusage: 获取系统资源使用情况 16 7) uselib: 选择要使用的二进制函数库 17 8) ioperm: 设置端口I/O权限 18 9) iopl 改变进程I/O权限级别 19 10) outb: 低级端口操作 20 11) reboot: 重新启动 21 12) swapon: 打开交换文件和设备 22 13) swapoff: 关闭交换文件和设备 23 14) bdflush: 控制bdflush守护进程 24 15) sysfs: 取核心支持的文件系统类型 25 16) sysinfo: 取得系统信息 26 17) adjtimex: 调整系统时钟 27 18) alarm: 设置进程的闹钟 28 19) getitimer: 获取计时器值 29 20) setitimer: 设置计时器值 30 21) gettimeofday: 取时间和时区 31 22) settimeofday: 设置时间和时区 32 23) stime: 设置系统日期和时间 33 24) time: 取得系统时间 34 25) times: 取进程运行时间 35 26) uname: 获取当前UNIX系统的名称、版本和主机等信息 36 27) vhangup: 挂起当前终端 37 28) nfsservctl: 对NFS守护进程进行控制 38 29) vm86: 进入模拟8086模式 39 30) create_module: 创建可装载的模块项 40 31) delete_module: 删除可装载的模块项 41 32) init_module: 初始化模块 42 33) query_module: 查询模块信息 43 34) *get_kernel_syms: 取得核心符号,已被query_module代替 44 2.2 用户管理 45 1) getuid: 获取用户标识号 46 2) setuid: 设置用户标志号 47 3) getgid: 获取组标识号 48 4) setgid: 设置组标志号 49 5) getegid: 获取有效组标识号 50 6) setegid: 设置有效组标识号 51 7) geteuid: 获取有效用户标识号 52 8) seteuid: 设置有效用户标识号 53 9) setregid: 分别设置真实和有效的的组标识号 54 10) setreuid: 分别设置真实和有效的用户标识号 55 11) getresgid: 分别获取真实的,有效的和保存过的组标识号 56 12) setresgid: 分别设置真实的,有效的和保存过的组标识号 57 13) getresuid: 分别获取真实的,有效的和保存过的用户标识号 58 14) setresuid: 分别设置真实的,有效的和保存过的用户标识号 59 15) setfsgid: 设置文件系统检查时使用的组标识号 60 16) setfsuid: 设置文件系统检查时使用的用户标识号 61 17) getgroups: 获取后补组标志清单 62 18) setgroups: 设置后补组标志清单 63 643. 进程管理 65系统调用接口是用来保证系统中进程能以多任务在虚拟内存环境下得以运行。比如 fork、clone、execve、exit等 66 1) fork: 创建一个新进程 67 2) clone: 按指定条件创建子进程 68 3) execve: 运行可执行文件(重要) 69 4) exit: 中止进程 70 5) _exit: 立即中止当前进程 71 6) getdtablesize: 进程所能打开的最大文件数 72 7) getpgid: 获取指定进程组标识号 73 8) setpgid: 设置指定进程组标志号 74 9) getpgrp: 获取当前进程组标识号 75 10) setpgrp: 设置当前进程组标志号 76 11) getpid: 获取进程标识号 77 12) getppid: 获取父进程标识号 78 13) getpriority: 获取调度优先级 79 14) setpriority: 设置调度优先级 80 15) modify_ldt: 读写进程的本地描述表 81 16) nanosleep: 使进程睡眠指定的时间 82 17) nice: 改变分时进程的优先级 83 18) pause: 挂起进程,等待信号 84 19) personality: 设置进程运行域 85 20) prctl: 对进程进行特定操作 86 21) ptrace: 进程跟踪 87 22) sched_get_priority_max: 取得静态优先级的上限 88 23) sched_get_priority_min: 取得静态优先级的下限 89 24) sched_getparam: 取得进程的调度参数 90 25) sched_getscheduler: 取得指定进程的调度策略 91 26) sched_rr_get_interval: 取得按RR算法调度的实时进程的时间片长度 92 27) sched_setparam: 设置进程的调度参数 93 28) sched_setscheduler: 设置指定进程的调度策略和参数 94 29) sched_yield: 进程主动让出处理器,并将自己等候调度队列队尾 95 30) vfork: 创建一个子进程,以供执行新程序,常与execve等同时使用 96 31) wait: 等待子进程终止 97 32) wait3: 参见wait 98 33) waitpid: 等待指定子进程终止 99 34) wait4: 参见waitpid 100 35) capget: 获取进程权限 101 36) capset: 设置进程权限 102 37) getsid: 获取会晤标识号 103 38) setsid: 设置会晤标识号 104 1054. 文件/文件系统 106 4.1 文件操作 107 1) fcntl: 文件控制 108 2) open: 打开文件 109 3) creat: 创建新文件 110 4) close: 关闭文件描述字 111 5) read: 读文件 112 6) write: 写文件 113 7) readv: 从文件读入数据到缓冲数组中 114 8) writev: 将缓冲数组里的数据写入文件 115 9) pread: 对文件随机读 116 10) pwrite: 对文件随机写 117 11) lseek: 移动文件指针 118 12) _llseek: 在64位地址空间里移动文件指针 119 13) dup: 复制已打开的文件描述字 120 14) dup2: 按指定条件复制文件描述字 121 15) flock: 文件加/解锁 122 16) poll: I/O多路转换 123 17) truncate: 截断文件 124 18) ftruncate: 参见truncate 125 19) umask: 设置文件权限掩码 126 20) fsync: 把文件在内存中的部分写回磁盘 127 4.2 文件系统操作 128 1) access: 确定文件的可存取性 129 2) chdir: 改变当前工作目录 130 3) fchdir: 参见chdir 131 4) chmod: 改变文件方式 132 5) fchmod: 参见chmod 133 6) chown: 改变文件的属主或用户组 134 7) fchown: 参见chown 135 8) lchown: 参见chown 136 9) chroot: 改变根目录 137 10) stat: 取文件状态信息 138 11) lstat: 参见stat 139 12) fstat: 参见stat 140 13) statfs: 取文件系统信息 141 14) fstatfs: 参见statfs 142 15) readdir: 读取目录项 143 16) getdents: 读取目录项 144 17) mkdir: 创建目录 145 18) mknod: 创建索引节点 146 19) rmdir: 删除目录 147 20) rename: 文件改名 148 21) link: 创建链接 149 22) symlink: 创建符号链接 150 23) unlink: 删除链接 151 24) readlink: 读符号链接的值 152 25) mount: 安装文件系统 153 26) umount: 卸下文件系统 154 27) ustat: 取文件系统信息 155 28) utime: 改变文件的访问修改时间 156 29) utimes: 参见utime 157 30) quotactl: 控制磁盘配额 158 1595. 内存操作 160 1) brk: 改变数据段空间的分配 161 2) sbrk: 参见brk 162 3) mlock: 内存页面加锁 163 4) munlock: 内存页面解锁 164 5) mlockall: 调用进程所有内存页面加锁 165 6) munlockall: 调用进程所有内存页面解锁 166 7) mmap: 映射虚拟内存页 167 8) munmap: 去除内存页映射 168 9) mremap: 重新映射虚拟内存地址 169 10) msync: 将映射内存中的数据写回磁盘 170 11) mprotect: 设置内存映像保护 171 12) getpagesize: 获取页面大小 172 13) sync: 将内存缓冲区数据写回硬盘 173 14) cacheflush: 将指定缓冲区中的内容写回磁盘 174 1756. 网络管理 176 1) getdomainname: 取域名 177 2) setdomainname: 设置域名 178 3) gethostid: 获取主机标识号 179 4) sethostid: 设置主机标识号 180 5) gethostname: 获取本主机名称 181 6) sethostname: 设置主机名称 182 7) socketcall: socket系统调用 183 8) socket: 建立socket 184 9) bind: 绑定socket到端口 185 10) connect: 连接远程主机 186 11) accept: 响应socket连接请求 187 12) send: 通过socket发送信息 188 13) sendto: 发送UDP信息 189 14) sendmsg: 参见send 190 15) recv: 通过socket接收信息 191 16) recvfrom: 接收UDP信息 192 17) recvmsg: 参见recv 193 18) listen: 监听socket端口 194 19) select: 对多路同步I/O进行轮询 195 20) shutdown: 关闭socket上的连接 196 21) getsockname: 取得本地socket名字 197 22) getpeername: 获取通信对方的socket名字 198 23) getsockopt: 取端口设置 199 24) setsockopt: 设置端口参数 200 25) sendfile: 在文件或端口间传输数据 201 26) socketpair: 创建一对已联接的无名socket 202 2037. 进程间通信 2041) ipc: 进程间通信总控制调用 205 7.1 信号 206 1) sigaction: 设置对指定信号的处理方法 207 2) sigprocmask: 根据参数对信号集中的信号执行阻塞/解除阻塞等操作 208 3) sigpending: 为指定的被阻塞信号设置队列 209 4) sigsuspend: 挂起进程等待特定信号 210 5) signal: 参见signal 211 6) kill: 向进程或进程组发信号 212 7) *sigblock: 向被阻塞信号掩码中添加信号,已被sigprocmask代替 213 8) *siggetmask: 取得现有阻塞信号掩码,已被sigprocmask代替 214 9) *sigsetmask: 用给定信号掩码替换现有阻塞信号掩码,已被sigprocmask代替 215 10) *sigmask: 将给定的信号转化为掩码,已被sigprocmask代替 216 11) *sigpause: 作用同sigsuspend,已被sigsuspend代替 217 12) sigvec: 为兼容BSD而设的信号处理函数,作用类似sigaction 218 13) ssetmask: ANSI C的信号处理函数,作用类似sigaction 219 7.2 消息 220 1) msgctl: 消息控制操作 221 2) msgget: 获取消息队列 222 3) msgsnd: 发消息 223 4) msgrcv: 取消息 224 7.3 管道 225 1) pipe: 创建管道 226 7.4 信号量 227 1) semctl: 信号量控制 228 2) semget: 获取一组信号量 229 3) semop: 信号量操作 230 7.5 共享内存 231 1) shmctl: 控制共享内存 232 2) shmget: 获取共享内存 233 3) shmat: 连接共享内存 234 4) shmdt: 拆卸共享内存系统调用是中断中的一种,属于陷入内中断(或者叫软中断) 235 236

 

3. Linux Shell命令执行

0x1: Shell简介

Linux系统的shell作为操作系统的外壳,为用户提供使用操作系统的接口。它是命令语言、命令解释程序及程序设计语言的统称。 

11. 命令语言 2从编程的角度来看,shell是一种命令导向的可编程语言 3 42. 命令解释语言 5shell是一个命令语言解释器,它拥有自己内建的shell命令集,shell也能被系统中其他应用程序所调用。用户在提示符下输入的命令都由shell先解释然后传给Linux核心。 6shell命令的可分为: 7 1) 内部命令 8 内部命令是shell内建的命令,比如改变工作目录命令cd 9 2) PATH系统变量路径下的某个应用程序(大多数情况下都是这种情况) 10 对于这类命令,shell只是起一个传达和启动的作用,shell负责寻找并以子进程的形式启动这些程序 11 123. 程序设计语言 13shell程序设计语言支持绝大多数在高级语言中能见到的程序元素,如函数、变量、数组和程序控制结构。shell编程语言简单易学,任何在提示符中能键入的命令都能放到一个可执行的shell程序中。 14

0x2: Shell执行程序步骤

1它的执行过程基本上按如下步骤: 2 3 41. 读取用户由键盘输入的命令行 52. 分析命令,以命令名作为文件名,并将其它参数改造为系统调用execve()内部处理所要求的形式。 63. 终端进程调用fork()建立一个子进程,这只是创建一个进程的空壳,包括一些必要的内核对象句柄,并没有实际运行进程 74. 终端进程本身用系统调用wait4()来等待子进程完成(如果是后台命令,则不等待)8当子进程运行时调用execve(),子进程根据文件名(即命令名)到目录中查找有关文件(这是命令解释程序构成的文件),将它调入内存,执行这个程序(解释这条命令) 95. 如果命令末尾有&(后台命令符号),则终端进程不用系统调用wait4()等待,立即发提示符,让用户输入下一个命令,转⑴。 10如果命令末尾没有&号,则终端进程要一直等待。 11当子进程(即运行命令的进程)完成处理后终止,向父进程(终端进程)报告,此时终端进程醒来,在做必要的判别等工作后,终端进程发提示符,让用户输入新的命令,重复上述处理过程。 12

用户在登录到Linux时由/etc/passwd文件来决定要使用哪个shell 

1fgrep zhenghan /etc/passwd 2zhenghan:x:500:500:Red Hat Enterprise Linux 6 32位:/home/zhenghan:/bin/bash 3//表明"zhenghan"这个用户的默认shell是/bin/bash 4

0x3: Linux Shell分类

Linux中的shell有多种类型

11. Bourne shell(sh) 2Bourne shell是UNIX最初使用的shell,并且在每种UNIX上都可以使用。Bourne shell在shell编程方面相当优秀,但在处理与用户的交互方面做得不如其他几种shell 3 42. Bourne Again shell(Bash)(默认) 5Linux操作系统缺省的shell是Bourne Again shell,它是Bourne shell的扩展,简称Bash,与Bourne shell完全向后兼容,并且在Bourne shell的基础上增加、增强了很多特性,可以提供如: 6 1) 命令补全 7 2) 命令编辑 8 3) 命令历史表 9 4) 包含了很多C shell和Korn shell中的优点 10 5) 有灵活和强大的编程接口 11 6) 同时又有很友好的用户界面 12 133. C shell(csh) 14C shell是一种比Bourne shell更适于编程的shell,它的语法与C语言很相似。 Linux为喜欢使用C shell的人提供了Tcsh。 15Tcsh是C shell的一个扩展版本。Tcsh包括: 16 1) 命令行编辑 17 2) 可编程单词补全 18 3) 拼写校正 19 4) 历史命令替换 20 5) 作业控制和类似C语言的语法 21 6) 它不仅和Bash shell是提示符兼容,而且还提供比Bash shell更多的提示符参数 22 234. Korn shell(ksh) 24Korn shell集合了C shell和Bourne shell的优点并且和Bourne shell完全兼容。 25Linux系统提供了pdksh(ksh的扩展),它支持任务控制,可以在命令行上挂起、后台执行、唤醒或终止程序 26

 

4. Linux系统调用日志收集

监控系统调用就是对"do_execve系统调用"进行注册监控回调,在本例中,我们监控的事件维度有:

11. path: 文件路径 22. uid: 执行当前系统调用的用户的UID 33. tid: 当前系统调用的TID(任务) 44. pid: 调用当前系统调用的进程的PID 55. tty: 当前用户所连接的tty 66. filename: 指令名 7

do_execvelog.c

1#include <linux/module.h> 2#include <linux/kprobes.h> 3#include <linux/slab.h> 4#include <linux/tty.h> 5 6 7static int execlog_do_execve(char *filename, char __user *__user *__argv, char __user *__user *__envp, struct pt_regs *not_used) 8{ 9 char tty[64]; 10 char __user *__user *__temp_argv; 11 char __user *__argv_part; 12 size_t argv_size = 2; 13 char *argv, *write_argv; 14 long written_argv; 15 16 __temp_argv = __argv; 17 while (get_user(__argv_part, __temp_argv) == 0 && __argv_part != NULL) 18 { 19 argv_size += strlen_user(__argv_part) + 1; 20 ++__temp_argv; 21 } 22 23 argv = kmalloc(argv_size, GFP_ATOMIC); 24 if (unlikely(argv == NULL)) 25 { 26 goto out; 27 } 28 29 __temp_argv = __argv; 30 write_argv = argv; 31 while (get_user(__argv_part, __temp_argv) == 0 && __argv_part != NULL) 32 { 33 written_argv = strncpy_from_user(write_argv, __argv_part, argv_size); 34 if (unlikely(written_argv < 0)) 35 { 36 goto free; 37 } 38 write_argv += written_argv; 39 *(write_argv++) = ' '; 40 argv_size -= (written_argv + 1); 41 ++__temp_argv; 42 } 43 *write_argv = '\0'; 44 45 printk(KERN_DEBUG KBUILD_MODNAME ": [path:%s uid:%d tid:%d pid:%d tty:%s filename:%s]: %s\n", 46 47 current->comm, current_uid(), current->pid, task_session_vnr(current), tty_name(current->signal->tty, tty), filename, argv); 48free: 49 kfree(argv); 50out: 51 jprobe_return(); 52 return 0; 53} 54 55static int signal_that_will_cause_exit(int trap_number) 56{ 57 switch(trap_number) 58 { 59 case SIGABRT: 60 case SIGSEGV: 61 case SIGQUIT: 62 //TODO Other signals that we need to handle? 63 return 1; 64 break; 65 default: 66 return 0; 67 break; 68 } 69} 70 71static int handler_fault(struct kprobe *p, struct pt_regs *regs, int trap_number) 72{ 73 if(signal_that_will_cause_exit(trap_number)) 74 { 75 printk(KERN_ERR KBUILD_MODNAME ": fault handler: Detected fault %d from inside probes.", trap_number); 76 } 77 78 return 0; 79} 80 81 82static struct jprobe execve_jprobe = 83{ 84 .entry = (kprobe_opcode_t *) execlog_do_execve, 85 .kp = 86 { 87 .symbol_name = "do_execve", 88 .fault_handler = handler_fault, 89 }, 90}; 91 92 93static int __init plant_probes(void) 94{ 95 int err; 96 97 err = register_jprobe(&execve_jprobe); 98 if(err < 0) 99 { 100 printk(KERN_ERR KBUILD_MODNAME ":\t[-] Failed to plant execve pre handler\n"); 101 return -1; 102 } 103 104 printk(KERN_INFO KBUILD_MODNAME ":\t[+] Planted execve pre handler\n"); 105 printk(KERN_INFO KBUILD_MODNAME ":\t[+] Deployed\n"); 106 107 return 0; 108} 109 110 111static void __exit unplant_probes(void) 112{ 113 unregister_jprobe(&execve_jprobe); 114 printk(KERN_INFO KBUILD_MODNAME ":\t[+] Unplanted execve pre handler probe\n"); 115} 116 117 118MODULE_LICENSE("GPL"); 119MODULE_AUTHOR("Alibaba"); 120MODULE_DESCRIPTION("execve monitor module"); 121 122//初始化内核模块 123module_init(plant_probes); 124module_exit(unplant_probes); 125

Makefile

1# 2# Variables needed to build the kernel module 3# 4name = do_execvelog 5 6obj-m += $(name).o 7 8all: build 9 10.PHONY: build install clean 11 12build: 13 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules CONFIG_DEBUG_SECTION_MISMATCH=y 14 15install: build 16 -mkdir -p /lib/modules/`uname -r`/kernel/arch/x86/kernel/ 17 cp $(name).ko /lib/modules/`uname -r`/kernel/arch/x86/kernel/ 18 depmod /lib/modules/`uname -r`/kernel/arch/x86/kernel/$(name).ko 19 20clean: 21 [ -d /lib/modules/$(shell uname -r)/build ] && \ 22 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 23

编译并加载内核模块

1make 2insmod do_execvelog.ko 3

测试效运行果

编写的内核模块成功地对所有的"do_execve系统调用(程序、指令执行)"进行了监控

 

5. Linux网络连接状态日志收集

对linux网络连接状态的监控就是对"inet_stream_connect系统调用"进行注册监控回调,在本例中,我们监控的事件维度有:

11. uid: 执行当前系统调用的用户的UID 22. tty: 当前用户所连接的tty 33. path: 文件路径 44. src_ip: 发起网络连接的源IP 55. dst_ip: 发起网络连接的目的IP 6

netlog.c

1#include <linux/module.h> 2#include <linux/kprobes.h> 3#include <linux/slab.h> 4#include <linux/tty.h> 5#include <linux/file.h> 6#include <linux/in.h> 7#include <linux/init.h> 8#include <linux/ipv6.h> 9#include <linux/kallsyms.h> 10#include <linux/net.h> 11#include <linux/socket.h> 12#include <linux/syscalls.h> 13#include <linux/unistd.h> 14#include <net/ip.h> 15#include <linux/version.h> 16#define MAX_EXEC_PATH 950 17#define CONNECT_PROBE_FAILED -1 18#define ACCEPT_PROBE_FAILED -2 19#define CLOSE_PROBE_FAILED -3 20#define BIND_PROBE_FAILED -4 21#define PROBE_CONNECTION_CLOSE 1 22#define PROBE_UDP 0 23#define MAX_ACTIVE 100 24#define DEFAULT_PROBES 0xFFFFFFFF 25#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 7, 0) 26#ifndef __must_hold 27#define __must_hold(x) 28#endif 29#ifndef __acquires 30#define __acquires(x) 31#endif 32#ifndef __releases 33#define __releases(x) 34#endif 35#endif 36#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 0, 25) 37 #define SADDR saddr 38 #define DADDR daddr 39 #define SPORT sport 40 #define DPORT dport 41#else 42 #define SADDR inet_saddr 43 #define DADDR inet_daddr 44 #define SPORT inet_sport 45 #define DPORT inet_dport 46#endif 47#ifdef CONFIG_X86 48#ifdef CONFIG_X86_64 49/* Calling conventions: RDI, RSI, RDX */ 50#define GET_ARG_1(regs) regs->di 51#define GET_ARG_2(regs) regs->si 52#define GET_ARG_3(regs) regs->dx 53#else /* !CONFIG_X86_64 */ 54/* Calling conventions: AX, DX, BX */ 55#define GET_ARG_1(regs) regs->ax 56#define GET_ARG_2(regs) regs->dx 57#define GET_ARG_3(regs) regs->bx 58#endif /* CONFIG_X86_64 ? */ 59#else 60#error Unsupported architecture 61#endif 62#define MOD_AUTHORS "Alibaba" 63#define MOD_DESC "netlog logs module" 64#define MOD_LICENSE "GPL" 65static u8 initialized; 66static u32 loaded_probes; 67static DEFINE_SPINLOCK(probe_lock); 68 69enum secure_log_protocol 70{ 71 PROTO_TCP = 0, 72 PROTO_UDP, 73}; 74enum secure_log_action 75{ 76 ACTION_CONNECT, 77 ACTION_ACCEPT, 78 ACTION_CLOSE, 79 ACTION_BIND, 80}; 81 82static char *path_from_mm(struct mm_struct *mm, char *buffer, int length) 83{ 84 char *p = NULL; 85 if (unlikely(mm == NULL)) 86 { 87 return NULL; 88 } 89 down_read(&mm->mmap_sem); 90 if (unlikely(mm->exe_file == NULL)) 91 { 92 p = NULL; 93 } 94 else 95 { 96 p = d_path(&mm->exe_file->f_path, buffer, length); 97 if (IS_ERR(p)) 98 { 99 p = NULL; 100 } 101 } 102 up_read(&mm->mmap_sem); 103 return p; 104} 105 106static void logdata(struct socket *sock, u8 protocol, u8 action) 107{ 108 char buffer[MAX_EXEC_PATH + 1], *path; 109 unsigned short family; 110 const void *dst_ip; 111 const void *src_ip; 112 int dst_port; 113 int src_port; 114 115 path = path_from_mm(current->mm, buffer, MAX_EXEC_PATH); 116 buffer[MAX_EXEC_PATH] = '\0'; 117 if (unlikely(path == NULL)) 118 { 119 return; 120 } 121 122 family = sock->sk->sk_family; 123 dst_port = ntohs(inet_sk(sock->sk)->DPORT); 124 src_port = ntohs(inet_sk(sock->sk)->SPORT); 125 switch (family) { 126 case AF_INET: 127 dst_ip = &inet_sk(sock->sk)->DADDR; 128 src_ip = &inet_sk(sock->sk)->SADDR; 129 break; 130 case AF_INET6: 131#if LINUX_VERSION_CODE > KERNEL_VERSION(3, 13, 0) 132 dst_ip = &sock->sk->sk_v6_daddr; 133#else /* LINUX_VERSION_CODE < KERNEL_VERSION(3, 13, 0) */ 134 dst_ip = &inet6_sk(sock->sk)->daddr; 135#endif /* LINUX_VERSION_CODE ? KERNEL_VERSION(3, 13, 0) */ 136 src_ip = &inet6_sk(sock->sk)->saddr; 137 break; 138 default: 139 dst_ip = NULL; 140 src_ip = NULL; 141 break; 142 } 143 char tty[64]; 144 char sip[16],dip[16]; 145 sprintf(sip,NIPQUAD_FMT,NIPQUAD(inet_sk(sock->sk)->SADDR)); 146 sprintf(dip,NIPQUAD_FMT,NIPQUAD(inet_sk(sock->sk)->DADDR)); 147 printk("uid:%d - tty:%s - path:%s - src_ip:%s:%d - dst_ip:%s:%d\n", current_uid(),tty_name(current->signal->tty, tty),path,sip,src_port,dip,dst_port); 148 149} 150 151int handler_fault(struct kprobe *p, struct pt_regs *regs, int trap_number) 152{ 153 switch (trap_number) 154 { 155 case SIGABRT: 156 case SIGSEGV: 157 case SIGQUIT: 158 pr_err(" fault handler: Detected fault %d from inside probes.", trap_number); 159 return 0; 160 default: 161 return 0; 162 } 163} 164struct probe_data 165{ 166 struct socket *sock; 167}; 168void unplant_kretprobe(struct kretprobe *probe) __must_hold(probe_lock) 169{ 170 pr_info("[+] Unplanting kretprobe on %s\n", probe->kp.symbol_name); 171 unregister_kretprobe(probe); 172 pr_info("[+] Unplanted kretprobe on %s\n", probe->kp.symbol_name); 173 probe->kp.addr = NULL; 174} 175 176static int pre_handler_store_sock(struct kretprobe_instance *ri, struct pt_regs *regs) 177{ 178 struct probe_data *priv = (struct probe_data*)ri->data; 179 180 if (likely(current != NULL)) 181 { 182 priv->sock = (struct socket*)GET_ARG_1(regs); 183 return 0; 184 } 185 return 1; 186} 187 188static int enter_inet_stream_connect(struct kretprobe_instance *ri, struct pt_regs *regs) 189{ 190 struct probe_data *priv = (struct probe_data*)ri->data; 191 struct socket *sock = priv->sock; 192 193 if (likely(current != NULL) && 194 likely(sock != NULL) && 195 likely(sock->sk != NULL) && 196 likely(sock->sk->sk_family == AF_INET || 197 sock->sk->sk_family == AF_INET6) && 198 likely(sock->sk->sk_protocol == IPPROTO_TCP)) 199 { 200 logdata(sock, PROTO_TCP, ACTION_CONNECT); 201 } 202 return 0; 203} 204 205static struct kretprobe tcpconn_kretprobe = 206{ 207 .entry_handler = pre_handler_store_sock, 208 .handler = enter_inet_stream_connect, 209 .data_size = sizeof(struct socket*), 210 .maxactive = 16 * NR_CPUS, 211 .kp = { 212 .symbol_name = "inet_stream_connect", 213 .fault_handler = handler_fault, 214 }, 215}; 216static void unplant_probes(u32 removed_probes) __must_hold(probe_lock) 217{ 218 unplant_kretprobe(&tcpconn_kretprobe); 219} 220 221void unplant_all(void) 222{ 223 unsigned long flags; 224 spin_lock_irqsave(&probe_lock, flags); 225 unplant_probes(loaded_probes); 226 spin_unlock_irqrestore(&probe_lock, flags); 227} 228 229int plant_kretprobe(struct kretprobe *probe) __must_hold(probe_lock) 230{ 231 int err; 232 pr_info("[+] Planting kretprobe on %s\n", probe->kp.symbol_name); 233 err = register_kretprobe(probe); 234 if (err < 0) 235 { 236 pr_err("[-] Failed to planted kretprobe on %s: %i\n", probe->kp.symbol_name, err); 237 } 238 else 239 { 240 pr_info("[+] Planted kretprobe on %s\n", probe->kp.symbol_name); 241 } 242 return err; 243} 244 245static int plant_probes(u32 new_probes) __must_hold(&probe_lock) 246{ 247 int err = 0; 248 err = plant_kretprobe(&tcpconn_kretprobe); 249 if (err < 0) 250 { 251 return -CONNECT_PROBE_FAILED; 252 } 253 return err; 254} 255 256int probes_init(void) 257{ 258 unsigned long flags; 259 int ret = 0; 260 spin_lock_irqsave(&probe_lock, flags); 261 initialized=2; 262 if (initialized != 0) 263 { 264 pr_info("Start plant_probes\n"); 265 ret = plant_probes(DEFAULT_PROBES); 266 if (ret >= 0) 267 { 268 initialized = 1; 269 } 270 } 271 spin_unlock_irqrestore(&probe_lock, flags); 272 return ret; 273} 274 275static int __init netlog_init(void) 276{ 277 int ret; 278 pr_info("Net Monitor Start....\n"); 279 ret = probes_init(); 280 if (ret != 0) 281 { 282 unplant_all(); 283 } 284 else 285 { 286 pr_info("Net Monitor Start Ok!ret is %d\n",ret); 287 } 288 return ret; 289} 290 291 292static void __exit netlog_exit(void) 293{ 294 unplant_all(); 295} 296module_init(netlog_init); 297module_exit(netlog_exit); 298 299MODULE_LICENSE(MOD_LICENSE); 300MODULE_AUTHOR(MOD_AUTHORS); 301MODULE_DESCRIPTION(MOD_DESC); 302

Makefile

1# 2# Variables needed to build the kernel module 3# 4name = netlog 5 6obj-m += $(name).o 7 8all: build 9 10.PHONY: build install clean 11 12build: 13 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules CONFIG_DEBUG_SECTION_MISMATCH=y 14 15install: build 16 -mkdir -p /lib/modules/`uname -r`/kernel/arch/x86/kernel/ 17 cp $(name).ko /lib/modules/`uname -r`/kernel/arch/x86/kernel/ 18 depmod /lib/modules/`uname -r`/kernel/arch/x86/kernel/$(name).ko 19 20clean: 21 [ -d /lib/modules/$(shell uname -r)/build ] && \ 22 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 23

编译并加载内核模块

1make 2insmod netlog.ko 3

测试运行效果

 

 

6. Linux磁盘I/O事件日志收集

对linux磁盘I/O状态的监控就是对"sys_open系统调用"进行注册监控回调,在本例中,我们监控的事件维度有:

11. 操作当前文件的进程,即什么程序打开了当前文件 22. 打开文件的绝对路径 33. 打开文件的标志 44. 打开文件的模式 5

openlog.c

1#include <linux/kernel.h> 2#include <linux/module.h> 3#include <linux/kprobes.h> 4#include <linux/slab.h> 5#include <linux/kallsyms.h> 6#include <linux/fs.h> 7#include <asm/uaccess.h> 8 9 10static struct jprobe jp; 11 12asmlinkage long jprobe_sys_open(const char __user *filename, int flags, int mode) 13{ 14 int len = PATH_MAX; 15 char * tmpfilename = NULL; 16 17 if (TASK_SIZE - (unsigned long) filename < PATH_MAX) 18 { 19 len = TASK_SIZE - (unsigned long) filename; 20 } 21 22 tmpfilename = kmalloc(len, GFP_ATOMIC); 23 if (tmpfilename == NULL) 24 { 25 return 0; 26 } 27 28 /* 29 copy_from_user可能导致睡眠,而kprobe并不允许在探测点处理函数中这么做 30 if (copy_from_user(tmpfilename, filename, len)) 31 { 32 return 0; 33 } 34 printk("process '%s' call open('%s', %d, %d)\n", current->comm, tmpfilename, flags, mode); 35 */ 36 37 printk("process '%s' call open('%s', %d, %d)\n", current->comm, filename, flags, mode); 38 jprobe_return(); 39 return 0; 40} 41 42int init_module(void) 43{ 44 int ret; 45 46 jp.entry = (kprobe_opcode_t *) jprobe_sys_open; 47 jp.kp.addr = (kprobe_opcode_t *)kallsyms_lookup_name("sys_open"); 48 if (!jp.kp.addr) 49 { 50 printk("Couldn't find the address of sys_open\n"); 51 return -1; 52 } 53 54 if ((ret = register_jprobe(&jp)) <0) 55 { 56 printk("register_jprobe failed, returned %d\n", ret); 57 return -1; 58 } 59 printk("Registered a jprobe.\n"); 60 return 0; 61} 62 63void cleanup_module(void) 64{ 65 unregister_jprobe(&jp); 66 printk("jprobe unregistered\n"); 67} 68 69MODULE_LICENSE("GPL"); 70

Makefile

1# 2# Variables needed to build the kernel module 3# 4name = openlog 5 6obj-m += $(name).o 7 8all: build 9 10.PHONY: build install clean 11 12build: 13 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules CONFIG_DEBUG_SECTION_MISMATCH=y 14 15install: build 16 -mkdir -p /lib/modules/`uname -r`/kernel/arch/x86/kernel/ 17 cp $(name).ko /lib/modules/`uname -r`/kernel/arch/x86/kernel/ 18 depmod /lib/modules/`uname -r`/kernel/arch/x86/kernel/$(name).ko 19 20clean: 21 [ -d /lib/modules/$(shell uname -r)/build ] && \ 22 make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean 23

编译并加载内核模块

1make 2insmod openlog.ko 3

测试运行效果

 

 

7. 后记

对于主机恶意行为检测来说,对于事件信息的捕获只是第一步,真正的重点在于:

11. 我们在系统中建立哪些监控Hook点,要监控哪些系统调用、哪些内核函数 22. 我们需要在监控回调注册点中获取哪些维度的信息 33. 搜集到相应的信息之后,我们怎样对这些信息进行建模,从而找出恶意行为轨迹 4

代码交流 2021