Is РуТрекер VPN - расширение для доступа к сайту safe?
The extension fetches proxy server configurations at runtime from remote dead-drop URLs, allowing operators to silently reroute all proxied browser traffic.
RuTracker VPN retrieves its proxy server list from external sources including GitHub Pages, Blogspot, Google Docs, and Telegram using ROT3-obfuscated JSON, replacing the built-in server list stored in browser storage. This design gives the extension operator the ability to redirect all proxied traffic through any server without user action or an extension update. The extension also ships multiple shared proxy credentials in plaintext within its bundle and sends a persistent checkout identifier to api.hhos.ru on every subscription check.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Proxy and payment-API endpoints set at runtime from rotating dead-drop URLs
This VPN extension's service worker fetches remote config from GitHub Pages, Blogspot, or a Google Doc.
The ROT3-obfuscated JSON replaces the proxy list and overrides the payment host, letting the controller redirect traffic or payments.
You open the extension popup to use the proxy.
The popup sends a message to the service worker requesting the current configuration.
The service worker fetches a configuration file from a remote dead-drop URL and the popup applies it to local storage.
The decoded response replaces the proxy server list and the payment-API host stored on the device.
The dead-drop URLs and the fetch-with-fallback routine
function getDcnfJson(data){
var c=showx(data.trim(),3); // ROT3 decode
var j=JSON.parse(c);
if(j['u'] && j['u'].length>0){
ucheckout="https://"+j['u'];
chrome.storage.local.set({"apiurl":j['u']}); // override payment host
}
if(j['s']!==undefined){
chrome.storage.local.set({"servers":JSON.stringify(j['s'])}); // replace proxy list
}
}
function showx(text,shift){ // Caesar/ROT shift
return text.split('').map((ch)=>{
if(ch.match(/[a-z]/i)){
const code=ch.charCodeAt(0);
const offset=code>=65&&code<=90?65:97;
return String.fromCharCode(((code-offset-shift+26)%26)+offset);
}
return ch;
}).join('');
}The configuration file is served as JSON with a uniform ROT3 letter shift applied to the whole document, so the keys and values are not human-readable on the wire. Applying the inverse shift recovers the JSON, which carries the payment-API host (key 'u') and a list of base64-encoded proxy-server records (key 's'). Values shown are from the response observed during dynamic analysis (the proxy record is illustrative of the decoded host:port:login:pass|expiry|country format).
{"u":"obratx.shop","s":["MTY4LjgxLjc1LjEyNjo5MDkxOnRGQzQ3TjpCRHBtSnB8MjAyNi0wNy0xNHxpbg=="]}
// 's' entry base64-decodes to:
// 168.81.75.126:9091:tFC47N:BDpmJp|2026-07-14|in
// host : port : login : pass | expiry | country
//
// 'u' = obratx.shop -> chrome.storage.local['apiurl'] (overrides default api.hhos.ru)| Cache-Control | no-cache |
- s-extension.github.io
Primary config source (GitHub Pages, path /DataExst/data.json). Served the proxy list and payment host during analysis.
- dtxtension.blogspot.com
Fallback config source (Blogspot page /p/kronshtat.html) used if the primary host fails.
- docs.google.com
Fallback config source (a Google Docs document) used if the first two fail.
Fetches the extension's configuration dead-drop URL, reverses the ROT3 letter shift, and prints the decoded JSON along with each base64-decoded proxy-server record so you can see the payment host and server list the extension would apply.
// node decode-rutracker-config.js
// Reverses the ROT3 obfuscation the extension applies to its remote config.
const URLS = [
'https://s-extension.github.io/DataExst/data.json',
'https://dtxtension.blogspot.com/p/kronshtat.html',
];
function rot(text, shift) {
return text.split('').map((ch) => {
if (ch.match(/[a-z]/i)) {
const code = ch.charCodeAt(0);
const offset = code >= 65 && code <= 90 ? 65 : 97;
return String.fromCharCode(((code - offset - shift + 26) % 26) + offset);
}
return ch;
}).join('');
}
(async () => {
for (const url of URLS) {
try {
const res = await fetch(url, { headers: { 'Cache-Control': 'no-cache' } });
if (!res.ok) continue;
const wire = (await res.text()).trim();
const decoded = rot(wire, 3); // showx(data, 3)
const j = JSON.parse(decoded);
console.log('source :', url);
console.log('payment host :', j.u || j.url || '(none)');
const servers = j.s || j.servers || [];
console.log('server records:', servers.length);
servers.forEach((b64, i) => {
const rec = Buffer.from(b64, 'base64').toString('utf8');
console.log(` [${i}] ${rec}`); // host:port:login:pass|expiry|country
});
return;
} catch (e) {
console.warn('skip', url, e.message);
}
}
console.error('no config source responded');
})();
- 1Save this file as decode-rutracker-config.js.
- 2Run `node decode-rutracker-config.js` (Node 18+ has a built-in fetch).
- 3Read the printed payment host and the base64-decoded proxy-server records (host:port:login:pass|expiry|country).
Shared proxy usernames and passwords shipped in plaintext in the bundle
This proxy/VPN extension ships its server list as plain text in js/conf.js.
Several entries include a username/password as host:port:login:pass, visible in the files.
The pairs recur across servers, auto-answering proxy-auth prompts.
You enable a proxy server from the extension popup.
The selected server entry is parsed into host, port, login, and password.
When the proxy asks for authentication, the extension replies with the username and password embedded in its bundle.
The onAuthRequired handler returns the login/password parsed from the host:port:login:pass entry in js/conf.js.
| Field | Value | Why it matters | |
|---|---|---|---|
Shared port-20000 login | DHaNuRsEhGAL007 : <redacted> | One username/password pair reused across 20-plus proxy server IP entries on port 20000. | |
Discord-named proxy login | discord_0.0.2 : <redacted> | Credential for srv10.undiscord.com on port 443. | |
freeruproxy.ink login | openproxy : <redacted> | Credential reused for nl-hub and us-hub freeruproxy.ink entries on port 443. | |
accessyoutube login | ytpass_50JtWMp4 : <redacted> | Credential for srv11.accessyoutube.com on port 443. |
Credentials in conf.js and the handler that supplies them
// getProxyData(): split the selected entry into its parts
const [host,port,login,pass]=dpx.split(":");
let ob={host,port,login,pass};
proxyData=ob;
// authHandler: respond to the server's auth prompt with those values
authHandler=function(details){
if(proxyData && proxyData.login && proxyData.pass){
return { authCredentials:{ username:proxyData.login, password:proxyData.pass } };
}
return { cancel:true };
};
chrome.webRequest.onAuthRequired.addListener(authHandler,{urls:["<all_urls>"]},["blocking"]);Reads js/conf.js from the unpacked extension, parses the serverConfigs array, and lists the distinct login:password pairs and how many server entries reuse each one. Run it against the extension package to confirm the credentials are present in plaintext.
// node extract-proxy-creds.js /path/to/extracted/js/conf.js
const fs = require('fs');
const path = process.argv[2] || 'js/conf.js';
const src = fs.readFileSync(path, 'utf8');
// Pull the serverConfigs array literal
const m = src.match(/serverConfigs\s*=\s*\[([\s\S]*?)\]/);
if (!m) { console.error('serverConfigs array not found'); process.exit(1); }
const entries = [...m[1].matchAll(/['\"]([^'\"]+)['\"]/g)].map((x) => x[1]);
const byPair = new Map();
for (const e of entries) {
const parts = e.split(':');
if (parts.length >= 4) { // host:port:login:pass
const login = parts[2];
const pass = parts[3];
const key = `${login}:${pass}`;
byPair.set(key, (byPair.get(key) || 0) + 1);
}
}
console.log(`entries: ${entries.length}, distinct credential pairs: ${byPair.size}`);
for (const [pair, count] of byPair) {
const [login] = pair.split(':');
console.log(` login=${login} reused across ${count} server entr${count === 1 ? 'y' : 'ies'}`);
}
- 1Unpack the .crx (it's a zip).
- 2Save as extract-proxy-creds.js.
- 3Run `node extract-proxy-creds.js path/to/extracted/js/conf.js`.
- 4Review the credential-pair count and reuse per login.