mirror of
https://github.com/thegeneralist01/twitter-tid-deobf-fork
synced 2026-01-11 15:40:28 +01:00
560 lines
No EOL
21 KiB
JavaScript
560 lines
No EOL
21 KiB
JavaScript
const fs = require('fs');
|
|
const t = require('@babel/types');
|
|
const parser = require('@babel/parser');
|
|
const traverse = require('@babel/traverse').default;
|
|
const generate = require('@babel/generator').default;
|
|
const vm = require('vm')
|
|
const {
|
|
readFileSync,
|
|
writeFile,
|
|
writeFileSync,
|
|
} = require("fs");
|
|
const {
|
|
exit
|
|
} = require('process');
|
|
var output = ""
|
|
|
|
let beautify_opts = {
|
|
comments: true,
|
|
minified: false,
|
|
concise: false,
|
|
}
|
|
const script = readFileSync('./source/a.js', 'utf-8');
|
|
|
|
const AST = parser.parse(script, {})
|
|
|
|
var decryptFuncCtx = vm.createContext();
|
|
var decryptCode = ""
|
|
var decryptFuncName = ""
|
|
|
|
const constantReplacer = {
|
|
VariableDeclarator(path) {
|
|
const {node} = path;
|
|
if(!node.id || !node.init || (node.init.type != "StringLiteral" && node.init.type != "NumericLiteral") || node.id.type != "Identifier") {
|
|
return
|
|
}
|
|
if((node.init.type == "NumericLiteral" && node.init.value == 0) || (node.init.type == "StringLiteral" && node.init.value == "")) {
|
|
return
|
|
}
|
|
let binding = path.scope.getBinding(node.id.name)
|
|
if(!binding) {
|
|
return
|
|
}
|
|
for(var i = 0; i < binding.referencePaths.length; i++) {
|
|
binding.referencePaths[i].replaceWith(node.init)
|
|
}
|
|
path.remove()
|
|
}
|
|
}
|
|
|
|
const replaceObjSimple = {
|
|
VariableDeclarator(path) {
|
|
const {node} = path;
|
|
if(!node.id || !node.init || node.init.type != "ObjectExpression" || node.id.type != "Identifier" || node.init.properties.length < 1) {
|
|
return
|
|
}
|
|
var valid = true
|
|
var map = {}
|
|
for(var i = 0; i < node.init.properties.length; i++) {
|
|
var prop = node.init.properties[i]
|
|
if(!prop.key || !prop.value || prop.key.type != "Identifier" || (prop.value.type != "NumericLiteral" && prop.value.type != "StringLiteral")) {
|
|
valid = false
|
|
break
|
|
}
|
|
map[prop.key.name] = prop.value
|
|
}
|
|
if(!valid) {
|
|
return
|
|
}
|
|
path.scope.crawl()
|
|
let binding = path.scope.getBinding(node.id.name)
|
|
if(!binding) {
|
|
return
|
|
}
|
|
for(var i = 0; i < binding.referencePaths.length; i++) {
|
|
let refPath = binding.referencePaths[i].parentPath
|
|
if(refPath.node.type != "MemberExpression" || !refPath.node.property) {
|
|
continue
|
|
}
|
|
let key;
|
|
if(refPath.node.property.type == "Identifier") {
|
|
key = refPath.node.property.name
|
|
} else{
|
|
key = refPath.node.property.value
|
|
}
|
|
refPath.replaceWith(map[key])
|
|
}
|
|
path.remove()
|
|
}
|
|
}
|
|
|
|
const replaceExprStmts = {
|
|
MemberExpression(path) {
|
|
const {node} = path;
|
|
if(!node.property || node.property.type != "SequenceExpression" || !node.property.expressions || node.property.expressions.length < 3) {
|
|
return
|
|
}
|
|
var callExprIndex = node.property.expressions.length-1
|
|
if(node.property.expressions[callExprIndex].type != "CallExpression") {
|
|
return
|
|
}
|
|
var values = []
|
|
var order = []
|
|
for(var i = 0; i < node.property.expressions.length; i++) {
|
|
var expr = node.property.expressions[i]
|
|
if(expr.type != "AssignmentExpression" || !expr.right || !expr.left) {
|
|
continue
|
|
}
|
|
values.push(generate(expr.right).code)
|
|
order.push(expr.left.name)
|
|
}
|
|
let newArgs = []
|
|
for(var i = 0; i < node.property.expressions[callExprIndex].arguments.length; i++) {
|
|
let arg = node.property.expressions[callExprIndex].arguments[i]
|
|
let str = generate(arg).code
|
|
if(str.match(/[A-z]/g) == null) {
|
|
newArgs.push(arg)
|
|
continue
|
|
}
|
|
let key = str.match(/[A-z]/g)[0]
|
|
let index = order.indexOf(key)
|
|
str = str.replace(key, values[index])
|
|
if(str.match(/[0-9]/g) != null && str.match(/[0-9]/g).length > 1 && !str.match(/[A-z"]/g)) {
|
|
newArgs.push(t.numericLiteral(eval(str)))
|
|
continue
|
|
}
|
|
str = str.slice(1)
|
|
str = str.slice(0, -1)
|
|
newArgs.push(t.stringLiteral(str))
|
|
}
|
|
path.replaceWith(t.memberExpression(node.object, t.callExpression(node.property.expressions[callExprIndex].callee, newArgs), true))
|
|
},
|
|
// ! same thing except ExpressionStatement, SequenceExpression
|
|
// ! example: (a = "7d]D", k = -497, m = -404, C = -368, uo(k - -1644, a - 298, a, m - 199, C - 208))
|
|
SequenceExpression(path) {
|
|
const {node} = path;
|
|
if(!node.expressions || node.expressions.length < 3) {
|
|
return
|
|
}
|
|
var callExprIndex = node.expressions.length-1
|
|
if(node.expressions[callExprIndex].type != "CallExpression") {
|
|
return
|
|
}
|
|
var values = []
|
|
var order = []
|
|
for(var i = 0; i < node.expressions.length; i++) {
|
|
var expr = node.expressions[i]
|
|
if(expr.type != "AssignmentExpression" || !expr.right || !expr.left) {
|
|
continue
|
|
}
|
|
values.push(generate(expr.right).code)
|
|
order.push(expr.left.name)
|
|
}
|
|
let newArgs = []
|
|
for(var i = 0; i < node.expressions[callExprIndex].arguments.length; i++) {
|
|
let arg = node.expressions[callExprIndex].arguments[i]
|
|
let str = generate(arg).code
|
|
if(str.match(/[A-z]/g) == null) {
|
|
newArgs.push(arg)
|
|
continue
|
|
}
|
|
let key = str.match(/[A-z]/g)[0]
|
|
let index = order.indexOf(key)
|
|
str = str.replace(key, values[index])
|
|
if(str.match(/[0-9]/g) != null && str.match(/[0-9]/g).length > 1 && !str.match(/[A-z"]/g)) {
|
|
newArgs.push(t.numericLiteral(eval(str)))
|
|
continue
|
|
}
|
|
str = str.slice(1)
|
|
str = str.slice(0, -1)
|
|
newArgs.push(t.stringLiteral(str))
|
|
}
|
|
path.replaceWith(t.callExpression(node.expressions[callExprIndex].callee, newArgs))
|
|
}
|
|
}
|
|
|
|
const replaceWeirdProxyCall = {
|
|
MemberExpression(path) {
|
|
const {node} = path;
|
|
if(!node.object || node.object.type != "Identifier" || !node.property || node.property.type != "CallExpression") {
|
|
return
|
|
}
|
|
if(!node.property.callee || node.property.callee.type != "FunctionExpression") {
|
|
return
|
|
}
|
|
let values = [generate(node.property.arguments[0]).code, generate(node.property.arguments[1]).code, generate(node.property.arguments[2]).code, generate(node.property.arguments[3]).code, generate(node.property.arguments[4]).code]
|
|
let order = [node.property.callee.params[0].name, node.property.callee.params[1].name, node.property.callee.params[2].name, node.property.callee.params[3].name, node.property.callee.params[4].name]
|
|
let newArgs = []
|
|
for(var i = 0; i < node.property.callee.body.body[0].argument.arguments.length; i++) {
|
|
let arg = node.property.callee.body.body[0].argument.arguments[i]
|
|
let str = generate(arg).code
|
|
if(str.match(/[A-z]/g) == null) {
|
|
newArgs.push(arg)
|
|
continue
|
|
}
|
|
let key = str.match(/[A-z]/g)[0]
|
|
let index = order.indexOf(key)
|
|
str = str.replace(key, values[index])
|
|
if(str.match(/[0-9]/g) != null && str.match(/[0-9]/g).length > 1&& !str.match(/[A-z"]/g)) {
|
|
newArgs.push(t.numericLiteral(eval(str)))
|
|
continue
|
|
}
|
|
str = str.slice(1)
|
|
str = str.slice(0, -1)
|
|
newArgs.push(t.stringLiteral(str))
|
|
}
|
|
path.replaceWith(t.memberExpression(node.object, t.callExpression(node.property.callee.body.body[0].argument.callee, newArgs), true))
|
|
}
|
|
}
|
|
|
|
const getStringDeobfFuncs = {
|
|
ExpressionStatement(path) {
|
|
const {node} = path;
|
|
if(!node.expression || node.expression.operator != "!" || !node.expression.prefix || !node.expression.argument || node.expression.argument.type != "CallExpression") {
|
|
return
|
|
}
|
|
// ! get array func
|
|
let binding = path.scope.getBinding(node.expression.argument.arguments[0].name)
|
|
if(!binding) {
|
|
return
|
|
}
|
|
decryptCode += generate(binding.path.node).code + "\n"
|
|
// ! get decrypt func
|
|
var bodyIndex = 0
|
|
for(var i = 0; i < node.expression.argument.callee.body.body.length; i++) {
|
|
if(node.expression.argument.callee.body.body[i].type == "FunctionDeclaration") {
|
|
bodyIndex = i
|
|
break
|
|
}
|
|
}
|
|
decryptFuncName = node.expression.argument.callee.body.body[bodyIndex].body.body[0].argument.callee.name
|
|
path.scope.crawl()
|
|
let binding1 = path.scope.getBinding(decryptFuncName)
|
|
if(!binding1){
|
|
return
|
|
}
|
|
decryptCode += generate(binding1.path.node).code + "\n"
|
|
decryptCode += generate(node).code + "\n"
|
|
binding1.path.remove()
|
|
binding.path.remove()
|
|
path.remove()
|
|
path.stop()
|
|
}
|
|
}
|
|
|
|
function makeid(length) {
|
|
let result = '';
|
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
const charactersLength = characters.length;
|
|
let counter = 0;
|
|
while (counter < length) {
|
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
counter += 1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const replaceInterceptingFuncNames = {
|
|
FunctionDeclaration(path) {
|
|
const {node} = path;
|
|
if(!node.id || node.id.type != "Identifier" || node.id.name != decryptFuncName || !node.body || !node.body.body || node.body.body.length != 1) {
|
|
return
|
|
}
|
|
path.scope.crawl()
|
|
let binding = path.parentPath.scope.getBinding(node.id.name)
|
|
if(!binding) {
|
|
return
|
|
}
|
|
var ID = t.identifier(makeid(10))
|
|
for(var i = 0; i < binding.referencePaths.length; i++) {
|
|
binding.referencePaths[i].replaceWith(ID)
|
|
}
|
|
node.id = ID
|
|
}
|
|
}
|
|
|
|
const deobfStrings = {
|
|
CallExpression(path) {
|
|
const {node} = path;
|
|
|
|
if(!node.callee || node.callee.type != "Identifier" || !node.arguments || node.arguments.length < 2) {
|
|
return
|
|
}
|
|
var valid = true
|
|
for(var i = 0; i < node.arguments.length; i++) {
|
|
var arg = node.arguments[i]
|
|
let str = generate(arg).code
|
|
if(arg.type == "StringLiteral" || str == "NaN") {
|
|
continue
|
|
}
|
|
if(arg.type != "UnaryExpression" && arg.type != "BinaryExpression" && arg.type != "NumericLiteral") {
|
|
valid = false
|
|
break
|
|
}
|
|
|
|
if(str.match(/[A-z]/g) != null) {
|
|
valid = false
|
|
break
|
|
}
|
|
}
|
|
if(!valid) {
|
|
return
|
|
}
|
|
// ! the logic here is we want to get the function this is calling
|
|
// ! then we want to keep getting the nested function calls until we get to the final function, aka the decryptFuncName
|
|
let code = ""
|
|
path.scope.crawl()
|
|
let binding = path.scope.getBinding(node.callee.name)
|
|
if(!binding) {
|
|
// ! hopefully no binding will always mean that the function in question is `r`???
|
|
path.replaceWith(t.valueToNode(vm.runInContext(generate(node).code, decryptFuncCtx)))
|
|
return
|
|
}
|
|
// ! loop until we get to a place where we can't get a binding (aka hopefully the root function)
|
|
while(true){
|
|
if(!binding){
|
|
let a = generate(node).code
|
|
if(a[0] == decryptFuncName) {
|
|
a[0] = "asd"
|
|
}
|
|
code += a
|
|
break
|
|
}
|
|
code += generate(binding.path.node).code + "\n"
|
|
path.scope.crawl()
|
|
binding = binding.path.scope.getBinding(binding.path.node.body.body[0].argument.callee.name)
|
|
}
|
|
// ! now we should have all the code we need
|
|
path.replaceWith(t.valueToNode(vm.runInContext(code, decryptFuncCtx)))
|
|
}
|
|
}
|
|
|
|
const deobfuscateStringConcatVisitor = {
|
|
BinaryExpression(path) {
|
|
let {
|
|
confident,
|
|
value
|
|
} = path.evaluate();
|
|
if (!confident) return;
|
|
if (typeof value == "string") {
|
|
path.replaceWith(t.stringLiteral(value));
|
|
}
|
|
},
|
|
}
|
|
|
|
const getObfioObjs = {
|
|
VariableDeclarator(path) {
|
|
const {node} = path;
|
|
if(!node.id || node.id.type != "Identifier" || !node.init || node.init.type != "ObjectExpression" || !node.init.properties || node.init.properties.length < 1) {
|
|
return
|
|
}
|
|
// ! further validation, just incase
|
|
let map = {}
|
|
let valid = true
|
|
for (var i = 0; i < node.init.properties.length; i++) {
|
|
var prop = node.init.properties[i]
|
|
if (!prop.key || !prop.value || prop.key.type != "Identifier") {
|
|
valid = false
|
|
break;
|
|
}
|
|
if (prop.value.type != "FunctionExpression" && prop.value.type != "StringLiteral" && prop.value.type != "MemberExpression") {
|
|
valid = false
|
|
break;
|
|
}
|
|
if (prop.key.name.length != 5) {
|
|
valid = false
|
|
break;
|
|
}
|
|
if (prop.value.type == "FunctionExpression" && prop.value.body.body[0].type != "ReturnStatement") {
|
|
valid = false
|
|
break;
|
|
}
|
|
map[prop.key.name] = prop.value
|
|
}
|
|
if (!valid) {
|
|
return
|
|
}
|
|
path.scope.crawl()
|
|
let binding = path.scope.getBinding(node.id.name)
|
|
if(!binding) {
|
|
return
|
|
}
|
|
var ID = t.identifier(makeid(20))
|
|
for(var i = 0; i < binding.referencePaths.length; i++) {
|
|
binding.referencePaths[i].replaceWith(ID)
|
|
}
|
|
obfioObjMap[ID.name] = map
|
|
path.remove()
|
|
}
|
|
}
|
|
|
|
function getArgs(arguments, cutFirst) {
|
|
var out = []
|
|
for (var i = cutFirst ? 1 : 0; i < arguments.length; i++) {
|
|
out.push(arguments[i])
|
|
}
|
|
return out
|
|
}
|
|
|
|
const objDeobfMemberExpr = {
|
|
MemberExpression(path) {
|
|
const {
|
|
node
|
|
} = path;
|
|
if (!node.object || !node.property || node.object.type != "Identifier" || !obfioObjMap[node.object.name]) {
|
|
return
|
|
}
|
|
let map = obfioObjMap[node.object.name]
|
|
let key;
|
|
if (node.property.type == "Identifier") {
|
|
key = node.property.name
|
|
} else {
|
|
key = node.property.value
|
|
}
|
|
let value = map[key]
|
|
if (value.type == "StringLiteral") {
|
|
path.replaceWith(value)
|
|
return
|
|
}
|
|
if (value.type == "MemberExpression") {
|
|
map = obfioObjMap[value.object.name]
|
|
if (value.property.type == "Identifier") {
|
|
key = value.property.name
|
|
} else {
|
|
key = value.property.value
|
|
}
|
|
value = map[key]
|
|
path.replaceWith(value)
|
|
return
|
|
}
|
|
output += `FAILED (1): ${generate(node).code}\n\n`
|
|
},
|
|
CallExpression(path) {
|
|
const {
|
|
node
|
|
} = path;
|
|
if (!node.callee || node.callee.type != "MemberExpression" || !node.callee.object || !node.callee.property || node.callee.object.type != "Identifier" || !obfioObjMap[node.callee.object.name]) {
|
|
return
|
|
}
|
|
let map = obfioObjMap[node.callee.object.name]
|
|
let key;
|
|
if (node.callee.property.type == "Identifier") {
|
|
key = node.callee.property.name
|
|
} else {
|
|
key = node.callee.property.value
|
|
}
|
|
let value = map[key]
|
|
// ! replace functions
|
|
let retNode = value.body.body[0].argument
|
|
// ! call expression
|
|
if (retNode.type == "CallExpression") {
|
|
var callExprID;
|
|
// ! check if it's a reference to another object
|
|
if (retNode.callee.type == "MemberExpression") {
|
|
callExprID = retNode.callee
|
|
} else {
|
|
callExprID = node.arguments[0]
|
|
}
|
|
var args = []
|
|
if (node.arguments.length > 1 || retNode.callee.type == "MemberExpression") {
|
|
args = getArgs(node.arguments, retNode.callee.type != "MemberExpression")
|
|
}
|
|
path.replaceWith(t.callExpression(callExprID, args))
|
|
return
|
|
}
|
|
// ! BinaryExpression
|
|
if (retNode.type == "BinaryExpression") {
|
|
path.replaceWith(t.binaryExpression(retNode.operator, node.arguments[0], node.arguments[1]))
|
|
return
|
|
}
|
|
output += `FAILED (2): ${generate(node).code}\n\n`
|
|
}
|
|
}
|
|
|
|
function evalValue(left, right, op) {
|
|
switch (op) {
|
|
case "===":
|
|
return left == right
|
|
case "!==":
|
|
return left != right
|
|
}
|
|
}
|
|
|
|
const cleanupDeadCode = {
|
|
FunctionDeclaration(path) {
|
|
const {node} = path;
|
|
if(!node.id || node.id.type != "Identifier" || !node.body || !node.body.body || !node.params || node.params.length < 2 || node.body.body.length != 1 || node.body.body[0].type != "ReturnStatement") {
|
|
return
|
|
}
|
|
path.remove()
|
|
},
|
|
"IfStatement|ConditionalExpression"(path) {
|
|
const {
|
|
node
|
|
} = path;
|
|
if (!node.test || !node.consequent || node.test.type != "BinaryExpression" || !node.test.left || !node.test.right || node.test.left.type != "StringLiteral" || node.test.right.type != "StringLiteral") {
|
|
// ! handle if(!("x" !== "x")) { } else { } here
|
|
if (!node.test || !node.consequent || node.test.type != "UnaryExpression" || !node.test.argument || node.test.argument.type != "BinaryExpression" || !node.test.argument.left || !node.test.argument.right || node.test.argument.left.type != "StringLiteral" || node.test.argument.right.type != "StringLiteral") {
|
|
return
|
|
}
|
|
if (!evalValue(node.test.argument.left.value, node.test.argument.right.value, node.test.argument.operator)) {
|
|
path.replaceWithMultiple(node.consequent)
|
|
return
|
|
}
|
|
if(!node.alternate){
|
|
path.remove()
|
|
return
|
|
}
|
|
path.replaceWithMultiple(node.alternate)
|
|
return
|
|
}
|
|
if (evalValue(node.test.left.value, node.test.right.value, node.test.operator)) {
|
|
path.replaceWithMultiple(node.consequent)
|
|
return
|
|
}
|
|
path.replaceWithMultiple(node.alternate)
|
|
}
|
|
}
|
|
|
|
// ! replace the `x = 123` and `y = "asd"` things
|
|
traverse(AST, constantReplacer)
|
|
// ! replace `const n = {T: 702}`
|
|
traverse(AST, replaceObjSimple)
|
|
// ! replace mr[(t = "7d]D", o = 896, r(o - 493, t)), mr[(t = "hyP7", r = 661, o = 735, $(t - 252, o - 1061, t, r - 204, o - 6))]
|
|
traverse(AST, replaceExprStmts)
|
|
/*
|
|
replace the stuff that looks like this:
|
|
iu[function (n, t, W, r, u) {
|
|
return On(n - 247, W, r - 606, r - 38, u - 235);
|
|
}(1095, 0, "e9so", 1006, 1053)]
|
|
*/
|
|
// ! if this code breaks, I'd assume it's likely because of the static way I'm doing the variable replacement
|
|
traverse(AST, replaceWeirdProxyCall)
|
|
// ! get the string deobf code and func name, the entry point is the array shifter exprstmt
|
|
traverse(AST, getStringDeobfFuncs)
|
|
// ! this is a really hacky way to fix this "problem" since I'm doing the string deobf in a bad way
|
|
// ! some func names are the same as the main decrypt func name so it'll error when you try to deobf the strings
|
|
traverse(AST, replaceInterceptingFuncNames)
|
|
writeFileSync("output.js", decryptCode, "utf-8")
|
|
vm.runInContext(decryptCode, decryptFuncCtx);
|
|
// ! finally we can decrypt/deobf our strings
|
|
traverse(AST, deobfStrings)
|
|
// ! now we need to concat strings so we can properly deobf the object obfuscation
|
|
// (stolen from pianoman)
|
|
traverse(AST, deobfuscateStringConcatVisitor)
|
|
let obfioObjMap = {}
|
|
// ! first we need to rename all objects and all uses of those objects
|
|
// ! some objects have conflicting names which can mess up this solution, easiest fix is just renaming them
|
|
// ! then we will populate the obfioObjMap
|
|
traverse(AST, getObfioObjs)
|
|
// ! now we can deobf the object obfuscation
|
|
traverse(AST, objDeobfMemberExpr)
|
|
// ! clean dead code, like the proxy functions we never removed
|
|
traverse(AST, cleanupDeadCode)
|
|
|
|
|
|
writeFileSync("output.txt", output, 'utf-8')
|
|
|
|
const final_code = generate(AST, beautify_opts).code;
|
|
|
|
fs.writeFileSync('./output/a.js', final_code); |