Skip to content

Commit

Permalink
Add more advanced malware download test cases (#257)
Browse files Browse the repository at this point in the history
* Add more advanced drive-by download techniques.

* Add sample malware files to host.

* Move delay to download route as arg.

* eslint --fix

* Fix download delay route.

* Formatting + add file content links

* Slightly modify all sample files.
  • Loading branch information
not-a-rootkit authored Feb 10, 2025
1 parent 1d6ee6d commit 1e4070f
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 16 deletions.
107 changes: 103 additions & 4 deletions security/badware/malware-download.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,92 @@
<title>Malware download page</title>
<script>
// eslint-disable-next-line no-unused-vars
function run() {
const url = "/security/badware/phishing-redirect/download";
function getUrl() {
const delayedCheckbox = document.getElementById('delayedCheckbox');
return delayedCheckbox.checked ? "/security/badware/phishing-redirect/download?delay=5000" : "/security/badware/phishing-redirect/download";
}

function linkHrefDownload() {
const url = getUrl();
const link = document.createElement('a');
link.href = url;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}

async function blobDownload() {
const url = getUrl();
const response = await fetch(url);
const blob = await response.blob();
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = 'example.exe';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(link.href);
}

function xhrDownload() {
const url = getUrl();
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function () {
const blob = new Blob([xhr.response], { type: 'application/octet-stream' });
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = 'example.exe';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(link.href);
};
xhr.send();
}

function iframeDownload() {
const url = getUrl();
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(() => {
document.body.removeChild(iframe);
}, 1000); // Clean up after a second
}

function windowLocationDownload() {
const url = getUrl();
window.location.href = url;
}

async function fetchStreamDownload() {
const url = getUrl();
const response = await fetch(url);
const reader = response.body.getReader();
const contentLength = +response.headers.get('Content-Length');
let receivedLength = 0; // Received bytes
const chunks = []; // Array of received binary chunks (comprises the body)

while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
receivedLength += value.length;
console.log(`Received ${receivedLength} of ${contentLength}`);
}

const blob = new Blob(chunks);
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = 'example.exe';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(link.href);
}
</script>
</head>

Expand All @@ -25,8 +103,29 @@ <h1>Malware download page</h1>
<p>This is an example malware page that DuckDuckGo clients intend to block. If you arrive here by mistake; there's
nothing to worry about, we just use this page to test if our client blocking is working.</p>

<button id="run" onclick="run()">Download Button</button>
<a href="/security/badware/phishing-redirect/download">Download Link</a>
<h2>Blocked URL Targets</h2>
<p>Click the buttons below to test various malware detection techniques based on JS file download techniques:</p>
<label>
<input type="checkbox" id="delayedCheckbox"> Delay Download
</label>
<ul>
<li><button id="run" onclick="linkHrefDownload()">Href Download Button</button></li>
<li><button id="run" onclick="blobDownload()">Blob Download Button</button></li>
<li><button id="run" onclick="xhrDownload()">XHR Download Button</button></li>
<li><button id="run" onclick="iframeDownload()">Iframe Download Button</button></li>
<li><button id="run" onclick="windowLocationDownload()">Window Location Download Button</button></li>
<li><button id="run" onclick="fetchStreamDownload()">Fetch Stream Download Button</button></li>
<li><a href="/security/badware/phishing-redirect/download">Download Link</a></li>
</ul>

<h2>Blocked File Content</h2>
<p>These links are to files where the file content should be blocked, as opposed to the file URL or JS technique:</p>
<ul>
<li><a href="/security/badware/phishing-redirect/files/bad_app_file_on_scan.exe">Uncommon, then "malicious" warning after deep scanning</a></li>
<li><a href="/security/badware/phishing-redirect/files/unknown.apk">File warning on Android</a></li>
<li><a href="/security/badware/phishing-redirect/files/content.exe">File warning on Desktop</a></li>
<li><a href="/security/badware/phishing-redirect/files/zip_password_1234.zip">Password Protected ZIP</a></li>
</ul>
</body>

</html>
2 changes: 2 additions & 0 deletions security/badware/server/files/bad_app_file_on_scan.exe
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bad file for app test
bda64933-b495-4a56-831e-ecb86351f038
14 changes: 14 additions & 0 deletions security/badware/server/files/content.exe
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
random_data_here_not_actually_an_exe_nor_malicious
all_this_data_can_be_safely_ignored
fghij12345klmno67890pqrstuvwxyz
abcdefgHIJKLMNOpqrstuvwxyz09876
sample_text_line_1: The quick brown fox jumps over the lazy dog.
sample_text_line_2: 42 is the answer to life, the universe, and everything.
sample_text_line_3: Random number: 9876543210
sample_text_line_4: {"name": "Test User", "age": 30, "active": true}
sample_text_line_5: 3.14159, 2.71828, 1.41421, 0.57721
dummy_data_entry_1: This is a placeholder for testing purposes.
dummy_data_entry_2: Another line of random data for this file.
dummy_data_entry_3: Just some more text to fill up space.
dummy_data_entry_4: 12345-ABCDE-67890-FGHIJ-KLMNOP
dummy_data_entry_5: 2025-02-10T17:40:00Z
1 change: 1 addition & 0 deletions security/badware/server/files/unknown.apk
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
not_a_real_apk_just_text
Binary file added security/badware/server/files/zip_password_1234.zip
Binary file not shown.
52 changes: 40 additions & 12 deletions security/badware/server/routes.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const express = require('express');
const path = require('path');
const fs = require('fs');
const router = express.Router();

// Returns a 301 redirect to the main phishing test page
Expand Down Expand Up @@ -61,20 +63,46 @@ router.post('/form', (req, res) => {
res.send('Form submitted');
});

// Serves an arbitrary executable file to test download detection
// Serves an arbitrary executable file to test download detection, with optional delay
router.get('/download', (req, res) => {
// Create a buffer with a minimal valid PE header
const fileData = Buffer.alloc(64);
// MZ header (magic bytes)
const magicBytes = [0x4d, 0x5a];
// DOS stub filled with zeros
const dosStub = new Uint8Array(58).fill(0);
fileData.set(magicBytes, 0);
fileData.set(dosStub, 2);
const returnFile = () => {
// Create a buffer with a minimal valid PE header
const fileData = Buffer.alloc(64);
// MZ header (magic bytes)
const magicBytes = [0x4d, 0x5a];
// DOS stub filled with zeros
const dosStub = new Uint8Array(58).fill(0);
fileData.set(magicBytes, 0);
fileData.set(dosStub, 2);

res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename="test.exe"');
res.send(fileData);
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader(
'Content-Disposition',
'attachment; filename="test.exe"'
);
res.send(fileData);
};
if (req.query.delay && !isNaN(req.query.delay)) {
if (req.query.delay > 5000) {
return res.status(400).send('Delay too long');
}
setTimeout(() => {
returnFile();
}, req.query.delay);
} else {
returnFile();
}
});

// serve ./files/
router.get('/files/:filename', (req, res) => {
const filePath = path.join(__dirname, 'files', req.params.filename);
fs.access(filePath, fs.constants.F_OK, (err) => {
if (err) {
return res.status(404).send('File not found');
}
res.sendFile(filePath);
});
});

module.exports = router;

0 comments on commit 1e4070f

Please sign in to comment.