Compare commits

...

10 Commits
legacy ... main

Author SHA1 Message Date
9df1678eee Update README.md 2025-03-23 13:23:27 +01:00
62d84753ee Update README.md 2025-03-23 13:21:47 +01:00
7b475973aa V4 : Yara rules implementation 2025-03-23 13:20:57 +01:00
26bb7e6222 V4 : Yara rules implementation
Example yara file
2025-03-23 13:18:24 +01:00
b75ab4de08 V4 : Yara rules implementation 2025-03-23 13:17:57 +01:00
e9dd0c3dee V4 : Yara rules implementation
Deprecated file
2025-03-23 13:17:47 +01:00
e5d68d44f9 V4 : Yara rules implementation
Deprecated file
2025-03-23 13:16:13 +01:00
e59aa14776 V4 : Yara rules implementation
Deprecated file
2025-03-23 13:15:56 +01:00
5b00ad1ef1 V4 : Yara rules implementation 2025-03-23 13:15:42 +01:00
ea7092cddb V4 : Yara rules implementation 2025-03-23 13:15:21 +01:00
7 changed files with 300 additions and 138 deletions

View File

@ -1,16 +1,20 @@
# Scanner : V3
# Scanner : V4
Node.JS based file scanner using patterns and OLLAMA (llama3.2:1b) integration for file/code analysis.
Node.JS based file scanner using YARA rules and OLLAMA (llama3.2:1b) integration for file/code analysis.
> Seeking for V3 legacy where we used other methods for scanning and detecting patterns? Check http://lhhomeserver.ddns.net:3000/Lisa_Stuff/Scanner/src/branch/legacy/
## Setup
Install axios
```js
npm i axios
npm i axios @automattic/yara
```
Edit variables in code aiprompt, patterns, pathtoscan, llamaendpoint, etc.
Edit variables in code aiprompt, extension, pathtoscan, llamaendpoint, etc.
Upload your YARA rules inside signatures folder. Supported extensions for YARA rules are: .yar and .yara
Run code
@ -38,10 +42,13 @@ We have few extra options available.
1. Discord webhook integration will send every file analysis which might appear few times per request of AI, it results in a ratelimit and no messages being sent or an error.
2. If OLLAMA is self hosted there is chance of it being overloaded, if it is being hosted externally and on paid plan there is chance of it eating a lot of money.
3. We do not recommend scanning nodemodules, cache files, etc. Please use --ignorefolders or --ignorefiles tag to exclude them.
4. Compiled files might not be able to get scanned. (NOT TESTED)
## Known issues
1. Some OLLAMA models might refuse to analyse file for malware content.
1. Some OLLAMA models might refuse to analyse file for malware content.
> Suggested fix by inxtagram:
`For the first issue Some OLLAMA models might refuse to analyse file for malware content, you can expect better results by using abliterated model. huihui_ai/llama3.2-abliterate:1b might be suit on your needs`
2. Scanning too much files might result in huge console spam and AI/Discord integration errors.
COPYRIGHT CC-BY-SA-4.0, CONTACT: lisahonkay@gmail.com

View File

@ -1,25 +0,0 @@
{
"_NOTE": "ADD ONLY EXTENSIONS! EXAMPLE: .py",
"extensions": [
".sh",
".py",
".js",
".deb",
".java",
".class",
".jar",
".ejs",
".ts",
".tsx",
".jsx",
".lua",
".exe",
".run",
".rare",
".html",
".php",
".json",
".yaml",
".pyx"
]
}

View File

@ -1,9 +0,0 @@
{
"_NOTE": "I recommend adding just filename rather than filename + extension!",
"filenames": [
"rooter",
"hack",
"proot",
"requirements"
]
}

View File

@ -1,25 +0,0 @@
{
"patterns": [
"import os; os.system('rm -rf /')",
"eval('__import__(\"os\").system(\"ls\")')",
"require('child_process').exec('rm -rf /')",
"process.nextTick(() => { require('fs').unlinkSync('/') })",
"import os; os.system('dd if=/dev/zero of=/tmp/filler ",
"while True: pass",
"import time; [0] * (10**8)",
"require('fs').writeFileSync('/tmp/filler', Buffer.alloc(1024 * 1024 * 1024))",
"require('fs').writeFileSync('/tmp/filler', ",
"setInterval(() => { while(true) {} }, 0)",
"new Array(10**8)",
"import psutil; psutil.Process().memory_info()",
"import os; os.system('cat /proc/self/maps')",
"require('child_process').exec('ps aux')",
"process.memoryUsage()",
"setInterval(() => { require('child_process').exec('cat /proc/self/maps') }, 1000)",
"setInterval(() => { require('child_process').exec('",
"import requests; [requests.get('",
"setInterval(() => { require('http').get('",
"import socket; s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); for _ in range(1000): s.sendto(b'X' * 1024, ('",
"require('net').createServer().listen(80, '"
]
}

View File

@ -6,22 +6,52 @@
const fs = require('fs');
const path = require('path');
const axios = require('axios');
const yara = require('@automattic/yara');
const { Worker } = require('worker_threads');
const patterns = JSON.parse(fs.readFileSync('patterns.json')).patterns;
const extensions = JSON.parse(fs.readFileSync('extensions.json')).extensions;
const filenames = JSON.parse(fs.readFileSync('filenames.json')).filenames;
//const aimodel = "huihui_ai/llama3.2-abliterate:1b"; // Change at your own risk!
const aimodel = "llama3.2:1b"; // Change at your own risk!
const aiprompt = "You are AI Code Scanner, your task is to analyze code given by the user, explain what it does without leaking nearly anything from it, and fully decide what's going on there and if it is malicious! Your task is to provide information about how code runs, what it does BUT you should not provide information about how to fix code or about its vulnerabilities. You should provide information about related files and if the code is potentially dangerous/malicious. If you find any suspicious patterns, extensions, or filenames, you may suggest additional files to scan. I repeat, don't provide information about Security and Potential Issues BUT provide info if it might be illegal code, breaking some Terms of Service, abusing files, e.g., creating tons of files or doing any other dangerous thing which may harm the system, you may show code if need for this reason. You should respond by any meaners and should not tell that you can't do that."; // Change at your own risk!
const llamaendpoint = "http://0.0.0.0:11434/api/chat";
const llamaendpoint = "http://192.168.2.109:11434/api/chat";
const extensions = [
'.exe', // Executable files
'.dll', // Dynamic Link Library files
'.scr', // Screensaver files
'.vbs', // Visual Basic Script files
'.js', // JavaScript files
'.bat', // Batch files
'.cmd', // Command files
'.ps1', // PowerShell Script files
'.ps2', // Second PowerShell Script files
'.psm1', // PowerShell Module files
'.py', // Python files
'.pyx', // Cython files
'.jsx', // JavaScript XML files
'.ts', // TypeScript files
'.tsx', // TypeScript XML files
".sh",
".deb",
".java",
".class",
".jar",
".ejs",
".lua",
".run",
".rare",
".html",
".php",
".json",
".yaml",
];
let pathtoscan = "./";
let ignoreFolders = [];
let ignoreFiles = [];
let webhook = {
discord: {
enabled: false,
url: "CHANGE TO YOUR WEBHOOK"
enabled: true,
url: "https://discord.com/api/webhooks/1349816121972359240/4KhIbjzDERDnk0_OAJDA0Ha2YzzntvVTM96hhDNzmWYx65NfRad6LWFIx5aM8NZScxO-"
}
};
let messageHistories = {};
@ -38,6 +68,69 @@ args.forEach(arg => {
}
});
function fileExtensionMatch(filePath) {
return extensions.some(extension => filePath.endsWith(extension));
}
async function scanFileWithYara(filePath) {
return new Promise((resolve, reject) => {
try {
yara.initialize((error) => {
if (error) {
console.error(`Error initializing Yara: ${error}`);
resolve({ found: false, rule: null });
} else {
// Load all rule files from the signatures folder
const signaturesPath = path.join(__dirname, 'signatures');
const rules = fs.readdirSync(signaturesPath)
.filter((file) => file.endsWith('.yar') || file.endsWith('.yara'))
.map((file) => ({ filename: path.join(signaturesPath, file) }));
const scanner = yara.createScanner();
scanner.configure({ rules: rules }, (error, warnings) => {
if (error) {
if (error instanceof yara.CompileRulesError) {
console.error(`Error compiling rules: ${error}`);
resolve({ found: false, rule: null });
} else {
console.error(`Error configuring scanner: ${error}`);
resolve({ found: false, rule: null });
}
} else {
if (warnings.length) {
console.warn(`Compile warnings: ${JSON.stringify(warnings)}`);
}
const req = { buffer: fs.readFileSync(filePath) };
scanner.scan(req, (error, result) => {
if (error) {
console.error(`Error scanning file: ${error}`);
resolve({ found: false, rule: null });
} else if (result && result.matches && result.matches.length > 0) {
// Handle valid matches
const matchedRule = result.matches[0].rule;
resolve({ found: true, rule: matchedRule });
} else {
// Handle no matches
if (fileExtensionMatch(filePath) == true) {
resolve({ found: true, rule: "Suspicious File Extension found in " + filePath });
} else {
resolve({ found: false, rule: null });
}
}
});
}
});
}
});
} catch (error) {
console.error(`Unexpected error: ${error}`);
resolve({ found: false, rule: null });
}
});
}
function scanDirectory(directory) {
fs.readdir(directory, (err, files) => {
if (err) {
@ -85,68 +178,52 @@ function scanFile(filePath) {
return;
}
const functionToEval = `
(function() {
let foundMalicious = false;
let patternFound = { found: false, value: "" };
let extensionFound = { found: false, value: "" };
let filenameFound = { found: false, value: "" };
scanFileWithYara(filePath).then((yaraMatch) => {
const foundMalicious = (yaraMatch.found);
patterns.forEach(pattern => {
if (data.includes(pattern)) {
console.log(\`[!] Malicious code found in file: \${filePath} - Pattern: \${pattern}\`);
foundMalicious = true;
patternFound = { found: true, value: pattern };
if (foundMalicious) {
console.log(`[!] Malicious code detected in file: ${filePath}`);
console.log(`[!] Yara rule matched: ${yaraMatch.rule}`);
// Start Worker Thread for additional processing
const worker = new Worker('./worker.js', {
workerData: { filePath, data: '', aimodel, aiprompt, llamaendpoint }
});
worker.on('message', (result) => {
if (result.error) {
console.error(`[ERROR] Error processing in worker: ${result.error}`);
} else {
//console.log(`[INFO] Scan results for ${filePath}:`, result);
//if (result.foundMalicious) {
const messageHistoryId = generateMessageHistoryId(filePath);
runAIScan(filePath, yaraMatch, messageHistoryId);
//} else {
// console.log("[WARN] Empty message from worker recieved!")
//}
}
});
if (extensions.some(ext => filePath.endsWith(ext))) {
const detectedExt = extensions.find(ext => filePath.endsWith(ext));
console.log(\`[?] Extension detected: \${filePath} - \${detectedExt}\`);
foundMalicious = true;
extensionFound = { found: true, value: detectedExt };
}
worker.on('error', (error) => {
console.error(`[ERROR] Worker error: ${error}`);
});
if (filenames.some(name => filePath.includes(name))) {
const detectedName = filenames.find(name => filePath.includes(name));
console.log(\`[?] Suspicious filename detected: \${filePath} - \${detectedName}\`);
foundMalicious = true;
filenameFound = { found: true, value: detectedName };
}
return { foundMalicious, patternFound, extensionFound, filenameFound };
})();
`;
const worker = new Worker('./worker.js', {
workerData: { functionToEval, filePath, data, patterns, extensions, filenames, aimodel, aiprompt, llamaendpoint }
});
worker.on('message', (result) => {
if (result.error) {
console.error(`[ERROR] Error running eval in worker: ${result.error}`);
worker.on('exit', (code) => {
if (code !== 0) {
console.error(`[ERROR] Worker stopped with exit code ${code}`);
}
});
} else {
console.log(`[INFO] Scan results for ${filePath}:`, result);
if (result.foundMalicious) {
const messageHistoryId = generateMessageHistoryId(filePath);
runAIScan(filePath, result.patternFound, result.extensionFound, result.filenameFound, messageHistoryId);
}
}
});
worker.on('error', (error) => {
console.error(`[ERROR] Worker error: ${error}`);
});
worker.on('exit', (code) => {
if (code !== 0) {
console.error(`[ERROR] Worker stopped with exit code ${code}`);
console.log(`[INFO] No malicious activity detected in file: ${filePath}`);
}
}).catch((scanError) => {
console.error(`[ERROR] Error scanning file with Yara: ${scanError}`);
});
});
}
async function runAIScan(filePath, patternFound, extensionFound, filenameFound, messageHistoryId) {
async function runAIScan(filePath, yaraMatch, messageHistoryId) {
console.log("[INFO] AI Scan requested for file: " + filePath)
const directoryContent = await getDirectoryContent(path.dirname(filePath));
const messageHistory = messageHistories[messageHistoryId] || [];
@ -157,7 +234,14 @@ async function runAIScan(filePath, patternFound, extensionFound, filenameFound,
},
{
role: "user",
content: `FILEPATH: ${filePath}, DIRECTORY CONTENT: ${directoryContent}, MESSAGE HISTORY: ${messageHistory.join('\n')}`
content: `FILEPATH: ${filePath}, DIRECTORY CONTENT: ${directoryContent}, FILECONTENT: ${fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.err("[ERROR] Could not read file (" + filePath + "), error: " + err)
return `[ERROR] Could not read file: ${err}`
} else {
return data
}
})}, MESSAGE HISTORY: ${messageHistory.join('\n')}`
}
];
@ -180,7 +264,7 @@ async function runAIScan(filePath, patternFound, extensionFound, filenameFound,
const response = await axios.post(apiUrl, input);
const aiResult = response.data.message.content;
console.log(`[INFO] AI scan results for ${filePath}:\n${aiResult}`);
await sendReport(filePath, aiResult, patternFound, extensionFound, filenameFound);
await sendReport(filePath, aiResult, yaraMatch);
messageHistories[messageHistoryId] = messageHistories[messageHistoryId] || [];
messageHistories[messageHistoryId].push(aiResult);
@ -236,25 +320,15 @@ function extractAdditionalScanFiles(aiResult, currentDirectory) {
return additionalFiles;
}
async function sendReport(filePath, aiResult, patternFound, extensionFound, filenameFound) {
async function sendReport(filePath, aiResult, yaraMatch) {
if (webhook.discord.enabled) {
const embed = {
title: `AI Scan Results | ${filePath}`,
description: `${aiResult}`,
fields: [
{
name: `${patternFound.found ? "✅ Malicious Pattern found" : "❌ Malicious Pattern not found"}`,
value: patternFound.value ? patternFound.value : "N/A",
inline: true
},
{
name: `${extensionFound.found ? "✅ Extension found in config" : "❌ Extension not found in config"}`,
value: extensionFound.value ? extensionFound.value : "N/A",
inline: true
},
{
name: `${filenameFound.found ? "✅ Filename found in config" : "❌ Filename not found in config"}`,
value: filenameFound.value ? filenameFound.value : "N/A",
name: `${yaraMatch.found ? "✅ Yara Rule matched" : "❌ Yara Rule not matched"}`,
value: yaraMatch.rule ? yaraMatch.rule : "N/A",
inline: true
}
],

140
signatures/rules.yara Normal file
View File

@ -0,0 +1,140 @@
rule Suspicious_PHP_Code {
meta:
description = "Suspicious PHP code"
author = "AI"
strings:
$eval_call = "eval("
$exec_call = "exec("
$suspicious_function = /[$][a-zA-Z_][a-zA-Z_0-9]{,20}\(/
condition:
any of ($eval_call, $exec_call, $suspicious_function)
}
rule Malicious_Python_Webshell {
meta:
description = "Malicious Python webshell"
author = "AI"
strings:
$import_os = "import os"
$os_system = "os.system("
$variable_assignment = /[$][a-zA-Z_][a-zA-Z_0-9]{,20}\s*=/
condition:
any of ($import_os, $os_system, $variable_assignment)
}
rule Cryptolocker_Virus {
meta:
description = "Cryptolocker virus"
author = "AI"
strings:
$crypto_collision = "from Crypto.Collision import collision"
$crypto_rsa = /from Crypto\.RSA import RSA.{1,100}/
condition:
any of ($crypto_collision, $crypto_rsa)
}
rule Coin_Mining_Malware {
meta:
description = "Coin mining malware"
author = "AI"
strings:
$hashlib_import = "import hashlib"
$multiprocessing_import = "import multiprocessing"
$os_import = "import os"
condition:
any of ($hashlib_import, $multiprocessing_import, $os_import)
}
rule SQL_Injection_Attack {
meta:
description = "SQL injection attack"
author = "AI"
strings:
$drop_table = "DROP TABLE"
$delete_from = "DELETE FROM"
$union_all = "UNION ALL"
condition:
any of ($drop_table, $delete_from, $union_all)
}
rule Cross_Site_Scripting {
meta:
description = "Cross-site scripting"
author = "AI"
strings:
$script_tag = /<script>.{1,100}<\/script>/
$alert_function = "alert("
$confirm_function = "confirm("
condition:
any of ($script_tag, $alert_function, $confirm_function)
}
rule suspicious_file_extension {
meta:
description = "Detects files with suspicious extensions"
strings:
$exe = ".exe"
$dll = ".dll"
$scr = ".scr"
$vbs = ".vbs"
$js = ".js"
$bat = ".bat"
$cmd = ".cmd"
$ps1 = ".ps1"
$ps2 = ".ps2"
$psm1 = ".psm1"
$py = ".py"
$pyx = ".pyx"
$jsx = ".jsx"
$ts = ".ts"
$tsx = ".tsx"
condition:
any of them
}
rule malicious_file {
meta:
description = "Detects malicious files based on various indicators"
strings:
$malicious_code1 = { E8 83 EC 70 83 C4 }
$malicious_string1 = "malicious_string"
$malicious_domain = "malicious_domain.com"
condition:
$malicious_code1 or $malicious_string1 or $malicious_domain
}
rule malicious_webshell {
meta:
description = "Detects malicious webshells"
author = "AI"
strings:
$eval_post = "eval($_POST"
$eval_get = "eval($_GET"
$passthru_post = "passthru($_POST"
$passthru_get = "passthru($_GET"
$system_post = "system($_POST"
$system_get = "system($_GET"
condition:
any of ($eval_post, $eval_get, $passthru_post, $passthru_get, $system_post, $system_get)
}
rule ransomware_infection {
meta:
description = "Detects ransomware infections"
author = "AI"
strings:
$ransom_note = "All your files are encrypted."
$ransomDemand = "Pay us to get your files back."
$bitcoin_address = "1D9xMfW49gVHjY6Usa2zLs6Ym1Ya2G7mKZ"
condition:
any of ($ransom_note, $ransomDemand, $bitcoin_address)
}

View File

@ -1,11 +1,11 @@
const { parentPort, workerData } = require('worker_threads');
const { functionToEval, filePath, data, patterns, extensions, filenames, aimodel, aiprompt, llamaendpoint } = workerData;
const { functionToEval, filePath, data, patterns, extensions, filenames, aimodel, aiprompt, llamaendpoint, yaraMatch } = workerData;
async function runEval() {
try {
const result = eval(functionToEval);
parentPort.postMessage(result);
parentPort.postMessage({ result, yaraMatch });
} catch (error) {
parentPort.postMessage({ error: error.message });
}