Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save UnbuiltAlmond8/bd5642276b702bf02999a62a4de2f0e7 to your computer and use it in GitHub Desktop.

Select an option

Save UnbuiltAlmond8/bd5642276b702bf02999a62a4de2f0e7 to your computer and use it in GitHub Desktop.
A reverse engineered version of PHPKobo's Element Tab Obfuscator.
(() => {
// Helper: Normalize selectors into an array of DOM elements
const normalizeSelector = (selector, fallback) => {
let result = [];
if (!selector) {
selector = fallback;
}
if (window.Array.isArray(selector)) {
result = selector;
} else if (typeof selector === 'string') {
const nodes = document.querySelectorAll(selector);
result = [].slice.call(nodes);
} else if (selector instanceof window.HTMLElement) {
result.push(selector);
} else if ((selector instanceof window.HTMLCollection) || (selector.constructor.name == 'NodeList')) {
result = [].slice.call(selector);
}
return result;
};
// Helper: Filter elements to ensure they exist within the document body
const filterVisibleElements = (elementArray) => {
const arr = []
elementArray.forEach((el) => {
if (document.body.contains(el)) {
arr.push(el);
}
});
return arr;
};
const randomInt = (min, max) => {
return window.Math.floor(window.Math.random() * (max - min) + min);
};
const randomString = (length) => {
let out = '';
const alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const alphaLen = alphabet.length;
for (let i = 0; i < length; i++) {
out += alphabet.charAt(window.Math.floor(window.Math.random() * alphaLen));
}
return out;
};
const ensureArray = (obj) => {
return (window.Array.isArray(obj)) ? obj : [obj];
};
// Configuration Getters
const getConfigValue = (config, property, fallback) => {
return (property in config) ? config[property] : fallback;
};
const getConfigIntNullable = (config, property, fallback) => {
const result = parseInt(getConfigValue(config, property, fallback));
return isNaN(result) ? null : result;
};
const getConfigInt = (config, property, fallback) => {
const result = parseInt(getConfigValue(config, property, fallback));
return isNaN(result) ? fallback : result;
};
const getConfigBool = (config, property, fallback) => {
return getConfigValue(config, property, fallback) ? 1 : 0;
};
// Handles splitting UTF-16 surrogate pairs (emojis, complex chars) correctly
const splitStringWithSurrogates = (inputString) => {
const result = [];
let buffer = []; // Originally string_from_hostname
for (let i = 0; i < inputString.length; i++) {
const charCode = inputString.charCodeAt(i);
// Check for High Surrogate or Low Surrogate
if (((0xD800 <= charCode) && (charCode <= 0xDBFF)) || ((0xDC00 <= charCode) && (charCode <= 0xDFFF))) {
buffer += inputString[i];
} else {
if (buffer) {
result.push(buffer);
buffer = '';
}
result.push(inputString[i]);
}
}
if (buffer) {
result.push(buffer);
}
return result;
};
const removeScriptTags = () => {
let count = 0
document.querySelectorAll('script').forEach((script) => {
script.remove();
count++;
});
return count;
};
// Originally _FqBsb
const removeComments = () => {
return ((count) => {
((root, iterator) => {
if (root) {
// Method 1: NodeIterator
((node, iter) => {
iter = document.createNodeIterator(root, window.NodeFilter.SHOW_COMMENT, (node) => {
return window.NodeFilter.FILTER_ACCEPT;
});
while ((node = iter.nextNode())) {
node.remove();
count++;
}
})();
// Method 2: Recursive Sibling Traversal (Backup/Thorough check)
(iterator = (node, direction) => {
if (node) {
iterator(!direction ? node.previousSibling : node.nextSibling, direction);
if (node.nodeType == window.Node.COMMENT_NODE) {
node.remove();
count++;
}
}
return iterator;
})(root.previousSibling, 0)(root.nextSibling, 1);
}
})(document.documentElement);
return count;
})(0);
};
let stats = {
n_run: 0,
n_root: 0,
n_text: 0,
n_char: 0,
n_script: 0,
n_comment: 0
};
let errorMessage = 'OK';
// Global Configuration Variables
let currentConfig, targetSelector, startDelay, obfuscatedTagName, applyClassName, excludeClassName;
let excludedTags, idPrefix, idPrefixLength, isContentEditable, shouldRemoveScripts, shouldRemoveComments;
let stylesheet, usedIds, processedNodesMap, whitespaceRegex;
// Originally eto_config
const parseConfig = (config) => {
try {
if (config === undefined) {
return;
}
currentConfig = Object.assign({}, config);
targetSelector = getConfigValue(currentConfig, 'selector', null);
startDelay = getConfigIntNullable(currentConfig, 'start', 0);
obfuscatedTagName = getConfigValue(currentConfig, 'tag_name', 'eto');
applyClassName = getConfigValue(currentConfig, 'apply_cname', 'eto-apply');
excludeClassName = getConfigValue(currentConfig, 'exclude_cname', 'eto-exclude');
// Originally _GdYbz3
excludedTags = ensureArray(('exclude_tag' in currentConfig) ? currentConfig.exclude_tag : ['script', 'style', 'textarea', 'select']).map((tag) => tag.toLowerCase());
idPrefix = getConfigValue(currentConfig, 'id_prefix', 'eto-');
idPrefixLength = window.Math.max(0, getConfigInt(currentConfig, 'id_prefix_len', 10));
isContentEditable = getConfigBool(currentConfig, 'contenteditable', 1);
shouldRemoveScripts = getConfigBool(currentConfig, 'remove_script', 1);
shouldRemoveComments = getConfigBool(currentConfig, 'remove_comment', 1);
} catch (error) {
errorMessage = error.name + ': ' + error.message;
}
};
// Originally _ZaA49f
const initEnvironment = () => {
if (document.currentScript) {
document.currentScript.remove();
}
const styleEl = document.createElement('style');
document.head.appendChild(styleEl);
stylesheet = styleEl.sheet;
usedIds = {};
processedNodesMap = new window.WeakMap(); // Originally weak_map
whitespaceRegex = new window.RegExp("^\\s*$"); // Originally _CRi31
};
const generateUniqueId = () => {
let i = 0;
let id;
do {
id = idPrefix + randomString(idPrefixLength + i);
i++;
} while (id in usedIds);
usedIds[id] = 1;
return id;
};
// Originally _Y77: Injects CSS to display a character via pseudo-element content
const injectCssRule = (elementId, pseudoType, charContent) => {
const selector = '#' + elementId;
// Obfuscate variable name
const randomVarSuffix = randomInt(0, 5) * 2 + ((pseudoType[0] == 'b') ? 0 : 1); // logic creates variance based on 'before' vs 'after'
const varName = '--' + elementId + '-' + randomVarSuffix.toString();
// Convert char to hex if needed for CSS content
const cssContentValue = (charContent.length == 1) ? "\\" + charContent.codePointAt(0).toString(16) : charContent;
const varDeclaration = varName + ':"' + cssContentValue + '";';
const rule1 = selector + '{' + varDeclaration + '}';
stylesheet.insertRule(rule1, 0);
const contentRule = 'content:var(' + varName + ');';
const rule2 = selector + '::' + pseudoType + '{' + contentRule + '}';
stylesheet.insertRule(rule2, 0);
};
// Originally _FkDBQ: Decides if chars go into ::before or ::after
const renderCharsViaCss = (id, charBefore, charAfter) => {
if (charBefore) {
injectCssRule(id, 'before', charBefore);
stats.n_char += charBefore.length;
}
if (charAfter) {
injectCssRule(id, 'after', charAfter);
stats.n_char += charAfter.length;
}
};
// Originally _H7a6x9: Creates a single wrapper element
const createObfuscatedElement = (charBefore, charAfter) => {
return ((id, element) => {
id = generateUniqueId();
renderCharsViaCss(id, charBefore, charAfter);
element = document.createElement(obfuscatedTagName);
element.id = id;
return element;
})();
};
// Originally _UBx: Recursively builds a tree of elements to hold the string
const buildObfuscatedTree = (charArray) => {
return ((char1, char2, len, wrapper, nextLen, batchSize, batch) => {
len = charArray.length;
// Pop chars for the current node's pseudo-elements
if (len) {
char1 = charArray.shift();
len--;
if (len) {
char2 = charArray.pop();
len--;
}
}
wrapper = createObfuscatedElement(char1, char2);
nextLen = charArray.length + 1;
// If chars remain, recurse and append children
while (charArray.length > 0) {
// Randomly determine how many chars the child node handles
batchSize = window.Math.min(charArray.length, randomInt(1, nextLen));
batch = charArray.slice(0, batchSize);
charArray = charArray.slice(batchSize);
wrapper.appendChild(buildObfuscatedTree(batch));
}
return wrapper;
})(null, null);
};
// Originally _C1QEmd: Replaces a Text Node with the obfuscated DOM tree
const obfuscateTextNode = (textNode) => {
return ((textValue) => {
textValue = textNode.nodeValue;
// Only process if it contains actual text (not just whitespace)
if (textValue && !whitespaceRegex.test(textValue)) {
textNode.replaceWith(buildObfuscatedTree(splitStringWithSurrogates(textValue)));
stats.n_text++;
}
})();
};
// Originally _Fr5G4v: Checks if a node should be processed
const shouldProcessNode = (node) => {
return ((attrVal) => {
if (processedNodesMap.has(node)) {
return false;
}
if ((excludeClassName != '') && node.classList.contains(excludeClassName)) {
return false;
}
if (excludedTags.indexOf(node.tagName.toLowerCase()) != -1) {
return false;
}
if (isContentEditable) {
attrVal = node.getAttribute('contenteditable');
if (attrVal && (attrVal.toLowerCase()) == 'true') {
return false;
}
}
return true;
})();
};
// Originally _S4a: Recursive DOM walker to find text nodes
const traverseAndCollect = (container, node) => {
return ((i, child, type) => {
for (i = 0; i < node.childNodes.length; i++) {
child = node.childNodes[i];
type = child.nodeType;
if (type == window.Node.ELEMENT_NODE) {
if (shouldProcessNode(child)) {
traverseAndCollect(container, child)
}
} else if (type == Node.TEXT_NODE) {
container.textNodes.push(child);
}
}
})();
};
// Originally _QvkL2: Wrapper to get all text nodes in an element
const getTextNodesInElement = (element) => {
return ((container) => {
container = {
textNodes: [] // Originally _D4ge
};
traverseAndCollect(container, element);
return container.textNodes;
})();
};
// Originally _BFp: Process array of root elements
const processElements = (elements) => {
return ((i, el, textNodes, j, textNode) => {
for (i = 0; i < elements.length; i++) {
el = elements[i];
if (shouldProcessNode(el)) {
stats.n_root++;
textNodes = getTextNodesInElement(el);
for (j = 0; j < textNodes.length; j++) {
textNode = textNodes[j];
obfuscateTextNode(textNode);
}
processedNodesMap.set(el, 1);
}
}
})();
};
// Originally _Qe63: Main Execution Controller
const executeObfuscation = () => {
try {
return ((targets) => {
stats.n_root = 0;
stats.n_text = 0;
stats.n_char = 0;
stats.n_script = 0;
stats.n_comment = 0;
if (stats.n_run == 0) {
initEnvironment();
}
stats.n_run++;
if (shouldRemoveScripts) {
stats.n_script = removeScriptTags();
}
if (shouldRemoveComments) {
stats.n_comment = removeComments();
}
// Determine targets: specific class or entire body fallback
targets = (applyClassName == '') ? [] : filterVisibleElements(normalizeSelector(targetSelector, '.' + applyClassName));
if (targets.length == 0) {
targets.push(document.body);
}
processElements(targets);
return stats;
})();
} catch (error) {
errorMessage = error.name + ': ' + error.message;
return false;
}
};
const run = () => {
executeObfuscation();
};
// Public API mappings
load_eto_config = (config) => parseConfig(config);
runObfuscator = () => run();
getParams = () => currentConfig;
getStats = () => stats;
getStatus = () => errorMessage;
// Initialization Logic
const defaultConfig = {
apply_cname: '',
exclude_cname: ''
};
load_eto_config(defaultConfig);
document.addEventListener('DOMContentLoaded', () => {
if (window.Number.isInteger(startDelay)) {
startDelay = window.Math.max(0, startDelay);
if (startDelay == 0) {
runObfuscator();
} else {
window.setTimeout(() => {
runObfuscator(); // Note: Original called _run() which seemed to be a typo for run() or _KVR()
}, startDelay);
}
}
});
// --- DRM / Trial Version Check Logic ---
// Checks hostname, URL parameters, and a hardcoded expiry date (Sept 2025)
// If trial, injects a large "Free Trial Version" watermark link.
const isFreeTrial = (() => {
if (document.location.href.indexOf('free-trial-version=etobf') !== -1) {
return true;
} else {
const hostClean = window.location.hostname.replace(/[\d\.\:]/g, '');
if ((hostClean == '') || hostClean.includes('php') || hostClean.includes('local')) {
return false;
}
// Simple checksum on hostname to limit use
const hostCheck = ((window.Array.from(hostClean).reduce((sum, char) => sum + char.codePointAt(0), 0) + 2) % 4);
if (hostCheck < 2) {
return true;
} else {
// Time bomb check
if ((new window.Date()) >= (new window.Date('2025-09-01'))) {
return true;
} else {
return false;
}
}
}
})();
if (isFreeTrial) {
const watermark = document.createElement('a');
watermark.style = "position:fixed;display:block;z-index:100000;bottom:-100%;left:0;right:0;padding:20px 10px;color:#c7f9cc;background:rgba(0,0,0,0.6);font-family:Verdana;font-size:28px;font-weight:bold;line-height:1.5;letter-spacing:1px;text-align:center;text-decoration:none;box-shadow:0 0 5px #888;transition:bottom 3s;";
watermark.href = 'http://www.phpkobo.com/etobf?rev=r122-3216-1752463467';
watermark.target = '_blank';
watermark.innerHTML = "<div style='margin-bottom:-1px;font-family:Times New Roman;font-size:70%;font-style:italic;letter-spacing:4px;color:#80ed99;'>Thank you for evaluating</div><div>Elements Tab Obfuscator</div><div style='font-size:45%;color:#57cc99;'>Free Trial Version</div>";
window.setTimeout(function () {
document.body.appendChild(watermark);
window.setTimeout(function () {
watermark.style.bottom = '0';
}, 1000);
}, (window.Math.floor(window.Math.random() * 3 + 3)) * 1000);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment