我是无依,做技术支持这些年,服务器文件被篡改、挂马的问题一直很头疼,排查时想快速定位被改文件、追溯操作痕迹,总觉得现有工具要么繁琐要么适配性一般。D 盾的文件监控功能用着很顺手,于是就想着用 Go 语言做一个简易版的文件夹监控工具,核心复刻 D 盾的文件状态监控、篡改识别能力,满足基础的溯源需求就够了,目前这个工具已经放在我的 Gitee 仓库 gotools 的 monitor 模块中(仓库地址:https://gitee.com/li-laihu/gotools/tree/master/monitor),代码轻量、逻辑简单,能解决日常运维中文件篡改溯源的基础问题。
之所以做这个简易版监控工具,核心就是贴合自己的基础运维需求,不用复杂的功能,只保留 D 盾最核心的文件夹实时监听和文件篡改识别,再加上基础的日志记录,能快速定位哪个文件被改了、什么时候被改的,就足够应对日常的挂马文件溯源、文件篡改排查了。相比市面上的成熟工具,这个简易版没有过多的配置项和冗余功能,编译后直接运行,上手就能用,适合和我一样有基础监控、简单溯源需求的运维同学。
选择 Go 语言开发,还是看中了它的轻量和跨平台性,编译后的可执行文件无需依赖任何运行环境,Linux、Windows 服务器上都能直接部署,而且 Go 的fsnotify包能很方便的实现文件系统监听,再结合原生的哈希计算包,几行核心代码就能实现文件指纹校验,开发成本低,运行也稳定,完全契合这个简易监控工具的开发初衷。
这个仿 D 盾的简易文件夹监控工具,目前只实现了核心基础功能,没有复杂的配置和拓展能力,主要就是三点:一是指定目录的文件创建、修改、删除事件实时监听;二是通过 MD5 计算文件指纹,对比判断文件是否被篡改;三是将文件操作事件、篡改信息记录到本地日志,为后续溯源提供依据。整体逻辑简单,代码量也不大,下面放几个核心功能的实现代码,能直观看到工具的核心逻辑:
1. 核心依赖引入
主要用到fsnotify做文件监听,crypto/md5做指纹计算,原生log做日志记录,都是 Go 的基础库或成熟轻量组件,无复杂依赖:
运行
package main
import (
"crypto/md5"
"encoding/hex"
"fmt"
"io"
"log"
"os"
"path/filepath"
"github.com/fsnotify/fsnotify"
)
2. MD5 文件指纹计算函数
这是判断文件是否被篡改的核心,读取文件内容计算 MD5 值,首次运行生成文件原始 MD5,后续文件变化时重新计算对比,不一致即判定为篡改:
// GetFileMD5 计算文件MD5值
func GetFileMD5(filePath string) (string, error) {
file, err := os.Open(filePath)
if err != nil {
return "", err
}
defer file.Close()
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
return "", err
}
md5Str := hex.EncodeToString(hash.Sum(nil))
return md5Str, nil
}
3. 初始化文件指纹库
首次监控指定目录时,遍历目录下所有文件,计算 MD5 值并存储在 map 中,作为文件的原始指纹基准:
// InitFileMD5Map 初始化指定目录的文件MD5映射
func InitFileMD5Map(dir string) (map[string]string, error) {
fileMD5Map := make(map[string]string)
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 忽略文件夹,只计算文件MD5
if !info.IsDir() {
md5Str, err := GetFileMD5(path)
if err != nil {
log.Printf("计算文件%sMD5失败: %vn", path, err)
return nil
}
fileMD5Map[path] = md5Str
}
return nil
})
return fileMD5Map, err
}
4. 核心文件监听逻辑
使用fsnotify监听指定目录,捕捉 Create、Write、Remove 事件,对创建 / 修改的文件重新计算 MD5 并与原始指纹对比,判断是否篡改,同时将所有事件记录到日志:
func main() {
// 要监控的目录,可手动修改或简单做命令行传参
monitorDir := "./monitor-test"
// 初始化文件MD5指纹库
fileMD5Map, err := InitFileMD5Map(monitorDir)
if err != nil {
log.Fatalf("初始化MD5指纹库失败: %vn", err)
}
log.Printf("初始化完成,当前监控目录%s下共%d个文件n", monitorDir, len(fileMD5Map))
// 创建监听器
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatalf("创建监听器失败: %vn", err)
}
defer watcher.Close()
// 添加监控目录
err = watcher.Add(monitorDir)
if err != nil {
log.Fatalf("添加监控目录失败: %vn", err)
}
// 监听事件
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
// 文件创建/修改事件
if event.Op&fsnotify.Create == fsnotify.Create || event.Op&fsnotify.Write == fsnotify.Write {
// 排除文件夹,只处理文件
if info, err := os.Stat(event.Name); err == nil && !info.IsDir() {
newMD5, _ := GetFileMD5(event.Name)
if oldMD5, ok := fileMD5Map[event.Name]; ok {
if newMD5 != oldMD5 {
log.Printf("[文件篡改] %s, 原MD5: %s, 新MD5: %sn", event.Name, oldMD5, newMD5)
fileMD5Map[event.Name] = newMD5 // 更新指纹库
} else {
log.Printf("[文件修改无内容变化] %sn", event.Name)
}
} else {
fileMD5Map[event.Name] = newMD5
log.Printf("[文件新建] %s, MD5: %sn", event.Name, newMD5)
}
}
}
// 文件删除事件
if event.Op&fsnotify.Remove == fsnotify.Remove {
delete(fileMD5Map, event.Name)
log.Printf("[文件删除] %sn", event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Printf("[监听错误] %vn", err)
}
}
}
以上就是这个简易监控工具的全部核心代码,逻辑非常直观,没有复杂的封装和拓展,目前的版本也仅支持单目录监控,无法忽略指定文件 / 后缀,也没有多目录配置、远程告警这些功能,完全是贴合基础需求的极简版本。
实际使用时,只需要修改代码中monitorDir为需要监控的服务器目录,然后在 Go 环境中执行go build -o monitor main.go编译成可执行文件,直接后台运行即可,工具会将所有文件操作、篡改信息以日志形式输出,也可以简单修改代码将日志写入本地文件,方便后续溯源查看。我自己在几台测试服务器上部署了这个工具,运行状态稳定,能精准捕捉文件的修改、创建、删除操作,也能快速识别出被篡改的文件,日常排查挂马、文件篡改问题,这个简易版完全够用。
当然,这个工具目前还有很多不足,后续会根据自己的实际使用需求做简单的优化,比如增加命令行传参指定监控目录,不用每次修改代码;增加日志写入本地文件的功能,方便长期追溯;还有忽略指定文件 / 后缀,避免监控日志冗余。暂时不会做太复杂的功能,始终保持轻量、简易的特点,毕竟核心需求就是基础监控和简单溯源,够用就好。
把这个简易的仿 D 盾监控工具放在 gotools 仓库,一方面是方便自己在不同服务器上部署,另一方面也希望能给有同样基础需求的运维同学一个参考,代码完全开源,大家可以根据自己的需求做简单的修改和拓展。其实运维工作中,很多时候不需要复杂的大型工具,一个轻量、简单、能解决实际问题的小工具,反而能提升工作效率。
后续如果对这个工具做了新的优化,我会在博客上同步更新,也会把优化后的代码提交到 Gitee 仓库。如果有同学对这个简易监控工具有不同的优化想法,也欢迎去仓库提 issue 交流,一起把这个小工具打磨得更贴合日常运维的基础需求。