brinkiepie
9/21/2015 - 7:24 PM

RCS deobf

RCS deobf

var fs = require('fs')
var esprima = require('esprima')
var escodegen = require('escodegen')
var estraverse = require('estraverse')
var esrefactor = require('esrefactor')

var origFile = process.argv[2] || 'rs.min.js'
var outFile = origFile.replace(/\.min\.js$/, '.js')

var signatures = {}
try {
  signatures = parseSignatures(fs.readFileSync('rcs-method-signatures.txt', 'utf8'))
}
catch (e) {}

fs.readFile(origFile, { encoding: 'utf8' }, function (e, content) {
  var ast = esprima.parse(content, { range: true, comment: true })
  var decl = getObfuscatedDeclaration(ast)
  var name = decl.id.name
  var map = decl.init.elements

  // replace a['b'] with a.b
  estraverse.replace(ast, {
    enter: function (node) {
      if (node.type === 'MemberExpression' && node.object.name === name) {
        return node.property && map[node.property.value] || node
      }
    },
    leave: function (node) {
      if (node.type === 'MemberExpression' && node.computed &&
          typeof node.property.value === 'string' &&
          /^[a-z_$][0-9a-z_$]*$/i.test(node.property.value)) {
        node.property = $smallRange({ type: 'Identifier', name: node.property.value })
        node.computed = false
      }
    }
  })

  ast = cleanAst(ast)

  if (signatures) {
    var getName = function (node) {
      if (node.type === 'Identifier') return node.name
      if (node.type === 'MemberExpression') {
        return getName(node.object) + '.' + getName(node.property)
      }
    }
    var renames = []
    estraverse.traverse(ast, {
      enter: function (node) {
        var name, fn
        if (node.type === 'FunctionDeclaration') {
          name = getName(node.id)
          fn = node
        }
        if (node.type === 'AssignmentExpression' &&
            node.right.type === 'FunctionExpression') {
          name = getName(node.left)
          fn = node.right
        }
        if (node.type === 'Property' &&
            node.value.type === 'FunctionExpression') {
          name = getName(node.key)
          fn = node.value
        }
        if (name && fn && name in signatures) {
          var params = signatures[name]
          params.forEach(function (newName, i) {
            if (fn.params[i])
            renames.push({
              idx: fn.params[i].range[0],
              to: newName
            })
          })
        }
        else {
          
        }
      }
    })
    processRenames(ast, renames)
  }

  var better = escodegen.generate(ast, {
    format: { indent: { style: '  ' } },
    comment: true
  })
  fs.writeFile(outFile, better, function (e) {
    console.log(e)
  })
})

function getObfuscatedDeclaration(ast) {
  var first
  var body = ast.body
  if (body[0].type === 'ExpressionStatement') {
    body = body[0].expression.argument.callee.body.body
  }
  // remove the obfuscated var map
  first = body.shift()
  if (first.type === 'VariableDeclaration') {
    var res = first.declarations.shift()
    // some scripts have more declarations bunched together, so we
    // should put the extra decls back in
    if (first.declarations.length > 0) {
      body.unshift(first)
    }
    return res
  }
}

function parseSignatures(string) {
  return string
    .split('\n')
    .filter(function (x) { return /^[a-z._0-9]+\((?:[a-z_0-9, ]*)\)$/i.test(x) })
    .map(function (line) { return /^([a-z._0-9]+)\((.*?)\)$/i.exec(line).slice(1) })
    .reduce(function (signatures, line) {
      var name = line[0], args = line[1].split(',').map(function (arg) { return arg.trim() })
      signatures[name] = args
      return signatures
    }, {})
}

function $id(name) {
  return $smallRange({ type: 'Identifier', name: name })
}
function $literal(value) { return { type: 'Literal', value: value } }
function $comment(ast, type, value) {
  ast.trailingComments = ast.trailingComments || []
  ast.trailingComments.push({
    type: type,
    value: value
  })
  return ast
}
function $largeRange(ast) { return ast.range = [ 0, Infinity ], ast }
function $smallRange(ast, n) {
  return ast.range = [ n || 0, n || 0 ], ast
}
function $statement(expr) {
  return {
    type: 'ExpressionStatement',
    expression: expr,
    range: expr.range
  }
}
function $block(stmt) {
  return stmt.type === 'BlockStatement' ? stmt : {
    type: 'BlockStatement',
    body: [ stmt ],
    range: stmt.range
  }
}

// expand ternary expression statements into if(){}else{} blocks
function expandTernary(expr) {
  return {
    type: 'IfStatement',
    range: expr.range,
    test: expr.test,
    consequent: $block($statement(expr.consequent)),
    alternate: expr.alternate.type === 'ConditionalExpression'
      ? expandTernary(expr.alternate)
      : $block($statement(expr.alternate))
  }
}

function wrapIfBranches(node) {
  if (node.consequent.type !== 'BlockStatement') {
    node.consequent = $block(node.consequent)
  }
  if (node.alternate && node.alternate !== 'BlockStatement') {
    node.alternate = $block(node.alternate)
  }
  return node
}
function wrapBody(node) {
  if (node.body.type !== 'BlockStatement') {
    node.body = $block(node.body)
  }
}
function expandAndOr(node) {
  if (node.expression.operator === '&&') {
    return {
      type: 'IfStatement',
      range: node.range,
      test: node.expression.left,
      consequent: $block($statement(node.expression.right))
    }
  }
  else if (node.expression.operator === '||') {
    return {
      type: 'IfStatement',
      range: node.range,
      test: {
        type: 'UnaryExpression',
        operator: '!',
        range: node.expression.left.range,
        argument: node.expression.left,
        prefix: true
      },
      consequent: $statement(node.expression.right)
    }
  }
  return node
}

var unyodaOperators = {
  '>': '<',
  '<': '>',
  '>=': '<=',
  '<=': '>='
}
function unyoda(node) {
  if (node.left.type === 'Literal' && node.right.type !== 'Literal') {
    var tmp = node.right
    node.right = node.left
    node.left = tmp
    if (node.operator in unyodaOperators) {
      node.operator = unyodaOperators[node.operator]
    }
  }
  return node
}

function findReturnVar(ast) {
  var lastSt = ast.body[ast.body.length - 1]
  if (lastSt && lastSt.type === 'ReturnStatement') {
    var retVal = lastSt.argument
    return retVal.type === 'NewExpression'
      ? retVal.callee
      : retVal.type === 'Identifier'
      ? retVal
      : null
  }
}

function cleanAst(ast) {
  estraverse.replace(ast, {
    enter: function (node) {
      // add braces around branch/loop constructs if they are not yet present
      if (node.type === 'IfStatement') {
        wrapIfBranches(node)
      }
      else if (node.type === 'ForStatement' ||
               node.type === 'WhileStatement') {
        wrapBody(node)
      }
      // turn !0, !1 into true, false
      else if (node.type === 'UnaryExpression' &&
               node.operator === '!' &&
               node.argument.type === 'Literal') {
        if (node.argument.value === 0) {
          return { type: 'Literal', value: true, raw: 'true' }
        }
        else if (node.argument.value === 1) {
          return { type: 'Literal', value: false, raw: 'false' }
        }
      }
      else if (node.type === 'BinaryExpression') {
        return unyoda(node)
      }
      // expand ternary ?: statements to if/else statements
      else if (node.type === 'ExpressionStatement' &&
               node.expression.type === 'ConditionalExpression') {
        return expandTernary(node.expression)
      }
      // expand compressed &&, || expressions into if/else statements
      else if (node.type === 'ExpressionStatement' &&
               node.expression.type === 'LogicalExpression') {
        return expandAndOr(node)
      }
      // expand some expressions into multiple statements
      else if (node.type === 'BlockStatement') {
        node.body = node.body.reduce(function (newBody, node) {
          // expand comma-separated expressions on a single line to multiple statements
          if (node.type === 'ExpressionStatement' &&
              node.expression.type === 'SequenceExpression') {
            return newBody.concat(node.expression.expressions.map($statement))
          }
          // expand comma-separated expressions in a return statement to multiple statements
          else if (node.type === 'ReturnStatement' &&
                   node.argument &&
                   node.argument.type === 'SequenceExpression') {
            var exprs = node.argument.expressions
            node.argument = exprs.pop()
            return newBody.concat(exprs.map($statement)).concat([ node ])
          }
          else if (node.type === 'EmptyStatement') {
            return newBody
          }
          return newBody.concat([ node ])
        }, [])
      }
    },
    leave: function (node) {
      // remove braces from else statements that contain only an if statement
      // (i.e. else if statements)
      if (node.type === 'IfStatement' &&
          node.alternate && node.alternate.type === 'BlockStatement' &&
          node.alternate.body.length === 1 && node.alternate.body[0].type === 'IfStatement') {
        node.alternate = node.alternate.body[0]
      }
      return node
    }
  })

  return ast
}

function processRenames(ast, renames) {
  var renaming = new esrefactor.Context(ast)
  renames.forEach(function (r) {
    var id = renaming.identify(r.idx)
    if (id) {
      // rename manually.
      // esrefactor renames things "in-place" in the source code,
      // which means that you have to parse the source again every
      // time you rename a variable. Since we don't need to retain
      // formatting (it's minified code at this point, after all)
      // we can manually rename all variables at once without ever
      // parsing the source again.
      if (id.identifier) id.identifier.name = r.to
      if (id.declaration) id.declaration.name = r.to
      id.references.forEach(function (node) { node.name = r.to })
    }
  })
  return ast
}
iterate(spec, object)
getModule(spec)
API.getUserByName(name)
f(command)
validURL(url)
htmlspecialchars(string)
striphtml(string)
isNumber(string)
sendSocket(action, param)
weirdEscape(string)
spawnNotification(title, message)
secondsToTimeClean(seconds)
addChatLog(className, iconClass, title, message)
addChatLogSmall(className, iconClass, title, message)
addRawChatLog(className, iconClass, title, message)
addRawChatLogSmall(className, iconClass, title, message)
addChatReceive(className, user, message)
contextClass(user)
getSpecialRank(uid)
getUserFromArgs(string)
langVar(string, values)
getFiles(done)
setCookie(name, value)
getCookie(name)
cleanASCII(string)
rsDelete(cid)
rsDebugMode(log)
parseDescription(description)
avatar(avatarId)
badge(badgeId)
language(langId)
rsCheckUpdates(__)
rsCheckUpdatesForced(__)
rsCheckLocation(__)
getSessionData(uid, key, defaultValue)
setSessionData(uid, key, value)
__onWaitlistUpdate(update)
__onWait_list_update(update)
__onDJAdvance(advance)
__autoJoin(advance)
__onChat(message)
rs.socket.onmessage(message)
__getPlaylists(force)
__onVoteUpdate(vote)
__onUserJoin(user)
__onUserLeave(user)
__onGrabUpdate(grab)
__onChatCommand(text)
__onScoreUpdate(score)
__onSkip(__)
__roomState(state)
__autoMute(advance)
__voteRecording(vote)
__grabRecording(grab)
__argumentParser(string)
__commandController(string)
__lengthAlert(advance)
__smartVote(advance)
__castVote(advance)
__userJoin(user)
__userLeave(user)
__userWoot(vote)
__userMeh(vote)
__userGrab(grab)
__chatLog(message)
__autoRespond(message)
__chatCMD(message)
__chatNotifications(message)
__mentionNotification(message)
__chatHistory(message)
__historyCheck(advance)
__getPermission(user)
__songStats(advance)
__mehRow(advance)
__nowPlaying(advance)
__inlineImages(message)
__customEmotes(message)
__checkImages(url, messageEl, __href)
__checkEmotes(url, messageEl, name)
__baBanUID(uid)
__baMuteUID(uid)
__baTrollUID(uid)
__baDelTrollUID(uid)
__userInformation(user, __)
__uiSkipButton(user)
__etaInfo(isSelf)
__checkLocked(message)
__onChatReceived(message)
{
  "dependencies": {
    "escodegen": "",
    "esprima": "",
    "esrefactor": "",
    "estraverse": ""
  }
}
#!/bin/sh

mkdir rcs/$(date '+%m%d')

curl https://code.radiant.dj/rs.min.js -o rcs/$(date '+%m%d')/rs.min.js
node unobfuscate rcs/$(date '+%m%d')/rs.min.js