1
Fork 0
mirror of https://github.com/thegeneralist01/twitter-tid-deobf-fork synced 2026-01-09 14:50:26 +01:00
twitter-tid-deobf-fork/deobf-fork.js
ふぁ 6f2c23b582
add fork
Signed-off-by: ふぁ <yuki@yuki0311.com>
2025-04-27 16:17:22 +09:00

750 lines
20 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 [_, __, inputPath, outputPath] = process.argv;
const script = readFileSync(inputPath, "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 = node.property.arguments.map((e) => generate(e).code);
let order = node.property.callee.params.map((p) => p.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`???
try {
path.replaceWith(
t.valueToNode(vm.runInContext(generate(node).code, decryptFuncCtx))
);
} catch {}
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();
for (const body of binding.path.node.body.body) {
if (body.type == "ReturnStatement") {
if (body.argument.type == "CallExpression") {
binding = binding.path.scope.getBinding(body.argument.callee.name);
} else if (body.argument.type == "SequenceExpression") {
binding = undefined;
}
} else if (body.type == "VariableDeclaration") {
path.scope.crawl();
binding.scope.crawl();
binding = binding.path.scope.getBinding(
body.declarations[0].init.callee.name
);
code += generate(binding.path.node).code + "\n";
binding = undefined;
}
}
}
// ! now we should have all the code we need
try {
path.replaceWith(t.valueToNode(vm.runInContext(code, decryptFuncCtx)));
} catch (e) {}
},
};
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 === undefined) {
return;
}
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];
if (value === undefined) {
return;
}
// ! 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);
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(outputPath, final_code);