• XSS.stack #1 – первый литературный журнал от юзеров форума

Обфускатор кода Rust на Node.js

Кодер

WebDev
Забанен
Регистрация
26.10.2022
Сообщения
360
Реакции
272
Гарант сделки
14
Депозит
1.5004 Ł и др.
Пожалуйста, обратите внимание, что пользователь заблокирован
Code.js:

JavaScript:
require('dotenv').config();

const fs = require('fs');
const path = require('path');
const Parser = require('tree-sitter');
const Rust = require('tree-sitter-rust');

// ============ ЗАГРУЗКА НАСТРОЕК ИЗ .env ============
const config = {
  enableInjection: process.env.ENABLE_INJECTION === 'true',
  enableAliases: process.env.ENABLE_ALIASES === 'true',
  enableRenameProgramMod: process.env.ENABLE_RENAME_PROGRAM_MOD === 'true',
  enableAddJunkStatements: process.env.ENABLE_ADD_JUNK_STATEMENTS === 'true',
  enableRenameIdentifiers: process.env.ENABLE_RENAME_IDENTIFIERS === 'true',

  enableMinify: process.env.ENABLE_MINIFY === 'false',
  snakeCasePrefix: process.env.SNAKE_CASE_PREFIX || 'obf',
  snakeCaseLength: parseInt(process.env.SNAKE_CASE_LENGTH || '4', 10),
  camelCasePrefix: process.env.CAMEL_CASE_PREFIX || 'En',
  camelCaseLength: parseInt(process.env.CAMEL_CASE_LENGTH || '4', 10),
  junkRatio: parseFloat(process.env.JUNK_RATIO || '20'),
};

let renameMap = new Map();

function randomIdentSnakeCase(
  prefix = config.snakeCasePrefix,
  length = config.snakeCaseLength
) {
  const firstChars = 'abcdefghijklmnopqrstuvwxyz';
  const allChars = 'abcdefghijklmnopqrstuvwxyz0123456789';

  let result = prefix.toLowerCase() + '_';
  if (length > 0) {
    result += firstChars.charAt(Math.floor(Math.random() * firstChars.length));
  }
  for (let i = 1; i < length; i++) {
    result += allChars.charAt(Math.floor(Math.random() * allChars.length));
  }
  return result;
}

function randomIdentCamelCase(
  prefix = config.camelCasePrefix,
  length = config.camelCaseLength
) {
  const firstChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  const allChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

  let result = prefix;
  if (length > 0) {
    result += firstChars.charAt(Math.floor(Math.random() * firstChars.length));
  }
  for (let i = 1; i < length; i++) {
    result += allChars.charAt(Math.floor(Math.random() * allChars.length));
  }
  return result;
}

const junkSnippets = [
  [
    'let mut {{VAR}} = 42;',
    '{{VAR}} += 1;',
    'if {{VAR}} < 50 {',
    '    {{VAR}} *= 2;',
    '}',
  ],
  [
    'let {{VAR}} = vec![1,2,3];',
    'for x in &{{VAR}} {',
    '    println!("x: {}", x);',
    '}',
  ],
  [
    'let {{VAR}} = "dummy_string";',
    'println!("Len: {}", {{VAR}}.len());',
  ],
  [
    'fn {{VAR}}_helper() {',
    '    let _tmp = 123;',
    '    // do nothing',
    '}',
    '{{VAR}}_helper();'
  ],
  [
    'let {{VAR}} = (10, 20);',
    'let sum = {{VAR}}.0 + {{VAR}}.1;',
    'println!("sum = {}", sum);',
    'if sum > 25 {',
    '    println!("big sum");',
    '}',
  ],
  [
    'let {{VAR}} = 0;',
    'while {{VAR}} < 3 {',
    '    {{VAR}} += 1;',
    '    println!("while loop: {}", {{VAR}});',
    '}'
  ],
  [
    'let {{VAR}} = Some(100);',
    'if let Some(val) = {{VAR}} {',
    '    println!("val = {}", val);',
    '} else {',
    '    println!("no val");',
    '}'
  ],
  [
    'let mut {{VAR}} = String::new();',
    '{{VAR}}.push_str("junk");',
    '{{VAR}}.push(\'_\');',
    'println!("junk str: {}", {{VAR}});',
  ],
  [
    'let {{VAR}} = [0; 4];',
    'println!("arr: {:?}", {{VAR}});',
  ],
  [
    'struct {{VAR}}Struct { x: i32, y: i32 }',
    'let point = {{VAR}}Struct { x: 1, y: 2 };',
    'println!("point.x = {}", point.x);'
  ],
];

function collectDeclarations(node, sourceCode) {
  const nodeType = node.type;

  if (nodeType === 'function_item') {
    if (config.enableRenameIdentifiers) {
      const fnNameNode = node.childForFieldName('name');
      if (fnNameNode) {
        const oldName = sourceCode.slice(fnNameNode.startIndex, fnNameNode.endIndex);
        const newName = randomIdentSnakeCase('fn', 4);
        renameMap.set(oldName, newName);

        const paramsNode = node.childForFieldName('parameters');
        if (paramsNode) {
          for (let i = 0; i < paramsNode.childCount; i++) {
            const child = paramsNode.child(i);
            if (child.type === 'parameter') {
              const patNode = child.childForFieldName('pattern');
              if (patNode) {
                const oldParamName = sourceCode.slice(
                  patNode.startIndex,
                  patNode.endIndex
                );
                if (/^[A-Za-z_]\w*$/.test(oldParamName)) {
                  const newParamName = randomIdentSnakeCase('prm', 3);
                  renameMap.set(oldParamName, newParamName);
                }
              }
            }
          }
        }
      }
    }
  } else if (nodeType === 'let_declaration') {
    if (config.enableRenameIdentifiers) {
      const patNode = node.childForFieldName('pattern');
      if (patNode) {
        const oldVarName = sourceCode.slice(patNode.startIndex, patNode.endIndex);
        if (/^[A-Za-z_]\w*$/.test(oldVarName)) {
          const newVarName = randomIdentSnakeCase('var', 4);
          renameMap.set(oldVarName, newVarName);
        }
      }
    }
  } else if (nodeType === 'enum_item') {
    if (config.enableRenameIdentifiers) {
      const nameNode = node.childForFieldName('name');
      if (nameNode) {
        const oldEnumName = sourceCode.slice(nameNode.startIndex, nameNode.endIndex);
        const newEnumName = randomIdentCamelCase('En', 4);
        renameMap.set(oldEnumName, newEnumName);
      }
      const bodyNode = node.childForFieldName('body');
      if (bodyNode) {
        for (let i = 0; i < bodyNode.childCount; i++) {
          const variant = bodyNode.child(i);
          if (variant.type === 'enum_variant') {
            const varNameNode = variant.childForFieldName('name');
            if (varNameNode) {
              const oldVariantName = sourceCode.slice(
                varNameNode.startIndex,
                varNameNode.endIndex
              );
              const newVariantName = randomIdentCamelCase('Var', 4);
              renameMap.set(oldVariantName, newVariantName);
            }
          }
        }
      }
    }
  } else if (nodeType === 'struct_item') {
    if (config.enableRenameIdentifiers) {
      const structNameNode = node.childForFieldName('name');
      if (structNameNode) {
        const oldStructName = sourceCode.slice(
          structNameNode.startIndex,
          structNameNode.endIndex
        );
        const newStructName = randomIdentSnakeCase('st', 4);
        renameMap.set(oldStructName, newStructName);
      }
      const bodyNode = node.childForFieldName('body');
      if (bodyNode) {
        for (let i = 0; i < bodyNode.childCount; i++) {
          const fieldDecl = bodyNode.child(i);
          if (fieldDecl.type === 'field_declaration') {
            const fieldNameNode = fieldDecl.childForFieldName('name');
            if (fieldNameNode) {
              const oldFieldName = sourceCode.slice(
                fieldNameNode.startIndex,
                fieldNameNode.endIndex
              );
              if (/^[A-Za-z_]\w*$/.test(oldFieldName)) {
                const newFieldName = randomIdentSnakeCase('fld', 4);
                renameMap.set(oldFieldName, newFieldName);
              }
            }
          }
        }
      }
    }
  }

  for (let i = 0; i < node.childCount; i++) {
    collectDeclarations(node.child(i), sourceCode);
  }
}

function replaceIdentifiers(node, sourceCode, chunks) {
  if (!config.enableRenameIdentifiers) {
    return;
  }

  const nodeType = node.type;
  if (
    nodeType === 'identifier' ||
    nodeType === 'field_identifier' ||
    nodeType === 'type_identifier'
  ) {
    const oldName = sourceCode.slice(node.startIndex, node.endIndex);
    const newName = renameMap.get(oldName);
    if (newName) {
      chunks.push({
        start: node.startIndex,
        end: node.endIndex,
        replacement: newName,
      });
    }
  }
  for (let i = 0; i < node.childCount; i++) {
    replaceIdentifiers(node.child(i), sourceCode, chunks);
  }
}

function applyReplacements(sourceCode, replacements) {
  let result = '';
  let currentPos = 0;

  replacements.sort((a, b) => a.start - b.start);

  for (const r of replacements) {
    result += sourceCode.slice(currentPos, r.start);
    result += r.replacement;
    currentPos = r.end;
  }
  if (currentPos < sourceCode.length) {
    result += sourceCode.slice(currentPos);
  }
  return result;
}

function generateOneJunkSnippet() {
  const snippetIndex = Math.floor(Math.random() * junkSnippets.length);
  const snippet = junkSnippets[snippetIndex];
  const varName = randomIdentSnakeCase('junk', 2);
  return snippet.map(line => line.replace(/{{VAR}}/g, varName));
}

function wrapJunkSnippetLines(snippetLines) {
  return `
    ${snippetLines.join('\n    ')}
  `;
}

function collectFunctionBodies(node, functionNodes) {
  if (node.type === 'function_item') {
    const bodyNode = node.childForFieldName('body');
    if (bodyNode && bodyNode.type === 'block') {
      functionNodes.push(bodyNode);
    }
  }
  for (let i = 0; i < node.childCount; i++) {
    collectFunctionBodies(node.child(i), functionNodes);
  }
}

function addJunkStatementsToFunctions(rootNode, sourceCode) {
  if (!config.enableAddJunkStatements) {
    return [];
  }

  let functionNodes = [];
  collectFunctionBodies(rootNode, functionNodes);

  const totalLines = sourceCode.split('\n').length;
  const junkLinesTotal = Math.floor(totalLines * (config.junkRatio / 100));
  if (junkLinesTotal < 1 || functionNodes.length === 0) {
    return [];
  }

  let leftover = junkLinesTotal;
  let replacements = [];

  for (let i = 0; i < functionNodes.length; i++) {
    if (leftover < 1) {
      break;
    }
    const snippetLines = generateOneJunkSnippet();
    const snippetSize = snippetLines.length;

    if (snippetSize <= leftover) {
      leftover -= snippetSize;
      const startPos = functionNodes[i].startIndex + 1;
      const junkBlock = wrapJunkSnippetLines(snippetLines);

      replacements.push({
        start: startPos,
        end: startPos,
        replacement: junkBlock,
      });
    }
  }

  return replacements;
}

function injectExtraCode(sourceCode) {
  if (!config.enableInjection) {
    return sourceCode;
  }
  const injection = `
// >>> Obfuscation extra code start
fn qyqo_a37() {
    let mut ksr_ua02 = 0;
    for i in 0..3 {
        ksr_ua02 += i;
    }
    let gxn_lq82 = format!("HhcR_{}", ksr_ua02);
    let _ = gxn_lq82;
}

fn ddn_tk19(x: u64) -> u64 {
    let mut ksy_vj61 = x;
    for _ in 0..5 {
        ksy_vj61 = ksy_vj61.wrapping_add(1);
    }
    ksy_vj61
}
// >>> Obfuscation extra code end
`;
  return injection + '\n' + sourceCode;
}

function insertAliasesAfterPrelude(lines) {
  if (!config.enableAliases) {
    return lines;
  }

  let inserted = false;
  return lines.map((line) => {
    if (!inserted && /^use\s+anchor_lang::prelude::\*;/.test(line)) {
      inserted = true;
      const alias1 = randomIdentSnakeCase('dgt', 2);
      const alias2 = randomIdentSnakeCase('fkm', 2);
      const alias3 = randomIdentSnakeCase('awp', 2);
      const extra = [
        `use std::collections::HashMap as ${alias1};`,
        `use std::fmt::Write as ${alias2};`,
        `use spl_token::instruction::AuthorityType as ${alias3};`,
      ].join('\n');
      return line + '\n' + extra;
    }
    return line;
  });
}

function renameProgramMod(lines) {
  if (!config.enableRenameProgramMod) {
    return lines;
  }

  let inProgramSection = false;
  const randomModName = randomIdentSnakeCase('mod', 3);

  return lines.map((line) => {
    const trimmed = line.trim();
    if (trimmed.startsWith('#[program]')) {
      inProgramSection = true;
      return line;
    }
    if (inProgramSection && trimmed.startsWith('mod ')) {
      inProgramSection = false;
      return line.replace(/mod\s+\w+\s*\{/, `mod ${randomModName} {`);
    }
    return line;
  });
}

function minifyRustCode(sourceCode) {
  const parser = new Parser();
  parser.setLanguage(Rust);
  const tree = parser.parse(sourceCode);

  const tokens = [];
  function traverse(node) {
    for (let i = 0; i < node.childCount; i++) {
      traverse(node.child(i));
    }
    if (node.childCount === 0) {
      const text = sourceCode.slice(node.startIndex, node.endIndex);
      if (node.type === 'comment') {
      } else if (/^\s+$/.test(text)) {
      } else {
        tokens.push({ text, start: node.startIndex, end: node.endIndex });
      }
    }
  }
  traverse(tree.rootNode);

  tokens.sort((a, b) => a.start - b.start);

  let result = '';
  const punctuation = /^[:;,\(\)\[\]\{\}\.<>\=\+\-\*\/&\|\^!%]+$/;

  for (let i = 0; i < tokens.length; i++) {
    if (i === 0) {
      result += tokens[i].text;
      continue;
    }
    const prev = tokens[i - 1].text;
    const curr = tokens[i].text;
    const needSpace = !punctuation.test(prev) && !punctuation.test(curr);

    if (needSpace) {
      result += ' ' + curr;
    } else {
      result += curr;
    }
  }

  return result;
}

function obfuscateRustCodeWithAst(sourceCode) {
  let modified = injectExtraCode(sourceCode);

  let lines = modified.split('\n');
  lines = insertAliasesAfterPrelude(lines);
  lines = renameProgramMod(lines);
  modified = lines.join('\n');

  const parser1 = new Parser();
  parser1.setLanguage(Rust);
  const tree1 = parser1.parse(modified);

  const junkReplacements = addJunkStatementsToFunctions(tree1.rootNode, modified);
  let codeWithJunk = applyReplacements(modified, junkReplacements);

  renameMap.clear();
  const parser2 = new Parser();
  parser2.setLanguage(Rust);
  const tree2 = parser2.parse(codeWithJunk);

  collectDeclarations(tree2.rootNode, codeWithJunk);

  let renameReplacements = [];
  replaceIdentifiers(tree2.rootNode, codeWithJunk, renameReplacements);
  let finalCode = applyReplacements(codeWithJunk, renameReplacements);

  if (config.enableMinify) {
    finalCode = minifyRustCode(finalCode);
  }

  return finalCode;
}

function main() {
  const args = process.argv.slice(2);
  if (args.length < 2) {
    console.error('Usage: node obfuscate-ast.js <input.rs> <output.rs>');
    process.exit(1);
  }
  const [inputFile, outputFile] = args;

  if (!fs.existsSync(inputFile)) {
    console.error(`File not found: ${inputFile}`);
    process.exit(1);
  }

  const sourceCode = fs.readFileSync(inputFile, 'utf8');
  const obf = obfuscateRustCodeWithAst(sourceCode);

  fs.writeFileSync(outputFile, obf, 'utf8');
  console.log(`Obfuscated code saved to ${outputFile}`);
}

main();

.env:

Код:
ENABLE_INJECTION=true
ENABLE_ALIASES=true
ENABLE_RENAME_PROGRAM_MOD=true
ENABLE_ADD_JUNK_STATEMENTS=true
ENABLE_RENAME_IDENTIFIERS=true


SNAKE_CASE_PREFIX=obf
SNAKE_CASE_LENGTH=4
CAMEL_CASE_PREFIX=En
CAMEL_CASE_LENGTH=4

JUNK_LINES_MAX=5
JUNK_RATIO=20

ENABLE_MINIFY=true
 
Do you use nvim by any chance?)

?
JavaScript:
enableMinify: process.env.ENABLE_MINIFY === 'true',
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Пожалуйста, обратите внимание, что пользователь заблокирован
But why do you need minified version for a compiled language? Or am I missing something?
Человек просил так сделать - я сделал)
 
Собрал код запустил
node .\index.js .\main.rs m12ain2.rs
создался идентичный файл без обус кода
не ворк что над делать ?
 
Собрал код запустил
node .\index.js .\main.rs m12ain2.rs
создался идентичный файл без обус кода
не ворк что над делать ?
мне кажется ты уже писал об этом в треде
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх