Skip to content

Instantly share code, notes, and snippets.

@ylt
Forked from goto-bus-stop/get-rcs.sh
Last active February 24, 2016 02:44
Show Gist options
  • Save ylt/c0e5c7889c5f4d4c6c12 to your computer and use it in GitHub Desktop.
Save ylt/c0e5c7889c5f4d4c6c12 to your computer and use it in GitHub Desktop.
RCS deobf
#!/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
{
"dependencies": {
"escodegen": "",
"esprima": "",
"esrefactor": "",
"estraverse": ""
}
}
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)
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: false })
ast = removeEval(ast)
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 removeEval(ast) {
var body = ast.body;
if (body[0].type === 'ExpressionStatement') {
body = body[0].expression.arguments[0]
}
//console.log(body);
var data = escodegen.generate(body, {
format: { indent: { style: ' ' } },
comment: true
});
var code = eval("("+data+")")
return esprima.parse(code, { range: true, comment: false })
}
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()
var code = escodegen.generate(res.init, {
format: { indent: { style: ' ' } },
comment: true
});
var vals = esprima.parse(JSON.stringify(eval("("+code+")")))
res.init = vals.body[0].expression
// 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
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment