├── .gitignore ├── Cargo.toml ├── devices └── chrome131.json └── src ├── ast ├── mod.rs ├── utils.rs └── visitor │ ├── case_encapsulation.rs │ ├── control_flow.rs │ ├── dead_code.rs │ ├── mod.rs │ ├── preprocess.rs │ ├── proxy.rs │ └── sequence.rs ├── decoder.rs ├── decompiler ├── mod.rs ├── simple.rs └── values.rs ├── device_checks.rs ├── device_checks ├── devices.rs └── types.rs ├── disassembler.rs ├── disassembler ├── instruction_set.rs ├── instruction_set │ ├── instructions.rs │ ├── instructions │ │ ├── extract.rs │ │ ├── impls.rs │ │ ├── memory.rs │ │ ├── values.rs │ │ └── values │ │ │ ├── entry_point.rs │ │ │ ├── float.rs │ │ │ ├── regex.rs │ │ │ └── string.rs │ ├── js_eval.rs │ └── match.rs ├── result.rs ├── step.rs ├── utils.rs └── vm.rs ├── lib.rs ├── main.rs ├── payload ├── checks.rs ├── compress.rs ├── encode.rs ├── init.rs ├── keys.rs ├── mod.rs ├── second.rs └── timings.rs ├── pow.rs ├── requests.rs ├── script ├── extract.rs ├── identifier.rs └── mod.rs ├── strings.rs └── task.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /debug 2 | Cargo.lock 3 | /target 4 | /testss 5 | decompile.js 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "turnstile-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | anyhow = "1.0.94" 8 | base64 = "0.22.1" 9 | chrono = "0.4.39" 10 | include_dir = "0.7.4" 11 | once_cell = "1.20.2" 12 | radix_fmt = "1.0.0" 13 | radix_str = "0.1.3" 14 | rand = "0.8.5" 15 | regex = "1.11.1" 16 | serde = { version = "1.0.216", features = ["derive"] } 17 | serde_json = { version = "1.0.134", features = ["preserve_order"] } 18 | sha256 = "1.5.0" 19 | swc_common = { version = "5.0.0", features = ["tty-emitter"] } 20 | swc_core = "9.0.0" 21 | swc_ecma_ast = "5.0.0" 22 | swc_ecma_codegen = "5.0.0" 23 | swc_ecma_parser = "6.0.0" 24 | swc_ecma_utils = "6.0.0" 25 | swc_ecma_visit = "5.0.0" 26 | uuid = { version = "1.11.0", features = ["v4", "fast-rng"] } 27 | rquest = { version = "1.0.0", features = ["cookies", "gzip", "brotli"] } 28 | tokio = { version = "1.42.0", features = ["full"] } 29 | 30 | [target."cfg(debug_assertions)".dependencies] 31 | arboard = "3.4.1" 32 | -------------------------------------------------------------------------------- /src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | pub mod visitor; 3 | 4 | 5 | use swc_ecma_utils::StmtOrModuleItem; 6 | use swc_ecma_visit::{VisitMutWith, VisitWith}; 7 | use utils::{generate_code, parse_func_str}; 8 | use visitor::{control_flow::CFUnflattenVisitor, dead_code::DeadCodeVisitor, preprocess::PreprocessVisitor, proxy::{ProxyCleanupVisitor, ProxyDeclVisitor, ProxyResolveVisitor}, sequence::SequenceVisitor}; 9 | 10 | 11 | 12 | pub fn apply_transformations(func_str: String) -> String { 13 | 14 | let mut ast = parse_func_str(func_str); 15 | 16 | ast.visit_mut_with(&mut PreprocessVisitor {}); 17 | ast.visit_mut_with(&mut SequenceVisitor {}); 18 | 19 | let mut proxy_visitor = ProxyDeclVisitor::default(); 20 | ast.visit_with(&mut proxy_visitor); 21 | let proxies = proxy_visitor.post_process_proxies(); 22 | //dbg!(proxies.get("syvjv")); 23 | ast.visit_mut_with(&mut ProxyResolveVisitor { proxies }); 24 | ast.visit_mut_with(&mut ProxyCleanupVisitor {}); 25 | 26 | ast.visit_mut_with(&mut CFUnflattenVisitor { order_str: None }); 27 | 28 | ast.visit_mut_with(&mut DeadCodeVisitor {}); 29 | 30 | let block = ast.into_stmt().unwrap() 31 | .decl().unwrap() 32 | .fn_decl().unwrap() 33 | .function.body.unwrap() 34 | .stmts 35 | .into_iter() 36 | .map(|stmt| generate_code(stmt.into())) 37 | .collect::>() 38 | .join(""); 39 | 40 | let func_str = block; 41 | 42 | func_str 43 | } -------------------------------------------------------------------------------- /src/ast/utils.rs: -------------------------------------------------------------------------------- 1 | use swc_common::input::StringInput; 2 | use swc_common::SourceMap; 3 | use swc_common::sync::Lrc; 4 | use swc_common::FileName; 5 | use swc_ecma_codegen::{Emitter, text_writer::JsWriter}; 6 | use swc_ecma_ast::ModuleItem; 7 | use swc_ecma_parser::{EsSyntax, Parser, Syntax}; 8 | 9 | 10 | pub fn parse_func_str(script: String) -> ModuleItem { 11 | let cm: Lrc = Default::default(); 12 | 13 | let fm = cm.new_source_file( 14 | FileName::Custom("test.js".into()).into(), 15 | script, 16 | ); 17 | 18 | let mut parser = Parser::new( 19 | Syntax::Es(EsSyntax::default()), 20 | StringInput::from(&*fm), 21 | None, 22 | ); 23 | 24 | let module = parser 25 | .parse_module_item() 26 | .expect("failed to parser module"); 27 | 28 | module 29 | } 30 | 31 | pub fn generate_code(ast: ModuleItem) -> String { 32 | let cm: Lrc = Default::default(); 33 | let mut buf = Vec::new(); 34 | let config: swc_ecma_codegen::Config = Default::default(); 35 | let mut emitter = Emitter { 36 | cfg: config, 37 | cm: cm.clone(), 38 | comments: None, 39 | wr: JsWriter::new(cm, "\n", &mut buf, None), 40 | }; 41 | emitter.emit_module_item(&ast).unwrap(); 42 | let code = String::from_utf8_lossy(&buf).to_string(); 43 | code 44 | } -------------------------------------------------------------------------------- /src/ast/visitor/case_encapsulation.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::collections::HashMap; 3 | 4 | use swc_ecma_ast::{BinExpr, BinaryOp, CondExpr, IfStmt}; 5 | use swc_ecma_utils::{ExprExt, ExprFactory, StmtOrModuleItem}; 6 | use swc_ecma_visit::{noop_visit_type, Visit, VisitWith}; 7 | 8 | use crate::ast::utils::{generate_code, parse_func_str}; 9 | 10 | struct CaseExtractVisitor { 11 | inner: HashMap 12 | } 13 | 14 | impl Visit for CaseExtractVisitor { 15 | noop_visit_type!(); 16 | fn visit_if_stmt(&mut self, path: &IfStmt) { 17 | 18 | path.visit_children_with(self); 19 | 20 | let BinExpr {op, left, right, ..} = path.test.as_bin().unwrap(); 21 | let case = if left.is_number() { 22 | left 23 | } else { 24 | right 25 | }.as_lit().unwrap().as_num().unwrap().value as i32; 26 | 27 | //dbg!(case); 28 | 29 | let consequent = match op { 30 | BinaryOp::EqEqEq => &path.cons, 31 | BinaryOp::NotEqEq => path.alt.as_ref().unwrap(), 32 | _ => panic!() 33 | }; 34 | 35 | self.inner.insert(case, generate_code(consequent.clone().into_stmt().unwrap().into())); 36 | } 37 | fn visit_cond_expr(&mut self, path: &CondExpr) { 38 | path.visit_children_with(self); 39 | 40 | let BinExpr {op, left, right, ..} = path.test.as_bin().unwrap(); 41 | let case = if left.is_number() { 42 | left 43 | } else { 44 | right 45 | }.as_lit().unwrap().as_num().unwrap().value as i32; 46 | 47 | //dbg!(case); 48 | 49 | let consequent = match op { 50 | BinaryOp::EqEqEq => &path.cons, 51 | BinaryOp::NotEqEq => &path.alt, 52 | _ => panic!() 53 | }.clone().into_stmt(); 54 | 55 | self.inner.insert(case, generate_code(consequent.into())); 56 | } 57 | fn visit_bin_expr(&mut self, path: &BinExpr) { 58 | path.visit_children_with(self); 59 | 60 | if path.op != BinaryOp::LogicalAnd { return } 61 | if !path.left.is_bin() { return } 62 | 63 | let bin = path.left.as_bin().unwrap(); 64 | let mut case_no = None; 65 | 66 | if let Some(left) = bin.left.as_lit() { 67 | if let Some(x) = left.as_num() { 68 | case_no = Some(x.value as i32) 69 | } 70 | } 71 | if let Some(right) = bin.right.as_lit() { 72 | if let Some(x) = right.as_num() { 73 | case_no = Some(x.value as i32) 74 | } 75 | } 76 | 77 | if let Some(case) = case_no { 78 | self.inner.insert(case, generate_code(path.right.clone().into_stmt().into())); 79 | } 80 | 81 | } 82 | } 83 | 84 | pub fn encapsulate_cases(exprs: &Vec) -> HashMap> { 85 | 86 | let block = format!("{{{}}}", exprs.join("\n")); 87 | let ast = parse_func_str(block); 88 | 89 | let mut visitor = CaseExtractVisitor { inner: HashMap::new() }; 90 | ast.visit_with(&mut visitor); 91 | 92 | visitor.inner.into_iter() 93 | .map(|(k, v)| { 94 | let exprs = v.split("\n") 95 | .filter(|s| s.len() != 0 && s != &"{" && s != &"}") 96 | .map(str::to_string) 97 | .collect(); 98 | (k, exprs) 99 | }) 100 | .collect() 101 | } -------------------------------------------------------------------------------- /src/ast/visitor/control_flow.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::collections::HashMap; 3 | 4 | use swc_ecma_ast::{Callee, Expr, ExprStmt, MemberExpr, Stmt}; 5 | use swc_ecma_utils::ExprExt; 6 | use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; 7 | 8 | 9 | 10 | pub struct CFUnflattenVisitor { 11 | pub order_str: Option> 12 | } 13 | 14 | impl VisitMut for CFUnflattenVisitor { 15 | noop_visit_mut_type!(); 16 | 17 | fn visit_mut_member_expr(&mut self, path: &mut MemberExpr) { 18 | path.visit_mut_children_with(self); 19 | 20 | if !path.obj.is_str() || !path.prop.is_computed() { 21 | return 22 | } 23 | 24 | let prop = path.prop.as_computed().unwrap(); 25 | 26 | let lit = if let Some(lit) = prop.expr.as_lit() { 27 | lit 28 | } else { 29 | return 30 | }; 31 | 32 | let string = if let Some(s) = lit.as_str() { 33 | s.value.to_string() 34 | } else { 35 | return 36 | }; 37 | 38 | if string != "split" { 39 | return 40 | } 41 | 42 | let order_str = path.obj.as_lit().unwrap() 43 | .as_str().unwrap() 44 | .value.as_str() 45 | .split('|') 46 | .map(str::to_owned) 47 | .collect(); 48 | 49 | self.order_str = Some(order_str); 50 | 51 | //dbg!(generate_code(prop.expr.clone().into_stmt().into())); 52 | //dbg!(); 53 | 54 | } 55 | 56 | /* 57 | fn visit_mut_stmt(&mut self, path: &mut Stmt) { 58 | path.visit_mut_children_with(self); 59 | 60 | let for_stmt = if let Some(x) = path.as_for_stmt() { 61 | x 62 | } else { 63 | return 64 | }; 65 | 66 | let block = if let Some(x) = for_stmt.body.as_block() { 67 | x 68 | } else { 69 | return 70 | }; 71 | 72 | let switch = if let Some(x) = block.stmts.first().unwrap().as_switch() { 73 | x 74 | } else { 75 | return 76 | }; 77 | 78 | let mut cases = switch.cases 79 | .iter() 80 | .map(|case| { 81 | let test = case.test.as_ref().unwrap() 82 | .as_lit().unwrap() 83 | .as_str().unwrap() 84 | .value.to_string(); 85 | let mut exprs = case.cons 86 | .iter() 87 | .map(|block| block.to_owned()) 88 | .collect::>(); 89 | exprs.pop(); 90 | (test, exprs) 91 | }) 92 | .collect::>>(); 93 | 94 | let mut new_block: Vec = Vec::new(); 95 | for case_test in self.order_str.as_ref().unwrap().into_iter() { 96 | for stmt in cases.remove(case_test).unwrap() { 97 | new_block.push(stmt); 98 | } 99 | } 100 | 101 | *path = Stmt::Block(BlockStmt { stmts: new_block, ..Default::default() }); 102 | 103 | //dbg!(generate_code(switch.clone().into())); 104 | 105 | 106 | 107 | } */ 108 | 109 | fn visit_mut_stmts(&mut self, path: &mut Vec) { 110 | 111 | path.visit_mut_children_with(self); 112 | 113 | let order = match path.get(0) { 114 | Some(stmt) => { 115 | match stmt { 116 | Stmt::Expr(ExprStmt { expr, .. }) if expr.is_assign() => { 117 | let assign_expr = expr.as_assign().unwrap(); 118 | //dbg!(generate_code(assign_expr.clone().into_stmt().into())); 119 | let call_expr = match assign_expr.right.as_ref() { 120 | Expr::Call(call) => call, 121 | _ => return 122 | }; 123 | let member = match &call_expr.callee { 124 | Callee::Expr(expr) if expr.is_member() => { 125 | expr.as_member().unwrap() 126 | }, 127 | _ => return 128 | }; 129 | let lit = if let Some(x) = member.obj.as_lit() { 130 | x 131 | } else { 132 | return 133 | }; 134 | if let Some(x) = lit.as_str() { 135 | x.value.to_string().split('|') 136 | .map(|s| s.to_owned()) 137 | .collect::>() 138 | } else { 139 | return 140 | } 141 | 142 | }, 143 | _ => return 144 | } 145 | }, 146 | _ => return 147 | }; 148 | path.remove(0); 149 | 150 | let for_stmt = path.remove(0); 151 | let for_stmt = for_stmt.as_for_stmt().unwrap(); 152 | let block = for_stmt.body.as_block().unwrap(); 153 | 154 | let mut cases = block.stmts.first().unwrap() 155 | .as_switch().unwrap() 156 | .cases 157 | .iter() 158 | .map(|case| { 159 | let test = case.test.as_ref().unwrap() 160 | .as_lit().unwrap() 161 | .as_str().unwrap() 162 | .value.to_string(); 163 | let mut exprs = case.cons 164 | .iter() 165 | .map(|block| block.to_owned()) 166 | .collect::>(); 167 | exprs.pop(); 168 | (test, exprs) 169 | }) 170 | .collect::>>(); 171 | 172 | let mut new_block: Vec = Vec::new(); 173 | let old_blocks = path.drain(..); 174 | 175 | for case_test in order.into_iter() { 176 | for stmt in cases.remove(&case_test).unwrap() { 177 | new_block.push(stmt); 178 | } 179 | } 180 | 181 | for stmt in old_blocks { 182 | new_block.push(stmt); 183 | } 184 | 185 | *path = new_block; 186 | } 187 | } -------------------------------------------------------------------------------- /src/ast/visitor/dead_code.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use swc_common::util::take::Take; 4 | use swc_ecma_ast::{BinaryOp, CondExpr, Expr, IfStmt, Stmt}; 5 | use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; 6 | 7 | 8 | 9 | pub struct DeadCodeVisitor {} 10 | 11 | impl VisitMut for DeadCodeVisitor { 12 | noop_visit_mut_type!(); 13 | fn visit_mut_stmts(&mut self, path: &mut Vec) { 14 | 15 | path.visit_mut_children_with(self); 16 | let stmts = path.drain(..).collect::>(); 17 | let mut new_stmts: Vec = Vec::new(); 18 | for stmt in stmts { 19 | new_stmts.push(stmt.clone()); 20 | match stmt { 21 | Stmt::If(IfStmt { test, cons, alt , .. }) => { 22 | let bin_expr = if let Some(bin_expr) = test.as_bin() { 23 | bin_expr 24 | } else { 25 | continue 26 | }; 27 | let left = if let Some(lit) = bin_expr.left.as_lit() { 28 | if let Some(string) = lit.as_str() { 29 | string.value.to_string() 30 | } else { 31 | continue 32 | } 33 | } else { 34 | continue 35 | }; 36 | let right = if let Some(lit) = bin_expr.right.as_lit() { 37 | if let Some(string) = lit.as_str() { 38 | string.value.to_string() 39 | } else { 40 | continue 41 | } 42 | } else { 43 | continue 44 | }; 45 | 46 | let use_cons = match bin_expr.op { 47 | BinaryOp::EqEqEq => left == right, 48 | BinaryOp::NotEqEq => left != right, 49 | _ => panic!() 50 | }; 51 | 52 | let mut stmts_to_push = if use_cons { 53 | cons 54 | } else { 55 | alt.unwrap() 56 | }; 57 | 58 | new_stmts.pop(); 59 | 60 | match *stmts_to_push.take() { 61 | Stmt::Block(block) => { 62 | for stmt in block.stmts { 63 | new_stmts.push(stmt) 64 | } 65 | }, 66 | Stmt::Return(return_stmt) => { 67 | new_stmts.push(return_stmt.into()) 68 | } 69 | _x => { 70 | new_stmts.push(_x.into()); 71 | //dbg!(generate_code(_x.into())); 72 | //panic!() 73 | } 74 | } 75 | }, 76 | _ => {} 77 | }; 78 | } 79 | 80 | *path = new_stmts 81 | } 82 | 83 | fn visit_mut_stmt(&mut self, path: &mut Stmt) { 84 | path.visit_mut_children_with(self); 85 | 86 | match path { 87 | Stmt::If(IfStmt { test, cons, alt , .. }) => { 88 | let bin_expr = if let Some(bin_expr) = test.as_bin() { 89 | bin_expr 90 | } else { 91 | return; 92 | }; 93 | let left = if let Some(lit) = bin_expr.left.as_lit() { 94 | if let Some(string) = lit.as_str() { 95 | string.value.to_string() 96 | } else { 97 | return; 98 | } 99 | } else { 100 | return; 101 | }; 102 | let right = if let Some(lit) = bin_expr.right.as_lit() { 103 | if let Some(string) = lit.as_str() { 104 | string.value.to_string() 105 | } else { 106 | return; 107 | } 108 | } else { 109 | return; 110 | }; 111 | 112 | let use_cons = match bin_expr.op { 113 | BinaryOp::EqEqEq => left == right, 114 | BinaryOp::NotEqEq => left != right, 115 | _ => panic!() 116 | }; 117 | 118 | let resolved = if use_cons { 119 | cons 120 | } else { 121 | alt.as_ref().unwrap() 122 | }; 123 | 124 | *path = *resolved.clone() 125 | 126 | }, 127 | _ => {} 128 | } 129 | } 130 | 131 | fn visit_mut_expr(&mut self, path: &mut Expr) { 132 | 133 | path.visit_mut_children_with(self); 134 | 135 | if !path.is_cond() {return} 136 | 137 | let CondExpr { test, cons, alt, .. } = path.as_cond().unwrap(); 138 | 139 | let bin_expr = if let Some(bin_expr) = test.as_bin() { 140 | bin_expr 141 | } else { 142 | return; 143 | }; 144 | let left = if let Some(lit) = bin_expr.left.as_lit() { 145 | if let Some(string) = lit.as_str() { 146 | string.value.to_string() 147 | } else { 148 | return 149 | } 150 | } else { 151 | return 152 | }; 153 | let right = if let Some(lit) = bin_expr.right.as_lit() { 154 | if let Some(string) = lit.as_str() { 155 | string.value.to_string() 156 | } else { 157 | return 158 | } 159 | } else { 160 | return 161 | }; 162 | 163 | let use_cons = match bin_expr.op { 164 | BinaryOp::EqEqEq => left == right, 165 | BinaryOp::NotEqEq => left != right, 166 | _ => panic!() 167 | }; 168 | 169 | let evaled = if use_cons { 170 | cons 171 | } else { 172 | alt 173 | }; 174 | 175 | *path = *evaled.clone(); 176 | } 177 | } -------------------------------------------------------------------------------- /src/ast/visitor/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod sequence; 2 | pub mod proxy; 3 | pub mod control_flow; 4 | pub mod dead_code; 5 | pub mod case_encapsulation; 6 | pub mod preprocess; -------------------------------------------------------------------------------- /src/ast/visitor/preprocess.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use swc_common::{util::take::Take, DUMMY_SP}; 4 | use swc_ecma_ast::{BlockStmt, ForStmt, IfStmt, Number}; 5 | use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; 6 | 7 | 8 | 9 | 10 | pub struct PreprocessVisitor {} 11 | 12 | impl VisitMut for PreprocessVisitor { 13 | noop_visit_mut_type!(); 14 | 15 | fn visit_mut_if_stmt(&mut self, path: &mut IfStmt) { 16 | 17 | path.visit_mut_children_with(self); 18 | 19 | if !path.cons.is_block() { 20 | path.cons = BlockStmt { stmts: vec![*path.cons.take()], ..Default::default() }.into(); 21 | } 22 | 23 | let alt = if let Some(x) = path.alt.as_ref() { 24 | x 25 | } else { 26 | return 27 | }; 28 | 29 | if !alt.is_block() { 30 | path.alt = Some(BlockStmt { stmts: vec![*path.alt.take().unwrap().take()], ..Default::default() }.into()); 31 | } 32 | 33 | } 34 | 35 | fn visit_mut_for_stmt(&mut self, path: &mut ForStmt) { 36 | path.visit_mut_children_with(self); 37 | 38 | if !path.body.is_block() { 39 | path.body = BlockStmt { stmts: vec![*path.body.take()], ..Default::default() }.into(); 40 | } 41 | 42 | } 43 | 44 | fn visit_mut_number(&mut self, path: &mut Number) { 45 | 46 | path.visit_mut_children_with(self); 47 | 48 | *path = Number { value: path.value.floor(), raw: None, span: DUMMY_SP }; 49 | 50 | //dbg!(generate_code(path.clone().into_stmt().into())); 51 | 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /src/ast/visitor/sequence.rs: -------------------------------------------------------------------------------- 1 | 2 | use swc_common::{util::take::Take, SyntaxContext, DUMMY_SP}; 3 | use swc_ecma_ast::{BlockStmt, ExprStmt, ReturnStmt, Stmt, ThrowStmt}; 4 | use swc_ecma_utils::ExprFactory; 5 | use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith}; 6 | 7 | 8 | pub struct SequenceVisitor; 9 | 10 | impl VisitMut for SequenceVisitor { 11 | noop_visit_mut_type!(); 12 | fn visit_mut_stmts(&mut self, path: &mut Vec) { 13 | 14 | path.visit_mut_children_with(self); 15 | 16 | let stmts = path.drain(..).collect::>(); 17 | let mut new_stmts: Vec = Vec::with_capacity(stmts.len()); 18 | 19 | for stmt in stmts { 20 | match stmt { 21 | Stmt::Expr(ExprStmt { mut expr, .. }) if expr.is_seq() => { 22 | let seq = expr.as_mut_seq().unwrap(); 23 | new_stmts.reserve(seq.exprs.len()); 24 | 25 | for expr in &mut seq.exprs { 26 | let expr = expr.take(); 27 | new_stmts.push(expr.into_stmt()); 28 | } 29 | }, 30 | Stmt::Expr(ExprStmt { mut expr, .. }) if expr.is_cond() => { 31 | 32 | let cond = expr.as_mut_cond().unwrap(); 33 | 34 | //println!("{}", generate_code(cond.clone().into_stmt().into())); 35 | //dbg!(cond.test.is_paren()); 36 | 37 | let paren = if let Some(x) = cond.test.as_mut_paren() { 38 | x.expr.take() 39 | } else { 40 | new_stmts.push(expr.take().into_stmt()); 41 | continue 42 | }; 43 | 44 | let mut seq = if let Some(x) = paren.seq() { 45 | x 46 | } else { 47 | new_stmts.push(expr.take().into_stmt()); 48 | continue 49 | }; 50 | 51 | 52 | let last = seq.exprs.pop().unwrap(); 53 | 54 | new_stmts.reserve(seq.exprs.len()); 55 | 56 | cond.test = last; 57 | 58 | for expr in seq.exprs { 59 | new_stmts.push(expr.into_stmt()); 60 | } 61 | 62 | new_stmts.push(Stmt::Expr(ExprStmt { expr, ..Default::default() })); 63 | 64 | } 65 | Stmt::Expr(ExprStmt { mut expr, .. }) if expr.is_assign() => { 66 | let assign_expr = expr.as_mut_assign().unwrap(); 67 | if let Some(paren) = assign_expr.right.clone().as_mut_paren() { 68 | if let Some(seq) = paren.expr.as_mut_seq() { 69 | new_stmts.reserve(seq.exprs.len() - 1); 70 | let right_side = seq.exprs.pop().unwrap(); 71 | for expr in &mut seq.exprs { 72 | let expr = expr.take(); 73 | new_stmts.push(expr.into_stmt()); 74 | } 75 | assign_expr.right = right_side; 76 | } 77 | //dbg!(generate_code(paren.clone().into_stmt().into())); 78 | } 79 | new_stmts.push(expr.into_stmt()) 80 | } 81 | Stmt::Throw(ThrowStmt { mut arg, ..} ) if arg.is_seq() => { 82 | let seq = arg.as_mut_seq().unwrap(); 83 | new_stmts.reserve(seq.exprs.len() - 1); 84 | 85 | let throw_arg = seq.exprs.pop().unwrap(); 86 | for expr in &mut seq.exprs { 87 | let expr = expr.take(); 88 | new_stmts.push(expr.into_stmt()); 89 | } 90 | 91 | let throw_stmt = ThrowStmt {span: DUMMY_SP, arg: throw_arg}; 92 | new_stmts.push(throw_stmt.into()) 93 | }, 94 | Stmt::Return(ReturnStmt { arg, ..} ) if arg.is_some() => { 95 | match arg { 96 | Some(mut arg) if arg.is_seq() => { 97 | let seq = arg.as_mut_seq().unwrap(); 98 | new_stmts.reserve(seq.exprs.len() - 1); 99 | 100 | let return_arg = seq.exprs.pop().unwrap(); 101 | for expr in &mut seq.exprs { 102 | let expr = expr.take(); 103 | new_stmts.push(expr.into_stmt()); 104 | } 105 | 106 | let throw_stmt = ReturnStmt {span: DUMMY_SP, arg: Some(return_arg)}; 107 | new_stmts.push(throw_stmt.into()) 108 | } 109 | _ => { 110 | let return_stmt = ReturnStmt {span: DUMMY_SP, arg: arg}; 111 | new_stmts.push(return_stmt.into()) 112 | } 113 | } 114 | }, 115 | Stmt::For(mut for_stmt) => { 116 | 117 | let mut init = for_stmt.init.unwrap(); 118 | if init.is_expr() { 119 | match init.as_mut_expr() { 120 | Some(expr) if expr.is_seq() => { 121 | let mut expr = expr.take(); 122 | let seq = expr.as_mut_seq().unwrap(); 123 | new_stmts.reserve(seq.exprs.len() - 1); 124 | 125 | let actual_init = seq.exprs.pop().unwrap(); 126 | for_stmt.init = Some(swc_ecma_ast::VarDeclOrExpr::Expr(actual_init)); 127 | 128 | for expr in &mut seq.exprs { 129 | let expr = expr.take(); 130 | new_stmts.push(expr.into_stmt()); 131 | } 132 | 133 | }, 134 | _ => { 135 | for_stmt.init = Some(init) 136 | } 137 | } 138 | } else { 139 | for_stmt.init = Some(init) 140 | } 141 | 142 | match for_stmt.update { 143 | Some(mut update) if update.is_seq() => { 144 | let mut expr = update.take(); 145 | let seq = expr.as_mut_seq().unwrap(); 146 | 147 | let actual_update = seq.exprs.pop().unwrap(); 148 | for_stmt.update = Some(actual_update); 149 | 150 | let body_stmts = seq.exprs.iter_mut() 151 | .map(|expr| expr.take().into_stmt()) 152 | .collect::>(); 153 | 154 | for_stmt.body = Stmt::Block(BlockStmt { 155 | span: DUMMY_SP, 156 | stmts: body_stmts, 157 | ctxt: SyntaxContext::default() 158 | }).into(); 159 | }, 160 | _ => {} 161 | } 162 | 163 | new_stmts.push(for_stmt.into()); 164 | } 165 | Stmt::If(mut if_stmt) => { 166 | let mut test = if_stmt.test; 167 | 168 | if test.is_seq() { 169 | let mut expr = test.take(); 170 | let seq = expr.as_mut_seq().unwrap(); 171 | new_stmts.reserve(seq.exprs.len() - 1); 172 | 173 | let actual_test = seq.exprs.pop().unwrap(); 174 | if_stmt.test = actual_test; 175 | 176 | for expr in &mut seq.exprs { 177 | let expr = expr.take(); 178 | new_stmts.push(expr.into_stmt()); 179 | } 180 | } else { 181 | if_stmt.test = test; 182 | } 183 | 184 | new_stmts.push(if_stmt.into()); 185 | } 186 | _ => { 187 | new_stmts.push(stmt) 188 | } 189 | }; 190 | } 191 | 192 | *path = new_stmts 193 | 194 | } 195 | } 196 | 197 | #[cfg(test)] 198 | mod tests { 199 | use swc_ecma_visit::VisitMutWith; 200 | 201 | use crate::ast::utils::{generate_code, parse_func_str}; 202 | 203 | use super::SequenceVisitor; 204 | 205 | #[test] 206 | fn ast_seq() { 207 | 208 | let raw = r#"function fe(hg, f, g, h, i, j, k) { if (hg = gJ, f = { 'INpHB': function (l, m) { return l + m }, 'HQIxS': function (l, m) { return l - m }, 'VYZHA': function (l, m) { return l ^ m }, 'Errvz': function (l, m) { return l ^ m }, 'VApqs': function (l, m) { return l === m }, 'BFlVJ': function (l, m) { return l ^ m }, 'yhJjC': function (l, m) { return m ^ l }, 'ZpcHY': function (l, m) { return m & l }, 'XAMtV': function (l, m) { return m === l }, 'mDSPe': function (l, m) { return l(m) } }, g = this.h[171 ^ this.g][3] ^ f[hg(1409)](f[hg(1136)](this.h[f[hg(1304)](171, this.g)][1][hg(606)](this.h[f[hg(1304)](171, this.g)][0]++), 101), 256) & 255.84 ^ 215.5, h = eU(this), i = this.h[f[hg(788)](171, this.g)][4], f[hg(1365)](228, g)) g = this.h[this.g ^ 171.68][3] ^ f[hg(1409)](this.h[171 ^ this.g][1][hg(606)](this.h[f[hg(184)](171, this.g)][0]++) - 101, 256) & 255 ^ 168, i[h].l = this.h[this.g ^ g]; else if (g === 196) g = this.h[f[hg(485)](171, this.g)][3] ^ f[hg(480)](155 + this.h[171 ^ this.g][1][hg(606)](this.h[this.g ^ 171.23][0]++), 255) ^ 168, this.h[g ^ this.g] = i[h].l; else if (f[hg(332)](197, g)) { for (g = 0; g < h; j = f[hg(851)](eU, this), k = {}, k.l = void 0, i[j] = k, g++); } }"#; 209 | let mut ast = parse_func_str(raw.to_string()); 210 | ast.visit_mut_with(&mut SequenceVisitor {}); 211 | 212 | println!("{}", generate_code(ast)); 213 | 214 | } 215 | } -------------------------------------------------------------------------------- /src/decoder.rs: -------------------------------------------------------------------------------- 1 | use base64::prelude::*; 2 | pub struct Decoder { 3 | c_ray: String 4 | } 5 | 6 | impl Decoder { 7 | pub fn new(c_ray: String) -> Self { 8 | Self { c_ray } 9 | } 10 | pub fn decode(&self, data: String) -> String { 11 | let mut h = 32; 12 | 13 | for byte in self.c_ray.as_bytes() { 14 | h ^= byte 15 | } 16 | h ^= '_' as u8; 17 | h ^= '0' as u8; 18 | 19 | BASE64_STANDARD.decode(data).unwrap() 20 | .into_iter() 21 | .enumerate() 22 | .map(|(index, charcode)| { 23 | ((charcode as i32 24 | - h as i32 25 | - (index % 65535) as i32 26 | + 65535 27 | ) % 255) as u8 as char 28 | }) 29 | .collect::() 30 | } 31 | 32 | #[cfg(debug_assertions)] 33 | pub fn encode(&self, decoded: String) -> String { 34 | 35 | let mut h = 32; 36 | 37 | for byte in self.c_ray.as_bytes() { 38 | h ^= byte 39 | } 40 | h ^= '_' as u8; 41 | h ^= '0' as u8; 42 | 43 | let x = decoded.as_bytes().into_iter() 44 | .enumerate() 45 | .map(|(index, decoded_char)| { 46 | let decoded_u8 = *decoded_char; // Convert char back to u8 47 | let original_charcode = (((decoded_u8 as i32 48 | + 255 // Undo modulo 255 49 | - (index % 65535) as i32 50 | + h as i32 51 | - 65535) % 255) as u8) as char; 52 | original_charcode 53 | }) 54 | .collect::(); 55 | 56 | 57 | BASE64_STANDARD.encode(x) 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use crate::decoder::Decoder; 64 | 65 | #[test] 66 | fn decoder() { 67 | assert_eq!( 68 | Decoder::new("8fa1e4096b7a01db".into()).decode("UX5kkYxpTWiLlIxpdJ1ZYW+Uf3FpqHt+iJqdoYihoXSIgLaIg4KtvHV0dbB9v5eYoaOjo5/Ko4udmaOvkL3B0a6OpLK2tZWbk8qr4bzjzaDC1+LGv6GzpsjF4tzosazcrfHF4LH0+Mvx+PjL/r3gvgbE3tELAvjW7cfd7A/OCAEMD+UVEBLN/PEO6QEVIfDdH9gTBxX9HQcCCQ7r6APvKiDv8zIkCgk5LTM3OCEADftGMzcUEwQ7RkUuTiY/QiYTITdWNhVJVFpRQDk6YC1UWmFUMmFUZEhGbkRnZWFcQG9RcWY0Ti1nXnFJPWo5ZGBOQ3yGW0REiXVaWmJ5k3uGgnOP".into()), 69 | "5aFrlH+EgofBLt/6CgQB9wIKTegjPhg9LCxICAky1//i5vMMUVUTOyQ8IDMX8dgvR1FSVT38/eEzTzc5VjtWO0A3TPlep82a1tGa1svHmsrDv4V3y7PBzpfCY2GUv5meoqGuop+YMhCYlwF2r+dWdLjSMSW40I5nc25rcHFuhmppX6B0yehDB2hrpXwNfhK7DYwV4gqvlZRRwCintfCqcrURyNpmhbEsTsg4M+dZlC6b0ZUB6nwK32vaEDKazakfVq" 70 | ); 71 | let decoder = Decoder::new("8fa5797cd892bc02".into()); 72 | assert_eq!( 73 | std::fs::read_to_string("./../Turnstile/task/bytecode").unwrap(), 74 | decoder.decode(std::fs::read_to_string("./../Turnstile/task/encoded_bytecode").unwrap()) 75 | ) 76 | } 77 | } -------------------------------------------------------------------------------- /src/decompiler/mod.rs: -------------------------------------------------------------------------------- 1 | mod values; 2 | mod simple; 3 | 4 | use std::collections::{HashMap, VecDeque}; 5 | 6 | use swc_ecma_ast::{BlockStmt, Stmt}; 7 | use crate::{ast::utils::generate_code, disassembler::step::{self, Step}}; 8 | 9 | type Block = Vec; 10 | 11 | struct Loop { 12 | test_reg: i32, 13 | 14 | start_ptr: i32, 15 | end_ptr: i32, 16 | 17 | exit_steps_ptr: Vec, 18 | continue_steps_ptr: Vec, 19 | } 20 | 21 | struct Branch { 22 | test_reg: i32, 23 | cons: Block, 24 | alt: Option 25 | } 26 | 27 | trait IntoStmt { 28 | fn to_stmt(&self) -> Option; 29 | } 30 | 31 | #[derive(Default)] 32 | pub struct Decompiler { 33 | func_blocks: HashMap, 34 | unhandled_blocks: VecDeque, 35 | handled_blocks: Vec 36 | } 37 | 38 | impl Decompiler { 39 | 40 | fn into_functions(&mut self, steps: &mut Vec, func_entries: Vec) { 41 | 42 | for idx in 0..func_entries.len() - 1 { 43 | let this_entry_ptr = *func_entries.get(idx).unwrap(); 44 | let next_entry_ptr = *func_entries.get(idx + 1).unwrap(); 45 | 46 | let mut iter = steps.iter(); 47 | let entry_index = iter.position(|s| s.pointer() == this_entry_ptr).unwrap(); 48 | let end_index = iter.position(|s| s.pointer() == next_entry_ptr).unwrap(); 49 | 50 | self.func_blocks.insert( 51 | this_entry_ptr, 52 | steps.drain(entry_index..=entry_index + end_index).collect() 53 | ); 54 | } 55 | 56 | let mut iter = steps.iter(); 57 | let last_entry_ptr = *func_entries.last().unwrap(); 58 | let last_entry_index = iter.position(|s| s.pointer() == last_entry_ptr).unwrap(); 59 | 60 | self.func_blocks.insert( 61 | last_entry_ptr, 62 | steps.drain(last_entry_index..steps.len() - 1).collect() 63 | ); 64 | } 65 | 66 | pub fn decompile(&mut self, mut steps: Vec, mut func_entries: Vec) { 67 | 68 | func_entries.sort(); 69 | func_entries.dedup(); 70 | 71 | self.into_functions(&mut steps, func_entries); 72 | 73 | let mut program_block: Vec = Vec::new(); 74 | let mut steps = steps.into_iter(); 75 | 76 | loop { 77 | match steps.next() { 78 | Some(step) => { 79 | match step { 80 | Step::Simple(simple_step) => { 81 | if let Some(stmt) = simple_step.to_stmt() { 82 | program_block.push(stmt) 83 | } 84 | }, 85 | Step::Value(value_step) => { 86 | if let Some(stmt) = value_step.to_stmt() { 87 | program_block.push(stmt) 88 | } 89 | }, 90 | Step::Jump(jump_step) => {}, 91 | Step::Branch(branch_step) => {}, 92 | Step::Func(func_step) => {}, 93 | Step::BinOp(bin_op_step) => {} 94 | } 95 | } 96 | None => break 97 | } 98 | } 99 | 100 | let code = generate_code(BlockStmt { stmts: program_block, ..Default::default() }.into()); 101 | std::fs::write("./debug/decompiled.js", code).unwrap(); 102 | 103 | //std::process::exit(0); 104 | 105 | } 106 | } -------------------------------------------------------------------------------- /src/decompiler/simple.rs: -------------------------------------------------------------------------------- 1 | use swc_common::DUMMY_SP; 2 | use swc_ecma_ast::{ArrayLit, AssignExpr, AssignOp, AssignTarget, BinExpr, BinaryOp, CallExpr, ComputedPropName, CondExpr, Expr, Ident, MemberExpr, NewExpr, Number, ObjectLit, ReturnStmt, Stmt, ThrowStmt}; 3 | use swc_ecma_utils::ExprFactory; 4 | 5 | use crate::disassembler::{instruction_set::r#match::InstructionType, step::SimpleStep}; 6 | 7 | use super::IntoStmt; 8 | 9 | fn reg_to_var(reg: i32) -> Ident { 10 | Ident::new_no_ctxt( 11 | format!("var_{reg}").into(), 12 | DUMMY_SP 13 | ) 14 | } 15 | 16 | fn new_assignment(lhs: L, rhs: R) -> Stmt 17 | where 18 | L: Into, 19 | R: Into 20 | { 21 | AssignExpr { 22 | op: AssignOp::Assign, 23 | left: lhs.into(), 24 | right: Box::new(rhs.into()), 25 | span: DUMMY_SP 26 | }.into_stmt() 27 | } 28 | 29 | impl IntoStmt for SimpleStep { 30 | fn to_stmt(&self) -> Option { 31 | let stmt = match self.instruction { 32 | 33 | InstructionType::Throw => ThrowStmt { arg: reg_to_var(self.register[0]).into(), span: DUMMY_SP }.into(), 34 | InstructionType::Terminate => ReturnStmt { arg: None, span: DUMMY_SP }.into(), 35 | 36 | InstructionType::NewArr => new_assignment( 37 | reg_to_var(self.dest.unwrap()), 38 | ArrayLit { elems: vec![], ..Default::default() } 39 | ), 40 | InstructionType::NewObject => new_assignment( 41 | reg_to_var(self.dest.unwrap()), 42 | ObjectLit { props: vec![], ..Default::default() } 43 | ), 44 | InstructionType::Copy => new_assignment( 45 | reg_to_var(self.dest.unwrap()), 46 | reg_to_var(self.register[0]) 47 | ), 48 | InstructionType::Pop => new_assignment( 49 | reg_to_var(self.dest.unwrap()), 50 | CallExpr { 51 | callee: swc_ecma_ast::Callee::Expr( 52 | MemberExpr { 53 | obj: reg_to_var(self.register[0]).into(), 54 | prop: swc_ecma_ast::MemberProp::Ident(Ident::new_no_ctxt("pop".into(), DUMMY_SP).into()) , 55 | span: DUMMY_SP 56 | }.into() 57 | ), 58 | args: vec![], 59 | ..Default::default() 60 | } 61 | ), 62 | InstructionType::Push => CallExpr { 63 | callee: swc_ecma_ast::Callee::Expr( 64 | MemberExpr { 65 | obj: reg_to_var(self.register[0]).into(), 66 | prop: swc_ecma_ast::MemberProp::Ident(Ident::new_no_ctxt("push".into(), DUMMY_SP).into()) , 67 | span: DUMMY_SP 68 | }.into() 69 | ), 70 | args: vec![ 71 | Expr::Ident(reg_to_var(self.register[1]).into()).into() 72 | ], 73 | ..Default::default() 74 | }.into_stmt(), 75 | 76 | InstructionType::GetProp => new_assignment( 77 | reg_to_var(self.dest.unwrap()), 78 | MemberExpr { 79 | obj: reg_to_var(self.register[0]).into(), 80 | prop: swc_ecma_ast::MemberProp::Computed( 81 | ComputedPropName { 82 | expr: reg_to_var(self.register[1]).into(), 83 | span: DUMMY_SP 84 | } 85 | ), 86 | span: DUMMY_SP 87 | } 88 | ), 89 | InstructionType::SetProp => new_assignment( 90 | MemberExpr { 91 | obj: reg_to_var(self.register[0]).into(), 92 | prop: swc_ecma_ast::MemberProp::Computed( 93 | ComputedPropName { 94 | expr: reg_to_var(self.register[1]).into(), 95 | span: DUMMY_SP 96 | } 97 | ), 98 | span: DUMMY_SP 99 | }, 100 | reg_to_var(self.register[2]), 101 | ), 102 | 103 | InstructionType::Constructor => new_assignment( 104 | reg_to_var(self.dest.unwrap()), 105 | NewExpr { 106 | callee: reg_to_var(self.register[0]).into(), 107 | args: Some( 108 | self.register[1..] 109 | .iter() 110 | .map(|reg| { 111 | Expr::Ident(reg_to_var(*reg)).into() 112 | }) 113 | .collect() 114 | ), 115 | ..Default::default() 116 | } 117 | ), 118 | InstructionType::FuncCall => new_assignment( 119 | reg_to_var(self.dest.unwrap()), 120 | CondExpr { 121 | test: BinExpr { 122 | op: BinaryOp::EqEqEq, 123 | left: Ident::new_no_ctxt("undefined".into(), DUMMY_SP).into(), 124 | right: reg_to_var(self.register[0]).into(), 125 | span: DUMMY_SP 126 | }.into(), 127 | cons: CallExpr { 128 | callee: swc_ecma_ast::Callee::Expr(reg_to_var(self.register[1]).into()), 129 | args: self.register[2..] 130 | .iter() 131 | .map(|reg| { 132 | Expr::Ident(reg_to_var(*reg)).into() 133 | }) 134 | .collect(), 135 | ..Default::default() 136 | }.into(), 137 | alt: CallExpr { 138 | callee: swc_ecma_ast::Callee::Expr( 139 | MemberExpr { 140 | obj: reg_to_var(self.register[0]).into(), 141 | prop: swc_ecma_ast::MemberProp::Computed( 142 | ComputedPropName { 143 | expr: reg_to_var(self.register[1]).into(), 144 | span: DUMMY_SP 145 | } 146 | ), 147 | span: DUMMY_SP 148 | }.into() 149 | ), 150 | args: self.register[2..] 151 | .iter() 152 | .map(|reg| { 153 | Expr::Ident(reg_to_var(*reg)).into() 154 | }) 155 | .collect(), 156 | ..Default::default() 157 | }.into(), 158 | span: DUMMY_SP 159 | } 160 | ), 161 | 162 | 163 | InstructionType::UnaryOp => return None, 164 | InstructionType::BinaryOp => return None, 165 | InstructionType::MemoryOp => return None, 166 | InstructionType::PrepReturn => return None, 167 | 168 | // Not needed 169 | InstructionType::NewValue => return None, 170 | InstructionType::Jump => return None, 171 | InstructionType::Swap => return None, 172 | InstructionType::Bind => return None, 173 | InstructionType::BuildFunc => return None, 174 | InstructionType::Branch => return None, 175 | }; 176 | Some(stmt) 177 | } 178 | } -------------------------------------------------------------------------------- /src/decompiler/values.rs: -------------------------------------------------------------------------------- 1 | use swc_common::DUMMY_SP; 2 | use swc_ecma_ast::{ArrayLit, AssignExpr, AssignOp, Bool, Expr, Ident, Number, Regex, Stmt, Str}; 3 | use swc_ecma_utils::ExprFactory; 4 | 5 | use crate::disassembler::step::ValueStep; 6 | use crate::disassembler::instruction_set::instructions::values::JSValue; 7 | 8 | use super::IntoStmt; 9 | 10 | impl IntoStmt for ValueStep { 11 | fn to_stmt(&self) -> Option { 12 | let lhs = Ident::new_no_ctxt(format!("var_{}", self.dest).into(), DUMMY_SP); 13 | let rhs: Expr = match &self.value { 14 | JSValue::Undefined => Ident::new_no_ctxt("undefined".into(), DUMMY_SP).into(), 15 | JSValue::Null => Ident::new_no_ctxt("null".into(), DUMMY_SP).into(), 16 | JSValue::Infinity => Ident::new_no_ctxt("Infinity".into(), DUMMY_SP).into(), 17 | JSValue::NaN => Ident::new_no_ctxt("NaN".into(), DUMMY_SP).into(), 18 | JSValue::Array => ArrayLit { elems: vec![], span: DUMMY_SP }.into(), 19 | JSValue::Bool(x) => Bool { value: *x, span: DUMMY_SP }.into(), 20 | JSValue::Number(x) => Number { value: *x, span: DUMMY_SP, raw: None }.into(), 21 | JSValue::String(x) => Str { value: x.clone().into(), span: DUMMY_SP, raw: None }.into(), 22 | JSValue::RegExp(x) => Str { value: x.clone().into(), span: DUMMY_SP, raw: None }.into(), 23 | JSValue::EntryPoint(_) => return None, 24 | }; 25 | let assignment = AssignExpr { 26 | op: AssignOp::Assign, 27 | left: lhs.into(), 28 | right: rhs.into(), 29 | ..Default::default() 30 | }; 31 | Some(assignment.into_stmt()) 32 | } 33 | } -------------------------------------------------------------------------------- /src/device_checks/devices.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use include_dir::{Dir, include_dir}; 3 | use once_cell::sync::Lazy; 4 | use serde::{Deserialize, Serialize}; 5 | use serde_json::Value; 6 | 7 | static DEVICE_DIR: Dir = include_dir!("devices"); 8 | 9 | static DEVICES: Lazy> = Lazy::new(|| { 10 | DEVICE_DIR.files() 11 | .map(|file| { 12 | ( 13 | file.path().to_str().unwrap().to_owned(), 14 | serde_json::from_str::(file.contents_utf8().unwrap()).unwrap() 15 | ) 16 | }) 17 | .collect() 18 | }); 19 | 20 | pub static DEVICE: Lazy = Lazy::new(|| { 21 | let device = std::fs::read_to_string("./devices/chrome131.json").unwrap(); 22 | serde_json::from_str(&device).unwrap() 23 | }); 24 | 25 | #[derive(Deserialize, Serialize)] 26 | pub struct WebGl { 27 | pub vendor: String, 28 | pub renderer: String, 29 | pub unmasked_vendor_webgl: String, 30 | pub unmasked_renderer_webgl: String, 31 | pub hash: String 32 | } 33 | 34 | #[derive(Deserialize, Serialize)] 35 | pub struct Device { 36 | pub inverted_map: Value, 37 | pub web_gl: WebGl, 38 | pub math_hash: String, 39 | pub div_client_rects_hash: String, 40 | pub user_agent_data: Value 41 | } -------------------------------------------------------------------------------- /src/device_checks/types.rs: -------------------------------------------------------------------------------- 1 | use super::DeviceCheck; 2 | 3 | impl DeviceCheck { 4 | pub fn detect(strings: &Vec<&String>) -> Self { 5 | 6 | let strings: Vec<&str> = strings.iter().map(|s| s.as_str()).collect(); 7 | 8 | if strings.contains(&"__proto__") && strings.contains(&"MimeTypeArray") { 9 | return DeviceCheck::NativeCodeChecks 10 | } 11 | if strings.contains(&"LOG10E") && strings.contains(&"SQRT1_2") { 12 | return DeviceCheck::Math 13 | } 14 | if strings.contains(&"UNMASKED_RENDERER_WEBGL") && strings.contains(&"WEBGL_debug_renderer_info") { 15 | return DeviceCheck::WebGL 16 | } 17 | if strings.contains(&"ssL") && strings.contains(&"lH") { 18 | return DeviceCheck::ApiJSDocumentData 19 | } 20 | if strings.contains(&"/cdn-cgi/challenge-platform") && strings.contains(&"onmessage") { 21 | return DeviceCheck::ImageFetch 22 | } 23 | 24 | if strings.contains(&"clientInformation") { return DeviceCheck::WeirdMap } 25 | if strings.contains(&"matchMedia") { return DeviceCheck::MatchMedia } 26 | if strings.contains(&"compactDisplay") { return DeviceCheck::LanguageFormatting } 27 | if strings.contains(&"-12-01") { return DeviceCheck::DateStuff } 28 | if strings.contains(&"UIPATH_EXTENSION_ID:") { return DeviceCheck::ApiJSOtherData } 29 | if strings.contains(&"50000000000000000000000000") { return DeviceCheck::V8Checks } 30 | if strings.contains(&"getEntries") { return DeviceCheck::PerformanceEntries } 31 | if strings.contains(&"CanvasGradient") { return DeviceCheck::TamperChecks } 32 | if strings.contains(&"private-token-client-replay") { return DeviceCheck::Pat } 33 | if strings.contains(&"getClientRects") { return DeviceCheck::DivRendering } 34 | if strings.contains(&"memory") { return DeviceCheck::PerformanceMemory } 35 | if strings.contains(&"CLIENT_HINTS_DATA_UNDEFINED_OR_NULL") { return DeviceCheck::UserAgentData } 36 | if strings.contains(&"") { return DeviceCheck::DivAppendTime } 37 | if strings.contains(&"the force is not strong with this one") { return DeviceCheck::Pow } 38 | 39 | if strings.len() == 16 { 40 | return DeviceCheck::Empty 41 | } 42 | 43 | panic!("Unknown CollectType: {strings:?}") 44 | 45 | } 46 | } -------------------------------------------------------------------------------- /src/disassembler.rs: -------------------------------------------------------------------------------- 1 | pub mod instruction_set; 2 | mod utils; 3 | mod vm; 4 | pub mod result; 5 | pub mod step; 6 | 7 | use std::collections::{HashMap, HashSet, VecDeque}; 8 | 9 | use base64::{engine::general_purpose::STANDARD, Engine as _}; 10 | use instruction_set::instructions::values::entry_point::EntryPoint; 11 | use instruction_set::instructions::values::JSValue; 12 | use instruction_set::r#match::InstructionType; 13 | use instruction_set::InstructionSet; 14 | use instruction_set::js_eval::JSEvaluator; 15 | use result::DisasmResult; 16 | use utils::{extract_calc_str, extract_dynamic_num_from_builder_func, extract_dynamic_num_update_calc_str, extract_stack_register_from_executor_func, extract_state_register_from_builder_func}; 17 | use vm::{VmFunctions, VmState}; 18 | use step::Step; 19 | 20 | use crate::script::TransformedFunctions; 21 | 22 | 23 | #[derive(Default)] 24 | pub struct Disassembler { 25 | state: VmState, 26 | funcs: VmFunctions, 27 | instruction_set: InstructionSet, 28 | 29 | default_entry: EntryPoint, 30 | entry_points: VecDeque, 31 | dealt_pointers: HashSet, 32 | func_entries: Vec, 33 | 34 | steps: Vec, 35 | 36 | trace_count: i32 37 | } 38 | 39 | impl Disassembler { 40 | 41 | fn write_trace(&mut self, filename: &str) { 42 | let trace = self.steps.iter() 43 | .map(|s| format!("{s}")) 44 | .collect::>() 45 | .join("\r\n"); 46 | std::fs::write(format!("./debug/trace/{filename}_{}.txt", self.trace_count), trace).unwrap(); 47 | self.trace_count += 1; 48 | } 49 | 50 | fn reset_state(&mut self) { 51 | self.entry_points.push_back(self.default_entry.clone()); 52 | self.state.terminate = false; 53 | 54 | self.func_entries.clear(); 55 | self.dealt_pointers.clear(); 56 | self.steps.clear(); 57 | } 58 | 59 | pub fn new(transformed_funcs: TransformedFunctions, init_bytecode: String) -> Self { 60 | 61 | let TransformedFunctions { vm_init, vm_prep, vm_execute, while_bitwise, instructions, .. } = transformed_funcs; 62 | 63 | let state = VmState { 64 | dynamic_num: extract_dynamic_num_from_builder_func(&vm_init), 65 | state_register: extract_state_register_from_builder_func(&vm_init), 66 | stack_register: extract_stack_register_from_executor_func(&vm_execute), 67 | bytecode: STANDARD.decode(init_bytecode).unwrap(), 68 | terminate: false, 69 | ..Default::default() 70 | }; 71 | 72 | let funcs = VmFunctions { 73 | next_opcode: JSEvaluator::from_str(extract_calc_str(&vm_prep)), 74 | varint_byte_calc: JSEvaluator::from_str(extract_calc_str(&while_bitwise)), 75 | next_dynamic: JSEvaluator::from_str(extract_dynamic_num_update_calc_str(&vm_prep).as_str()), 76 | reg_rearrange_mapper: (0..256).into_iter().map(|x| (x, x)).collect(), 77 | }; 78 | 79 | let mut disassembler = Self { 80 | state: state, 81 | funcs: funcs, 82 | instruction_set: InstructionSet::build(instructions), 83 | 84 | ..Default::default() 85 | }; 86 | 87 | disassembler.init(); 88 | disassembler 89 | } 90 | 91 | fn update_state(&mut self) { 92 | self.state.last_op_pointer = self.state.pointer; 93 | let last_unmapped_opcode = self.funcs.next_opcode.eval(&mut self.state); 94 | //dbg!(last_unmapped_opcode); 95 | //if last_unmapped_opcode == 129 { 96 | // dbg!(&self.register_rearrange_mapper); 97 | //} 98 | //self.state.last_opcode = self.register_rearrange_mapper.get(&last_unmapped_opcode).unwrap().clone(); 99 | self.state.last_opcode = last_unmapped_opcode; 100 | self.state.dynamic_num = self.funcs.next_dynamic.eval(&mut self.state); 101 | } 102 | 103 | fn init(&mut self) { 104 | 105 | //dbg!(&self.funcs.next_opcode); 106 | //dbg!(&self.funcs.next_dynamic); 107 | 108 | let mut swaps: Vec<(i32, i32)> = Vec::new(); 109 | 110 | while !self.state.terminate { 111 | 112 | self.update_state(); 113 | 114 | let step = self.instruction_set.run_next_op(&mut self.state, &self.funcs); 115 | 116 | match &step { 117 | Step::Simple(simple_step) => { 118 | match simple_step.instruction { 119 | InstructionType::Bind => { 120 | self.instruction_set.bind_opcode( 121 | *simple_step.register.get(0).unwrap(), 122 | simple_step.dest.unwrap() 123 | ); 124 | }, 125 | InstructionType::Swap => { 126 | self.instruction_set.apply_swap( 127 | *simple_step.register.get(0).unwrap() as usize, 128 | *simple_step.register.get(1).unwrap() as usize 129 | ); 130 | } 131 | _ => {} 132 | } 133 | }, 134 | Step::Value(step) => { 135 | match step.value { 136 | JSValue::Number(val) => self.default_entry.dynamic_num = val as i32, 137 | _ => {} 138 | } 139 | }, 140 | _ => {} 141 | } 142 | #[cfg(debug_assertions)] 143 | self.steps.push(step); 144 | } 145 | //dbg!(&self.funcs.reg_rearrange_mapper); 146 | //panic!(); 147 | #[cfg(debug_assertions)] 148 | self.write_trace("init"); 149 | } 150 | 151 | pub fn disassemble(&mut self, bytecode: String) -> DisasmResult { 152 | 153 | self.reset_state(); 154 | 155 | self.state.bytecode = STANDARD.decode(bytecode).unwrap(); 156 | 157 | loop { 158 | 159 | //dbg!(&self.entry_points); 160 | 161 | let entry_point = if let Some(ep) = self.entry_points.pop_front() { 162 | ep 163 | } else { 164 | break 165 | }; 166 | 167 | self.state.pointer = entry_point.pointer; 168 | self.state.dynamic_num = entry_point.dynamic_num; 169 | self.state.terminate = false; 170 | 171 | while !self.state.terminate { 172 | 173 | // state.dynamic_num is a temporary solution how it handles brackets 174 | // might crash on other scripts. 175 | 176 | if self.dealt_pointers.contains(&self.state.pointer) { 177 | break 178 | } 179 | 180 | self.update_state(); 181 | 182 | //println!("ptr {}, opcode {}, dynamic_num {}", state.last_op_pointer, state.last_opcode, state.dynamic_num); 183 | 184 | let step = self.instruction_set.run_next_op(&mut self.state, &self.funcs); 185 | 186 | match &step { 187 | Step::Value(val_step) => { 188 | match &val_step.value { 189 | JSValue::EntryPoint(ep) => { 190 | self.entry_points.push_back(ep.clone()) 191 | }, 192 | _ => {} 193 | } 194 | }, 195 | Step::Branch(branch_step) => { 196 | self.entry_points.push_back(EntryPoint { 197 | pointer: branch_step.jump_to, 198 | dynamic_num: branch_step.dynamic_num 199 | }); 200 | }, 201 | Step::Func(func_step) => { 202 | self.func_entries.push(func_step.entry_pointer); 203 | self.entry_points.push_back(EntryPoint { 204 | pointer: func_step.entry_pointer, 205 | dynamic_num: func_step.dynamic_num 206 | }); 207 | } 208 | _ => {} 209 | } 210 | 211 | self.steps.push(step); 212 | self.dealt_pointers.insert(self.state.last_op_pointer); 213 | } 214 | 215 | } 216 | 217 | self.steps.sort_by(|a, b| a.pointer().cmp(&b.pointer())); 218 | 219 | #[cfg(debug_assertions)] 220 | self.write_trace("main"); 221 | 222 | DisasmResult::new( 223 | std::mem::take(&mut self.steps), 224 | std::mem::take(&mut self.func_entries) 225 | ) 226 | } 227 | 228 | } -------------------------------------------------------------------------------- /src/disassembler/instruction_set.rs: -------------------------------------------------------------------------------- 1 | pub mod r#match; 2 | pub mod instructions; 3 | pub mod js_eval; 4 | 5 | use std::{collections::HashMap, fmt::Display}; 6 | 7 | use instructions::*; 8 | use r#match::{match_instruction, InstructionType}; 9 | use swc_ecma_ast::{BinaryOp, UnaryOp}; 10 | use values::JSValue; 11 | use crate::script::InstructionFunc; 12 | 13 | use super::{step::Step, VmFunctions, VmState}; 14 | 15 | 16 | 17 | pub trait Instruction { 18 | fn build(exprs: Vec) -> Self; 19 | fn execute(&self, state: &mut VmState, funcs: &VmFunctions) -> Step; 20 | } 21 | 22 | #[derive(Default)] 23 | pub struct InstructionSet { 24 | throw: ThrowInstruction, 25 | bind: BindInstruction, 26 | swap: SwapInstruction, 27 | arr: NewArrInstruction, 28 | obj: NewObjInstruction, 29 | unary: UnaryOpInstruction, 30 | binary: BinaryOpInstruction, 31 | get_prop: GetPropInstruction, 32 | set_prop: SetPropInstruction, 33 | copy: CopyInstruction, 34 | pop: PopInstruction, 35 | push: PushInstruction, 36 | call: CallInstruction, 37 | branch: BranchInstruction, 38 | value: NewValueInstruction, 39 | constructor: ConstructorInstruction, 40 | memory: MemoryInstruction, 41 | build: BuildFuncInstruction, 42 | prep_ret: PrepReturnInstruction, 43 | update: JumpInstruction, 44 | 45 | opcode_instruction_map: HashMap, 46 | 47 | binary_op_opcode_to_operator_map: HashMap, 48 | unary_op_opcode_to_operator_map: HashMap 49 | } 50 | 51 | impl InstructionSet { 52 | 53 | pub fn bind_opcode(&mut self, opcode: i32, dest: i32) { 54 | self.opcode_instruction_map.insert( 55 | dest as usize, 56 | self.opcode_instruction_map.get(&(opcode as usize)).unwrap().clone() 57 | ); 58 | } 59 | 60 | pub fn swap_opcodes(&mut self, index1: usize, index2: usize) { 61 | let element1 = self.opcode_instruction_map.remove(&index1); 62 | let element2 = self.opcode_instruction_map.remove(&index2); 63 | 64 | if let Some(e) = element1 { 65 | self.opcode_instruction_map.insert(index2, e); 66 | } 67 | if let Some(e) = element2 { 68 | self.opcode_instruction_map.insert(index1, e); 69 | } 70 | } 71 | 72 | pub fn swap_bin_ops(&mut self, index1: i32, index2: i32) { 73 | 74 | let element1 = self.binary.operator_map.remove(&index1); 75 | let element2 = self.binary.operator_map.remove(&index2); 76 | 77 | if let Some(e) = element1 { 78 | self.binary.operator_map.insert(index2, e); 79 | } 80 | if let Some(e) = element2 { 81 | self.binary.operator_map.insert(index1, e); 82 | } 83 | } 84 | 85 | pub fn apply_swap(&mut self, index1: usize, index2: usize) { 86 | self.swap_opcodes(index1, index2); 87 | self.swap_bin_ops(index1 as i32, index2 as i32); 88 | } 89 | 90 | pub fn run_next_op(&self, state: &mut VmState, funcs: &VmFunctions) -> Step { 91 | let opcode = state.last_opcode as usize; 92 | match self.opcode_instruction_map.get(&opcode) { 93 | Some(instruction) => { 94 | match instruction { 95 | InstructionType::Throw => self.throw.execute(state, funcs), 96 | InstructionType::NewArr => self.arr.execute(state, funcs), 97 | InstructionType::NewObject => self.obj.execute(state, funcs), 98 | InstructionType::Push => self.push.execute(state, funcs), 99 | InstructionType::GetProp => self.get_prop.execute(state, funcs), 100 | InstructionType::SetProp => self.set_prop.execute(state, funcs), 101 | InstructionType::Copy => self.copy.execute(state, funcs), 102 | InstructionType::Pop => self.pop.execute(state, funcs), 103 | InstructionType::FuncCall => self.call.execute(state, funcs), 104 | InstructionType::Branch => self.branch.execute(state, funcs), 105 | InstructionType::Swap => self.swap.execute(state, funcs), 106 | InstructionType::Bind => self.bind.execute(state, funcs), 107 | InstructionType::Constructor => self.constructor.execute(state, funcs), 108 | InstructionType::BuildFunc => self.build.execute(state, funcs), 109 | InstructionType::PrepReturn => self.prep_ret.execute(state, funcs), 110 | InstructionType::Jump => self.update.execute(state, funcs), 111 | 112 | InstructionType::UnaryOp => self.unary.execute(state, funcs), 113 | InstructionType::BinaryOp => self.binary.execute(state, funcs), 114 | InstructionType::MemoryOp => self.memory.execute(state, funcs), 115 | InstructionType::NewValue => self.value.execute(state, funcs), //self.value.execute(state) 116 | 117 | InstructionType::Terminate => panic!() 118 | } 119 | }, 120 | None => panic!("No instruction with opcode {opcode}") 121 | } 122 | } 123 | 124 | pub fn build(func_strings: Vec) -> Self { 125 | let mut matched_types: Vec = Vec::new(); 126 | let mut instruction_set = Self::default(); 127 | 128 | for InstructionFunc { opcode, expressions: exprs } in func_strings { 129 | let instruction_type = match_instruction(&exprs); 130 | 131 | if matched_types.iter().any(|t| *t == instruction_type) { 132 | panic!("Two instructions with same type: {instruction_type:?}") 133 | } 134 | matched_types.push(instruction_type.clone()); 135 | 136 | match instruction_type { 137 | InstructionType::Throw => instruction_set.throw = ThrowInstruction::build(exprs), 138 | InstructionType::Bind => instruction_set.bind = BindInstruction::build(exprs), 139 | InstructionType::Swap => instruction_set.swap = SwapInstruction::build(exprs), 140 | InstructionType::NewArr => instruction_set.arr = NewArrInstruction::build(exprs), 141 | InstructionType::NewObject => instruction_set.obj = NewObjInstruction::build(exprs), 142 | InstructionType::Push => instruction_set.push = PushInstruction::build(exprs), 143 | InstructionType::GetProp => instruction_set.get_prop = GetPropInstruction::build(exprs), 144 | InstructionType::SetProp => instruction_set.set_prop = SetPropInstruction::build(exprs), 145 | InstructionType::Copy => instruction_set.copy = CopyInstruction::build(exprs), 146 | InstructionType::Pop => instruction_set.pop = PopInstruction::build(exprs), 147 | InstructionType::FuncCall => instruction_set.call = CallInstruction::build(exprs), 148 | InstructionType::Branch => instruction_set.branch = BranchInstruction::build(exprs), 149 | InstructionType::Constructor => instruction_set.constructor = ConstructorInstruction::build(exprs), 150 | InstructionType::BuildFunc => instruction_set.build = BuildFuncInstruction::build(exprs), 151 | InstructionType::PrepReturn => instruction_set.prep_ret = PrepReturnInstruction::build(exprs), 152 | InstructionType::Jump => instruction_set.update = JumpInstruction::build(exprs), 153 | 154 | InstructionType::UnaryOp => instruction_set.unary = UnaryOpInstruction::build(exprs), 155 | InstructionType::NewValue => instruction_set.value = NewValueInstruction::build(exprs), 156 | InstructionType::MemoryOp => instruction_set.memory = MemoryInstruction::build(exprs), 157 | 158 | InstructionType::BinaryOp => instruction_set.binary = BinaryOpInstruction::build(exprs), 159 | 160 | _ => {} 161 | 162 | } 163 | 164 | instruction_set.opcode_instruction_map.insert(opcode, instruction_type); 165 | } 166 | 167 | instruction_set 168 | } 169 | } -------------------------------------------------------------------------------- /src/disassembler/instruction_set/instructions.rs: -------------------------------------------------------------------------------- 1 | mod impls; 2 | pub mod values; 3 | mod extract; 4 | mod memory; 5 | 6 | use std::collections::HashMap; 7 | 8 | use impls::BinaryOpDynamicValues; 9 | use memory::MemoryOp; 10 | use swc_ecma_ast::BinaryOp; 11 | use values::ValueFactory; 12 | 13 | use super::js_eval::JSEvaluator; 14 | 15 | 16 | 17 | 18 | #[derive(Debug, Default)] 19 | pub struct NewArrInstruction { 20 | dest_calc: JSEvaluator 21 | } 22 | 23 | #[derive(Debug, Default)] 24 | pub struct BindInstruction { 25 | dest_calc: JSEvaluator, 26 | func_index_calc: JSEvaluator, 27 | arg_calc: JSEvaluator 28 | } 29 | 30 | #[derive(Debug, Default)] 31 | pub struct NewObjInstruction { 32 | dest_calc: JSEvaluator 33 | } 34 | 35 | #[derive(Debug, Default)] 36 | pub struct SwapInstruction { 37 | var_1_calc: JSEvaluator, 38 | var_2_calc: JSEvaluator 39 | } 40 | 41 | #[derive(Debug, Default)] 42 | pub struct ThrowInstruction { 43 | index_calc: JSEvaluator 44 | } 45 | 46 | #[derive(Debug, Default)] 47 | pub struct PushInstruction { 48 | target_arr_calc: JSEvaluator, 49 | element_to_push_calc: JSEvaluator 50 | } 51 | 52 | #[derive(Debug, Default)] 53 | pub struct PopInstruction { 54 | target_arr_calc: JSEvaluator, 55 | dest_calc: JSEvaluator 56 | } 57 | 58 | #[derive(Debug, Default)] 59 | pub struct GetPropInstruction { 60 | dest_calc: JSEvaluator, 61 | target_arr_calc: JSEvaluator, 62 | target_index_calc: JSEvaluator 63 | } 64 | 65 | #[derive(Debug, Default)] 66 | pub struct SetPropInstruction { 67 | target_arr_calc: JSEvaluator, 68 | target_index_calc: JSEvaluator, 69 | element_to_add_calc: JSEvaluator 70 | } 71 | 72 | #[derive(Debug, Default)] 73 | pub struct CopyInstruction { 74 | dest_calc: JSEvaluator, 75 | origin_calc: JSEvaluator 76 | } 77 | 78 | #[derive(Debug, Default)] 79 | pub struct CallInstruction { 80 | dest_calc: JSEvaluator, 81 | obj_calc: JSEvaluator, 82 | func_calc: JSEvaluator, 83 | arg_count_calc: JSEvaluator, 84 | arg_calc: JSEvaluator 85 | } 86 | 87 | #[derive(Debug, Default)] 88 | pub struct BranchInstruction { 89 | test_calc: JSEvaluator, 90 | pointer_calc: JSEvaluator, 91 | dynamic_val_calc: JSEvaluator, 92 | xor_val: i32 93 | } 94 | 95 | #[derive(Debug, Default)] 96 | pub struct ConstructorInstruction { 97 | dest_calc: JSEvaluator, 98 | constructor_calc: JSEvaluator, 99 | arg_count_calc: JSEvaluator, 100 | arg_calc: JSEvaluator 101 | } 102 | 103 | #[derive(Debug, Default)] 104 | pub struct BuildFuncInstruction { 105 | dest_calc: JSEvaluator, 106 | entry_pointer_calc: JSEvaluator, 107 | dynamic_num_calc: JSEvaluator 108 | } 109 | 110 | #[derive(Debug, Default)] 111 | pub struct PrepReturnInstruction { 112 | calc: JSEvaluator 113 | } 114 | 115 | #[derive(Debug, Default)] 116 | pub struct JumpInstruction { 117 | pointer_calc: JSEvaluator, 118 | dynamic_num_calc: JSEvaluator 119 | } 120 | 121 | #[derive(Debug, Default)] 122 | pub struct NewValueInstruction { 123 | dest_calc: JSEvaluator, 124 | case_calc: JSEvaluator, 125 | value_factory: ValueFactory 126 | } 127 | 128 | 129 | #[derive(Debug, Default)] 130 | pub struct MemoryInstruction { 131 | case_calc: JSEvaluator, 132 | store_calc: JSEvaluator, 133 | fetch_calc: JSEvaluator, 134 | 135 | case_map: HashMap 136 | } 137 | 138 | #[derive(Debug, Default)] 139 | pub struct UnaryOpInstruction { 140 | dest_calc: JSEvaluator, 141 | arg_calc: JSEvaluator 142 | } 143 | 144 | #[derive(Debug, Default)] 145 | pub struct BinaryOpInstruction { 146 | dest_calc: JSEvaluator, 147 | lhs_calc: JSEvaluator, 148 | rhs_calc: JSEvaluator, 149 | pub operator_map: HashMap 150 | } -------------------------------------------------------------------------------- /src/disassembler/instruction_set/instructions/extract.rs: -------------------------------------------------------------------------------- 1 | use std::slice::Iter; 2 | 3 | use once_cell::sync::Lazy; 4 | use regex::Regex; 5 | 6 | use crate::lazy_regex; 7 | 8 | pub fn get_between_brackets(expr: &str) -> &str { 9 | expr.split_once('[') 10 | .unwrap().1 11 | .split_once(']') 12 | .unwrap().0 13 | } 14 | 15 | static BETWEEN_PAREN_PATTERN: Lazy = lazy_regex!(r#"\((.*)\)"#); 16 | 17 | pub fn get_between_paren(expr: &str) -> &str { 18 | BETWEEN_PAREN_PATTERN.captures(expr) 19 | .unwrap() 20 | .get(1) 21 | .unwrap() 22 | .as_str() 23 | //expr.split_once('(') 24 | // .unwrap().1 25 | // .split_once(')') 26 | // .unwrap().0 27 | } 28 | 29 | pub fn get_assignment_right(expr: &str) -> &str { 30 | expr.split_once('=') 31 | .unwrap().1 32 | } 33 | 34 | pub fn filter_calc_exprs<'a>(exprs: &'a Vec) -> std::vec::IntoIter<&'a String> { 35 | exprs.iter() 36 | .filter(|expr| expr.contains("DYNAMIC_NUM")) 37 | .collect::>() 38 | .into_iter() 39 | } -------------------------------------------------------------------------------- /src/disassembler/instruction_set/instructions/memory.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use crate::{ast::visitor::case_encapsulation::encapsulate_cases, disassembler::instruction_set::js_eval::JSEvaluator}; 3 | use super::MemoryInstruction; 4 | 5 | 6 | #[derive(Debug, PartialEq)] 7 | pub enum MemoryOp { 8 | Store, 9 | Fetch, 10 | Flush 11 | } 12 | 13 | fn extract_calc(expr: &str) -> &str { 14 | expr.split(['=', ',']) 15 | .nth(1) 16 | .unwrap() 17 | } 18 | 19 | pub fn build_case_memory_op_map(exprs: &Vec, instruction: &mut MemoryInstruction) { 20 | let mut map: HashMap = HashMap::new(); 21 | 22 | for (case, cons) in encapsulate_cases(exprs) { 23 | let last_expr = cons.last().unwrap(); 24 | let op = if cons.iter().any(|l| l.contains("void 0")) { 25 | MemoryOp::Flush 26 | } 27 | else if last_expr.ends_with("];") { 28 | instruction.store_calc = JSEvaluator::from_str(extract_calc(exprs.get(0).unwrap())); 29 | MemoryOp::Store 30 | } 31 | else if last_expr.ends_with(";") { 32 | instruction.fetch_calc = JSEvaluator::from_str(extract_calc(exprs.get(0).unwrap())); 33 | MemoryOp::Fetch 34 | } 35 | else { 36 | panic!(); 37 | }; 38 | map.insert(case, op); 39 | } 40 | 41 | instruction.case_map = map; 42 | } -------------------------------------------------------------------------------- /src/disassembler/instruction_set/instructions/values.rs: -------------------------------------------------------------------------------- 1 | mod string; 2 | mod regex; 3 | pub mod entry_point; 4 | mod float; 5 | 6 | use std::{collections::HashMap, fmt::write}; 7 | 8 | use entry_point::{EntryPoint, EntryPointBuilder}; 9 | use float::FloatBuilder; 10 | use once_cell::sync::Lazy; 11 | use regex::RegexBuilder; 12 | use string::StringBuilder; 13 | 14 | use crate::{ast::visitor::case_encapsulation::encapsulate_cases, disassembler::{instruction_set::{instructions::extract::get_assignment_right, js_eval::JSEvaluator}, VmFunctions, VmState}}; 15 | 16 | trait ValueBaker { 17 | fn new(exprs: Vec) -> Self; 18 | fn build(&self, state: &mut VmState, funcs: &VmFunctions) -> JSValue; 19 | } 20 | 21 | #[derive(Debug, PartialEq, Clone)] 22 | pub enum JSValue { 23 | Undefined, 24 | Null, 25 | Infinity, 26 | NaN, 27 | Array, 28 | Bool(bool), 29 | Number(f64), 30 | String(String), 31 | RegExp(String), 32 | EntryPoint(EntryPoint) 33 | } 34 | 35 | impl Default for JSValue { 36 | fn default() -> Self { 37 | Self::Undefined 38 | } 39 | } 40 | 41 | impl JSValue { 42 | pub fn as_string(&self) -> Option<&String> { 43 | match self { 44 | JSValue::String(s) => Some(s), 45 | _ => None 46 | } 47 | } 48 | pub fn as_i64(&self) -> Option { 49 | match self { 50 | JSValue::Number(n) => Some(*n as i64), 51 | _ => None 52 | } 53 | } 54 | } 55 | 56 | impl std::fmt::Display for JSValue { 57 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 58 | match self { 59 | JSValue::Undefined => write!(f, "undefined"), 60 | JSValue::Null => write!(f, "null"), 61 | JSValue::Infinity => write!(f, "Infinity"), 62 | JSValue::NaN => write!(f, "NaN"), 63 | JSValue::Array => write!(f, "[]"), 64 | JSValue::Bool(b) => write!(f, "{b}"), 65 | JSValue::Number(n) => write!(f, "{n}"), 66 | JSValue::String(s) => write!(f, "\"{s}\""), 67 | JSValue::RegExp(r) => write!(f, "\"{r}\""), 68 | JSValue::EntryPoint(entry_point) => write!(f, "entry ({}, {})", entry_point.pointer, entry_point.dynamic_num), 69 | } 70 | } 71 | } 72 | 73 | #[derive(Debug)] 74 | enum ValueType { 75 | Null, 76 | True, 77 | False, 78 | Infinity, 79 | NaN, 80 | Varint, 81 | String, 82 | Regex, 83 | Array, 84 | Float, 85 | Uint8, 86 | EntryPoint 87 | } 88 | 89 | #[derive(Debug, Default)] 90 | pub struct ValueFactory { 91 | mapper: HashMap, 92 | 93 | string_builder: StringBuilder, 94 | regex_builder: RegexBuilder, 95 | entry_point_builder: EntryPointBuilder, 96 | float_builder: FloatBuilder, 97 | uint8_calc: JSEvaluator 98 | } 99 | 100 | impl ValueFactory { 101 | pub fn new(exprs: &Vec) -> Self { 102 | 103 | let mut case_type_map: HashMap = HashMap::new(); 104 | let mut factory = Self::default(); 105 | 106 | for (case, exprs) in encapsulate_cases(exprs) { 107 | let last_expr = exprs.last().unwrap(); 108 | let val_type = if last_expr.ends_with("!0;") { 109 | ValueType::True 110 | } 111 | else if last_expr.ends_with("!1;") { 112 | ValueType::False 113 | } 114 | else if last_expr.ends_with("null;") { 115 | ValueType::Null 116 | } 117 | else if last_expr.ends_with("NaN;") { 118 | ValueType::NaN 119 | } 120 | else if last_expr.ends_with("Infinity;") { 121 | ValueType::Infinity 122 | } 123 | else if last_expr.ends_with("(this);") { 124 | ValueType::Varint 125 | } 126 | else if exprs.iter().any(|e| e.contains("[0] = ")) { 127 | factory.entry_point_builder = EntryPointBuilder::new(exprs); 128 | ValueType::EntryPoint 129 | } 130 | else if exprs.iter().any(|e| e.contains("RegExp")) { 131 | factory.regex_builder = RegexBuilder::new(exprs); 132 | ValueType::Regex 133 | } 134 | else if exprs.iter().any(|e| e.contains("= ''")) { 135 | factory.string_builder = StringBuilder::new(exprs); 136 | ValueType::String 137 | } 138 | else if exprs.iter().any(|e| e.contains("= []")) { 139 | ValueType::Array 140 | } 141 | else if exprs.iter().any(|e| e.contains("Math[\"pow")) { 142 | factory.float_builder = FloatBuilder::new(exprs); 143 | ValueType::Float 144 | } 145 | else if exprs.len() == 1 && last_expr.contains("DYNAMIC_NUM") { 146 | factory.uint8_calc = JSEvaluator::from_str(get_assignment_right(exprs.get(0).unwrap())); 147 | ValueType::Uint8 148 | } 149 | else { 150 | dbg!(case); 151 | dbg!(exprs); 152 | panic!() 153 | }; 154 | 155 | case_type_map.insert(case, val_type); 156 | }; 157 | 158 | factory.mapper = case_type_map; 159 | 160 | factory 161 | } 162 | 163 | pub fn bake(&self, case: i32, state: &mut VmState, funcs: &VmFunctions) -> JSValue { 164 | match self.mapper.get(&case) { 165 | Some(val_type) => { 166 | match val_type { 167 | ValueType::Null => JSValue::Null, 168 | ValueType::True => JSValue::Bool(true), 169 | ValueType::False => JSValue::Bool(false), 170 | ValueType::Infinity => JSValue::Infinity, 171 | ValueType::NaN => JSValue::NaN, 172 | ValueType::Array => JSValue::Array, 173 | ValueType::Varint => JSValue::Number(funcs.calc_varint(state) as f64), 174 | ValueType::String => self.string_builder.build(state, funcs), 175 | ValueType::Regex => self.regex_builder.build(state, funcs), 176 | ValueType::Float => self.float_builder.build(state, funcs), 177 | ValueType::Uint8 => JSValue::Number(self.uint8_calc.eval(state) as f64), 178 | ValueType::EntryPoint => self.entry_point_builder.build(state, funcs) 179 | } 180 | }, 181 | None => JSValue::Undefined 182 | } 183 | } 184 | 185 | } -------------------------------------------------------------------------------- /src/disassembler/instruction_set/instructions/values/entry_point.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use regex::Regex; 3 | 4 | use crate::{disassembler::{instruction_set::{instructions::extract::{filter_calc_exprs, get_assignment_right, get_between_brackets}, js_eval::JSEvaluator, Instruction}, VmFunctions, VmState}, lazy_regex}; 5 | 6 | use super::{JSValue, ValueBaker}; 7 | 8 | #[derive(Clone, Default, Debug, PartialEq)] 9 | pub struct EntryPoint { 10 | pub pointer: i32, 11 | pub dynamic_num: i32 12 | } 13 | 14 | #[derive(Debug, Default)] 15 | pub struct EntryPointBuilder { 16 | pointer_calc: JSEvaluator, 17 | dynamic_num_calc: JSEvaluator 18 | } 19 | 20 | static ENTRY_POINTER_PATTERN: Lazy = lazy_regex!(r#"\[0\]\s?=\s?(.*?)[,;]"#); 21 | static DYNAMIC_NUM_PATTERN: Lazy = lazy_regex!(r#"\[3\]\s?=\s?(.*?)[,;]"#); 22 | 23 | impl ValueBaker for EntryPointBuilder { 24 | fn new(exprs: Vec) -> Self { 25 | //dbg!(&exprs); 26 | let mut exprs = filter_calc_exprs(&exprs); 27 | //let expr = exprs.first().unwrap(); 28 | //let pointer_calc_str = ENTRY_POINTER_PATTERN.captures(expr).unwrap(); 29 | //let dynamic_num_calc_str = DYNAMIC_NUM_PATTERN.captures(expr).unwrap(); 30 | Self { 31 | //pointer_calc: JSEvaluator::from_str(pointer_calc_str.get(1).unwrap().as_str()), 32 | //dynamic_num_calc: JSEvaluator::from_str(dynamic_num_calc_str.get(1).unwrap().as_str()), 33 | 34 | pointer_calc: JSEvaluator::from_str(get_assignment_right(exprs.next().unwrap())), 35 | dynamic_num_calc: JSEvaluator::from_str(get_assignment_right(exprs.next().unwrap().as_str())), 36 | } 37 | } 38 | 39 | fn build(&self, state: &mut VmState, _: &VmFunctions) -> JSValue { 40 | 41 | let ep = EntryPoint { 42 | pointer: self.pointer_calc.eval(state), 43 | dynamic_num: self.dynamic_num_calc.eval(state) 44 | }; 45 | 46 | JSValue::EntryPoint(ep) 47 | } 48 | } -------------------------------------------------------------------------------- /src/disassembler/instruction_set/instructions/values/float.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use regex::Regex; 3 | 4 | use crate::{disassembler::{instruction_set::{instructions::extract::{get_assignment_right, get_between_brackets}, js_eval::JSEvaluator, Instruction}, VmFunctions, VmState}, lazy_regex}; 5 | 6 | use super::{JSValue, ValueBaker}; 7 | 8 | #[derive(Debug, Default)] 9 | pub struct FloatBuilder { 10 | var_1_calc: JSEvaluator, 11 | var_2_calc: JSEvaluator, 12 | var_3_calc: JSEvaluator 13 | } 14 | 15 | static PATTERN: Lazy = lazy_regex!(r#"\w\s=\s([^=]*?DYNAMIC_NUM.*?255)"#); 16 | 17 | impl ValueBaker for FloatBuilder { 18 | fn new(exprs: Vec) -> Self { 19 | let haystack = exprs.join("\n"); 20 | let mut matches = PATTERN.captures_iter(&haystack); 21 | let var_1_calc_str = matches.next().unwrap().get(1).unwrap().as_str(); 22 | let var_2_calc_str = matches.next().unwrap().get(1).unwrap().as_str(); 23 | let var_3_calc_str = matches.next().unwrap().get(1).unwrap().as_str(); 24 | Self { 25 | var_1_calc: JSEvaluator::from_str(var_1_calc_str), 26 | var_2_calc: JSEvaluator::from_str(var_2_calc_str), 27 | var_3_calc: JSEvaluator::from_str(var_3_calc_str) 28 | } 29 | } 30 | 31 | fn build(&self, state: &mut VmState, _: &VmFunctions) -> JSValue { 32 | 33 | let i = self.var_1_calc.eval(state) as f64; 34 | let m = self.var_2_calc.eval(state) as f64; 35 | let exp = ((i as i32 & 255) << 4 | m as i32 >> 4) - 1023; 36 | let mut j = 2_f64.powi(exp); 37 | 38 | let mut o = 1.0 + (1.0 / 2.0) * (m as i32 >> 3 & 1) as f64; 39 | o += (1.0 / 4.0) * (m as i32 >> 2 & 1) as f64; 40 | o += (1.0 / 8.0) * (m as i32 >> 2 & 1) as f64; 41 | o += (1.0 / 16.0) * (m as i32 >> 0 & 1) as f64; 42 | 43 | let mut fraction = 1.0 / 16.0; 44 | for _ in 0..6 { 45 | fraction = fraction / 2.0; 46 | let s = self.var_3_calc.eval(state); 47 | for v in 0..=7 { 48 | o += fraction * (1 & s >> v) as f64 49 | } 50 | } 51 | j *= (1.0 + -2.0 * (i as i32 >> 7) as f64) * o; 52 | 53 | JSValue::Number(j) 54 | } 55 | } -------------------------------------------------------------------------------- /src/disassembler/instruction_set/instructions/values/regex.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | 3 | use crate::disassembler::{instruction_set::{instructions::extract::{get_assignment_right, get_between_brackets}, js_eval::JSEvaluator, Instruction}, VmFunctions, VmState}; 4 | 5 | use super::{JSValue, ValueBaker}; 6 | 7 | static ASCII_SET: Lazy::<[char; 256]> = Lazy::new(|| { 8 | (0..256).enumerate() 9 | .map(|(i, _)| i as u8 as char) 10 | .collect::>() 11 | .try_into() 12 | .unwrap() 13 | }); 14 | 15 | #[derive(Debug, Default)] 16 | pub struct RegexBuilder { 17 | pattern_calc: JSEvaluator, 18 | flags_length_calc: JSEvaluator, 19 | flags_calc: JSEvaluator 20 | } 21 | 22 | impl ValueBaker for RegexBuilder { 23 | fn new(exprs: Vec) -> Self { 24 | let mut exprs = exprs.iter().filter(|expr| expr.contains("DYNAMIC_NUM")); 25 | Self { 26 | pattern_calc: JSEvaluator::from_str(get_between_brackets(exprs.next().unwrap())), 27 | flags_length_calc: JSEvaluator::from_str(get_assignment_right(exprs.next().unwrap())), 28 | flags_calc: JSEvaluator::from_str(get_between_brackets(exprs.next().unwrap())), 29 | } 30 | } 31 | 32 | fn build(&self, state: &mut VmState, funcs: &VmFunctions) -> JSValue { 33 | let mut regex = String::from("/"); 34 | let pattern_length = funcs.calc_varint(state); 35 | for _ in 0..pattern_length { 36 | regex.push(*ASCII_SET.get(self.pattern_calc.eval(state) as usize).unwrap()); 37 | } 38 | regex.push('/'); 39 | let flags_length = self.flags_length_calc.eval(state); 40 | for _ in 0..flags_length { 41 | regex.push(*ASCII_SET.get(self.flags_calc.eval(state) as usize).unwrap()); 42 | } 43 | JSValue::RegExp(regex) 44 | } 45 | } -------------------------------------------------------------------------------- /src/disassembler/instruction_set/instructions/values/string.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | 3 | use crate::disassembler::{instruction_set::{instructions::extract::get_between_brackets, js_eval::JSEvaluator, Instruction}, VmFunctions, VmState}; 4 | 5 | use super::{JSValue, ValueBaker}; 6 | 7 | static ASCII_SET: Lazy::<[char; 256]> = Lazy::new(|| { 8 | (0..256).enumerate() 9 | .map(|(i, _)| i as u8 as char) 10 | .collect::>() 11 | .try_into() 12 | .unwrap() 13 | }); 14 | 15 | #[derive(Debug, Default)] 16 | pub struct StringBuilder { 17 | char_calc: JSEvaluator 18 | } 19 | 20 | impl ValueBaker for StringBuilder { 21 | fn new(exprs: Vec) -> Self { 22 | Self { 23 | char_calc: JSEvaluator::from_str(get_between_brackets(exprs.get(3).unwrap())) 24 | } 25 | } 26 | 27 | fn build(&self, state: &mut VmState, funcs: &VmFunctions) -> JSValue { 28 | let mut string = String::new(); 29 | let str_len = funcs.calc_varint(state); 30 | for _ in 0..str_len { 31 | string.push(*ASCII_SET.get(self.char_calc.eval(state) as usize).unwrap()); 32 | } 33 | JSValue::String(string) 34 | } 35 | } -------------------------------------------------------------------------------- /src/disassembler/instruction_set/js_eval.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::{self, Debug}, str::Chars}; 2 | use crate::disassembler::VmState; 3 | 4 | #[derive(Debug, Clone, PartialEq)] 5 | enum Operator { 6 | Mul, 7 | Add, 8 | Sub, 9 | 10 | And, 11 | Xor, 12 | Or, 13 | LShift, 14 | } 15 | 16 | impl Operator { 17 | fn calc(&self, left: i32, right: i32) -> i32 { 18 | match self { 19 | Self::Mul => left.wrapping_mul(right), 20 | Self::Add => left.wrapping_add(right), 21 | Self::Sub => left.wrapping_sub(right), 22 | 23 | Self::LShift => left.wrapping_shl(right as u32), 24 | Self::And => left & right, 25 | Self::Xor => left ^ right, 26 | Self::Or => left | right 27 | } 28 | } 29 | } 30 | 31 | #[derive(PartialEq, Clone)] 32 | enum Element { 33 | DynamicNum, 34 | NextByte, 35 | Opcode, 36 | OpcodeDynamicNum, 37 | Number(i32), 38 | Operator(Operator), 39 | Bracket(JSEvaluator) 40 | } 41 | 42 | impl Element { 43 | fn to_num(&self) -> i32 { 44 | match self { 45 | Element::Number(num) => *num, 46 | _ => panic!() 47 | } 48 | } 49 | } 50 | 51 | impl Debug for Element { 52 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 53 | match self { 54 | Element::DynamicNum => write!(f, "DYNAMIC_NUM"), 55 | Element::NextByte => write!(f, "NEXT_BYTE"), 56 | Element::Opcode => write!(f, "OPCODE"), 57 | Element::OpcodeDynamicNum => write!(f, "XY"), 58 | Element::Number(num) => write!(f, "{num}"), 59 | Element::Bracket(eval) => write!(f, "({eval:?})"), 60 | Element::Operator(op) => { 61 | let operator = match op { 62 | Operator::Mul => "*", 63 | Operator::Add => "+", 64 | Operator::Sub => "-", 65 | Operator::Xor => "^", 66 | Operator::And => "&", 67 | Operator::LShift => "<<", 68 | Operator::Or => "|" 69 | }; 70 | write!(f, "{operator}") 71 | }, 72 | } 73 | 74 | } 75 | } 76 | 77 | #[derive(Default, Clone, PartialEq)] 78 | pub struct JSEvaluator { 79 | steps: Vec 80 | } 81 | 82 | impl JSEvaluator { 83 | 84 | fn parse_bracket(chars: &mut Chars, steps: &mut Vec) { 85 | 86 | let mut inner_steps: Vec = Vec::new(); 87 | 88 | 'outer: loop { 89 | match chars.next() { 90 | Some(c) => { 91 | let element = match c { 92 | '(' => { 93 | Self::parse_bracket(chars, &mut inner_steps); 94 | continue; 95 | }, 96 | ')' => break, 97 | 98 | '*' => Element::Operator(Operator::Mul), 99 | '+' => Element::Operator(Operator::Add), 100 | '-' => Element::Operator(Operator::Sub), 101 | '&' => Element::Operator(Operator::And), 102 | '^' => Element::Operator(Operator::Xor), 103 | '<' => { 104 | chars.next(); 105 | Element::Operator(Operator::LShift) 106 | }, 107 | '|' => Element::Operator(Operator::Or), 108 | 109 | 'D' => Element::DynamicNum, 110 | 'N' => Element::NextByte, 111 | 'O' => Element::Opcode, 112 | 'X' => Element::OpcodeDynamicNum, 113 | 114 | _ if c.is_ascii_digit() => { 115 | let mut digit = c.to_digit(10).unwrap(); 116 | loop { 117 | let c = chars.next(); 118 | if c.is_none() { 119 | inner_steps.push(Element::Number(digit as i32)); 120 | break 'outer 121 | } 122 | let c = c.unwrap(); 123 | if c.is_ascii_digit() { 124 | digit *= 10; 125 | digit += c.to_digit(10).unwrap(); 126 | continue 127 | } 128 | inner_steps.push(Element::Number(digit as i32)); 129 | if c == ')' { 130 | break 'outer 131 | } 132 | else { 133 | let element = match c { 134 | '*' => Element::Operator(Operator::Mul), 135 | '+' => Element::Operator(Operator::Add), 136 | '-' => Element::Operator(Operator::Sub), 137 | '&' => Element::Operator(Operator::And), 138 | '^' => Element::Operator(Operator::Xor), 139 | '<' => { 140 | chars.next(); 141 | Element::Operator(Operator::LShift) 142 | }, 143 | '|' => Element::Operator(Operator::Or), 144 | _ => panic!() 145 | }; 146 | break element 147 | } 148 | } 149 | } 150 | 151 | _ => panic!("{}", chars.collect::()) 152 | }; 153 | 154 | inner_steps.push(element); 155 | }, 156 | _ => break // panic!("{inner_steps:?}") 157 | } 158 | } 159 | 160 | steps.push(Element::Bracket(JSEvaluator { steps: inner_steps })); 161 | } 162 | 163 | pub fn from_str(expr: &str) -> Self { 164 | 165 | //dbg!(expr.trim_matches(';').trim()); 166 | 167 | let expr = expr 168 | .replace(' ', "") 169 | .replace("DYNAMIC_NUM", "D") 170 | .replace("NEXT_BYTE", "N") 171 | .replace("OPCODE", "O") 172 | .replace("XY", "X"); 173 | 174 | let mut chars = expr 175 | .trim_matches(';') 176 | .trim() 177 | .chars(); 178 | 179 | let mut steps: Vec = Vec::new(); 180 | Self::parse_bracket(&mut chars, &mut steps); 181 | 182 | let mut evaluator = JSEvaluator { steps }; 183 | 184 | if evaluator.steps.len() == 1 { 185 | match evaluator.steps.remove(0) { 186 | Element::Bracket(bracket) => evaluator.steps = bracket.steps, 187 | _ => panic!() 188 | } 189 | } 190 | 191 | //dbg!(&evaluator); 192 | 193 | evaluator 194 | } 195 | 196 | fn eval_operator(&self, result: &mut Vec, ops: &[Operator]) { 197 | 198 | let mut new_result: Vec = Vec::new(); 199 | let mut iter = result.drain(..).collect::>().into_iter(); 200 | 201 | loop { 202 | match iter.next() { 203 | Some(element) => { 204 | let operator = match element { 205 | Element::Operator(op) if ops.contains(&op) => { 206 | op 207 | }, 208 | _ => { 209 | new_result.push(element); 210 | continue; 211 | }, 212 | }; 213 | 214 | let left = new_result.pop().unwrap().to_num(); 215 | let right = iter.next().unwrap().to_num(); 216 | 217 | let z = operator.calc(left, right); 218 | 219 | new_result.push(Element::Number(z)); 220 | }, 221 | None => { 222 | *result = new_result; 223 | return 224 | } 225 | } 226 | } 227 | 228 | } 229 | 230 | pub fn eval(&self, state: &mut VmState) -> i32 { 231 | 232 | //dbg!(&self); 233 | 234 | let mut result = self.steps.clone(); 235 | result.iter_mut() 236 | .for_each(|e| match e { 237 | Element::Opcode => *e = Element::Number(state.last_opcode), 238 | Element::DynamicNum => *e = Element::Number(state.dynamic_num), 239 | Element::NextByte => *e = Element::Number(state.next_byte()), 240 | Element::OpcodeDynamicNum => *e = Element::Number(state.last_opcode + state.dynamic_num), 241 | Element::Bracket(evaluator) => *e = Element::Number(evaluator.eval(state)), 242 | _ => {} 243 | }); 244 | 245 | self.eval_operator(&mut result, &[Operator::Mul]); 246 | self.eval_operator(&mut result, &[Operator::Add, Operator::Sub]); 247 | self.eval_operator(&mut result, &[Operator::And]); 248 | self.eval_operator(&mut result, &[Operator::Xor]); 249 | self.eval_operator(&mut result, &[Operator::LShift]); 250 | self.eval_operator(&mut result, &[Operator::Or]); 251 | 252 | if result.len() != 1 { 253 | panic!("Unhandled operators {result:?}"); 254 | } 255 | 256 | if let Element::Number(res) = result[0] { 257 | return res 258 | } else { 259 | panic!() 260 | } 261 | 262 | // https://doc.rust-lang.org/reference/expressions.html 263 | 264 | } 265 | } 266 | 267 | 268 | impl fmt::Debug for JSEvaluator { 269 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 270 | let joined_steps: String = self.steps 271 | .iter() 272 | .map(|e| format!("{:?}", e)) // Use `Debug` implementation of `Element` 273 | .collect::>() // Collect into Vec 274 | .join(" "); // Join with a space 275 | 276 | write!(f, "{}", joined_steps) 277 | } 278 | } 279 | 280 | #[cfg(test)] 281 | mod tests { 282 | use crate::disassembler::VmState; 283 | 284 | use super::JSEvaluator; 285 | 286 | 287 | #[test] 288 | fn math() { 289 | 290 | let mut state = VmState { dynamic_num: 1, bytecode: vec![101, 157, 6], ..Default::default() }; 291 | 292 | //assert_eq!(114, JSEvaluator::from_str("DYNAMIC_NUM ^ NEXT_BYTE - 101 + 256 & 255 ^ 219").eval(&mut state)); 293 | //assert_eq!(237, JSEvaluator::from_str("DYNAMIC_NUM ^ NEXT_BYTE - 101 + 256 & 255 << 16 | DYNAMIC_NUM ^ NEXT_BYTE - 101 + 256 & 255 << 8 | DYNAMIC_NUM ^ NEXT_BYTE - 101 + 256 & 255").eval(&mut state)); 294 | assert_eq!(4, JSEvaluator::from_str("1+3").eval(&mut state)); 295 | assert_eq!(11141295, JSEvaluator::from_str(" (66 ^ 77 - 101 + 256 & 255) << 16 | 11 ^ 155 + 12 & 255 << 8 | 13 ^ 14 - 101 + 256 & 255").eval(&mut state)); 296 | assert_eq!(10263716, JSEvaluator::from_str("((((1 ^ (2 - 101) + 256 & 255) << 16) | ((3 ^ (4 - 101 + 256) & 255) << 8)) | 5 ^ ((6 - 101 + 256) & 255))").eval(&mut state)); 297 | 298 | dbg!(JSEvaluator::from_str("1 ^ 155 + 6 & 255").eval(&mut state)); 299 | dbg!(JSEvaluator::from_str("(1 ^ 155 + 157 & 255) << 8").eval(&mut state)); 300 | dbg!(JSEvaluator::from_str("(1 ^ 101 - 101 + 256 & 255) << 16").eval(&mut state)); 301 | 302 | dbg!(JSEvaluator::from_str("(1 ^ 101 - 101 + 256 & 255) << 16 | (1 ^ 155 + 157 & 255) << 8 | 1 ^ 155 + 6 & 255").eval(&mut state)); 303 | dbg!(JSEvaluator::from_str("((1 ^ ((101 - 101) + 256) & 255)) << 16 | (1 ^ (155 + 157 & 255) << 8) | 1 ^ 155 + 6 & 255").eval(&mut state)); 304 | 305 | dbg!(JSEvaluator::from_str("((((DYNAMIC_NUM ^ (NEXT_BYTE - 101) + 256 & 255) << 16) | ((DYNAMIC_NUM ^ (NEXT_BYTE - 101 + 256) & 255) << 8)) | DYNAMIC_NUM ^ ((NEXT_BYTE - 101 + 256) & 255))").eval(&mut state)); 306 | //dbg!(JSEvaluator::from_str("(((DYNAMIC_NUM ^ (((NEXT_BYTE - 101) + 256) & 255)) << 16 | (DYNAMIC_NUM ^ (155 + NEXT_BYTE & 255) << 8)) | DYNAMIC_NUM ^ 155 + NEXT_BYTE & 255)").eval(&mut state)); 307 | } 308 | 309 | 310 | } 311 | -------------------------------------------------------------------------------- /src/disassembler/instruction_set/match.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use regex::Regex; 3 | 4 | #[macro_export] 5 | macro_rules! lazy_regex { 6 | ( $( $x:expr ),* ) => { 7 | $( 8 | Lazy::new(|| Regex::new($x).unwrap()) 9 | )* 10 | }; 11 | } 12 | 13 | #[derive(PartialEq, Debug, Clone)] 14 | pub enum InstructionType { 15 | Throw, 16 | NewArr, 17 | NewObject, 18 | 19 | Push, 20 | UnaryOp, 21 | BinaryOp, 22 | GetProp, 23 | SetProp, 24 | Copy, 25 | Pop, 26 | FuncCall, 27 | Branch, 28 | Swap, 29 | Bind, 30 | NewValue, 31 | Constructor, 32 | MemoryOp, 33 | BuildFunc, 34 | PrepReturn, 35 | Jump, 36 | 37 | Terminate 38 | } 39 | 40 | impl std::fmt::Display for InstructionType { 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 42 | match self { 43 | InstructionType::Throw => write!(f, "THROW"), 44 | InstructionType::NewArr => write!(f, "NEW ARR"), 45 | InstructionType::NewObject => write!(f, "NEW OBJ"), 46 | InstructionType::Push => write!(f, "PUSH"), 47 | InstructionType::UnaryOp => write!(f, "UNARY OP"), 48 | InstructionType::BinaryOp => write!(f, "BINARY OP"), 49 | InstructionType::GetProp => write!(f, "GET PROP"), 50 | InstructionType::SetProp => write!(f, "SET PROP"), 51 | InstructionType::Copy => write!(f, "COPY"), 52 | InstructionType::Pop => write!(f, "POP"), 53 | InstructionType::FuncCall => write!(f, "CALL"), 54 | InstructionType::Branch => write!(f, "BRANCH"), 55 | InstructionType::Swap => write!(f, "SWAP"), 56 | InstructionType::Bind => write!(f, "BIND"), 57 | InstructionType::NewValue => write!(f, "NEW VALUE"), 58 | InstructionType::Constructor => write!(f, "CONSTRUCTOR"), 59 | InstructionType::MemoryOp => write!(f, "MEMORY OP"), 60 | InstructionType::BuildFunc => write!(f, "BUILD FUNC"), 61 | InstructionType::PrepReturn => write!(f, "PREP RET"), 62 | InstructionType::Jump => write!(f, "UPDATE STATE"), 63 | InstructionType::Terminate => write!(f, "TERMINATE"), 64 | } 65 | } 66 | } 67 | 68 | static SET_PROP_PATTERN: Lazy = lazy_regex!(r#"[a-z]\[[a-z]\]\s=\sthis"#); 69 | static GET_PROP_PATTERN: Lazy = lazy_regex!(r#"this\.[a-z]\[[a-z]\]\s=\s[a-z]\[[a-z]\];"#); 70 | static COPY_REG_PATTERN: Lazy = lazy_regex!(r#"this\.[a-z]\[[a-z]\]\s=\s[a-z];"#); 71 | static BRANCH_PATTERN: Lazy = lazy_regex!(r#"^[a-z]\s&&"#); 72 | static SWAP_PATTERN: Lazy = lazy_regex!(r#"this\.[a-z]\[[a-z]\]\s=\sthis\.[a-z]\[[a-z]\];"#); 73 | 74 | pub fn match_instruction(expressions: &Vec) -> InstructionType { 75 | 76 | let first_expr = expressions.iter().next().unwrap(); 77 | let last_expr = expressions.iter().last().unwrap(); 78 | 79 | match expressions.len() { 80 | 1..=2 => { 81 | if last_expr.starts_with("throw") { 82 | return InstructionType::Throw 83 | } 84 | if last_expr.ends_with("{};") { 85 | return InstructionType::NewObject 86 | } 87 | if last_expr.ends_with("[];") { 88 | return InstructionType::NewArr 89 | } 90 | }, 91 | 3 => { 92 | if last_expr.ends_with("\"pop\"]();") { 93 | return InstructionType::Pop 94 | } 95 | if SET_PROP_PATTERN.is_match(&last_expr) { 96 | return InstructionType::SetProp 97 | } 98 | if COPY_REG_PATTERN.is_match(&last_expr) { 99 | return InstructionType::Copy 100 | } 101 | }, 102 | 4 => { 103 | if last_expr.contains(r#"push"](this"#) { 104 | return InstructionType::Push 105 | } 106 | if last_expr.contains(r#"bind"#) { 107 | return InstructionType::Bind 108 | } 109 | if GET_PROP_PATTERN.is_match(&last_expr) { 110 | return InstructionType::GetProp 111 | } 112 | if expressions.iter().any(|expr| expr.contains("typeof this")) { 113 | return InstructionType::UnaryOp 114 | } 115 | if last_expr.contains("[0] =") && !last_expr.contains("[3]") { 116 | return InstructionType::Jump 117 | } 118 | }, 119 | 5 => { 120 | let second_last = expressions.iter().nth(expressions.len() - 2).unwrap(); 121 | if SWAP_PATTERN.is_match(&second_last) { 122 | return InstructionType::Swap 123 | } 124 | if last_expr.ends_with(r#"["pop"]());"#) { 125 | return InstructionType::PrepReturn 126 | } 127 | if last_expr.contains("bind") { 128 | return InstructionType::BuildFunc 129 | } 130 | } 131 | _ => { 132 | if last_expr.ends_with(r#"["pop"]());"#) { 133 | return InstructionType::PrepReturn 134 | } 135 | if last_expr.contains("prototype") { 136 | return InstructionType::Constructor 137 | } 138 | if last_expr.contains("apply") { 139 | return InstructionType::FuncCall 140 | } 141 | if BRANCH_PATTERN.is_match(&last_expr) { 142 | return InstructionType::Branch 143 | } 144 | if expressions.iter().any(|expr| expr.contains("instanceof")) { 145 | return InstructionType::BinaryOp 146 | } 147 | if expressions.iter().any(|expr| expr.contains("typeof this")) { 148 | return InstructionType::UnaryOp 149 | } 150 | if expressions.iter().any(|expr| expr.contains("Infinity")) { 151 | return InstructionType::NewValue 152 | } 153 | if expressions.iter().any(|expr| expr.contains("(this)")) { 154 | return InstructionType::MemoryOp 155 | } 156 | } 157 | } 158 | 159 | panic!("Unmatched instruction func:\n{}", expressions.join("\n")) 160 | } -------------------------------------------------------------------------------- /src/disassembler/result.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::{instruction_set::{instructions::values::JSValue, r#match::InstructionType}, step::Step}; 4 | 5 | pub type Functions = HashMap; 6 | 7 | pub struct Function { 8 | pub steps: Vec 9 | } 10 | 11 | impl Function { 12 | pub fn new(steps: Vec) -> Self { 13 | Self { 14 | steps 15 | } 16 | } 17 | pub fn get_strings(&self) -> Vec<&String> { 18 | self.steps.iter() 19 | .filter_map(|step| { 20 | match step { 21 | Step::Value(step) => { 22 | match &step.value { 23 | JSValue::String(string) => Some(string), 24 | _ => None 25 | } 26 | }, 27 | _ => None 28 | } 29 | }) 30 | .collect() 31 | } 32 | pub fn get_inner_funcs(&self) -> Vec { 33 | self.steps.iter() 34 | .filter_map(|step| { 35 | match step { 36 | Step::Simple(step) => { 37 | match step.instruction { 38 | InstructionType::BuildFunc => Some(*step.register.get(1).unwrap()), 39 | _ => None 40 | } 41 | }, 42 | _ => None 43 | } 44 | }) 45 | .collect() 46 | } 47 | } 48 | 49 | pub fn get_values_recursively(steps: &Vec, functions: &Functions) -> Vec { 50 | let mut values: Vec = Vec::new(); 51 | 52 | for step in steps.iter() { 53 | match step { 54 | Step::Value(value_step) => values.push(value_step.value.clone()), 55 | Step::Func(func_step) => { 56 | let steps = &functions.get(&func_step.entry_pointer).unwrap().steps; 57 | values.extend(get_values_recursively(steps, functions)) 58 | }, 59 | _ => {} 60 | } 61 | } 62 | 63 | values 64 | } 65 | 66 | pub struct DisasmResult { 67 | pub steps: Vec, 68 | pub func_entries: Vec 69 | } 70 | 71 | impl DisasmResult { 72 | 73 | pub fn new(steps: Vec, mut func_entries: Vec) -> Self { 74 | func_entries.sort(); 75 | func_entries.dedup(); 76 | Self { 77 | steps, func_entries 78 | } 79 | } 80 | 81 | pub fn get_global_strings(&self) -> Vec { 82 | let mut iter = self.steps.iter(); 83 | let mut global_strings: Vec<_> = Vec::new(); 84 | loop { 85 | match iter.next() { 86 | Some(step) => match step { 87 | Step::Value(value_step) => match &value_step.value { 88 | JSValue::String(s) => global_strings.push(s.clone()), 89 | _ => {} 90 | }, 91 | Step::Func(_) => break, 92 | _ => {} 93 | }, 94 | None => break, 95 | } 96 | } 97 | global_strings 98 | } 99 | 100 | pub fn into_functions(mut self) -> Functions { 101 | 102 | let mut functions: HashMap = HashMap::new(); 103 | 104 | for idx in 0..self.func_entries.len() - 1 { 105 | let this_entry_ptr = *self.func_entries.get(idx).unwrap(); 106 | let next_entry_ptr = *self.func_entries.get(idx + 1).unwrap(); 107 | 108 | let mut iter = self.steps.iter(); 109 | let entry_index = iter.position(|s| s.pointer() == this_entry_ptr).unwrap(); 110 | let end_index = iter.position(|s| s.pointer() == next_entry_ptr).unwrap(); 111 | 112 | functions.insert( 113 | this_entry_ptr, 114 | Function::new(self.steps.drain(entry_index..=entry_index + end_index).collect()) 115 | ); 116 | } 117 | 118 | let mut iter = self.steps.iter(); 119 | let last_entry_ptr = *self.func_entries.last().unwrap(); 120 | let last_entry_index = iter.position(|s| s.pointer() == last_entry_ptr).unwrap(); 121 | 122 | functions.insert( 123 | last_entry_ptr, 124 | Function::new(self.steps.drain(last_entry_index..self.steps.len() - 1).collect()) 125 | ); 126 | 127 | functions 128 | } 129 | } 130 | 131 | -------------------------------------------------------------------------------- /src/disassembler/step.rs: -------------------------------------------------------------------------------- 1 | use swc_ecma_ast::BinaryOp; 2 | 3 | use super::instruction_set::{instructions::values::JSValue, r#match::InstructionType}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub enum Step { 7 | Simple(SimpleStep), 8 | Value(ValueStep), 9 | Jump(JumpStep), 10 | Branch(BranchStep), 11 | Func(FuncStep), 12 | BinOp(BinOpStep) 13 | } 14 | 15 | impl Step { 16 | pub fn pointer(&self) -> i32 { 17 | match self { 18 | Step::Simple(s) => s.pointer, 19 | Step::Value(s) => s.pointer, 20 | Step::Jump(s) => s.pointer, 21 | Step::Branch(s) => s.pointer, 22 | Step::Func(s) => s.pointer, 23 | Self::BinOp(s) => s.pointer 24 | } 25 | } 26 | pub fn instruction(&self) -> &InstructionType { 27 | match self { 28 | Step::Simple(s) => &s.instruction, 29 | Step::Value(_) => &InstructionType::NewValue, 30 | Step::Jump(_) => &InstructionType::Jump, 31 | Step::Branch(_) => &InstructionType::Branch, 32 | Step::Func(_) => &InstructionType::BuildFunc, 33 | Step::BinOp(_) => &InstructionType::BinaryOp 34 | } 35 | } 36 | pub fn value(&self) -> Option<&JSValue> { 37 | match self { 38 | Step::Value(s) => Some(&s.value), 39 | _ => None 40 | } 41 | } 42 | pub fn as_jump(&self) -> Option<&JumpStep> { 43 | match self { 44 | Step::Jump(jump_step) => Some(&jump_step), 45 | _ => None 46 | } 47 | } 48 | pub fn as_branch(&self) -> Option<&BranchStep> { 49 | match self { 50 | Step::Branch(branch_step) => Some(&branch_step), 51 | _ => None 52 | } 53 | } 54 | pub fn as_func(&self) -> Option<&FuncStep> { 55 | match self { 56 | Step::Func(func_step) => Some(&func_step), 57 | _ => None 58 | } 59 | } 60 | 61 | } 62 | 63 | impl std::fmt::Display for Step { 64 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 65 | match self { 66 | Step::Simple(simple_step) => write!(f, "{}", simple_step), 67 | Step::Value(value_step) => write!(f, "{}", value_step), 68 | Step::Jump(jump_step) => write!(f, "{}", jump_step), 69 | Step::Branch(branch_step) => write!(f, "{}", branch_step), 70 | Step::Func(func_step) => write!(f, "{}", func_step), 71 | Step::BinOp(bin_op_step) => write!(f, "{}", bin_op_step), 72 | } 73 | } 74 | } 75 | 76 | #[derive(Debug, Clone)] 77 | pub struct SimpleStep { 78 | pub pointer: i32, 79 | pub instruction: InstructionType, 80 | pub register: Vec, 81 | pub dest: Option 82 | } 83 | 84 | impl std::fmt::Display for SimpleStep { 85 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 86 | let message = match self.instruction { 87 | InstructionType::Throw => format!("REG {}", self.register.get(0).unwrap()), 88 | InstructionType::NewArr => format!("[] -> REG {}", self.dest.unwrap()), 89 | InstructionType::NewObject => format!("{{}} -> REG {}", self.dest.unwrap()), 90 | InstructionType::Push => format!("REG {} -> REG {}", self.register[1], self.register[0]), 91 | InstructionType::UnaryOp => format!(""), 92 | InstructionType::BinaryOp => panic!(), 93 | InstructionType::GetProp => format!("REG {}[REG {}] -> REG {}", self.register[0], self.register[1], self.dest.unwrap()), 94 | InstructionType::SetProp => format!("REG {} -> REG {}[REG {}]", self.register[2], self.register[0], self.register[1]), 95 | InstructionType::Copy => format!(""), 96 | InstructionType::Pop => format!("REG {} -> REG {}", self.register[0], self.dest.unwrap()), 97 | InstructionType::FuncCall => format!(""), 98 | InstructionType::Branch => format!(""), 99 | InstructionType::Swap => format!("REG {} <-> REG {}", self.register[0], self.register[1]), 100 | InstructionType::Bind => format!("Instruction {}, Args: {} -> REG {}", self.register[0], self.register[1], self.dest.unwrap()), 101 | InstructionType::NewValue => format!(""), 102 | InstructionType::Constructor => format!(""), 103 | InstructionType::MemoryOp => format!(""), 104 | InstructionType::BuildFunc => format!(""), 105 | InstructionType::PrepReturn => format!(""), 106 | InstructionType::Jump => format!(""), 107 | InstructionType::Terminate => format!(""), 108 | }; 109 | let formatted = format!("{: >6} {: <16}{message}", 110 | self.pointer, 111 | format!("{}", self.instruction) 112 | ); 113 | write!(f, "{formatted}") 114 | } 115 | } 116 | 117 | #[allow(dead_code)] 118 | #[derive(Debug, Clone)] 119 | pub struct ValueStep { 120 | pub pointer: i32, 121 | pub value: JSValue, 122 | pub dest: i32 123 | } 124 | 125 | impl std::fmt::Display for ValueStep { 126 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 127 | let formatted = format!("{: >6} {: <16}{} -> REG {}", 128 | self.pointer, 129 | "NEW VALUE", 130 | self.value, 131 | self.dest 132 | ); 133 | write!(f, "{formatted}") 134 | } 135 | } 136 | 137 | #[derive(Debug, Clone)] 138 | pub struct JumpStep { 139 | pub pointer: i32, 140 | pub jump_to: i32 141 | } 142 | 143 | impl std::fmt::Display for JumpStep { 144 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 145 | let formatted = format!("{: >6} {: <16}{}", 146 | self.pointer, 147 | "JUMP", 148 | self.jump_to 149 | ); 150 | write!(f, "{formatted}") 151 | } 152 | } 153 | 154 | #[derive(Debug, Clone)] 155 | pub struct BranchStep { 156 | pub pointer: i32, 157 | pub test: i32, 158 | pub jump_to: i32, 159 | pub dynamic_num: i32 160 | } 161 | 162 | impl std::fmt::Display for BranchStep { 163 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 164 | let formatted = format!("{: >6} {: <16}{}", 165 | self.pointer, 166 | "BRANCH", 167 | self.jump_to 168 | ); 169 | write!(f, "{formatted}") 170 | } 171 | } 172 | 173 | #[derive(Debug, Clone)] 174 | pub struct FuncStep { 175 | pub pointer: i32, 176 | pub entry_pointer: i32, 177 | pub dynamic_num: i32, 178 | pub dest: i32 179 | } 180 | 181 | impl std::fmt::Display for FuncStep { 182 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 183 | let formatted = format!("{: >6} {: <16}{}", 184 | self.pointer, 185 | "BUILD FUNC", 186 | self.entry_pointer 187 | ); 188 | write!(f, "{formatted}") 189 | } 190 | } 191 | 192 | #[derive(Debug, Clone)] 193 | pub struct BinOpStep { 194 | pub pointer: i32, 195 | pub lhs: i32, 196 | pub rhs: i32, 197 | pub dest: i32, 198 | pub op: String 199 | } 200 | 201 | impl BinOpStep { 202 | fn op_def(&self) -> &str { 203 | match self.op.as_str() { 204 | "==" => "EQUAL", 205 | "!=" => "INEQUAL", 206 | "===" => "STRICT_EQUAL", 207 | "!==" => "STRICT_INEQUAL", 208 | "<" => "LESS_THAN", 209 | "<=" => "LESS_EQ_THAN", 210 | ">" => "GREATER_THAN", 211 | ">=" => "GREATER_EQ_THAN", 212 | "<<" => "LSHIFT", 213 | ">>" => "RSHIFT", 214 | ">>>" => "U_RSHIFT", 215 | "+" => "ADD", 216 | "-" => "SUB", 217 | "*" => "MUL", 218 | "/" => "DIV", 219 | "%" => "MOD", 220 | "|" => "OR", 221 | "^" => "XOR", 222 | "&" => "AND", 223 | "||" => "LOGIC_OR", 224 | "&&" => "LOGIC_AND", 225 | "in" => "IN", 226 | "instance of" => "INSTANCE_OF", 227 | _ => "" 228 | } 229 | } 230 | } 231 | 232 | impl std::fmt::Display for BinOpStep { 233 | 234 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 235 | let formatted = format!("{: >6} {: <16}REG {} {} REG {}", 236 | self.pointer, 237 | self.op_def(), 238 | self.lhs, 239 | self.op, 240 | self.rhs 241 | ); 242 | write!(f, "{formatted}") 243 | } 244 | } -------------------------------------------------------------------------------- /src/disassembler/utils.rs: -------------------------------------------------------------------------------- 1 | use once_cell::sync::Lazy; 2 | use regex::Regex; 3 | 4 | use crate::lazy_regex; 5 | 6 | pub fn extract_dynamic_num_from_builder_func(func: &str) -> i32 { 7 | func.lines() 8 | .map(str::trim) 9 | .skip_while(|s| !s.starts_with("atob")) 10 | .nth(1) 11 | .unwrap() 12 | .replace(|c: char| !c.is_ascii_digit(), "") 13 | .parse() 14 | .unwrap() 15 | } 16 | 17 | pub fn extract_state_register_from_builder_func(func: &str) -> i32 { 18 | //println!("{func}"); 19 | func.lines() 20 | .map(str::trim) 21 | .skip_while(|s| !s.ends_with("] = [")) 22 | .next() 23 | .unwrap() 24 | .split(['[', ']']) 25 | .nth(1) 26 | .unwrap() 27 | .parse() 28 | .unwrap() 29 | } 30 | 31 | pub fn extract_stack_register_from_executor_func(func: &str) -> i32 { 32 | func.lines() 33 | .find(|l| l.contains("push")) 34 | .unwrap() 35 | .split_once('[') 36 | .unwrap().1 37 | .split_once(']') 38 | .unwrap().0 39 | .parse::() 40 | .unwrap() 41 | } 42 | 43 | pub fn extract_calc_str(func: &str) -> &str { 44 | func.lines() 45 | .find(|l| l.contains("DYNAMIC_NUM")) 46 | .unwrap() 47 | .split_once('=') 48 | .unwrap().1 49 | .split(',') 50 | .nth(0) 51 | .unwrap() 52 | .trim_end_matches(';') 53 | } 54 | 55 | static DYNAMIC_NUM_REPLACE_PATTERN: Lazy = lazy_regex!(r#"[a-z]\[3\]"#); 56 | static OPCODE_REPLACE_PATTERN: Lazy = lazy_regex!(r#"[a-z]\.[a-z]"#); 57 | static IDENTIFIER_REPLACE_PATTERN: Lazy = lazy_regex!(r#"[a-z]"#); 58 | 59 | pub fn extract_dynamic_num_update_calc_str(func: &str) -> String { 60 | let calc_str = func.lines() 61 | .find(|l| l.contains("[3] =")) 62 | .unwrap() 63 | .split_once('=') 64 | .unwrap().1 65 | .trim_end_matches(';'); 66 | let calc_str = DYNAMIC_NUM_REPLACE_PATTERN.replace_all(calc_str, "DYNAMIC_NUM"); 67 | let calc_str = OPCODE_REPLACE_PATTERN.replace_all(&calc_str, "OPCODE"); 68 | let calc_str = IDENTIFIER_REPLACE_PATTERN.replace_all(&calc_str, "XY"); 69 | 70 | let calc_str = calc_str.replace("DYNAMIC_NUM + OPCODE", "XY"); 71 | 72 | calc_str.to_string() 73 | } -------------------------------------------------------------------------------- /src/disassembler/vm.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::instruction_set::js_eval::JSEvaluator; 4 | 5 | #[derive(Default)] 6 | pub struct VmFunctions { 7 | pub next_opcode: JSEvaluator, 8 | pub next_dynamic: JSEvaluator, 9 | pub varint_byte_calc: JSEvaluator, 10 | 11 | pub reg_rearrange_mapper: HashMap 12 | } 13 | 14 | impl VmFunctions { 15 | pub fn calc_varint(&self, state: &mut VmState) -> i32 { 16 | let mut varint = 0; 17 | let mut bit_offset = 0; 18 | 19 | loop { 20 | let byte = self.varint_byte_calc.eval(state); 21 | varint |= (127 & byte) << bit_offset; 22 | bit_offset += 7; 23 | if byte & 128 == 0 { 24 | break 25 | } 26 | } 27 | 28 | varint 29 | } 30 | } 31 | 32 | 33 | #[derive(Default)] 34 | pub struct VmState { 35 | pub bytecode: Vec, 36 | pub pointer: i32, 37 | pub last_op_pointer: i32, 38 | pub dynamic_num: i32, 39 | pub last_opcode: i32, 40 | 41 | pub state_register: i32, 42 | pub stack_register: i32, 43 | 44 | pub terminate: bool 45 | } 46 | 47 | impl VmState { 48 | pub fn next_byte(&mut self) -> i32 { 49 | let byte = *self.bytecode.get(self.pointer as usize).unwrap(); 50 | self.pointer += 1; 51 | //dbg!(byte); 52 | byte as i32 53 | } 54 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod script; 2 | mod strings; 3 | pub mod ast; 4 | pub mod payload; 5 | mod device_checks; 6 | mod decoder; 7 | mod decompiler; 8 | mod pow; 9 | mod requests; 10 | pub mod task; 11 | pub mod disassembler; 12 | 13 | 14 | use anyhow::anyhow; 15 | use decoder::Decoder; 16 | use decompiler::Decompiler; 17 | use disassembler::{result::DisasmResult, step::Step, Disassembler}; 18 | use payload::{checks::StaticKeys, encode::Encoder, second::MainPayloadData, Payload}; 19 | use requests::TlsClient; 20 | use script::parse_functions_from_script; 21 | use task::TurnstileChallengeData; 22 | 23 | pub struct TurnstileInstance { 24 | disassembler: Disassembler, 25 | decoder: Decoder, 26 | 27 | challenge_data: TurnstileChallengeData, 28 | 29 | pub payload: Payload, 30 | 31 | pub script_url: String, 32 | pub collector_url: String, 33 | pub challenge_token: String 34 | } 35 | 36 | impl TurnstileInstance { 37 | 38 | pub fn get_challenge_type(&self) -> &str { 39 | &self.challenge_data.chlApiMode 40 | } 41 | 42 | pub fn new(script: String, task_html: String, script_url: String, website: &str) -> Self { 43 | let (mut transformed_functions, string_resolver) = parse_functions_from_script(&script); 44 | 45 | let challenge = TurnstileChallengeData::from_html(task_html); 46 | 47 | let mut payload = Payload::new(string_resolver.get_compressor_charset(), &challenge.cRay); 48 | 49 | payload.init( 50 | std::mem::take(&mut transformed_functions.init_payload), 51 | std::mem::take(&mut transformed_functions.init_payload_obj), 52 | &challenge, 53 | website 54 | ); 55 | 56 | let collector_url = vec![ 57 | "https:", 58 | "", 59 | &challenge.cZone, 60 | "cdn-cgi/challenge-platform", 61 | "h", 62 | &challenge.cFPWv, 63 | "flow/ov1", 64 | &string_resolver.get_collector_ep_val().replace('/', ""), 65 | &challenge.cRay, 66 | &challenge.cH 67 | ].join("/"); 68 | 69 | Self { 70 | script_url: script_url, 71 | decoder: Decoder::new(challenge.cRay.clone()), 72 | challenge_token: challenge.cH.clone(), 73 | collector_url: collector_url, 74 | payload: payload, 75 | challenge_data: challenge, 76 | disassembler: Disassembler::new(transformed_functions, string_resolver.get_init_bytecode()) 77 | } 78 | } 79 | 80 | pub fn get_init_payload(&self) -> String { 81 | self.payload.finalize() 82 | } 83 | 84 | pub fn disassemble_resp(&mut self, data: String) -> DisasmResult { 85 | let bytecode = self.decoder.decode(data); 86 | let disassembled = self.disassembler.disassemble(bytecode); 87 | disassembled 88 | } 89 | 90 | pub fn get_second_payload(&mut self, data: String) -> String { 91 | let disassembled = self.disassemble_resp(data); 92 | 93 | #[cfg(debug_assertions)] 94 | { 95 | Decompiler::default() 96 | .decompile(disassembled.steps.clone(), disassembled.func_entries.clone()); 97 | } 98 | 99 | let global_strings = disassembled.get_global_strings(); 100 | let mut functions = disassembled.into_functions(); 101 | 102 | self.payload.utils.challenge_data = self.challenge_data.clone(); 103 | self.payload.utils.script_url = self.script_url.clone(); 104 | self.payload.utils.collector_url = self.collector_url.clone(); 105 | self.payload.utils.encoder = Encoder::from_funcs(&mut functions, &self.challenge_data.cRay); 106 | 107 | let payload_data = MainPayloadData::from_funcs(functions, global_strings); 108 | 109 | self.payload.utils.global_keys = payload_data.global_strings.iter() 110 | .filter(|string| { 111 | (string.len() == 5 || string.len() == 6) && 112 | string.chars().last().unwrap().is_ascii_digit() 113 | }) 114 | .map(Into::into) 115 | .collect(); 116 | self.payload.utils.static_keys = StaticKeys::find(&payload_data.ordered_payload_entries, &self.payload.utils.global_keys); 117 | 118 | self.payload.init_main_payload(&payload_data); 119 | self.payload.process_payload_entries(payload_data); 120 | 121 | #[cfg(debug_assertions)] 122 | { 123 | std::fs::write("./debug/payload_main.json", self.payload.pretty()).unwrap(); 124 | } 125 | 126 | self.payload.finalize() 127 | } 128 | 129 | pub fn get_click_payload(&mut self, data: String) -> String { 130 | let disassembled = self.disassemble_resp(data); 131 | 132 | let global_strings = disassembled.get_global_strings(); 133 | let functions = disassembled.into_functions(); 134 | 135 | let payload_data = MainPayloadData::from_funcs(functions, global_strings); 136 | 137 | //dbg!(payload_data.last_case.strings()); 138 | //panic!(); 139 | 140 | self.payload.process_payload_entries(payload_data); 141 | 142 | #[cfg(debug_assertions)] 143 | { 144 | std::fs::write("./debug/payload_click.json", self.payload.pretty()).unwrap(); 145 | } 146 | 147 | self.payload.finalize() 148 | } 149 | 150 | pub fn try_get_turnstile_token(steps: &Vec) -> Option { 151 | steps 152 | .into_iter() 153 | .filter_map(|step| step.value().and_then(|v| Some(v.clone()))) 154 | .filter_map(|value| value.as_string().and_then(|s| Some(s.clone()))) 155 | .find(|s| s.starts_with("0.") && s.len() > 500) 156 | } 157 | } 158 | 159 | pub struct TurnstileTask { 160 | url: String, 161 | sitekey: String, 162 | proxy: String 163 | } 164 | 165 | impl TurnstileTask { 166 | 167 | pub fn new(url: String, sitekey: String, proxy: String) -> Self { 168 | Self { url: url, sitekey: sitekey, proxy: proxy } 169 | } 170 | 171 | pub async fn solve(self) -> Result { 172 | 173 | let tls_client = TlsClient::new(&self.proxy, &self.sitekey)?; 174 | 175 | let task_html = tls_client.get(&tls_client.task_init_url).await 176 | .map_err(|_| anyhow!("Error fetching task_html"))?; 177 | let script_ep = format!("https://challenges.cloudflare.com{}", 178 | task_html.split_once("src=\"") 179 | .ok_or(anyhow!("No script ep in task html"))? 180 | .1 181 | .split_once('"') 182 | .ok_or(anyhow!("No script ep in task html"))? 183 | .0 184 | ); 185 | 186 | let script = tls_client.get(&script_ep).await 187 | .map_err(|_| anyhow!("Error fetching script"))?; 188 | 189 | let mut instance = TurnstileInstance::new( 190 | script, 191 | task_html, 192 | script_ep, 193 | &self.url 194 | ); 195 | let init_payload = instance.get_init_payload(); 196 | 197 | let resp = tls_client.post(&instance.collector_url, init_payload, &instance.challenge_token).await 198 | .map_err(|_| anyhow!("Error posting init payload"))?; 199 | 200 | let second_payload = instance.get_second_payload(resp); 201 | let resp = tls_client.post(&instance.collector_url, second_payload, &instance.challenge_token).await 202 | .map_err(|_| anyhow!("Error posting second payload"))?; 203 | 204 | let result = match instance.get_challenge_type() { 205 | "managed" => { 206 | let click_payload = instance.get_click_payload(resp); 207 | let resp = tls_client.post(&instance.collector_url, click_payload, &instance.challenge_token).await 208 | .map_err(|_| anyhow!("Error posting click payload"))?; 209 | let disassembled = instance.disassemble_resp(resp); 210 | TurnstileInstance::try_get_turnstile_token(&disassembled.steps) 211 | }, 212 | "non-interactive" | "invisible" => { 213 | let disassembled = instance.disassemble_resp(resp); 214 | TurnstileInstance::try_get_turnstile_token(&disassembled.steps) 215 | }, 216 | t => return Err(anyhow!("Unknown challenge type {t}")) 217 | }; 218 | 219 | result.ok_or(anyhow!("Error solving Turnstile task")) 220 | } 221 | } 222 | 223 | #[cfg(test)] 224 | mod tests { 225 | use crate::TurnstileTask; 226 | 227 | #[tokio::test] 228 | async fn managed() { 229 | 230 | let website = "https://peet.ws/turnstile-test/managed.html"; 231 | let ctype = "managed"; 232 | 233 | match TurnstileTask::new(website.into(), "0x4AAAAAAABS7TtLxsNa7Z2e".into(), "127.0.0.1:8888".into()).solve().await { 234 | Ok(token) => println!("{website} ({ctype}) passed: {}..", &token[..40]), 235 | Err(e) => println!("{}", e) 236 | } 237 | } 238 | 239 | #[tokio::test] 240 | async fn all() { 241 | 242 | let test_keys = [ 243 | ("https://peet.ws/turnstile-test/invisible.html", "0x4AAAAAAABS78iP9t4tO6NV", "invisible"), 244 | ("https://peet.ws/turnstile-test/non-interactive.html", "0x4AAAAAAABS7vwvV6VFfMcD", "non-interactive"), 245 | ("https://peet.ws/turnstile-test/managed.html", "0x4AAAAAAABS7TtLxsNa7Z2e", "managed") 246 | ]; 247 | 248 | for (website, sitekey, ctype) in test_keys { 249 | match TurnstileTask::new(website.into(), sitekey.into(), "127.0.0.1:8888".into()).solve().await { 250 | Ok(token) => println!("{website} ({ctype}) passed: {}..", &token[..60]), 251 | Err(e) => println!("{}", e) 252 | } 253 | } 254 | } 255 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::read_to_string; 2 | 3 | use regex::Regex; 4 | use turnstile_rs::TurnstileInstance; 5 | 6 | 7 | 8 | 9 | fn main() { 10 | 11 | std::env::set_var("RUST_BACKTRACE", "1"); 12 | 13 | let script = std::fs::read_to_string("./../Turnstile/task/script.js").unwrap(); 14 | let bytecode_0 = std::fs::read_to_string("./../Turnstile/task/bytecode_0").unwrap(); 15 | let bytecode_1 = std::fs::read_to_string("./../Turnstile/task/bytecode_1").unwrap(); 16 | let bytecode_2 = std::fs::read_to_string("./../Turnstile/task/bytecode_2").unwrap(); 17 | let task_json = std::fs::read_to_string("./../Turnstile/task/task.json").unwrap(); 18 | 19 | let dummy_task_html = std::fs::read_to_string("./debug/task/task.html").unwrap(); 20 | let replace_pattern = Regex::new(r#"window._cf_chl_opt=[\S\s]+?refresh"#).unwrap(); 21 | 22 | let task_json = Regex::new(r#""(.*?)":"#).unwrap() 23 | .replace_all(&task_json, "$1:"); 24 | 25 | let task_json = task_json.replace('"', "'"); 26 | 27 | let task_html = replace_pattern.replace( 28 | &dummy_task_html, 29 | format!( 30 | "window._cf_chl_opt={},\n refresh", 31 | task_json.trim_end_matches('}').trim_end() 32 | ) 33 | ); 34 | 35 | //println!("{task_html}"); 36 | //panic!(); 37 | 38 | let mut instance = TurnstileInstance::new(script, task_html.to_string(), "".to_string(), ""); 39 | 40 | std::fs::write("./debug/task/genned_init_payload.json", instance.payload.pretty()).unwrap(); 41 | 42 | instance.get_second_payload(bytecode_0); 43 | instance.get_click_payload(bytecode_1); 44 | //instance.try(bytecode_2); 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/payload/checks.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, ops::Deref}; 2 | 3 | use serde_json::{json, Map, Value}; 4 | use swc_common::pass::util; 5 | 6 | use crate::{device_checks::DeviceCheck, disassembler::{instruction_set::instructions::values::JSValue, step::Step}, payload::second::PayloadEntryCases, task::TurnstileChallengeData}; 7 | 8 | use super::{encode::Encoder, second::{Case, MainPayloadData}, Payload}; 9 | 10 | pub type PayloadEntry = Map; 11 | 12 | fn init_entry(init_case: &Case, static_keys: &StaticKeys, scope_strings: &HashMap) -> PayloadEntry { 13 | 14 | let mut entry = PayloadEntry::new(); 15 | 16 | for key in [&static_keys.check_type, &static_keys.second_key, &static_keys.third_key] { 17 | let val_step = init_case.steps.iter() 18 | .skip_while(|step| match *step { 19 | Step::Value(val_step) => { 20 | match &val_step.value { 21 | JSValue::String(string) if string == key => { 22 | false 23 | }, 24 | _ => true 25 | } 26 | }, 27 | _ => true 28 | }) 29 | .skip(1) 30 | .next() 31 | .unwrap(); 32 | 33 | let value = match val_step { 34 | Step::Value(val_step) => { 35 | match &val_step.value { 36 | JSValue::String(string) => { 37 | string.clone() 38 | }, 39 | _ => panic!() 40 | } 41 | }, 42 | Step::Simple(step) => { 43 | let index = step.register.get(2).unwrap(); 44 | scope_strings.get(&index).unwrap().clone() 45 | }, 46 | _ => { 47 | panic!(); 48 | } 49 | }; 50 | 51 | entry.insert( 52 | key.clone(), 53 | value.clone().into() 54 | ); 55 | } 56 | 57 | entry 58 | } 59 | 60 | #[derive(Debug, Default)] 61 | pub struct StaticKeys { 62 | pub check_type: String, 63 | pub second_key: String, 64 | pub third_key: String, 65 | pub collect_time: String, 66 | pub collect_status: String 67 | } 68 | 69 | impl StaticKeys { 70 | pub fn find(ordered_payload_entries: &Vec, global_keys: &Vec) -> Self { 71 | //dbg!(ordered_payload_entries.first().unwrap() 72 | //.init_case.strings()); 73 | let mut iter = ordered_payload_entries.first().unwrap().init_case.string_iter(); 74 | Self { 75 | check_type: iter.next_key().unwrap().to_string(), 76 | second_key: iter.next_key().unwrap().to_string(), 77 | third_key: iter.next_key().unwrap().to_string(), 78 | collect_time: iter.next_key().unwrap().to_string(), 79 | collect_status: global_keys.into_iter() 80 | .filter(|s| (s.len() == 5 || s.len() == 6) && s.chars().last().unwrap().is_ascii_digit()) 81 | .nth(1) 82 | .unwrap() 83 | .clone() 84 | } 85 | } 86 | } 87 | 88 | #[derive(Default)] 89 | pub struct PayloadUtils { 90 | pub encoder: Encoder, 91 | pub global_keys: Vec, 92 | pub challenge_data: TurnstileChallengeData, 93 | pub script_url: String, 94 | pub collector_url: String, 95 | pub static_keys: StaticKeys, 96 | 97 | pub image_url: Option, 98 | pub pat_url: Option 99 | } 100 | 101 | 102 | 103 | impl Payload { 104 | 105 | pub fn init_main_payload( 106 | &mut self, 107 | MainPayloadData { 108 | payload_switch_scope_strings, 109 | last_case, 110 | keys: extra_keys, 111 | init_keys_order, .. }: &MainPayloadData 112 | ) { 113 | 114 | let mut iter = init_keys_order.into_iter(); 115 | 116 | loop { 117 | match iter.next() { 118 | //Some(string) if string == "fromCharCode" => break, 119 | Some(key) => { 120 | if let Some(x) = self.init_payload.get(key) { 121 | self.main_payload.insert(key.clone(), x.clone()); 122 | } 123 | else if key == &extra_keys.c_ray { 124 | self.main_payload.insert(key.clone(), self.utils.challenge_data.cRay.clone().into()); 125 | } 126 | else if key == &extra_keys.worker { 127 | self.main_payload.insert(key.clone(), json!({})); 128 | } 129 | else if key == &extra_keys.blob { 130 | self.main_payload.insert( 131 | key.clone(), 132 | json!("") 133 | ); 134 | } 135 | 136 | } 137 | None => break 138 | } 139 | } 140 | 141 | self.main_payload.shift_insert(3, self.keys.device_checks_count.clone(), self.device_checks_count.into()); 142 | 143 | let document_props_entry = self.main_payload.get_mut(&self.keys.document_props_encoded).unwrap(); 144 | *document_props_entry = self.utils.encoder.encode(json!([null,"closed","\n","\n",4,4,37])).into(); 145 | } 146 | 147 | pub fn xxx( 148 | &mut self, 149 | MainPayloadData { 150 | payload_switch_scope_strings, 151 | keys: extra_keys, 152 | last_case, .. }: &MainPayloadData 153 | ) { 154 | 155 | let old_device_checks_count = self.main_payload.get(&self.keys.device_checks_count).unwrap(); 156 | *self.main_payload.get_mut(&self.keys.old_device_checks_count).unwrap() = old_device_checks_count.clone(); 157 | 158 | let blob_url = format!("blob:https://{}/{}", 159 | self.utils.challenge_data.cZone, 160 | uuid::Uuid::new_v4().to_string() 161 | ); 162 | *self.main_payload.get_mut(&extra_keys.blob).unwrap() = blob_url.into(); 163 | 164 | let mut iter = last_case.string_iter(); 165 | self.main_payload.insert(iter.next_key().unwrap().into(), json!([])); 166 | 167 | let sess_str = payload_switch_scope_strings.values() 168 | .find(|string| string.len() > 40 && string.len() < 50) 169 | .unwrap() 170 | .clone(); 171 | self.main_payload.insert(iter.next_key().unwrap().into(), sess_str.into()); 172 | 173 | iter.next_key(); 174 | let short_sess_str_key = iter.next_key().unwrap(); 175 | let short_sess_str_step = last_case.steps.iter() 176 | .skip_while(|step| step.value() != Some(&JSValue::String(short_sess_str_key.clone()))) 177 | .skip(1) 178 | .next(); 179 | let short_sess_str = match short_sess_str_step.unwrap() { 180 | Step::Simple(simple_step) => { 181 | let index = simple_step.register.get(2).unwrap(); 182 | payload_switch_scope_strings.get(index).unwrap().clone() 183 | }, 184 | Step::Value(value_step) => value_step.value.as_string().unwrap().clone(), 185 | _ => panic!() 186 | }; 187 | self.main_payload.insert(short_sess_str_key.into(), short_sess_str.into()); 188 | 189 | iter.next_key(); 190 | self.main_payload.insert(iter.next_key().unwrap().into(), 0.into()); 191 | 192 | let arr = self.init_payload.get(&self.keys.arr_xx).unwrap(); 193 | self.main_payload.insert(self.keys.arr_xx.clone(), arr.clone()); 194 | 195 | self.main_payload.insert(self.keys.xxi.clone(), 0.into()); 196 | } 197 | 198 | pub fn process_payload_entries( 199 | &mut self, 200 | main_payload_data: MainPayloadData 201 | ) { 202 | self.xxx(&main_payload_data); 203 | 204 | let MainPayloadData { 205 | payload_switch_scope_strings, 206 | ordered_payload_entries, .. } = main_payload_data; 207 | 208 | for PayloadEntryCases { init_case, collector_case, .. } in ordered_payload_entries { 209 | 210 | let collector_strings = collector_case.strings(); 211 | 212 | let mut entry = init_entry( 213 | &init_case, 214 | &self.utils.static_keys, 215 | &payload_switch_scope_strings 216 | ); 217 | let device_check_type = DeviceCheck::detect(&collector_strings); 218 | 219 | //println!("{} {device_check_type:?}", self.device_checks_count + 1); 220 | 221 | entry.insert(self.utils.static_keys.collect_time.clone(), device_check_type.generate_collect_time().into()); 222 | entry.insert(self.utils.static_keys.collect_status.clone(), 3.into()); 223 | 224 | device_check_type.insert_device_data(&mut entry, &collector_case, &mut self.utils); 225 | 226 | //println!("{}", serde_json::to_string_pretty(&entry).unwrap()); 227 | 228 | self.main_payload.shift_insert( 229 | self.device_checks_count, 230 | (self.device_checks_count + 1).to_string(), 231 | serde_json::Value::Object(entry) 232 | ); 233 | self.device_checks_count += 1; 234 | 235 | } 236 | 237 | *self.main_payload.get_mut(&self.keys.device_checks_count).unwrap() = self.device_checks_count.into(); 238 | 239 | } 240 | } -------------------------------------------------------------------------------- /src/payload/compress.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | pub struct Compressor { 4 | charset: Vec, 5 | offset: usize 6 | } 7 | 8 | impl Compressor { 9 | pub fn new(charset: String) -> Self { 10 | Self { 11 | charset: charset.into(), 12 | offset: 6 13 | } 14 | } 15 | pub fn compress(&self, payload: String) -> String { 16 | 17 | let mut x: HashMap = HashMap::new(); // IDENTICAL 18 | let mut b: HashMap = HashMap::new(); 19 | let mut c = String::new(); 20 | let mut d = 2; 21 | let mut e = 3; 22 | let mut f = 2; 23 | let mut h = 0; 24 | let mut i = 0; 25 | 26 | let mut o; 27 | 28 | let mut chars = payload.chars().into_iter(); 29 | let mut encoded = String::new(); 30 | 31 | loop { 32 | match chars.next() { 33 | Some(k) => { 34 | let k = k.to_string(); 35 | if x.get(&k).is_none() { 36 | x.insert(k.clone(), e); 37 | e += 1; 38 | b.insert(k.clone(), true); 39 | } 40 | let l = format!("{c}{k}"); 41 | 42 | if x.get(&l).is_some() { 43 | c = l; 44 | continue 45 | } 46 | 47 | if b.get(&c).is_some() { 48 | for _ in 0..f { 49 | h <<= 1; 50 | if i == self.offset - 1 { 51 | i = 0; 52 | let to_insert = *self.charset.get(h).unwrap(); 53 | encoded.push(to_insert as char); 54 | h = 0; 55 | } else { 56 | i += 1; 57 | } 58 | } 59 | o = *c.as_bytes().first().unwrap() as usize; 60 | for _ in 0..8 { 61 | h = h << 1 | 1 & o as usize; 62 | if i == self.offset - 1 { 63 | i = 0; 64 | let to_insert = *self.charset.get(h).unwrap(); 65 | encoded.push(to_insert as char); 66 | h = 0; 67 | } else { 68 | i += 1; 69 | } 70 | o >>= 1; 71 | } 72 | d -= 1; 73 | if d == 0 { 74 | d = 2_i32.pow(f); 75 | f += 1; 76 | } 77 | b.remove(&c).unwrap(); 78 | } 79 | else { 80 | o = *x.get(&c).unwrap(); 81 | for _ in 0..f { 82 | h = h << 1 | o as usize & 1; 83 | if i == self.offset - 1 { 84 | i = 0; 85 | let to_insert = *self.charset.get(h).unwrap(); 86 | //println!("X {}", to_insert as char); 87 | encoded.push(to_insert as char); 88 | h = 0; 89 | } else { 90 | i += 1; 91 | } 92 | o >>= 1; 93 | } 94 | } 95 | d -= 1; 96 | if d == 0 { 97 | d = 2_i32.pow(f); 98 | f += 1; 99 | } 100 | x.insert(l, e); 101 | e += 1; 102 | c = k; 103 | }, 104 | None => break 105 | } 106 | } 107 | 108 | //dbg!(&x); 109 | // 110 | 111 | if c != "" { 112 | if b.get(&c).is_some() { 113 | for _ in 0..f { 114 | h <<= 1; 115 | if i == self.offset - 1 { 116 | i = 0; 117 | let to_insert = *self.charset.get(h).unwrap(); 118 | encoded.push(to_insert as char); 119 | h = 0; 120 | } else { 121 | i += 1; 122 | } 123 | } 124 | o = *c.as_bytes().first().unwrap() as usize; 125 | for _ in 0..8 { 126 | h = h << 1 | 1 & o as usize; 127 | if i == self.offset - 1 { 128 | i = 0; 129 | let to_insert = *self.charset.get(h).unwrap(); 130 | encoded.push(to_insert as char); 131 | h = 0; 132 | } else { 133 | i += 1; 134 | } 135 | o >>= 1; 136 | } 137 | d -= 1; 138 | if d == 0 { 139 | d = 2_i32.pow(f); 140 | f += 1; 141 | } 142 | b.remove(&c).unwrap(); 143 | } 144 | else { 145 | o = *x.get(&c).unwrap(); 146 | for _ in 0..f { 147 | h = h << 1 | o as usize & 1; 148 | if i == self.offset - 1 { 149 | i = 0; 150 | let to_insert = *self.charset.get(h).unwrap(); 151 | encoded.push(to_insert as char); 152 | h = 0; 153 | } else { 154 | i += 1; 155 | } 156 | o >>= 1; 157 | } 158 | } 159 | d -= 1; 160 | if d == 0 { 161 | f += 1; 162 | } 163 | } 164 | 165 | o = 2; 166 | for _ in 0..f { 167 | h = h << 1 | o as usize & 1; 168 | if i == self.offset - 1 { 169 | i = 0; 170 | let to_insert = *self.charset.get(h).unwrap(); 171 | encoded.push(to_insert as char); 172 | h = 0; 173 | } else { 174 | i += 1; 175 | } 176 | o >>= 1; 177 | } 178 | 179 | loop { 180 | h <<= 1; 181 | if i == self.offset - 1 { 182 | let to_insert = *self.charset.get(h).unwrap(); 183 | encoded.push(to_insert as char); 184 | break 185 | } else { 186 | i += 1; 187 | } 188 | } 189 | 190 | encoded.replacen('+', "%2b", 1) 191 | } 192 | } 193 | 194 | #[cfg(test)] 195 | mod tests { 196 | 197 | use super::Compressor; 198 | 199 | #[test] 200 | fn compress() { 201 | let encoder = Compressor::new("VfLI4yhpPdu$6nAXWYSD-o0lRGxz2svwNHtCK31JObE9+8ZqierckmB5M7gTFaQjU".into()); 202 | 203 | //assert_eq!("pjNecdHX", encoder.compress("aaffs".into())); 204 | //assert_eq!("tBA+7yRywyfJvkDoKlNpQMeNiCWKXs6PBBNYXJpX", encoder.compress("ufwaghfwagzufguwazfzugawzgfuwagzussfwahfiuwahf".into())); 205 | assert_eq!( 206 | "nMPN3NrN%2bNaNcLVlLVeNLiIRpkLhVp6$V0eVfOWflypVIipRKWyrVeVPiL2f7V-WVRKw2NhKVgNLK2LY4df2VbN44BVSWV+SVuiVgVdeFIHqb8kVBX18n9n7VLMfo2WL+VC4HwxVp42nEmsXCNmDcorx34VskoDs5dyV4kk0iGKycOM-e2VdtcnV$PW0rse--ahyVVGyDVVnrNV$o6xNf4--WV3hqEVAeNJVh+Q64-VADMV4iVd6fRxVLFVRWHyySHazl4gV4sDDifxnPc+5PLITWF$qwhVPl+JICO6-WfcykmnhVVoV4-kVVkvIWMyKvhVgVfepVi2R+$VVGm6K5E55EiJxLcihLr5cNu4FlINxhQyf2wcWc-Q1CV0BgpfWtHW4Vipfeb3f5DHM1kliVJtijHVxXkPy+VhR-yHc2yqXN-DDs2zLIVCGbEfOYyCxyNF8CNrGRyclzd2$VE--W$$lGTB2RoL9L0iF2RRbiCINmDrBhHAx1xw9eyViXtDLNkvcsnN+vb+XTlAIs4G4zWEdrvxEu24VfDHxfNpfRLiM2f-JcV0D1D1x2$OJHiwc-Ob-hy+XA4pyy-pt2AZyYE1wfO+cVIoQV+vdF2fD9KAOXRHFdnpnTX7DV4Fhi89OJ4YnALPEPM$bZKXe38T$OxE6x1ivY-HTGRjPknZOyVFudnhN+bF+naDnaBCQDed1gXMih4nN-f7IHBJtSGyfRfMYKkYY1KcdeHy6oRl4QLfEY7W-JLiPsYphyVJfi2WoLpyHBNExM+tOMWYt2A4OfiwOMp4YYypNMWOVEyedHMviMpxz7zfXhVFIWviR4kLHyIHPHyykPNQpxPRQy62YTIivYz0ZLfshvSnBK1xMAy6pfdKQpN8C1X7d1xnNMh46VkAZhHDVy-Y8p$RWMVNAtPCWRWjJ4nHdJ-7MxDWp3zVE-YM1vfRlfWIVxIQfD1DWDYDIHnY7IO--QLN6fzI9LHzy6IVQhxvY8kFx2+V5AGzL+T7XJfLHsp4XHQJx2YsphLPvYExMxK1SGJH7vbMNEzM+ptTN+pgdeshxhNvIOnWZf-PNvCQhORp4$nXf7D3XhQyiHmypK+COYWeYkpHdHyzYyhQvP8tPGMxIHX76r69y9tNLdiSPANy$Nvb6cNlHZJx$P2IWpH7LpIHeJ+wbH4auVuVilWuCHsbw9aGbThvpfnI-Afz$O5YdHOoh6yt9NxykV-JhvLPiVEPNjCMAR8HihMvlgpNMk-Dn8y4SRHAgo$ao4$fXNMlWly6yf65E4GHI1vbHtpLHDrPnWCm6IOvS2yW-IM$deH2nYgC-diQl4hVZLrssiWixhy14zWFp+--ZhZsbQNMLdZ3yzWx3tt8Yy4YY93yA48Jtz7nIQzYgJgLO+pxz5qCpLOxhQvi8HW2YqKvQPgzOhN4FQ0i+Jt-o9JkZNwr2xhtfNyi04D-XSn4-ecIr0eJKFfRsu2nVkfO$0yJxnNMeRx7DhZARBy6Psq0QzW7K1XOmNhHRxHRLbyyyXVJIRvi+4kx7ZKklliD1wbXpKwOxLiLNjWiIwIifE--OH1CLLr$O--wiJLuhkyi-itHzLwpRX-DM8ekNFfX3+yW5iOLukFy+2LaW4ObeOuFlhYVAVoIM4LvuFGJCN3k$O-Wfn6ct4Q6kd0uNkV45xL6zMKeMCBIOyVXMOOKNFN0dODiyWAVMfdpik-nV-LwL14OF4FfeLGJtnO2hObXLx0Qyx4RPiPfbVEhV242tHca4kpNANTWiIBL-G0yiTWJfGpx$RlWxH$bXNNIVhKTI8V-fvfi6+yWBVkIJOOAVLe6eRL56k4RJttf4pfyK$RMPjNPJtI-LNyWufk4N2ERWyYgC11gIWnWMeYYiIQu6yN6V0Lxf7LOdOFNPCrfWp6XN6fBHkyOOOAR5WvV-LMf7RW4YQH2IiX-YrMPZIB4qCFyNxVuC8VOnkhOAf7V+peuRR1jYQNRyy$ODinW+LDy6f-AV-fqNRfNnGzMZNFVC41XTIP+MyHOy20iXNON7tTX8POPN1GyVoyc3ZIV+2vV2pfpD0i3M-fRpwf4nNVWhESBQ6FGMwf6rBIVd6vA0eMe4hgd1yNZNYCNJ+yKuVcWvYN4DXM9VKW+IeIRA6vOEnXLctP4MfZpVFfFIiVKBSJiAtO9RhVDi1GBtNXgyVzWxfgCNK7I-LVAfXHcVtuTXVnaXVOLmIrGbnfZtn5xAL2Mq4-VCVvH42RFH1NhpxLND+$V-IsL582RWZNOLXIzyEAVxIqCshkyN2Wzk+SkpfpNLVMPLM$pE$Rn0FWLCLfOqR6OtfWViIQIWxMYfbLrf$lFhdzNGIKVMPOGMFWxBrpxfiSR8Hr5KfinkpKYVMSx44h4XMONAfI1zvW6VpfgHIN4$0KVOOsRopgfOhWVfw2NMiMDsy+LZKVJiuImpEVP2NnYiYALNnP4VfYzV$Vi4AysVf8VVZZWlNuVyHG4pSVLVMHRLlqK$R27kWLHjLBnyzOnxWIpbV$dA4FY$5k$-IeGIX4CPakSNI4RAVgImLrXPzn6ukfsujoMJK13hVHykhi--FH9I-v+V-L7QT$mdy+pNA4tVVIkJpkVMVifNvWS87fi1kf3hVb8YWRfifivWO1Rf44MTRLNpVAWKO47e1OwfAXMIVnOotVfQW6YVFVMy+VVlHKaWXV8Hv6yVGDNuV6YKL+wRmecCQKVXxVtpLYDrJonNV45+0iiJzL2xPnBJVciI$E8+4WIVfYefOhvA6DMJTJABIReeTCpJzfvzHV-ltcWvI8y5l6uV3IQtgfMAj-WQ$6e2fZOORWKijtwyyy+yiMenInIOXkYBhEoLVDiIrAR53BNYVAAKDPAWFHFIApV2M7iaN7dhhNhO1Ws7CX-$RhVsVkfF4aXkLPwVtLwLNd6DizVkIthvp6$PW26VEID2WpNfYqHzfMXt18k6XVffFVTu7I8rImVNfVI4+gwqQlQiITi0YrIqJEn7$axV4dmujw-p0ixKIWmKIi4Ra7zLc8KINhCW7r5xIMuiEJ9LmLfykdOAVTiMwcZ44WyN2dGkvpkXaKi7H$I0pPXRGgMPp3Py6uQ+KLiCSLhkpVxpwNVHf4FfKosWV", 207 | encoder.compress(r#"{"iSMo3":"chl_api_m","ucax7":"3","FbrOE0":0,"KWJa3":0,"OeAbI4":5.9000000059604645,"tUJj1":1.8000000044703484,"caWwA6":1,"AYht7":"1736122570","tjJW8":"cLIivMZ6vDcWRvZnojk0iAN0dHiFxzCSAIM90.J7q68-1736122570-1.1.1.1-dBUjr7lWg155oTQhXEx32Ei7GaodF_s6HZQZKRCpl2Q3c8O3hQu1XhDT5iqnrcAcSlrQ36EinsJ.oTyFXSxkSqfuFzG150u5ndKlND4MZwF.FybGH_1B12O_IfS2BgUdLLKtEdCpjNrv8txjiq5kYco0HuxUnbFZbXB3nNm.Ij8IiAGPshorFCr3ITi2zjDs78aX4fIltyKrfMYsBLvPGfPVE0CETaDTBVMUVK5zU47670pbq9jm5GwW39nVdqU9vANvbyLVtXBehjmMMpW5h2T_Q6QRX8HnXiKbcyrRYHAFr7N5ADPjK6IM1TSiqySxj_VWdcjaJIVFRLB2JIKNjsMak3JAAHAKML1HEU3RQtVdophUo5uAsmA9m0R0i06JUpedLBS9gbFRHBr6aHTEqzXHDb3KiDPBx6XSW.2zK3SRLwhKXJbHm0gqnhytLWYgC1clxX5ulrh56s4e7f0MitaXut6l0Dejm8BV_bqztr6QTGu9J0kRJ9dwyvBvHHgyZYLQ_hyrO_kj9DxE74OImZnXvoJGP8ysJ8Q1Vfy27Aqf5.EHSq.AQYqmgxsXPBSl2BMwGm75u3ZP3OEnR2ahEeVrdUkK8tMul9LsRqm5kDUga543HTRL9HxahlgqOx9XSjgwiLmX0zqadBykEB3AP.WH1HngCH_W_Tr916cPZNKzZiAVudqm49n6WRyjIc02d14Z4jyFJoO2pbGl75CK.pUfOBRkrwnf.vgqzjWWABfsqYQ5X.ZkL_RO2QCELxQUqcJQ27Y437xvgTt2YfBpe7T_j75mo1FNmUfWdW66057tg8C6Jjhhx16CRbB3FB6Bzb6VxjQ.zHojM90kCvFLAfpjbU_5Bn5PZGzk8ekx_ukiQpwFfWDK29fXjA.O6AtTFbZTBU73EzHqIQM1Hn8qiKZytWAxg1kKmrL0x3hy3ngG99WMuPJTDngIhMuRNQHYU7MXfhtyC58Lrau1IhfxcTdHauGo3JPY.Mwf8R0QBgNNS17LiG58Vk5p7RiUaujD1Rj2Rt94ysehSoqnWM.uWgri2LbxC6A3CP_9jDN1RmmG2qGXs6YvuLu7DKlNKLZfSnx4df3XANFoF5yI6vewecz0oR7FRAznEuOiDKwkOpZzXGOUCvTQFhLRunvaeF8O69Lkklf98v_H6L3MQlo6aQ9pNHl736MUChiUb0WzunLKAmPALX.9jlnfErG43tvAc_pWwb70DekEQgg0PtiH.vq3nmnPF9NGU2MDyiNm94_Ww_YbdZah8MUmjY4rp0pA3hUTWRtvbi8PRpwkM.hZGsJvSaTzwZ4pLA8c.FyXAxn437RG.1AHcNy2wdeD00RcMYFcnB6BK9M7lKr.F3TFQn._JEHl2axGpSGkLCZt_TY","KVFU8":{"oXxBm3":0,"xBXLb1":0,"rAgY1":0,"RrHt3":0,"anyms5":0,"cXWrV1":0,"irZV0":0,"fcPLk4":0},"dsnt4":"TuZZ0","vGjM3":"","tClV4":[],"OEcC2":0,"SJMo3":"etklA6","XqHmn1":"0","hUsY3":"0x4AAAAAAABS7TtLxsNa7Z2e","KLXhI6":0,"RjyR0":"849bfe45bf45","lfTu3":"https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha","GUaM6":"https://peet.ws/turnstile-test/managed.html","JhgdT2":"https://peet.ws","AHDER4":"zMxgk9E5PWQgPOQ1AKHXTfq1d5D7k2dhZIRXpyzx6Rw-1736122570-1.3.1.1-2qNhv61BzAzI4to7h08tkw0mTMH8bhJgz2K.lICZ_mE","QqEQg1":751.7000000029802,"ZSSCp0":751.2000000029802,"pWZx6":0,"hAFx4":0,"ZcPM7":750,"ENVtt0":0,"afeV3":0.09999999403953552,"fKmFs0":1.9000000059604645,"UAqGL3":6,"OXhCB1":0.30000000447034836}"#.into()) 208 | ); 209 | 210 | assert_eq!( 211 | std::fs::read_to_string("./debug/payload_compressed").unwrap(), 212 | Compressor::new("VfLI4yhpPdu$6nAXWYSD-o0lRGxz2svwNHtCK31JObE9+8ZqierckmB5M7gTFaQjU".into()) 213 | .compress(std::fs::read_to_string("./debug/payload_plain").unwrap()) 214 | ); 215 | 216 | } 217 | } -------------------------------------------------------------------------------- /src/payload/encode.rs: -------------------------------------------------------------------------------- 1 | use base64::Engine; 2 | use serde_json::Value; 3 | 4 | use crate::disassembler::result::Functions; 5 | 6 | fn find_dynamic_encoding_string(functions: &mut Functions) -> String { 7 | 8 | functions.values() 9 | .skip_while(|func| !func.get_strings().contains(&&"TextEncoder".to_string())) 10 | .next() 11 | .unwrap() 12 | .get_strings() 13 | .first() 14 | .unwrap() 15 | .to_string() 16 | 17 | } 18 | 19 | #[derive(Default)] 20 | pub struct Encoder { 21 | xor_charset: Vec 22 | } 23 | 24 | impl Encoder { 25 | pub fn new(dynamic_string: &str, c_ray: &str) -> Self { 26 | Self { 27 | xor_charset: format!("{dynamic_string}{c_ray}").as_bytes().into() 28 | } 29 | } 30 | pub fn from_funcs(functions: &mut Functions, c_ray: &str) -> Self { 31 | let dynamic_string = find_dynamic_encoding_string(functions); 32 | Self::new(&dynamic_string, c_ray) 33 | } 34 | pub fn encode(&self, data: Value) -> String { 35 | 36 | //dbg!(String::from_utf8(self.xor_charset.clone()).unwrap()); 37 | 38 | let encoded = serde_json::to_string(&data) 39 | .unwrap() 40 | .as_bytes() 41 | .into_iter() 42 | .enumerate() 43 | .map(|(index, charcode)| { 44 | let xor_val = self.xor_charset.get(index % self.xor_charset.len()).unwrap(); 45 | charcode ^ *xor_val 46 | }) 47 | .collect::>(); 48 | 49 | base64::prelude::BASE64_STANDARD.encode(encoded) 50 | } 51 | } 52 | 53 | mod tests { 54 | 55 | #[test] 56 | fn encode() { 57 | use serde_json::json; 58 | 59 | use super::Encoder; 60 | 61 | let encoder = Encoder::new("ifSkvxprQCgAfwkl", "8fb21ee1f8f3be72"); 62 | let data = json!([{"emdVX8":{"nRJF8":"WebKit","FvZx8":"WebKit WebGL"},"WwNlm2":{"OtYtr7":"Google Inc. (NVIDIA)","gMUAw8":"ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Ti SUPER (0x00002705) Direct3D11 vs_5_0 ps_5_0, D3D11)"}},"b6fa6387a870a108c7c2b28e9afe6147dxnNU7","b6fa6387a870a108c7c2b28e9afe6147dxnNU7"]); 63 | assert_eq!( 64 | "Mh1xDhscJippYV06RBk5Jn5eQAgTMgBTLVESEU5HcUQzHmtJTFonFzMIDjVGIA4OfypATx1HMkYoVAsBQF9MECYSCh8ET1JIcwQILgEbDkxxCAEcEU0rZy98L3JLRxsQDisGKgFAUkhzAikGKjJLRHYwK3Z4JEkRKG4vdyskF3UMIDwZFR1QIAUbR3VWQFtMbA9CYWQ1IGNGEFZLUlUHAltRY15fWDQbIyYENVUzWl0YEBFtBDpVERZLOQY9VRsSLVUXWkdRUg8sb0UjUBEKWgteVVMJUlVQVwheUFUGBVBbXjZSFx4VRGB3UCUeGSU5D0ROEFNTA1BQC14EA10AAghXY1MVTxNAM3FfJF8WDQkOV1YFVR0LfzMPRG4=", 65 | encoder.encode(data) 66 | ) 67 | } 68 | } -------------------------------------------------------------------------------- /src/payload/init.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde_json::{json, Map, Value}; 4 | 5 | use crate::task::TurnstileChallengeData; 6 | 7 | use super::{timings::InitTimings, Payload, keys::PayloadKeys}; 8 | 9 | static API_JS_CH_STRING: &'static str = "849bfe45bf45"; 10 | static API_JS_URL: &'static str = "https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha"; 11 | 12 | 13 | impl Payload { 14 | pub fn init(&mut self, 15 | init_payload_func: String, 16 | init_payload_obj_func: String, 17 | challenge_data: &TurnstileChallengeData, 18 | website: &str 19 | ) { 20 | 21 | let url = website; 22 | let origin = website.split('/') 23 | .take(3) 24 | .collect::>() 25 | .join("/"); 26 | 27 | let k_v_pairs = init_payload_func.lines() 28 | .skip_while(|l| !l.contains("100,") || !l.contains("setTimeout")) 29 | .collect::>() 30 | .into_iter() 31 | .skip(1) 32 | .take_while(|l| l.contains("':")) 33 | .map(|l| ( 34 | l.split('\'').nth(1).unwrap().to_owned(), 35 | l.split(": ").nth(1).unwrap().trim_matches(',').to_owned().replace('"', "") 36 | )) 37 | .collect::>(); 38 | 39 | let keys = PayloadKeys::new(k_v_pairs.clone().into_iter().map(|p| p.0).collect()); 40 | let init_timings = InitTimings::generate(); 41 | 42 | let k_v_pairs: HashMap = k_v_pairs.into_iter().collect(); 43 | 44 | let obj = init_payload_obj_func.lines() 45 | .filter(|l| l.contains("= 0")) 46 | .map(|l| ( 47 | l.split('"').nth(1).unwrap().to_owned(), 48 | 0_i32.into() 49 | )) 50 | .collect::>(); 51 | 52 | self.init_payload.insert(keys.c_type.clone(), challenge_data.cType.clone().into()); 53 | self.init_payload.insert(keys.cv_id.clone(), challenge_data.cvId.clone().into()); 54 | self.init_payload.insert(keys.device_checks_count.clone(), 0.into()); 55 | self.init_payload.insert(keys.old_device_checks_count.clone(), 0.into()); 56 | self.init_payload.insert(keys.perf_now_diff_0.clone(), init_timings.perf_now_diff_0.into()); 57 | self.init_payload.insert(keys.perf_now_diff_1.clone(), init_timings.perf_now_diff_1.into()); 58 | self.init_payload.insert(keys.xxc.clone(), 1.into()); 59 | self.init_payload.insert(keys.c_i_time.clone(), challenge_data.cITimeS.clone().into()); 60 | self.init_payload.insert(keys.md.clone(), challenge_data.md.clone().into()); 61 | self.init_payload.insert(keys.obj_xx.clone(), obj.into()); 62 | self.init_payload.insert(keys.xxf.clone(), k_v_pairs.get(&keys.xxf).unwrap().clone().into()); 63 | self.init_payload.insert(keys.document_props_encoded.clone(), String::new().into()); 64 | self.init_payload.insert(keys.arr_xx.clone(), json!([ 65 | "window.frameElement", 66 | "window.frameElement", 67 | "window.frameElement" 68 | ])); 69 | //self.payload.insert(keys.xxh.clone(), 0.into()); 70 | self.init_payload.insert(keys.xxi.clone(), k_v_pairs.get(&keys.xxi).unwrap().clone().into()); 71 | self.init_payload.insert(keys.api_v_id.clone(), challenge_data.chlApivId.clone().into()); 72 | self.init_payload.insert(keys.api_site_key.clone(), challenge_data.chlApiSitekey.clone().into()); 73 | self.init_payload.insert(keys.api_timeout_encountered.clone(), 0.into()); 74 | self.init_payload.insert(keys.api_acch.clone(), API_JS_CH_STRING.into()); 75 | self.init_payload.insert(keys.api_u.clone(), API_JS_URL.into()); 76 | self.init_payload.insert(keys.api_url.clone(), url.into()); 77 | self.init_payload.insert(keys.api_origin.clone(), origin.into()); 78 | self.init_payload.insert(keys.api_rc_v.clone(), challenge_data.chlApiRcV.clone().into()); 79 | self.init_payload.insert(keys.turnstile_age.clone(), init_timings.turnstile_age.into()); 80 | self.init_payload.insert(keys.widget_age.clone(), init_timings.widget_age.into()); 81 | self.init_payload.insert(keys.upgrade_attempts.clone(), 0.into()); 82 | self.init_payload.insert(keys.upgrade_completed_count.clone(), 0.into()); 83 | self.init_payload.insert(keys.time_to_init.clone(), init_timings.time_to_init.into()); 84 | self.init_payload.insert(keys.time_to_render.clone(), init_timings.time_to_render.into()); 85 | self.init_payload.insert(keys.time_to_params.clone(), init_timings.time_to_params.into()); 86 | self.init_payload.insert(keys.perf_now_diff_2.clone(), init_timings.perf_now_diff_2.into()); 87 | self.init_payload.insert(keys.perf_now_diff_3.clone(), init_timings.perf_now_diff_3.into()); 88 | self.init_payload.insert(keys.tief_time.clone(), init_timings.tief_time.into()); 89 | 90 | self.keys = keys; 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/payload/keys.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | #[derive(Default, Debug)] 3 | pub struct PayloadKeys { 4 | pub c_type: String, 5 | pub cv_id: String, 6 | pub device_checks_count: String, 7 | pub old_device_checks_count: String, 8 | pub perf_now_diff_0: String, 9 | pub perf_now_diff_1: String, 10 | pub xxc: String, 11 | pub c_i_time: String, 12 | pub md: String, 13 | pub obj_xx: String, 14 | pub xxd: String, 15 | pub xxe: String, 16 | pub xxf: String, 17 | pub document_props_encoded: String, 18 | pub arr_xx: String, // hfdFG3 19 | pub xxh: String, 20 | pub xxi: String, // oVNgw6 21 | pub api_v_id: String, 22 | pub api_site_key: String, 23 | pub api_action: String, 24 | pub api_c_data: String, 25 | pub api_chl_page_data: String, 26 | pub api_timeout_encountered: String, 27 | pub api_acch: String, 28 | pub api_u: String, 29 | pub api_url: String, 30 | pub api_origin: String, 31 | pub api_rc_v: String, 32 | pub turnstile_age: String, 33 | pub widget_age: String, 34 | pub upgrade_attempts: String, 35 | pub upgrade_completed_count: String, 36 | pub time_to_init: String, 37 | pub time_to_render: String, 38 | pub time_to_params: String, 39 | pub perf_now_diff_2: String, 40 | pub perf_now_diff_3: String, 41 | pub tief_time: String 42 | } 43 | 44 | impl PayloadKeys { 45 | pub fn empty() -> Self { 46 | Self { ..Default::default() } 47 | } 48 | pub fn new(keys: Vec) -> Self { 49 | let mut iter = keys.into_iter(); 50 | Self { 51 | c_type : iter.next().unwrap(), 52 | cv_id : iter.next().unwrap(), 53 | device_checks_count : iter.next().unwrap(), 54 | old_device_checks_count : iter.next().unwrap(), 55 | perf_now_diff_0 : iter.next().unwrap(), 56 | perf_now_diff_1 : iter.next().unwrap(), 57 | xxc : iter.next().unwrap(), 58 | c_i_time : iter.next().unwrap(), 59 | md : iter.next().unwrap(), 60 | obj_xx : iter.next().unwrap(), 61 | xxd : iter.next().unwrap(), 62 | xxe : iter.next().unwrap(), 63 | xxf : iter.next().unwrap(), 64 | document_props_encoded : iter.next().unwrap(), 65 | arr_xx : iter.next().unwrap(), 66 | xxh : iter.next().unwrap(), 67 | xxi : iter.next().unwrap(), 68 | api_v_id : iter.next().unwrap(), 69 | api_site_key : iter.next().unwrap(), 70 | api_action : iter.next().unwrap(), 71 | api_c_data : iter.next().unwrap(), 72 | api_chl_page_data : iter.next().unwrap(), 73 | api_timeout_encountered: iter.next().unwrap(), 74 | api_acch : iter.next().unwrap(), 75 | api_u : iter.next().unwrap(), 76 | api_url : iter.next().unwrap(), 77 | api_origin : iter.next().unwrap(), 78 | api_rc_v : iter.next().unwrap(), 79 | turnstile_age : iter.next().unwrap(), 80 | widget_age : iter.next().unwrap(), 81 | upgrade_attempts : iter.next().unwrap(), 82 | upgrade_completed_count: iter.next().unwrap(), 83 | time_to_init : iter.next().unwrap(), 84 | time_to_render : iter.next().unwrap(), 85 | time_to_params : iter.next().unwrap(), 86 | perf_now_diff_2 : iter.next().unwrap(), 87 | perf_now_diff_3 : iter.next().unwrap(), 88 | tief_time : iter.next().unwrap(), 89 | } 90 | } 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/payload/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | use checks::PayloadUtils; 3 | use compress::Compressor; 4 | use keys::PayloadKeys; 5 | use second::MainPayloadData; 6 | use serde_json::Value; 7 | 8 | pub mod second; 9 | mod compress; 10 | pub mod timings; 11 | mod init; 12 | mod keys; 13 | pub mod encode; 14 | pub mod checks; 15 | 16 | 17 | pub struct Payload { 18 | compressor: Compressor, 19 | keys: PayloadKeys, 20 | c_ray: String, 21 | 22 | device_checks_count: usize, 23 | 24 | init_payload: serde_json::Map, 25 | main_payload: serde_json::Map, 26 | 27 | pub(super) utils: PayloadUtils 28 | } 29 | 30 | impl Payload { 31 | pub fn new(charset: String, c_ray: &str) -> Self { 32 | Self { 33 | compressor: Compressor::new(charset), 34 | keys: PayloadKeys::empty(), 35 | c_ray: c_ray.to_owned(), 36 | 37 | device_checks_count: 0, 38 | 39 | init_payload: serde_json::Map::new(), 40 | main_payload: serde_json::Map::new(), 41 | 42 | utils: PayloadUtils::default() 43 | } 44 | } 45 | 46 | pub fn finalize(&self) -> String { 47 | let stringified = if self.device_checks_count == 0 { 48 | serde_json::to_string(&self.init_payload).unwrap() 49 | } else { 50 | serde_json::to_string(&self.main_payload).unwrap() 51 | }; 52 | let compressed = self.compressor.compress(stringified); 53 | format!( 54 | "v_{}={}", 55 | self.c_ray, 56 | compressed//.replace("+", "%2b") 57 | ) 58 | } 59 | 60 | pub fn pretty(&self) -> String { 61 | if self.device_checks_count == 0 { 62 | serde_json::to_string_pretty(&self.init_payload).unwrap() 63 | } else { 64 | serde_json::to_string_pretty(&self.main_payload).unwrap() 65 | } 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /src/payload/second.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::ops::Deref; 3 | use std::vec::IntoIter; 4 | use crate::disassembler::{instruction_set::instructions::values::JSValue, result::{Function, Functions}, step::Step}; 5 | use crate::disassembler::{instruction_set::r#match::InstructionType, result::get_values_recursively}; 6 | 7 | 8 | fn take_payload_builder_func(functions: &mut Functions) -> Function { 9 | let payload_switch_func_ptr = *functions.iter() 10 | .find(|(_, func)| func.get_strings().contains(&&String::from("life goes on"))) 11 | .unwrap().0; 12 | functions.remove(&payload_switch_func_ptr).unwrap() 13 | } 14 | 15 | fn get_payload_builder_func_scope_strings(func: &Function) -> HashMap { 16 | 17 | let mut register_string_map: HashMap = HashMap::new(); 18 | 19 | let mut iter = func.steps.iter(); 20 | loop { 21 | match iter.next() { 22 | Some(step) => match step { 23 | Step::Value(value_step) => match &value_step.value { 24 | JSValue::String(s) if s == "life goes on" => break, 25 | JSValue::String(s) => {register_string_map.insert(value_step.dest, s.clone());}, 26 | _ => {} 27 | }, 28 | _ => continue 29 | }, 30 | None => break, 31 | } 32 | } 33 | register_string_map 34 | 35 | } 36 | 37 | pub type ControlFlow = HashMap>; 38 | 39 | #[derive(Clone)] 40 | pub struct StringIter<'a> { 41 | iter: IntoIter<&'a String> 42 | } 43 | 44 | impl<'a> StringIter<'a> { 45 | pub fn skip_to_str(&mut self, target: &str) -> &mut Self { 46 | loop { 47 | let string = self.iter.next().unwrap(); 48 | if *string == target { 49 | return self 50 | } 51 | } 52 | } 53 | pub fn next_key(&mut self) -> Option<&'a String> { 54 | loop { 55 | match self.iter.next() { 56 | Some(string) if (string.len() == 5 || string.len() == 6) && 57 | string.chars().last().unwrap().is_ascii_digit() => return Some(string), 58 | None => break, 59 | _ => {} 60 | } 61 | } 62 | None 63 | } 64 | pub fn next(&mut self) -> Option<&'a String> { 65 | self.iter.next() 66 | } 67 | pub fn skip(&mut self, count: usize) -> &mut Self { 68 | for _ in 0..count { 69 | self.iter.next(); 70 | } 71 | self 72 | } 73 | pub fn skip_pattern(&mut self, strings: &[&str]) -> &mut Self { 74 | let mut vec_index = 0; 75 | while let Some(item) = self.iter.next() { 76 | if item == strings[vec_index] { 77 | vec_index += 1; 78 | if vec_index == strings.len() { 79 | return self; 80 | } 81 | } else { 82 | // Reset index if sequence breaks 83 | vec_index = 0; 84 | } 85 | } 86 | self 87 | } 88 | pub fn collect(self) -> Vec<&'a String> { 89 | self.iter.collect::>() 90 | } 91 | } 92 | 93 | #[derive(Debug, Default)] 94 | pub struct Case { 95 | pub identifier: JSValue, 96 | pub entry_pointer: i32, 97 | pub steps: Vec, 98 | pub values: Vec 99 | } 100 | 101 | impl Case { 102 | pub fn strings<'a>(&'a self) -> Vec<&'a String> { 103 | self.values.iter().filter_map(|value| match value { 104 | JSValue::String(s) => Some(s), 105 | _ => None 106 | }).collect() 107 | } 108 | pub fn string_iter(&self) -> StringIter { 109 | StringIter { iter: self.strings().into_iter() } 110 | } 111 | } 112 | 113 | pub fn next_key<'a, T>(iter: &mut T) -> &'a String 114 | where T: Iterator { 115 | loop { 116 | let string = iter.next().unwrap(); 117 | if (string.len() == 5 || string.len() == 6) && 118 | string.chars().last().unwrap().is_ascii_digit() { 119 | return string 120 | } 121 | 122 | } 123 | } 124 | 125 | pub struct PayloadEntryCases { 126 | pub init_case: Case, 127 | pub collector_case: Case, 128 | pub post_case: Case 129 | } 130 | 131 | fn isolate_switch_cases(steps: &mut Vec, last_case_index: usize, functions: &mut Functions) -> Vec { 132 | 133 | let default_case_entry_pointer = steps[last_case_index + 1] 134 | .as_jump().unwrap().jump_to; 135 | 136 | let mut unordered_cases: Vec = Vec::new(); 137 | 138 | let mut idx = last_case_index; 139 | loop { 140 | if steps[idx].instruction() != &InstructionType::Branch { break } 141 | 142 | unordered_cases.push(Case { 143 | identifier: steps[idx - 2].value().unwrap().clone(), 144 | entry_pointer: steps[idx].as_branch().unwrap().jump_to, 145 | steps: Vec::new(), 146 | values: Vec::new() 147 | }); 148 | 149 | idx -= 3; 150 | } 151 | unordered_cases.reverse(); 152 | 153 | for idx in 0..unordered_cases.len() - 1 { 154 | let mut iter = steps.iter(); 155 | let start_index = iter.position(|step| step.pointer() == unordered_cases.get(idx).unwrap().entry_pointer).unwrap(); 156 | let end_index = iter.position(|step| step.pointer() == unordered_cases.get(idx + 1).unwrap().entry_pointer).unwrap(); 157 | let case = unordered_cases.get_mut(idx).unwrap(); 158 | case.steps = steps.drain(start_index..=start_index + end_index).collect(); 159 | case.values = get_values_recursively(&case.steps, &functions) 160 | } 161 | 162 | // handle last case 163 | { 164 | let mut iter = steps.iter(); 165 | let start_index = iter.position(|step| step.pointer() == unordered_cases.last().unwrap().entry_pointer).unwrap(); 166 | let end_index = iter.position(|step| step.pointer() == default_case_entry_pointer).unwrap(); 167 | let case = unordered_cases.last_mut().unwrap(); 168 | case.steps = steps.drain(start_index..=start_index + end_index).collect(); 169 | case.values = get_values_recursively(&case.steps, &functions) 170 | } 171 | 172 | unordered_cases 173 | } 174 | 175 | fn flatten_control_flow(unordered_cases: &Vec, stop_case: &Case) -> ControlFlow { 176 | let case_identifier: HashSet = unordered_cases.iter().map(|c| c.identifier.as_string().unwrap().clone()).collect(); 177 | //dbg!(&case_identifier); 178 | let control_flow: HashMap> = unordered_cases.iter() 179 | .map(|case| { 180 | let mut contained_identifier = case.strings().into_iter() 181 | .filter(|string| { 182 | *string != "" && 183 | *string != stop_case.identifier.as_string().unwrap() && 184 | *string != case.identifier.as_string().unwrap() && 185 | case_identifier.contains(*string) 186 | }) 187 | .collect::>(); 188 | contained_identifier.sort(); 189 | contained_identifier.dedup(); 190 | if contained_identifier.len() > 1 { panic!() } 191 | 192 | ( 193 | case.identifier.as_string().unwrap().clone(), 194 | contained_identifier.get(0).map_or_else(|| None, |x| Some((*x).clone())) 195 | ) 196 | }) 197 | .collect(); 198 | 199 | control_flow 200 | } 201 | 202 | pub fn extract_payload_switch_cases(functions: &mut Functions, payload_switch_func: Function) -> Vec { 203 | let mut payload_switch_func_steps = payload_switch_func.steps; 204 | 205 | let last_case_index = payload_switch_func_steps.iter() 206 | .enumerate() 207 | .position(|(idx, step)| { 208 | step.instruction() == &InstructionType::Branch && 209 | payload_switch_func_steps[idx + 3].instruction() == &InstructionType::Branch && 210 | payload_switch_func_steps[idx + 4].instruction() == &InstructionType::Jump 211 | }) 212 | .unwrap() + 3; 213 | 214 | let unordered_cases = isolate_switch_cases( 215 | &mut payload_switch_func_steps, 216 | last_case_index, 217 | functions 218 | ); 219 | 220 | unordered_cases 221 | } 222 | 223 | pub fn get_ordered_payload_entries(unordered_cases: &mut Vec) -> Vec { 224 | 225 | let _init_case = unordered_cases.remove(unordered_cases.iter().position(|case| case.identifier == JSValue::Undefined ).unwrap()); 226 | let stop_case = unordered_cases.remove(unordered_cases.iter().position(|case| { 227 | case.steps.last().unwrap().instruction() == &InstructionType::Terminate && 228 | case.values.len() == 1 && 229 | case.values.get(0).unwrap() == &JSValue::Undefined 230 | }).unwrap()); 231 | 232 | let control_flow = flatten_control_flow(&unordered_cases, &stop_case); 233 | 234 | let mut take_case_by_identifier = |identifier: &str| -> Case { 235 | let index = unordered_cases.iter() 236 | .position(|case| case.identifier.as_string().unwrap() == identifier) 237 | .unwrap(); 238 | unordered_cases.swap_remove(index) 239 | }; 240 | 241 | let mut payload_entries: Vec = Vec::new(); 242 | let mut entry_case = ""; 243 | 244 | loop { 245 | let handler_case = if let Some(x) = control_flow.get(entry_case).unwrap() { 246 | x 247 | } else { 248 | break 249 | }; 250 | let post_process_case = control_flow.get(handler_case).unwrap().as_ref().unwrap(); 251 | 252 | payload_entries.push(PayloadEntryCases { 253 | init_case: take_case_by_identifier(entry_case), 254 | collector_case: take_case_by_identifier(&handler_case), 255 | post_case: take_case_by_identifier(&post_process_case), 256 | }); 257 | 258 | //let case = unordered_cases.iter().find(|case| case.identifier.as_string().unwrap() == handler_case ).unwrap(); 259 | //dbg!(DeviceCheck::detect(case.strings())); 260 | entry_case = &control_flow.get(post_process_case).unwrap().as_ref().unwrap(); 261 | } 262 | 263 | payload_entries 264 | } 265 | 266 | #[derive(Default)] 267 | pub struct SecondPayloadKeys { 268 | pub c_ray: String, 269 | pub blob: String, 270 | pub worker: String 271 | } 272 | 273 | impl SecondPayloadKeys { 274 | pub fn new(functions: &Functions) -> (Self, Vec) { 275 | 276 | let mut entry_func = functions.keys() 277 | .into_iter() 278 | .map(|v| *v) 279 | .collect::>(); 280 | entry_func.sort(); 281 | let target_func = functions.get(entry_func.last().unwrap()).unwrap(); 282 | 283 | let strings = target_func.get_strings(); 284 | 285 | let c_ray_key_index = strings.iter().position(|s| *s == "cRay").unwrap() - 1; 286 | let blob_key_index = strings.iter().position(|s| *s == "URL").unwrap() - 1; 287 | let worker_key_index = strings.iter().position(|s| *s == "Worker").unwrap() - 1; 288 | 289 | ( 290 | Self { 291 | c_ray: strings.get(c_ray_key_index).unwrap().to_string(), 292 | blob: strings.get(blob_key_index).unwrap().to_string(), 293 | worker: strings.get(worker_key_index).unwrap().to_string() 294 | }, 295 | strings.into_iter().map(Into::into).collect() 296 | ) 297 | 298 | } 299 | } 300 | 301 | #[derive(Default)] 302 | pub struct MainPayloadData { 303 | pub global_strings: Vec, 304 | pub payload_switch_scope_strings: HashMap, 305 | pub ordered_payload_entries: Vec, 306 | pub last_case: Case, 307 | pub init_keys_order: Vec, 308 | pub keys: SecondPayloadKeys 309 | } 310 | 311 | impl MainPayloadData { 312 | pub fn from_funcs(mut functions: Functions, global_strings: Vec) -> Self { 313 | 314 | let payload_switch_func = take_payload_builder_func(&mut functions); 315 | let payload_switch_scope_strings = get_payload_builder_func_scope_strings(&payload_switch_func); 316 | 317 | let mut unordered_payload_cases = extract_payload_switch_cases(&mut functions, payload_switch_func); 318 | 319 | let ordered_payload_entries = get_ordered_payload_entries(&mut unordered_payload_cases); 320 | 321 | assert_eq!(unordered_payload_cases.len(), 1); 322 | let last_case = unordered_payload_cases.remove(0); 323 | 324 | let (keys, init_keys_order) = SecondPayloadKeys::new(&functions); 325 | 326 | Self { 327 | global_strings, 328 | payload_switch_scope_strings, 329 | ordered_payload_entries, 330 | last_case, 331 | keys, 332 | init_keys_order 333 | } 334 | } 335 | } -------------------------------------------------------------------------------- /src/payload/timings.rs: -------------------------------------------------------------------------------- 1 | use rand::{seq::SliceRandom as _, thread_rng}; 2 | 3 | const POST_COMMA: [f64; 18] = [ 4 | 0.09999999962747097, 5 | 0.10000000055879354, 6 | 0.7999999998137355, 7 | 0.0, 8 | 0.900000000372529, 9 | 0.8999999994412065, 10 | 0.20000000018626451, 11 | 0.19999999925494194, 12 | 0.5, 13 | 0.39999999944120646, 14 | 0.8000000007450581, 15 | 0.2999999998137355, 16 | 0.7000000001862645, 17 | 0.40000000037252903, 18 | 0.599999999627471, 19 | 0.30000000074505806, 20 | 0.6999999992549419, 21 | 0.6000000005587935 22 | ]; 23 | 24 | pub fn random_time(min: i32, incr: i32) -> f64 { 25 | let base_time = (rand::random::() * incr as f64 + min as f64).floor(); 26 | let float_part = POST_COMMA.choose(&mut thread_rng()).unwrap(); 27 | let elapsed_time = base_time + float_part; 28 | elapsed_time 29 | } 30 | 31 | pub struct InitTimings { 32 | pub turnstile_age: f64, 33 | pub widget_age: f64, 34 | pub time_to_init: f64, 35 | pub time_to_params: f64, 36 | pub tief_time: f64, 37 | pub perf_now_diff_0: f64, 38 | pub perf_now_diff_1: f64, 39 | pub perf_now_diff_2: f64, 40 | pub perf_now_diff_3: f64, 41 | pub time_to_render: f64 42 | } 43 | 44 | impl InitTimings { 45 | pub fn generate() -> Self { 46 | Self { 47 | turnstile_age : random_time(100, 5), 48 | widget_age : random_time(98, 2), 49 | time_to_init : random_time(96, 2), 50 | time_to_params : random_time(0, 1), 51 | tief_time : random_time(0, 1), 52 | perf_now_diff_0: random_time(15, 5) - random_time(0, 1), 53 | perf_now_diff_1: random_time(2, 1) - random_time(0, 1), 54 | perf_now_diff_2: random_time(2, 1) - random_time(0, 1), 55 | perf_now_diff_3: random_time(15, 5) - random_time(0, 1), 56 | time_to_render : 0.0, 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/pow.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::format; 2 | 3 | use rand::Rng; 4 | 5 | pub struct PowConfig<'a> { 6 | pub c_ray: &'a str, 7 | pub performance_now: f64, 8 | pub difficulty: i64, 9 | pub random_bytes: &'a str, 10 | pub target_hash: &'a str 11 | } 12 | 13 | pub struct PowResult { 14 | pub solution: String, 15 | pub digest: String, 16 | pub duration: u128 17 | } 18 | 19 | pub fn solve_pow(config: PowConfig) -> PowResult { 20 | 21 | let timer = tokio::time::Instant::now(); 22 | 23 | let start_offset = rand::thread_rng().gen_range(0..10_000); 24 | let mut count = start_offset; 25 | 26 | let target_hash = config.target_hash.as_bytes(); 27 | let mut digest; 28 | 29 | 'outer: loop { 30 | let nonce = format!("{}|{}|{}|{}{}", 31 | config.c_ray, 32 | config.performance_now, 33 | config.difficulty, 34 | count, 35 | config.random_bytes 36 | ); 37 | 38 | digest = sha256::digest(nonce); 39 | 40 | for i in 0..config.difficulty { 41 | if target_hash[(63 - i) as usize] != digest.as_bytes()[(63 - i) as usize] { 42 | count += 1; 43 | continue 'outer; 44 | } 45 | } 46 | break 47 | } 48 | 49 | let plain = format!("{}|{}|{}|{}", 50 | config.c_ray, 51 | config.performance_now, 52 | config.difficulty, 53 | count 54 | ); 55 | 56 | PowResult { 57 | solution: plain, 58 | digest, 59 | duration: timer.elapsed().as_millis(), 60 | } 61 | } 62 | 63 | mod tests { 64 | use super::solve_pow; 65 | 66 | #[test] 67 | fn pow() { 68 | let pow_solution = solve_pow(super::PowConfig { 69 | c_ray: "8fd7848f0ba601f8", 70 | performance_now: 1076.0999999940395, 71 | difficulty: 4, 72 | random_bytes: "bLnsoTfImdRfQYFxIwDCKatZvcbPvRTMsKaUhOaVszlBunAfPGjLKquzfyKzeKEgVnhRQGLncvFUUHfgqFgwoWXOFTCljgfVgIGkNJhLeukbwHbhgsiSDJZncqXPiIUZ", 73 | target_hash: "8f026e7b3ad4d07f71c3fd6a689f30e54eac6802c1c70555d026e8a90c1056a1" 74 | }); 75 | 76 | assert_eq!( 77 | pow_solution.solution, 78 | "8fd7848f0ba601f8|1076.0999999940395|4|226130" 79 | ); 80 | 81 | assert_eq!( 82 | pow_solution.solution, 83 | "5a6d390fc25311acb2aa100006bf98d0b0daec902ea80b23f0b677711fb456a1" 84 | ); 85 | } 86 | } -------------------------------------------------------------------------------- /src/requests.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use base64::Engine as _; 4 | use rand::Rng; 5 | use rquest::{tls::Impersonate, Client, Proxy}; 6 | use anyhow::anyhow; 7 | 8 | pub struct TlsClient { 9 | client: Client, 10 | pub task_init_url: String 11 | } 12 | 13 | impl TlsClient { 14 | pub fn new(proxy: &str, sitekey: &str) -> Result { 15 | let proxy = if proxy.starts_with("http://") { 16 | proxy.to_string() 17 | } else { 18 | format!("http://{proxy}") 19 | }; 20 | 21 | #[cfg(not(debug_assertions))] 22 | let self_signed_certs = false; 23 | #[cfg(debug_assertions)] 24 | let self_signed_certs = true; 25 | 26 | let proxy = Proxy::all(proxy).map_err(|_| anyhow!("Invalid/malformed proxy"))?; 27 | let client = Client::builder() 28 | .timeout(Duration::from_secs(8)) 29 | .proxy(proxy) 30 | .impersonate_skip_headers(Impersonate::Chrome131) 31 | .danger_accept_invalid_certs(self_signed_certs) 32 | .build()?; 33 | 34 | let task_init_url = format!( 35 | "https://challenges.cloudflare.com/cdn-cgi/challenge-platform/h/b/turnstile/if/ov2/av0/rcv/{}/{}/light/fbE/normal/auto/", 36 | (0..5).map(|_| rand::thread_rng().gen_range(97..=122) as u8 as char).collect::(), 37 | sitekey 38 | ); 39 | 40 | Ok( 41 | TlsClient { client, task_init_url } 42 | ) 43 | } 44 | 45 | const USER_AGENT: &str = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"; 46 | const SEC_CH_UA: &str = "\"Google Chrome\";v=\"131\", \"Chromium\";v=\"131\", \"Not_A Brand\";v=\"24\""; 47 | 48 | pub async fn get(&self, url: &str) -> Result { 49 | let resp = self.client.get(url) 50 | .header("sec-ch-ua-platform", "\"Windows\"") 51 | .header("user-agent", TlsClient::USER_AGENT) 52 | .header("sec-ch-ua", TlsClient::SEC_CH_UA) 53 | .header("sec-ch-ua-mobile", "?0") 54 | .header("accept", "*/*") 55 | .header("sec-fetch-site", "same-origin") 56 | .header("sec-fetch-mode", "no-cors") 57 | .header("sec-fetch-dest", "script") 58 | .header("referer", &self.task_init_url) 59 | .header("accept-encoding", "gzip, deflate, br, zstd") 60 | .header("accept-language", "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7") 61 | .header("priority", "u=1") 62 | .send() 63 | .await? 64 | .text() 65 | .await?; 66 | 67 | Ok(resp) 68 | } 69 | 70 | pub async fn post(&self, url: &str, payload: String, challenge_token: &str) -> Result { 71 | let resp = self.client.post(url) 72 | .header("content-length", payload.len()) 73 | .header("pragma", "no-cache") 74 | .header("cache-control", "no-cache") 75 | .header("sec-ch-ua-platform", "\"Windows\"") 76 | .header("cf-chl-retryattempt", "0") 77 | .header("user-agent", TlsClient::USER_AGENT) 78 | .header("sec-ch-ua", TlsClient::SEC_CH_UA) 79 | .header("content-type", "application/x-www-form-urlencoded") 80 | .header("cf-challenge", challenge_token) 81 | .header("sec-ch-ua-mobile", "?0") 82 | .header("accept", "*/*") 83 | .header("origin", "https://challenges.cloudflare.com") 84 | .header("sec-fetch-site", "same-origin") 85 | .header("sec-fetch-mode", "cors") 86 | .header("sec-fetch-dest", "empty") 87 | .header("referer", &self.task_init_url) 88 | .header("accept-encoding", "gzip, deflate, br, zstd") 89 | .header("accept-language", "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7") 90 | .header("priority", "u=1, i") 91 | .body(payload) 92 | .send() 93 | .await? 94 | .text() 95 | .await?; 96 | 97 | base64::prelude::BASE64_STANDARD.decode(&resp) 98 | .map_err(|_| anyhow!("Non-b64 response from submitting payload: {resp}"))?; 99 | 100 | Ok(resp) 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /src/script/extract.rs: -------------------------------------------------------------------------------- 1 | use crate::strings::StringResolver; 2 | 3 | use regex::{Captures, Regex}; 4 | use once_cell::sync::Lazy; 5 | 6 | #[derive(Debug, PartialEq)] 7 | pub enum FuncType { 8 | Decl, 9 | Expr 10 | } 11 | 12 | #[derive(Debug)] 13 | pub struct FuncString { 14 | pub _type: FuncType, 15 | pub id: String, 16 | pub full_str: String 17 | } 18 | 19 | impl FuncString { 20 | fn new(func: &str) -> Self { 21 | let id_start = 8; 22 | let id_end = func.find('(').unwrap(); 23 | let id = &func[id_start..id_end]; 24 | Self { 25 | _type: if id == "" { FuncType::Expr } else { FuncType::Decl }, 26 | id: id.replace(' ', ""), 27 | full_str: func.to_owned() 28 | } 29 | } 30 | fn get_body(&self) -> &str { 31 | let start = self.full_str.find('{').unwrap() + 1; 32 | &self.full_str[start..self.full_str.len()] 33 | } 34 | } 35 | 36 | fn extract_funcs(script: &str) -> Vec { 37 | 38 | let mut top_level_funcs = Vec::new(); 39 | let mut slice = &script[script.find("function").unwrap()+10..]; 40 | 41 | loop { 42 | 43 | let func_start = if let Some(x) = slice.find("function") { 44 | x 45 | } else { 46 | return top_level_funcs 47 | }; 48 | 49 | let func_body_start = func_start + slice[func_start..].find('{').unwrap() + 1; 50 | let mut unclosed_brackets = 1; 51 | let mut index = func_body_start; 52 | 53 | while unclosed_brackets != 0 { 54 | let next_bracket_index = index + slice[index..].find(&['{', '}']).unwrap(); 55 | match &slice[next_bracket_index..next_bracket_index+1] { 56 | "{" => unclosed_brackets += 1, 57 | "}" => unclosed_brackets -= 1, 58 | _ => panic!() 59 | } 60 | index = next_bracket_index + 1; 61 | } 62 | 63 | let func = &slice[func_start..index]; 64 | 65 | top_level_funcs.push(FuncString::new(func)); 66 | 67 | slice = &slice[index..]; 68 | } 69 | } 70 | 71 | static STRING_RESOLVE_CALL_PATTERN: Lazy = Lazy::new(|| Regex::new(r#"\b([A-Za-z0-9]{2,3}\(([0-9]+)\))"#).unwrap()); 72 | 73 | fn resolve_strings(func: &mut FuncString, resolver: &StringResolver) { 74 | func.full_str = func.full_str.replace("(1e3)", "(1000)"); 75 | func.full_str = STRING_RESOLVE_CALL_PATTERN.replace_all(&func.full_str, |caps: &Captures| -> String { 76 | let (_, [_call, index]) = caps.extract(); 77 | let index = index.parse().unwrap(); 78 | let resolved = if let Some(s) = resolver.get(index) { 79 | s 80 | } else { 81 | _call 82 | }; 83 | format!("\"{}\"", resolved.replace('"', "\\\"")) 84 | }).to_string(); 85 | } 86 | 87 | pub fn parse_top_level_funcs(script: &str) -> (Vec, StringResolver) { 88 | 89 | let mut funcs = extract_funcs(script); 90 | let string_resolver = StringResolver::new(&mut funcs); 91 | 92 | funcs.retain(|func| func.id != ""); 93 | funcs.iter_mut() 94 | .for_each(|func| resolve_strings(func, &string_resolver)); 95 | 96 | (funcs, string_resolver) 97 | } 98 | 99 | mod tests { 100 | #[test] 101 | fn exp_expr() { 102 | assert_eq!("1e3".parse::().unwrap(), 1000); 103 | } 104 | } -------------------------------------------------------------------------------- /src/script/identifier.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::{ast::apply_transformations, script::normalize_func_string}; 4 | 5 | use super::extract::FuncString; 6 | 7 | #[derive(Debug)] 8 | pub struct FuncIdentifier { 9 | pub instruction_set: Vec, 10 | pub while_bitwise: String, 11 | pub vm_builder: String, 12 | pub vm_prep: String, 13 | pub vm_execute: String, 14 | pub init_payload: String, 15 | pub init_payload_obj: String 16 | } 17 | 18 | fn find_vm_builder_func(funcs: &Vec) -> &FuncString { 19 | let builder_func = funcs.iter() 20 | .find(|func| 21 | func.full_str.contains("this.g=") && 22 | func.full_str.contains("Array") && 23 | func.full_str.contains("256") 24 | ) 25 | .unwrap(); 26 | builder_func 27 | } 28 | 29 | fn find_vm_prep_func(funcs: &Vec) -> &FuncString { 30 | let init_func = funcs.iter() 31 | .find(|func| 32 | func.full_str.contains("isNaN") && 33 | func.full_str.contains("255") && 34 | func.full_str.contains("=void 0") && 35 | func.full_str.contains("][3]") && 36 | !func.full_str.contains("=this;") && 37 | !func.full_str.contains("=this,") 38 | ) 39 | .unwrap(); 40 | 41 | init_func 42 | } 43 | 44 | fn find_vm_executor_func(funcs: &Vec) -> &FuncString { 45 | let executor_func = funcs.iter() 46 | .find(|func| 47 | func.full_str.contains("isNaN") && 48 | ( 49 | func.full_str.contains("=this;") || 50 | func.full_str.contains("=this,") 51 | ) 52 | ) 53 | .unwrap(); 54 | executor_func 55 | } 56 | 57 | fn find_varint_func(funcs: &Vec) -> &FuncString { 58 | let varint_func = funcs.iter() 59 | .find(|func| 60 | func.full_str.contains("do") && 61 | func.full_str.contains("|=") && 62 | func.full_str.contains("128") && 63 | apply_transformations(func.full_str.clone()).contains("while") 64 | ) 65 | .unwrap(); 66 | varint_func 67 | } 68 | 69 | fn find_init_payload_func(funcs: &Vec) -> &FuncString { 70 | let func = funcs.iter() 71 | .find(|func| 72 | func.full_str.contains("setTimeout") && 73 | func.full_str.contains("performance") && 74 | func.full_str.contains(".md") 75 | ) 76 | .unwrap(); 77 | func 78 | } 79 | 80 | fn build_opcode_map(init_func: &FuncString) -> HashMap { 81 | let func_str = normalize_func_string(init_func.full_str.clone()); 82 | //println!("{func_str}"); 83 | func_str.lines() 84 | .filter(|expr| expr.contains("this.h[")) 85 | .map(|expr| { 86 | let splits = expr.split(" = ").collect::>(); 87 | (*splits.get(0).unwrap(), splits.get(1).unwrap().trim_matches(';')) 88 | }) 89 | .filter(|expr| expr.1.len() == 2) 90 | .map(|(left, right)| { 91 | let opcode = left.split(['[', ']']) 92 | .nth(1) 93 | .unwrap(); 94 | //dbg!(opcode); 95 | (right.to_string(), opcode.parse::().unwrap()) 96 | }) 97 | .collect() 98 | } 99 | 100 | pub fn find_func_identifier(funcs: &Vec) -> (FuncIdentifier, HashMap) { 101 | 102 | let builder_func = find_vm_builder_func(funcs); 103 | let opcode_map = build_opcode_map(builder_func); 104 | 105 | assert_ne!(opcode_map.len(), 0); 106 | 107 | let vm_prep_func = find_vm_prep_func(funcs); 108 | let vm_executor_func = find_vm_executor_func(funcs); 109 | let while_bitwise_func = find_varint_func(funcs); 110 | let init_payload_func = find_init_payload_func(funcs); 111 | let init_payload_obj_func = funcs.into_iter().find(|func| { 112 | func.full_str.contains("performance") && 113 | func.full_str.contains("={}") && 114 | func.full_str.split("=0").count() == 9 115 | }).unwrap(); 116 | 117 | //dbg!(&vm_prep_func); 118 | 119 | let identifier = FuncIdentifier { 120 | instruction_set: opcode_map.keys().map(|k| k.to_string()).collect(), 121 | while_bitwise: while_bitwise_func.id.clone(), 122 | vm_builder: builder_func.id.clone(), 123 | vm_prep: vm_prep_func.id.clone(), 124 | vm_execute: vm_executor_func.id.clone(), 125 | init_payload: init_payload_func.id.clone(), 126 | init_payload_obj: init_payload_obj_func.id.clone() 127 | }; 128 | 129 | (identifier, opcode_map) 130 | } -------------------------------------------------------------------------------- /src/script/mod.rs: -------------------------------------------------------------------------------- 1 | mod identifier; 2 | pub mod extract; 3 | 4 | use extract::FuncString; 5 | use identifier::{find_func_identifier, FuncIdentifier}; 6 | use once_cell::sync::Lazy; 7 | use regex::Regex; 8 | 9 | use crate::{ast::apply_transformations, lazy_regex, strings::StringResolver}; 10 | 11 | fn remove_unnecessary_funcs(funcs: Vec, ids: &FuncIdentifier) -> Vec { 12 | funcs.into_iter() 13 | .filter(|f| { 14 | f.id == ids.vm_execute || 15 | f.id == ids.vm_prep || 16 | f.id == ids.while_bitwise || 17 | f.id == ids.vm_builder || 18 | f.id == ids.init_payload || 19 | f.id == ids.init_payload_obj || 20 | ids.instruction_set.iter().any(|id| *id == f.id) 21 | }) 22 | .collect() 23 | } 24 | 25 | static UNECESSARY_PAREN_PATTERN: Lazy = lazy_regex!(r#"\[\(([0-9a-z]+)\)\]"#); 26 | static DYNAMIC_NUM_PATTERN: Lazy = lazy_regex!(r#"[A-Za-z]+\.h[^\s.]+\[3\]"#); 27 | static NEXT_BYTE_PATTERN: Lazy = lazy_regex!(r#"[a-z]+\.h\[[0-9]+\]\[1\]\["charCodeAt"\]\([a-z]+\.h\[[0-9]+\]\[0\]\+\+\)"#); 28 | static CONFIDENT_XOR_PATTERN_1: Lazy = lazy_regex!(r#"[A-Za-z]+\.g \^ "#); 29 | static CONFIDENT_XOR_PATTERN_2: Lazy = lazy_regex!(r#" \^ [A-Za-z]+\.g"#); 30 | static EDGE_CASE_1_PATTERN: Lazy = lazy_regex!(r#"([a-z]\s/=\s2)"#); 31 | static IN_PLACE_DIVISION: Lazy = lazy_regex!(r#"([A-Za-z]\s/=\s2)"#); 32 | 33 | fn apply_regex_patterns(mut func_str: String) -> String { 34 | func_str = CONFIDENT_XOR_PATTERN_1.replace_all(&func_str, "").to_string(); 35 | func_str = CONFIDENT_XOR_PATTERN_2.replace_all(&func_str, "").to_string(); 36 | func_str = UNECESSARY_PAREN_PATTERN.replace_all(&func_str, "[$1]").to_string(); 37 | func_str = DYNAMIC_NUM_PATTERN.replace_all(&func_str, "DYNAMIC_NUM").to_string(); 38 | func_str = NEXT_BYTE_PATTERN.replace_all(&func_str, "NEXT_BYTE").to_string(); 39 | func_str = EDGE_CASE_1_PATTERN.replace_all(&func_str, "($1)").to_string(); 40 | func_str = IN_PLACE_DIVISION.replace_all(&func_str, "($1)").to_string(); 41 | func_str 42 | } 43 | 44 | #[derive(Debug)] 45 | pub struct InstructionFunc { 46 | pub opcode: usize, 47 | pub expressions: Vec 48 | } 49 | 50 | #[derive(Debug)] 51 | pub struct TransformedFunctions { 52 | pub vm_init: String, 53 | pub vm_prep: String, 54 | pub vm_execute: String, 55 | pub while_bitwise: String, 56 | pub init_payload: String, 57 | pub init_payload_obj: String, 58 | pub instructions: Vec 59 | } 60 | 61 | fn take_func_str(func_strings: &mut Vec, id: &str) -> String { 62 | let func_str = func_strings.swap_remove( 63 | func_strings.iter().position(|func_str| func_str.id == id) 64 | .unwrap() 65 | ).full_str; 66 | func_str 67 | } 68 | 69 | fn normalize_func_string(mut func_str: String) -> String { 70 | func_str = apply_transformations(func_str); 71 | func_str = apply_regex_patterns(func_str); 72 | func_str 73 | } 74 | 75 | pub fn parse_functions_from_script(script: &str) -> (TransformedFunctions, StringResolver) { 76 | 77 | let (mut func_strings, string_resolver) = extract::parse_top_level_funcs(&script); 78 | let (relevant_func_ids, opcode_map) = find_func_identifier(&func_strings); 79 | 80 | func_strings = remove_unnecessary_funcs(func_strings, &relevant_func_ids); 81 | 82 | for func_str in func_strings.iter_mut() { 83 | 84 | #[cfg(debug_assertions)] 85 | std::fs::write(format!("./debug/functions/{}.js", func_str.id), func_str.full_str.as_bytes()).unwrap(); 86 | 87 | //println!("{}", func_str.full_str); 88 | 89 | func_str.full_str = apply_transformations(std::mem::take(&mut func_str.full_str)); 90 | func_str.full_str = apply_regex_patterns(std::mem::take(&mut func_str.full_str)); 91 | //func_str.full_str = normalize_func_string(func_str.full_str.clone()) 92 | 93 | #[cfg(debug_assertions)] 94 | std::fs::write(format!("./debug/functions/{}.js", func_str.id), func_str.full_str.as_bytes()).unwrap(); 95 | } 96 | 97 | //panic!(); 98 | //dbg!(&relevant_func_ids); 99 | 100 | ( 101 | TransformedFunctions { 102 | vm_init: take_func_str(&mut func_strings, &relevant_func_ids.vm_builder), 103 | vm_prep: take_func_str(&mut func_strings, &relevant_func_ids.vm_prep), 104 | vm_execute: take_func_str(&mut func_strings, &relevant_func_ids.vm_execute), 105 | while_bitwise: take_func_str(&mut func_strings, &relevant_func_ids.while_bitwise), 106 | init_payload: take_func_str(&mut func_strings, &relevant_func_ids.init_payload), 107 | init_payload_obj: take_func_str(&mut func_strings, &relevant_func_ids.init_payload_obj), 108 | 109 | instructions: func_strings.drain(..).map(|fn_str| { 110 | InstructionFunc { 111 | opcode: *opcode_map.get(&fn_str.id).unwrap(), 112 | expressions: fn_str.full_str.lines().map(str::to_owned).collect() 113 | } 114 | }).collect() 115 | }, 116 | string_resolver 117 | ) 118 | 119 | 120 | } -------------------------------------------------------------------------------- /src/strings.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashSet, VecDeque}; 2 | 3 | use base64::{prelude::BASE64_STANDARD, Engine}; 4 | 5 | use crate::script::extract::FuncString; 6 | 7 | pub struct StringResolver { 8 | strings: VecDeque, 9 | offset: usize 10 | } 11 | 12 | impl StringResolver { 13 | 14 | fn find_str_func(funcs: &mut Vec) -> VecDeque { 15 | let index = funcs.iter() 16 | .position(|func| func.id == "a") 17 | .unwrap(); 18 | let func = funcs.swap_remove(index); 19 | let strings = func.full_str.split('\'') 20 | .nth(1) 21 | .unwrap() 22 | .split('~') 23 | .map(String::from) 24 | .collect::>(); 25 | strings 26 | } 27 | 28 | fn find_offset(funcs: &mut Vec) -> usize { 29 | let index = funcs.iter() 30 | .position(|func| func.id == "b") 31 | .unwrap(); 32 | let func = funcs.swap_remove(index); 33 | let offset = func.full_str.split_once('-') 34 | .unwrap().1 35 | .split_once(',') 36 | .unwrap().0 37 | .trim() 38 | .parse::() 39 | .unwrap(); 40 | offset 41 | } 42 | 43 | fn is_parse_int_not_nan(string: &str) -> bool { 44 | let first_char = string.chars() 45 | .nth(0) 46 | .unwrap(); 47 | char::is_ascii_digit(&first_char) 48 | } 49 | 50 | fn rearrange_strings(&mut self, rearrange_func_str: &str) { 51 | let indices = rearrange_func_str.split(['(', ')']) 52 | .filter(|s| s.len() != 0 && s.chars().all(|c| char::is_ascii_digit(&c))) 53 | .map(|s| s.parse().unwrap()) 54 | .collect::>(); 55 | 56 | loop { 57 | let done = indices.iter() 58 | .all(|index| StringResolver::is_parse_int_not_nan(self.get(*index).unwrap())); 59 | 60 | if done { 61 | break 62 | } else { 63 | self.strings.rotate_left(1); 64 | } 65 | } 66 | 67 | } 68 | 69 | pub fn new(funcs: &mut Vec) -> Self { 70 | let strings = Self::find_str_func(funcs); 71 | let offset = Self::find_offset(funcs); 72 | 73 | let mut string_map = Self { 74 | strings, offset 75 | }; 76 | 77 | let rearrange_func = funcs.swap_remove(0); 78 | string_map.rearrange_strings(&rearrange_func.full_str); 79 | 80 | string_map 81 | } 82 | 83 | pub fn get_init_bytecode(&self) -> String { 84 | self.strings.iter() 85 | .find(|s| s.len() > 300 && s.len() < 500 && BASE64_STANDARD.decode(s).is_ok()) 86 | .unwrap() 87 | .clone() 88 | } 89 | 90 | pub fn get_compressor_charset(&self) -> String { 91 | self.strings.iter().find(|s| { 92 | s.len() == 65 && 93 | HashSet::::from_iter(s.bytes()).len() == 65 94 | }) 95 | .unwrap() 96 | .clone() 97 | } 98 | 99 | pub fn get_collector_ep_val(&self) -> String { 100 | self.strings.iter().find(|s| { 101 | s.starts_with("/") && 102 | s.split(":").count() == 3 103 | }) 104 | .unwrap() 105 | .clone() 106 | } 107 | 108 | pub fn get(&self, index: usize) -> Option<&String> { 109 | if self.offset > index { 110 | return None 111 | } 112 | self.strings.get(index - self.offset) 113 | } 114 | } -------------------------------------------------------------------------------- /src/task.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[allow(non_snake_case)] 4 | #[derive(Deserialize, Serialize, Debug, Default, Clone)] 5 | pub struct TurnstileChallengeData { 6 | pub cvId: String, 7 | pub cZone: String, 8 | pub cTplV: i32, 9 | pub chlApivId: String, 10 | pub chlApiWidgetId: String, 11 | pub chlApiSitekey: String, 12 | pub chlApiMode: String, 13 | pub chlApiSize: String, 14 | pub chlApiRcV: String, 15 | pub chlApiTimeoutEncountered: i32, 16 | pub chlApiOverrunBudgetMs: i32, 17 | pub chlTimeoutMs: i32, 18 | pub cType: String, 19 | pub cRay: String, 20 | pub cH: String, 21 | pub cFPWv: String, 22 | pub cLt: String, 23 | pub chlApiFailureFeedbackEnabled: bool, 24 | pub chlApiLoopFeedbackEnabled: bool, 25 | pub wOL: bool, 26 | pub wT: String, 27 | pub wS: String, 28 | pub md: String, 29 | pub cITimeS: String 30 | } 31 | 32 | impl TurnstileChallengeData { 33 | pub fn from_html(html: String) -> TurnstileChallengeData { 34 | let start = html.find("window._cf_chl_opt={").unwrap() + "window._cf_chl_opt={".len(); 35 | let end = html[start..].find("refresh").unwrap() + start; 36 | 37 | //let mut map = serde_json::Map::new(); 38 | 39 | let k_v_pairs: Vec<(String, serde_json::Value)> = html[start..end].lines() 40 | .filter(|l| !l.trim().is_empty()) 41 | .map(|line| line.split_once(':').unwrap()) 42 | .map(|(key, val)| { 43 | let val = val.trim().trim_matches(','); 44 | let val = match val { 45 | "true" => serde_json::Value::Bool(true), 46 | "false" => serde_json::Value::Bool(false), 47 | _ if val.chars().all(|c| char::is_ascii_digit(&c)) => serde_json::Value::Number(val.parse().unwrap()), 48 | _ => serde_json::Value::String(val.trim_matches('\'').to_string()), 49 | 50 | }; 51 | (key.trim().to_string(), val) 52 | }) 53 | .collect(); 54 | 55 | let map: serde_json::Map = k_v_pairs.into_iter().collect(); 56 | let object = serde_json::Value::Object(map); 57 | 58 | let challenge: TurnstileChallengeData = serde_json::from_value(object).unwrap(); 59 | 60 | challenge 61 | } 62 | } --------------------------------------------------------------------------------