
{"id":63,"date":"2025-05-31T13:31:51","date_gmt":"2025-05-31T12:31:51","guid":{"rendered":"https:\/\/blog.mkcloudai.com\/?p=63"},"modified":"2025-05-31T13:34:50","modified_gmt":"2025-05-31T12:34:50","slug":"cli-based-rootkit-and-anomaly-scanner-for-linux-systems","status":"publish","type":"post","link":"https:\/\/blog.mkcloudai.com\/?p=63","title":{"rendered":"CLI-based rootkit and anomaly scanner for Linux systems"},"content":{"rendered":"\n<p>CLI-based rootkit and anomaly scanner for Linux systems. Written in Go for speed and portability.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\ude80 Features<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Scans for suspicious processes, ports, and modules<\/li>\n\n\n\n<li>Detects tampered <code>\/etc\/ld.so.preload<\/code><\/li>\n\n\n\n<li>Lists hidden dotfiles in root<\/li>\n\n\n\n<li>Verifies critical binary hashes (<code>ls<\/code>, <code>ps<\/code>, <code>top<\/code>)<\/li>\n\n\n\n<li>Outputs reports in <code>text<\/code>, <code>json<\/code>, or <code>html<\/code> formats<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udd27 Installation<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">1. Install Go (if not installed)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update &amp;&amp; sudo apt install golang -y<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. Clone the Repo<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>git clone https:\/\/https:\/\/github.com\/mxkdevops\/mkrootkitscan\ncd mkrootkitscan<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. Build the Scanner<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>go build -o mkrootkitscan main.go<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u25b6\ufe0f Usage<\/h3>\n\n\n\n<p>Run a scan and generate report:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo .\/mkrootkitscan --format=html       # Save as scan_report.html\nsudo .\/mkrootkitscan --format=json       # Save as scan_report.json\nsudo .\/mkrootkitscan --format=text       # Console output\nsudo .\/mkrootkitscan --format=html --quiet  # No console output<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udcc1 Output Files<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>scan_report.json<\/code> \u2014 JSON report<\/li>\n\n\n\n<li><code>scan_report.html<\/code> \u2014 User-friendly HTML report<\/li>\n\n\n\n<li><code>scan_report.txt<\/code> <\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udcf8 Screenshots<\/h3>\n\n\n\n<h3 class=\"wp-block-heading\">JSON Output:<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n  \"Processes\": &#91;\"\u26a0\ufe0f Suspicious Process 101: \/usr\/sbin\/apache2\"],\n  \"Ports\": &#91;\"\ud83d\udd0c Open Port: 0.0.0.0:22\"],\n  \"Modules\": &#91;],\n  \"Preload\": \"\",\n  \"Hidden\": &#91;\"\ud83d\udd12 Hidden File\/Dir: \/.dockerenv\"],\n  \"Hashes\": &#91;\"\ud83d\udcc1 \/bin\/ls MD5: 1a79a4d60de6718e8e5b326e338ae533\"],\n  \"Timestamp\": \"2025-05-31T12:34:56Z\"\n}<\/code><\/pre>\n\n\n\n<p>main.go Full code <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ mkrootkitscan - CLI Rootkit Scanner\n\/\/ GitHub-ready version with CLI flags and report generation\npackage main\n\nimport (\n    \"crypto\/md5\"\n    \"encoding\/json\"\n    \"flag\"\n    \"fmt\"\n    \"io\/ioutil\"\n    \"net\"\n    \"os\"\n    \"strconv\"\n    \"strings\"\n    \"time\"\n)\n\ntype ScanResult struct {\n    Processes &#91;]string `json:\"processes\"`\n    Ports     &#91;]string `json:\"ports\"`\n    Modules   &#91;]string `json:\"modules\"`\n    Preload   string   `json:\"preload\"`\n    Hidden    &#91;]string `json:\"hidden\"`\n    Hashes    &#91;]string `json:\"hashes\"`\n    Timestamp string   `json:\"timestamp\"`\n}\n\nfunc ScanProcesses() &#91;]string {\n    results := make(&#91;]string, 0) \/\/ initialize empty slice\n    entries, _ := ioutil.ReadDir(\"\/proc\")\n    for _, entry := range entries {\n        if pid, err := strconv.Atoi(entry.Name()); err == nil {\n            cmd, err := ioutil.ReadFile(fmt.Sprintf(\"\/proc\/%d\/cmdline\", pid))\n            if err == nil &amp;&amp; strings.Contains(string(cmd), \"ld.so.preload\") {\n                results = append(results, fmt.Sprintf(\"\u26a0\ufe0f Suspicious Process %d: %s\", pid, string(cmd)))\n            }\n        }\n    }\n    return results\n}\n\nfunc parseHexIPPort(hexStr string) string {\n    parts := strings.Split(hexStr, \":\")\n    ip := parts&#91;0]\n    port := parts&#91;1]\n    ipBytes := make(&#91;]byte, 4)\n    for i := 0; i &lt; 4; i++ {\n        b, _ := strconv.ParseUint(ip&#91;2*i:2*i+2], 16, 8)\n        ipBytes&#91;i] = byte(b)\n    }\n    ipStr := net.IPv4(ipBytes&#91;3], ipBytes&#91;2], ipBytes&#91;1], ipBytes&#91;0]).String()\n    p, _ := strconv.ParseInt(port, 16, 32)\n    return fmt.Sprintf(\"%s:%d\", ipStr, p)\n}\n\nfunc ScanPorts() &#91;]string {\n    results := make(&#91;]string, 0) \/\/ initialize empty slice\n    tcp, _ := ioutil.ReadFile(\"\/proc\/net\/tcp\")\n    lines := strings.Split(string(tcp), \"\\n\")&#91;1:]\n    for _, line := range lines {\n        if strings.TrimSpace(line) == \"\" {\n            continue\n        }\n        parts := strings.Fields(line)\n        localHex := parts&#91;1]\n        state := parts&#91;3]\n        if state == \"0A\" {\n            results = append(results, \"\ud83d\udd0c Open Port: \"+parseHexIPPort(localHex))\n        }\n    }\n    return results\n}\n\nfunc ScanModules() &#91;]string {\n    results := make(&#91;]string, 0) \/\/ initialize empty slice\n    data, _ := ioutil.ReadFile(\"\/proc\/modules\")\n    lines := strings.Split(string(data), \"\\n\")\n    for _, line := range lines {\n        if strings.Contains(line, \"rootkit\") {\n            results = append(results, \"\u26a0\ufe0f Suspicious Module: \"+line)\n        }\n    }\n    return results\n}\n\nfunc ScanLDPreload() string {\n    content, err := ioutil.ReadFile(\"\/etc\/ld.so.preload\")\n    if err == nil &amp;&amp; strings.TrimSpace(string(content)) != \"\" {\n        return \"\u26a0\ufe0f Non-empty \/etc\/ld.so.preload: \" + string(content)\n    }\n    return \"\"\n}\n\nfunc ScanHiddenFiles() &#91;]string {\n    results := make(&#91;]string, 0) \/\/ initialize empty slice\n    files, _ := ioutil.ReadDir(\"\/\")\n    for _, f := range files {\n        if strings.HasPrefix(f.Name(), \".\") {\n            results = append(results, \"\ud83d\udd12 Hidden File\/Dir: \/\"+f.Name())\n        }\n    }\n    return results\n}\n\nfunc ScanCriticalFileHashes() &#91;]string {\n    results := make(&#91;]string, 0) \/\/ initialize empty slice\n    paths := &#91;]string{\"\/bin\/ls\", \"\/bin\/ps\", \"\/usr\/bin\/top\"}\n    for _, path := range paths {\n        content, err := ioutil.ReadFile(path)\n        if err == nil {\n            hash := fmt.Sprintf(\"%x\", md5sum(content))\n            results = append(results, fmt.Sprintf(\"\ud83d\udcc1 %s MD5: %s\", path, hash))\n        }\n    }\n    return results\n}\n\nfunc md5sum(data &#91;]byte) &#91;]byte {\n    h := md5.New()\n    h.Write(data)\n    return h.Sum(nil)\n}\n\nfunc GenerateReport(results ScanResult, format string) {\n    switch format {\n    case \"json\":\n        b, _ := json.MarshalIndent(results, \"\", \"  \")\n        ioutil.WriteFile(\"scan_report.json\", b, 0644)\n    case \"html\":\n        f, _ := os.Create(\"scan_report.html\")\n        defer f.Close()\n        f.WriteString(\"&lt;html&gt;&lt;body&gt;&lt;h1&gt;mkrootkitscan Report&lt;\/h1&gt;&lt;ul&gt;\")\n        for _, p := range results.Processes {\n            f.WriteString(\"&lt;li&gt;\" + p + \"&lt;\/li&gt;\")\n        }\n        for _, p := range results.Ports {\n            f.WriteString(\"&lt;li&gt;\" + p + \"&lt;\/li&gt;\")\n        }\n        for _, m := range results.Modules {\n            f.WriteString(\"&lt;li&gt;\" + m + \"&lt;\/li&gt;\")\n        }\n        if results.Preload != \"\" {\n            f.WriteString(\"&lt;li&gt;\" + results.Preload + \"&lt;\/li&gt;\")\n        }\n        for _, h := range results.Hidden {\n            f.WriteString(\"&lt;li&gt;\" + h + \"&lt;\/li&gt;\")\n        }\n        for _, h := range results.Hashes {\n            f.WriteString(\"&lt;li&gt;\" + h + \"&lt;\/li&gt;\")\n        }\n        f.WriteString(\"&lt;\/ul&gt;&lt;\/body&gt;&lt;\/html&gt;\")\n    default:\n        fmt.Println(\"&#91;Text Output]\")\n        for _, l := range results.Processes {\n            fmt.Println(l)\n        }\n        for _, l := range results.Ports {\n            fmt.Println(l)\n        }\n        for _, l := range results.Modules {\n            fmt.Println(l)\n        }\n        if results.Preload != \"\" {\n            fmt.Println(results.Preload)\n        }\n        for _, l := range results.Hidden {\n            fmt.Println(l)\n        }\n        for _, l := range results.Hashes {\n            fmt.Println(l)\n        }\n    }\n}\n\nfunc main() {\n    outputFormat := flag.String(\"format\", \"text\", \"Output format: text, html, or json\")\n    quiet := flag.Bool(\"quiet\", false, \"Suppress console output\")\n    flag.Parse()\n\n    results := ScanResult{\n        Processes: ScanProcesses(),\n        Ports:     ScanPorts(),\n        Modules:   ScanModules(),\n        Preload:   ScanLDPreload(),\n        Hidden:    ScanHiddenFiles(),\n        Hashes:    ScanCriticalFileHashes(),\n        Timestamp: time.Now().Format(time.RFC3339),\n    }\n\n    if !*quiet {\n        fmt.Println(\"\u2705 Scan complete. Generating report...\")\n    }\n    GenerateReport(results, *outputFormat)\n    if !*quiet {\n        fmt.Println(\"\ud83d\udcc4 Report saved\")\n    }\n}\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 What You\u2019ve Achieved with <code>mkrootkitscan<\/code><\/h3>\n\n\n\n<p>You\u2019ve built a <strong>custom rootkit scanner<\/strong> that does the following:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udd0d Core Features<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Open Port Scanning<\/strong>: Parses <code>\/proc\/net\/tcp<\/code> to find open ports in LISTEN state.<\/li>\n\n\n\n<li><strong>Process Analysis<\/strong>: Detects suspicious processes referencing <code>ld.so.preload<\/code>.<\/li>\n\n\n\n<li><strong>Kernel Module Check<\/strong>: Scans <code>\/proc\/modules<\/code> for \u201crootkit\u201d-related modules.<\/li>\n\n\n\n<li><strong>LD Preload Check<\/strong>: Flags if <code>\/etc\/ld.so.preload<\/code> is being used maliciously.<\/li>\n\n\n\n<li><strong>Hidden Files Detection<\/strong>: Lists hidden files in root (<code>\/<\/code>) directory.<\/li>\n\n\n\n<li><strong>Critical Binary Hashing<\/strong>: Computes MD5 hashes of sensitive binaries like <code>\/bin\/ls<\/code>, <code>\/bin\/ps<\/code>.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udcdd Reporting<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Generates output in <strong>text<\/strong>, <strong>JSON<\/strong>, and styled <strong>HTML<\/strong> formats.<\/li>\n\n\n\n<li>Includes timestamps and emojis for quick visual scanning.<\/li>\n\n\n\n<li>CLI flags for output format and quiet mode.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83c\udd9a How It&#8217;s Different from Traditional Tools (e.g., chkrootkit, rkhunter)<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Feature<\/th><th><code>mkrootkitscan<\/code><\/th><th><code>chkrootkit<\/code> \/ <code>rkhunter<\/code><\/th><\/tr><\/thead><tbody><tr><td>Language<\/td><td>Go (compiled binary)<\/td><td>Shell + Perl (scripts, not compiled)<\/td><\/tr><tr><td>Portability<\/td><td>Static binary, very portable<\/td><td>Requires dependencies and config tweaking<\/td><\/tr><tr><td>Customizability<\/td><td>Easy to modify <\/td><td>Harder to extend or modify<\/td><\/tr><tr><td>Output<\/td><td>Clean CLI + JSON + HTML<\/td><td>Mostly terminal text<\/td><\/tr><tr><td>Real-Time Integration<\/td><td>Can be embedded into CI\/CD tools<\/td><td>Not ideal for automation<\/td><\/tr><tr><td>Modern Style<\/td><td>Minimal and fast<\/td><td>Outdated UX, not DevOps-native<\/td><\/tr><\/tbody><tfoot><tr><td><\/td><td><\/td><td><\/td><\/tr><\/tfoot><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83c\udfe2 Can It Be Used on Production Servers?<\/h3>\n\n\n\n<p><strong>Technically yes, but here\u2019s the full picture:<\/strong><\/p>\n\n\n\n<h3 class=\"wp-block-heading\">\u2705 Strengths for Production:<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Lightweight, no dependencies.<\/li>\n\n\n\n<li>Safe reads from <code>\/proc<\/code>, no invasive operations.<\/li>\n\n\n\n<li>No installation \u2014 run as a binary or cron job.<\/li>\n\n\n\n<li>JSON output is automation-friendly (integrate into monitoring\/alerting systems).<\/li>\n\n\n\n<li>Fast \u2014 suitable for cloud instances or containers.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">\u26a0\ufe0f Limitations (So Far):<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>No rootkit signature database<\/strong> (like rkhunter uses).<\/li>\n\n\n\n<li><strong>No heuristic detection<\/strong> (e.g., behavior-based anomalies).<\/li>\n\n\n\n<li><strong>Limited file\/path coverage<\/strong> \u2014 it scans only a few binaries and top-level <code>\/<\/code>.<\/li>\n\n\n\n<li><strong>No logging or alerting mechanism<\/strong> (you\u2019d need to hook this into a SIEM or alert system).<\/li>\n\n\n\n<li><strong>No check for file permission anomalies, syscall hooks, or kernel-level stealth techniques<\/strong> (common in real rootkits).<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\udee0\ufe0f How to Make It Production-Ready (Future Roadmap)<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Area<\/th><th>What to Add<\/th><\/tr><\/thead><tbody><tr><td>Logging<\/td><td>Syslog support or file logging<\/td><\/tr><tr><td>Alerts<\/td><td>Email, Slack, or webhook alert when issues found<\/td><\/tr><tr><td>Scheduled Runs<\/td><td>Set up as a cron job or systemd service<\/td><\/tr><tr><td>Config File<\/td><td>Allow user config for paths, thresholds, etc.<\/td><\/tr><tr><td>File Integrity<\/td><td>Compare MD5s against known-safe values<\/td><\/tr><tr><td>Signature Matching<\/td><td>Maintain JSON DB of known rootkit hashes or names<\/td><\/tr><tr><td>Behavioral Analysis<\/td><td>Flag anomalous port\/process behavior over time<\/td><\/tr><tr><td>OS Support<\/td><td>Expand compatibility testing (e.g., Ubuntu, CentOS)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">\ud83d\ude4b\u200d\u2642\ufe0f Author<\/h3>\n\n\n\n<p><strong>[Mo]<\/strong> | <a href=\"http:\/\/mkcloudai.com\/\">MKCLOUDAI<\/a> | GitHub: @mxkdevops<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>CLI-based rootkit and anomaly scanner for Linux systems. Written in Go for speed and portability. \ud83d\ude80 Features \ud83d\udd27 Installation 1. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[8,4,7,9],"tags":[],"class_list":["post-63","post","type-post","status-publish","format-standard","hentry","category-how-to","category-linux","category-project","category-tutorial"],"_links":{"self":[{"href":"https:\/\/blog.mkcloudai.com\/index.php?rest_route=\/wp\/v2\/posts\/63","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.mkcloudai.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mkcloudai.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mkcloudai.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mkcloudai.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=63"}],"version-history":[{"count":3,"href":"https:\/\/blog.mkcloudai.com\/index.php?rest_route=\/wp\/v2\/posts\/63\/revisions"}],"predecessor-version":[{"id":66,"href":"https:\/\/blog.mkcloudai.com\/index.php?rest_route=\/wp\/v2\/posts\/63\/revisions\/66"}],"wp:attachment":[{"href":"https:\/\/blog.mkcloudai.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=63"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mkcloudai.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=63"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mkcloudai.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=63"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}