1
Fork 0
mirror of https://github.com/thegeneralist01/twitter-tid-deobf-fork synced 2026-01-11 07:30:38 +01:00
twitter-tid-deobf-fork/deobf.js
ふぁ 0b0d6f516a
cleanup
Signed-off-by: ふぁ <yuki@yuki0311.com>
2024-12-15 19:45:11 +09:00

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.log", output, 'utf-8')
const final_code = generate(AST, beautify_opts).code;
fs.writeFileSync('./output/a.js', final_code);