cfsolve 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +10 -9
- data/UPSTREAM.md +6 -6
- data/lib/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
- data/lib/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
- data/lib/cfsolve/profiles/safari-26-ios.json +0 -8
- data/lib/cfsolve/version.rb +2 -2
- data/vendor/cfsolve/README.md +0 -35
- data/vendor/cfsolve/examples/build_profile.rs +17 -15
- data/vendor/cfsolve/examples/capture_session.rs +37 -0
- data/vendor/cfsolve/examples/check_answers.rs +66 -0
- data/vendor/cfsolve/examples/compare_session.rs +190 -0
- data/vendor/cfsolve/examples/stage2_snapshot.rs +4 -1
- data/vendor/cfsolve/profiles/safari-26-ios/answers.json +2562 -1887
- data/vendor/cfsolve/profiles/safari-26-ios/challenge-page-answers.json +1873 -1198
- data/vendor/cfsolve/profiles/safari-26-ios.json +0 -8
- data/vendor/cfsolve/scripts/capture_cssom.mjs +42 -0
- data/vendor/cfsolve/scripts/serve_cssom.py +111 -0
- data/vendor/cfsolve/src/capture/profile.rs +7 -18
- data/vendor/cfsolve/src/capture/recorded.rs +42 -7
- data/vendor/cfsolve/src/challenge/installed/audit.rs +131 -0
- data/vendor/cfsolve/src/challenge/installed.rs +122 -271
- data/vendor/cfsolve/src/challenge/payload.rs +107 -5
- data/vendor/cfsolve/src/challenge/state.rs +98 -16
- data/vendor/cfsolve/src/deobfuscate/encoded_strings.rs +3 -2
- data/vendor/cfsolve/src/deobfuscate/helper_objects.rs +17 -32
- data/vendor/cfsolve/src/deobfuscate/register_arrays.rs +18 -5
- data/vendor/cfsolve/src/embedding_page/extra_params.rs +0 -14
- data/vendor/cfsolve/src/embedding_page/page.rs +28 -2
- data/vendor/cfsolve/src/http/client.rs +11 -0
- data/vendor/cfsolve/src/js/ast.rs +12 -3
- data/vendor/cfsolve/src/js/objects.rs +9 -2
- data/vendor/cfsolve/src/listing/fold.rs +0 -83
- data/vendor/cfsolve/src/listing/query.rs +25 -244
- data/vendor/cfsolve/src/listing.rs +0 -1
- data/vendor/cfsolve/src/session/initial.rs +14 -5
- data/vendor/cfsolve/src/session/probes.rs +160 -125
- data/vendor/cfsolve/src/session/timing.rs +58 -53
- data/vendor/cfsolve/src/session/widget.rs +5 -6
- data/vendor/cfsolve/src/stage2/analysis/analyze.rs +50 -33
- data/vendor/cfsolve/src/stage2/analysis/discovery.rs +61 -66
- data/vendor/cfsolve/src/stage2/analysis/envelope.rs +31 -21
- data/vendor/cfsolve/src/stage2/analysis/recognize.rs +3 -5
- data/vendor/cfsolve/src/stage2/analysis.rs +0 -1
- data/vendor/cfsolve/src/stage2/audit_globals.rs +310 -0
- data/vendor/cfsolve/src/stage2/benchmark.rs +5 -8
- data/vendor/cfsolve/src/stage2/call_stack.rs +72 -16
- data/vendor/cfsolve/src/stage2/interaction/native.rs +2 -1
- data/vendor/cfsolve/src/stage2/interaction/plan.rs +0 -3
- data/vendor/cfsolve/src/stage2/payload/recover.rs +54 -23
- data/vendor/cfsolve/src/stage2/probes/ice.rs +11 -10
- data/vendor/cfsolve/src/stage2/probes/media.rs +47 -33
- data/vendor/cfsolve/src/stage2/probes/performance.rs +1 -0
- data/vendor/cfsolve/src/stage2/probes/rects.rs +12 -17
- data/vendor/cfsolve/src/stage2/probes/stack.rs +50 -103
- data/vendor/cfsolve/src/stage2/probes/websocket_echo.rs +17 -86
- data/vendor/cfsolve/src/stage2/probes.rs +0 -1
- data/vendor/cfsolve/src/stage2/records.rs +4 -13
- data/vendor/cfsolve/src/stage2/reference.json +1 -1
- data/vendor/cfsolve/src/stage2/reference.rs +98 -100
- data/vendor/cfsolve/src/stage2/registry.rs +41 -34
- data/vendor/cfsolve/src/stage2/replay.rs +182 -254
- data/vendor/cfsolve/src/stage2/stylesheets.rs +176 -0
- data/vendor/cfsolve/src/stage2/trace.rs +18 -0
- data/vendor/cfsolve/src/stage2/vocabulary.rs +111 -401
- data/vendor/cfsolve/src/stage2.rs +2 -1
- data/vendor/cfsolve/src/vm/disassemble.rs +39 -10
- data/vendor/cfsolve/src/vm/interpret/access.rs +5 -21
- data/vendor/cfsolve/src/vm/interpret/builtin.rs +6 -53
- data/vendor/cfsolve/src/vm/interpret/call.rs +86 -9
- data/vendor/cfsolve/src/vm/interpret/compile.rs +626 -0
- data/vendor/cfsolve/src/vm/interpret/copies.rs +232 -120
- data/vendor/cfsolve/src/vm/interpret/evaluate/tests.rs +96 -8
- data/vendor/cfsolve/src/vm/interpret/evaluate.rs +219 -92
- data/vendor/cfsolve/src/vm/interpret/explore.rs +155 -39
- data/vendor/cfsolve/src/vm/interpret/frozen.rs +171 -0
- data/vendor/cfsolve/src/vm/interpret/intrinsic.rs +350 -0
- data/vendor/cfsolve/src/vm/interpret/plan.rs +71 -44
- data/vendor/cfsolve/src/vm/interpret/run.rs +202 -13
- data/vendor/cfsolve/src/vm/interpret.rs +7 -2
- data/vendor/cfsolve/src/vm/machine.rs +6 -2
- data/vendor/cfsolve/src/vm/packed_names.rs +307 -0
- data/vendor/cfsolve/src/vm/session.rs +36 -14
- data/vendor/cfsolve/src/vm/value.rs +13 -0
- data/vendor/cfsolve/src/vm.rs +1 -0
- data/vendor/cfsolve/vendor/btls-sys/src/lib.rs +3 -0
- metadata +13 -6
- data/vendor/cfsolve/src/listing/normalize.rs +0 -264
- data/vendor/cfsolve/src/stage2/analysis/outputs.rs +0 -327
- data/vendor/cfsolve/src/stage2/probes/stylesheets.rs +0 -249
- data/vendor/cfsolve/src/stage2/reference-expanded.json +0 -42
- data/vendor/cfsolve/src/stage2/session_names.rs +0 -470
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
//! journal that undoes speculative attempts.
|
|
3
3
|
|
|
4
4
|
use std::borrow::Cow;
|
|
5
|
+
use std::cell::RefCell;
|
|
5
6
|
use std::hash::Hash;
|
|
6
7
|
use std::mem;
|
|
7
8
|
use std::rc::Rc;
|
|
@@ -10,12 +11,13 @@ use swc_core::atoms::Atom;
|
|
|
10
11
|
use swc_core::ecma::ast::{BinaryOp, UnaryOp};
|
|
11
12
|
|
|
12
13
|
use super::builtin;
|
|
13
|
-
use super::copies::{self, Copies};
|
|
14
|
+
use super::copies::{self, Copies, Fork};
|
|
14
15
|
use super::explore::Invocation;
|
|
16
|
+
use super::frozen::{self, Frozen};
|
|
15
17
|
use crate::vm::output::{Constant, Effect, Exit, Path, Position, Term};
|
|
16
18
|
use crate::vm::session::Session;
|
|
17
19
|
use crate::vm::value::{Callable, Env, Scope, Value};
|
|
18
|
-
use crate::vm::{HashMap, convert};
|
|
20
|
+
use crate::vm::{HashMap, convert, disassemble};
|
|
19
21
|
|
|
20
22
|
/// Why evaluation stopped before completing normally.
|
|
21
23
|
pub(crate) enum Stop<'ast> {
|
|
@@ -51,6 +53,8 @@ pub(crate) struct Run<'ast, 'session> {
|
|
|
51
53
|
pub(super) program: &'session [u8],
|
|
52
54
|
/// Instance fields derived from program bytes by the constructor, once known.
|
|
53
55
|
constructed: Option<&'session HashMap<String, Value<'ast>>>,
|
|
56
|
+
/// Strings decrypted from the program's records, once a program is loaded.
|
|
57
|
+
pub(super) decrypted: Option<&'session Decrypted<'ast>>,
|
|
54
58
|
pub(super) registers: HashMap<u8, Value<'ast>>,
|
|
55
59
|
/// Tags written before their register values; see [`crate::vm::registers`].
|
|
56
60
|
pub(super) tags: HashMap<u8, Value<'ast>>,
|
|
@@ -76,6 +80,23 @@ pub(crate) struct Run<'ast, 'session> {
|
|
|
76
80
|
facts: Vec<(Term, Value<'ast>)>,
|
|
77
81
|
journal: Journal<'ast>,
|
|
78
82
|
pub(super) spare: Spare<'ast>,
|
|
83
|
+
/// Whether the run shares [`Frozen`] session objects instead of copying them.
|
|
84
|
+
pub(super) sharing: bool,
|
|
85
|
+
/// Whether the run wrote to a shared object, thawing it. Its results are discarded.
|
|
86
|
+
pub(super) thawed: bool,
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Strings decrypted from a program's string records, by record contents (see
|
|
90
|
+
/// [`RecordKey`]).
|
|
91
|
+
pub(crate) type Decrypted<'ast> = RefCell<HashMap<RecordKey, Value<'ast>>>;
|
|
92
|
+
|
|
93
|
+
/// The contents of a string record after the program: numbers and arrays of numbers, as bits.
|
|
94
|
+
pub(crate) type RecordKey = Vec<RecordPart>;
|
|
95
|
+
|
|
96
|
+
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
97
|
+
pub(crate) enum RecordPart {
|
|
98
|
+
Number(u64),
|
|
99
|
+
Numbers(Vec<u64>),
|
|
79
100
|
}
|
|
80
101
|
|
|
81
102
|
/// Cleared buffers from completed calls. Handler runs take them from the session and
|
|
@@ -222,6 +243,7 @@ impl<'ast, 'session> Run<'ast, 'session> {
|
|
|
222
243
|
session,
|
|
223
244
|
program,
|
|
224
245
|
constructed: None,
|
|
246
|
+
decrypted: None,
|
|
225
247
|
registers: HashMap::default(),
|
|
226
248
|
tags: HashMap::default(),
|
|
227
249
|
fields: HashMap::default(),
|
|
@@ -240,21 +262,15 @@ impl<'ast, 'session> Run<'ast, 'session> {
|
|
|
240
262
|
facts: Vec::new(),
|
|
241
263
|
journal: Journal::default(),
|
|
242
264
|
spare: Spare::default(),
|
|
265
|
+
sharing: false,
|
|
266
|
+
thawed: false,
|
|
243
267
|
}
|
|
244
268
|
}
|
|
245
269
|
|
|
246
270
|
/// A handler run that follows `decisions`, then takes any further symbolic branch as true.
|
|
247
271
|
pub(super) fn handler(invocation: &Invocation<'session, 'ast>, decisions: Vec<bool>) -> Self {
|
|
248
|
-
let
|
|
249
|
-
|
|
250
|
-
session,
|
|
251
|
-
invocation.program,
|
|
252
|
-
decisions,
|
|
253
|
-
Some(Copies::default()),
|
|
254
|
-
);
|
|
255
|
-
run.constructed = Some(invocation.fields);
|
|
256
|
-
run.spare = session.spare.take();
|
|
257
|
-
for (register, value) in &session.initial {
|
|
272
|
+
let mut run = Self::invoked(invocation, decisions, Some(Copies::default()));
|
|
273
|
+
for (register, value) in &invocation.session.initial {
|
|
258
274
|
let value = run.isolate(value);
|
|
259
275
|
run.registers.insert(*register, value);
|
|
260
276
|
}
|
|
@@ -263,6 +279,20 @@ impl<'ast, 'session> Run<'ast, 'session> {
|
|
|
263
279
|
run
|
|
264
280
|
}
|
|
265
281
|
|
|
282
|
+
/// A run of `invocation`'s handler with no registers set, sharing frozen session objects.
|
|
283
|
+
fn invoked(
|
|
284
|
+
invocation: &Invocation<'session, 'ast>,
|
|
285
|
+
decisions: Vec<bool>,
|
|
286
|
+
copies: Option<Copies<'ast>>,
|
|
287
|
+
) -> Self {
|
|
288
|
+
let mut run = Self::new(invocation.session, invocation.program, decisions, copies);
|
|
289
|
+
run.constructed = Some(invocation.fields);
|
|
290
|
+
run.decrypted = Some(invocation.decrypted);
|
|
291
|
+
run.spare = invocation.session.spare.take();
|
|
292
|
+
run.sharing = true;
|
|
293
|
+
run
|
|
294
|
+
}
|
|
295
|
+
|
|
266
296
|
/// Sets the program counter to `pc` and the dispatch key to `key`.
|
|
267
297
|
pub(super) fn start_at(&mut self, pc: u32, key: u8) {
|
|
268
298
|
let layout = self.session.layout;
|
|
@@ -311,6 +341,19 @@ impl<'ast, 'session> Run<'ast, 'session> {
|
|
|
311
341
|
},
|
|
312
342
|
},
|
|
313
343
|
};
|
|
344
|
+
// A catch handler can be pushed onto a constructor-created register stack.
|
|
345
|
+
// Native array operations mutate that stack without producing an effect; retain
|
|
346
|
+
// its frame so disassembly can also visit the exception continuation.
|
|
347
|
+
for (register, _) in &self.session.initial {
|
|
348
|
+
if let Some(value @ Value::Array(elements)) = self.registers.get(register)
|
|
349
|
+
&& elements.borrow().iter().any(|element| {
|
|
350
|
+
matches!(element.expr(), Term::Array(ref frame)
|
|
351
|
+
if disassemble::catch_frame(frame).is_some())
|
|
352
|
+
})
|
|
353
|
+
{
|
|
354
|
+
self.effects.push(Effect::Set(*register, value.expr()));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
314
357
|
// The listing retains every path, so release unused vector capacity.
|
|
315
358
|
let mut conditions = self.branches.conditions;
|
|
316
359
|
conditions.shrink_to_fit();
|
|
@@ -578,10 +621,156 @@ impl<'ast, 'session> Run<'ast, 'session> {
|
|
|
578
621
|
|
|
579
622
|
/// Copies a session value for use by this run, preserving run-local sharing.
|
|
580
623
|
pub(super) fn isolate(&mut self, value: &Value<'ast>) -> Value<'ast> {
|
|
624
|
+
let frozen = self.frozen();
|
|
581
625
|
let Some(copies) = &mut self.copies else {
|
|
582
626
|
return value.clone();
|
|
583
627
|
};
|
|
584
|
-
copies::copy(value, copies)
|
|
628
|
+
copies::copy(value, copies, frozen)
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/// The session objects this run shares, if it shares them.
|
|
632
|
+
fn frozen(&self) -> Option<&'session Frozen> {
|
|
633
|
+
self.sharing.then_some(&self.session.frozen)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/// Checks that the run may write to `object`. A write to a shared session object thaws its
|
|
637
|
+
/// group and stops the run, whose caller reruns the instruction with the group copied.
|
|
638
|
+
pub(super) fn writable(&mut self, object: &Value<'ast>) -> Evaluation<'ast, ()> {
|
|
639
|
+
if let Some(frozen) = self.frozen()
|
|
640
|
+
&& let Some(address) = frozen::address(object)
|
|
641
|
+
&& frozen.contains(address)
|
|
642
|
+
{
|
|
643
|
+
frozen.thaw(address);
|
|
644
|
+
self.thawed = true;
|
|
645
|
+
return Err(Stop::unsupported("a write to a shared session object"));
|
|
646
|
+
}
|
|
647
|
+
Ok(())
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/// Takes a snapshot of a handler run before top-level statement `statement`, with the
|
|
651
|
+
/// handler's `locals`. Returns `None` if the run is inside a speculative attempt or setup
|
|
652
|
+
/// changed which names are unavailable, which a snapshot does not restore.
|
|
653
|
+
pub(super) fn snapshot(
|
|
654
|
+
&self,
|
|
655
|
+
statement: usize,
|
|
656
|
+
locals: &[Option<Value<'ast>>],
|
|
657
|
+
) -> Option<Snapshot<'ast>> {
|
|
658
|
+
if self.journal.recording()
|
|
659
|
+
|| matches!(self.unavailable, Cow::Owned(_))
|
|
660
|
+
|| self.frames.len() != 1
|
|
661
|
+
|| self.setup.is_some()
|
|
662
|
+
|| self.constructed.is_none()
|
|
663
|
+
{
|
|
664
|
+
return None;
|
|
665
|
+
}
|
|
666
|
+
let mut fork = Fork::new(self.frozen());
|
|
667
|
+
let frame = self.frame();
|
|
668
|
+
Some(Snapshot {
|
|
669
|
+
statement,
|
|
670
|
+
decided: self.branches.decisions[..self.branches.decided].to_vec(),
|
|
671
|
+
registers: fork.map(&self.registers),
|
|
672
|
+
tags: fork.map(&self.tags),
|
|
673
|
+
fields: fork.map(&self.fields),
|
|
674
|
+
globals: fork.map(&self.globals),
|
|
675
|
+
copies: self.copies.as_ref().map(|copies| fork.copies(copies)),
|
|
676
|
+
closed: self.closed.iter().map(|scope| fork.scope(scope)).collect(),
|
|
677
|
+
effects: self.effects.clone(),
|
|
678
|
+
temps: self.temps,
|
|
679
|
+
conditions: self.branches.conditions.clone(),
|
|
680
|
+
facts: self
|
|
681
|
+
.facts
|
|
682
|
+
.iter()
|
|
683
|
+
.map(|(term, value)| (term.clone(), fork.value(value)))
|
|
684
|
+
.collect(),
|
|
685
|
+
this: fork.value(&frame.this),
|
|
686
|
+
scope: fork.scope(&frame.scope),
|
|
687
|
+
locals: locals
|
|
688
|
+
.iter()
|
|
689
|
+
.map(|local| local.as_ref().map(|value| fork.value(value)))
|
|
690
|
+
.collect(),
|
|
691
|
+
copied: fork.copied,
|
|
692
|
+
})
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/// A handler run resumed from `snapshot`, following `decisions`, whose first decisions are
|
|
696
|
+
/// the snapshot's. Returns the run, the handler's locals, and the scopes copied for it,
|
|
697
|
+
/// which the caller clears when the run ends.
|
|
698
|
+
pub(super) fn resume(
|
|
699
|
+
invocation: &Invocation<'session, 'ast>,
|
|
700
|
+
snapshot: &Snapshot<'ast>,
|
|
701
|
+
decisions: Vec<bool>,
|
|
702
|
+
) -> (Self, Vec<Option<Value<'ast>>>, Vec<Scope<'ast>>) {
|
|
703
|
+
let mut fork = Fork::new(Some(&invocation.session.frozen));
|
|
704
|
+
let copies = snapshot.copies.as_ref().map(|copies| fork.copies(copies));
|
|
705
|
+
let mut run = Self::invoked(invocation, decisions, copies);
|
|
706
|
+
run.registers = fork.map(&snapshot.registers);
|
|
707
|
+
run.tags = fork.map(&snapshot.tags);
|
|
708
|
+
run.fields = fork.map(&snapshot.fields);
|
|
709
|
+
run.globals = fork.map(&snapshot.globals);
|
|
710
|
+
run.closed = snapshot
|
|
711
|
+
.closed
|
|
712
|
+
.iter()
|
|
713
|
+
.map(|scope| fork.scope(scope))
|
|
714
|
+
.collect();
|
|
715
|
+
run.effects = snapshot.effects.clone();
|
|
716
|
+
run.temps = snapshot.temps;
|
|
717
|
+
run.branches.decided = snapshot.decided.len();
|
|
718
|
+
run.branches.conditions = snapshot.conditions.clone();
|
|
719
|
+
run.facts = snapshot
|
|
720
|
+
.facts
|
|
721
|
+
.iter()
|
|
722
|
+
.map(|(term, value)| (term.clone(), fork.value(value)))
|
|
723
|
+
.collect();
|
|
724
|
+
run.frames.push(Frame {
|
|
725
|
+
this: fork.value(&snapshot.this),
|
|
726
|
+
scope: fork.scope(&snapshot.scope),
|
|
727
|
+
});
|
|
728
|
+
let locals = snapshot
|
|
729
|
+
.locals
|
|
730
|
+
.iter()
|
|
731
|
+
.map(|local| local.as_ref().map(|value| fork.value(value)))
|
|
732
|
+
.collect();
|
|
733
|
+
(run, locals, fork.copied)
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/// A handler run's state before one of the top-level statements of its body. Paths that share
|
|
738
|
+
/// the decisions made before that statement resume here instead of rerunning the statements
|
|
739
|
+
/// before it.
|
|
740
|
+
pub(super) struct Snapshot<'ast> {
|
|
741
|
+
/// The top-level statement to resume at.
|
|
742
|
+
pub(super) statement: usize,
|
|
743
|
+
/// The decisions consumed before the statement.
|
|
744
|
+
decided: Vec<bool>,
|
|
745
|
+
registers: HashMap<u8, Value<'ast>>,
|
|
746
|
+
tags: HashMap<u8, Value<'ast>>,
|
|
747
|
+
fields: HashMap<String, Value<'ast>>,
|
|
748
|
+
globals: HashMap<Atom, Value<'ast>>,
|
|
749
|
+
copies: Option<Copies<'ast>>,
|
|
750
|
+
closed: Vec<Scope<'ast>>,
|
|
751
|
+
effects: Vec<Effect>,
|
|
752
|
+
temps: usize,
|
|
753
|
+
conditions: Vec<(Term, bool)>,
|
|
754
|
+
facts: Vec<(Term, Value<'ast>)>,
|
|
755
|
+
this: Value<'ast>,
|
|
756
|
+
scope: Scope<'ast>,
|
|
757
|
+
locals: Vec<Option<Value<'ast>>>,
|
|
758
|
+
/// Every scope the snapshot holds, cleared when it is dropped to break cycles.
|
|
759
|
+
copied: Vec<Scope<'ast>>,
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
impl Drop for Snapshot<'_> {
|
|
763
|
+
fn drop(&mut self) {
|
|
764
|
+
for scope in &self.copied {
|
|
765
|
+
scope.borrow_mut().variables.clear();
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
impl<'ast> Snapshot<'ast> {
|
|
771
|
+
/// Whether a run following `decisions` makes the same decisions before the statement.
|
|
772
|
+
pub(super) fn resumes(&self, decisions: &[bool]) -> bool {
|
|
773
|
+
decisions.starts_with(&self.decided)
|
|
585
774
|
}
|
|
586
775
|
}
|
|
587
776
|
|
|
@@ -11,16 +11,21 @@
|
|
|
11
11
|
mod access;
|
|
12
12
|
mod builtin;
|
|
13
13
|
mod call;
|
|
14
|
+
mod compile;
|
|
14
15
|
mod copies;
|
|
15
16
|
mod evaluate;
|
|
16
17
|
mod explore;
|
|
18
|
+
mod frozen;
|
|
19
|
+
mod intrinsic;
|
|
17
20
|
mod plan;
|
|
18
21
|
mod run;
|
|
19
22
|
mod semantics;
|
|
20
23
|
mod setup;
|
|
21
24
|
|
|
22
25
|
pub(crate) use builtin::Builtin;
|
|
23
|
-
pub(crate) use explore::{Invocation, MAXIMUM_PATHS, Way, decode, explore};
|
|
26
|
+
pub(crate) use explore::{Branching, Invocation, MAXIMUM_PATHS, Way, decode, explore};
|
|
27
|
+
pub(crate) use frozen::Frozen;
|
|
28
|
+
pub(crate) use intrinsic::{Intrinsics, intrinsics};
|
|
24
29
|
pub(crate) use plan::Plans;
|
|
25
|
-
pub(crate) use run::{Run, Spare};
|
|
30
|
+
pub(crate) use run::{Decrypted, Run, Spare};
|
|
26
31
|
pub(crate) use setup::setup;
|
|
@@ -6,7 +6,7 @@ use swc_core::ecma::ast::{BlockStmt, Class, Script, Stmt};
|
|
|
6
6
|
use super::HashMap;
|
|
7
7
|
use super::dispatch::{Decoded, Dispatch};
|
|
8
8
|
use super::extract::{self, Finder};
|
|
9
|
-
use super::interpret::{self, Invocation, MAXIMUM_PATHS, Way};
|
|
9
|
+
use super::interpret::{self, Decrypted, Invocation, MAXIMUM_PATHS, Way};
|
|
10
10
|
use super::output::{Cause, Failure, Instruction, Position};
|
|
11
11
|
use super::session::Session;
|
|
12
12
|
use super::value::Value;
|
|
@@ -48,11 +48,13 @@ pub struct Machine<'definition> {
|
|
|
48
48
|
pub(super) session: Session<'definition>,
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/// A program, with the instance fields the constructor derives from it
|
|
51
|
+
/// A program, with the instance fields the constructor derives from it and the strings
|
|
52
|
+
/// decrypted from it so far.
|
|
52
53
|
#[derive(Clone)]
|
|
53
54
|
pub(super) struct Loaded<'program, 'definition> {
|
|
54
55
|
program: &'program [u8],
|
|
55
56
|
pub(super) fields: HashMap<String, Value<'definition>>,
|
|
57
|
+
decrypted: Decrypted<'definition>,
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
impl<'definition> Machine<'definition> {
|
|
@@ -69,6 +71,7 @@ impl<'definition> Machine<'definition> {
|
|
|
69
71
|
Loaded {
|
|
70
72
|
program,
|
|
71
73
|
fields: self.session.constructed(program),
|
|
74
|
+
decrypted: Decrypted::default(),
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
|
|
@@ -144,6 +147,7 @@ impl<'definition> Machine<'definition> {
|
|
|
144
147
|
session: &self.session,
|
|
145
148
|
program: loaded.program,
|
|
146
149
|
fields: &loaded.fields,
|
|
150
|
+
decrypted: &loaded.decrypted,
|
|
147
151
|
pc: pc + 1,
|
|
148
152
|
key: next,
|
|
149
153
|
handler,
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
//! Folds names the program unpacks at run time. Some programs store a name as
|
|
2
|
+
//! `"\0\x19" + name + NUL padding` and unpack it with
|
|
3
|
+
//! `text.slice(2, 256 * text.charCodeAt(0) + text.charCodeAt(1) + 2)`. Folding `charCodeAt`,
|
|
4
|
+
//! `slice`, `+` and `*` on constant registers rewrites each unpacking call to the resulting
|
|
5
|
+
//! string, so readers of the listing see an ordinary string load.
|
|
6
|
+
|
|
7
|
+
use std::collections::{BTreeMap, HashMap, HashSet};
|
|
8
|
+
use std::convert::Infallible;
|
|
9
|
+
use std::ops::ControlFlow;
|
|
10
|
+
use std::rc::Rc;
|
|
11
|
+
|
|
12
|
+
use swc_core::ecma::ast::BinaryOp;
|
|
13
|
+
|
|
14
|
+
use super::output::{Constant, Effect, Listing, Path, Term};
|
|
15
|
+
use super::{dataflow, disassemble};
|
|
16
|
+
|
|
17
|
+
/// Constant registers on entry to an instruction, shared as the dataflow copies them per path.
|
|
18
|
+
type Registers = BTreeMap<u8, Rc<Constant>>;
|
|
19
|
+
|
|
20
|
+
/// Constants held by temporaries.
|
|
21
|
+
type Temporaries = HashMap<usize, Rc<Constant>>;
|
|
22
|
+
|
|
23
|
+
pub(super) fn fold(listing: &mut Listing) {
|
|
24
|
+
// Unrecorded entries, such as functions and hidden targets, start with nothing known.
|
|
25
|
+
let functions = listing.functions.iter().map(|function| function.pc);
|
|
26
|
+
let hidden = listing.hidden.iter().map(|hidden| hidden.target.pc);
|
|
27
|
+
let incoming = forward(listing, functions.chain(hidden));
|
|
28
|
+
// A catch path must not erase facts already recovered on the normal path.
|
|
29
|
+
// Fold its exclusive instructions separately with unknown entry registers.
|
|
30
|
+
let exceptional = forward(listing, disassemble::exception_entries(listing));
|
|
31
|
+
for (pc, instruction) in &mut listing.instructions {
|
|
32
|
+
let Some(entry) = incoming.get(pc).or_else(|| exceptional.get(pc)) else {
|
|
33
|
+
continue;
|
|
34
|
+
};
|
|
35
|
+
for path in &mut instruction.paths {
|
|
36
|
+
rewrite(path, entry.clone());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// The constant registers on entry to each instruction reachable from `entries`.
|
|
42
|
+
fn forward(listing: &Listing, entries: impl IntoIterator<Item = u32>) -> BTreeMap<u32, Registers> {
|
|
43
|
+
let entries = entries.into_iter().map(|pc| (pc, Registers::new()));
|
|
44
|
+
let ControlFlow::Continue(incoming) = dataflow::forward(listing, entries, |_, path, known| {
|
|
45
|
+
let mut temporaries = Temporaries::new();
|
|
46
|
+
for effect in &path.effects {
|
|
47
|
+
step(effect, known, &mut temporaries);
|
|
48
|
+
}
|
|
49
|
+
ControlFlow::<Infallible, bool>::Continue(true)
|
|
50
|
+
});
|
|
51
|
+
incoming
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Rewrites folded string-method results in `path` to constants.
|
|
55
|
+
fn rewrite(path: &mut Path, mut known: Registers) {
|
|
56
|
+
let mut temporaries = Temporaries::new();
|
|
57
|
+
// Temporaries holding a folded call.
|
|
58
|
+
let mut folded_temporaries = HashSet::new();
|
|
59
|
+
for effect in &mut path.effects {
|
|
60
|
+
let folded = match &*effect {
|
|
61
|
+
// Only `charCodeAt` and `slice` calls fold.
|
|
62
|
+
Effect::Let(_, term @ Term::Call { .. }) | Effect::Set(_, term @ Term::Call { .. }) => {
|
|
63
|
+
value(term, &known, &temporaries)
|
|
64
|
+
}
|
|
65
|
+
Effect::Set(_, Term::Temp(temporary)) if folded_temporaries.contains(temporary) => {
|
|
66
|
+
temporaries.get(temporary).cloned()
|
|
67
|
+
}
|
|
68
|
+
_ => None,
|
|
69
|
+
};
|
|
70
|
+
step(effect, &mut known, &mut temporaries);
|
|
71
|
+
if let Effect::Let(temporary, _) = effect {
|
|
72
|
+
if folded.is_some() {
|
|
73
|
+
folded_temporaries.insert(*temporary);
|
|
74
|
+
} else {
|
|
75
|
+
folded_temporaries.remove(temporary);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if let (Some(constant), Effect::Let(_, term) | Effect::Set(_, term)) = (folded, effect) {
|
|
79
|
+
*term = Term::Const(Rc::unwrap_or_clone(constant));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Records the constant an effect writes, if any. As in the other listing folds, calls leave the
|
|
85
|
+
/// caller's registers alone.
|
|
86
|
+
fn step(effect: &Effect, known: &mut Registers, temporaries: &mut Temporaries) {
|
|
87
|
+
match effect {
|
|
88
|
+
Effect::Let(temporary, term) => match value(term, known, temporaries) {
|
|
89
|
+
Some(constant) => temporaries.insert(*temporary, constant),
|
|
90
|
+
None => temporaries.remove(temporary),
|
|
91
|
+
},
|
|
92
|
+
Effect::Set(register, term) => match value(term, known, temporaries) {
|
|
93
|
+
Some(constant) => known.insert(*register, constant),
|
|
94
|
+
None => known.remove(register),
|
|
95
|
+
},
|
|
96
|
+
_ => None,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// The string or number `term` evaluates to, if constant.
|
|
101
|
+
fn value(term: &Term, known: &Registers, temporaries: &Temporaries) -> Option<Rc<Constant>> {
|
|
102
|
+
let value = |term| value(term, known, temporaries);
|
|
103
|
+
let number = |term| match *value(term)? {
|
|
104
|
+
Constant::Number(number) => Some(number),
|
|
105
|
+
_ => None,
|
|
106
|
+
};
|
|
107
|
+
let number_constant = |number| Some(Rc::new(Constant::Number(number)));
|
|
108
|
+
match term {
|
|
109
|
+
Term::Const(constant @ (Constant::String(_) | Constant::Number(_))) => {
|
|
110
|
+
Some(Rc::new(constant.clone()))
|
|
111
|
+
}
|
|
112
|
+
Term::Reg(register) => known.get(register).cloned(),
|
|
113
|
+
Term::Temp(temporary) => temporaries.get(temporary).cloned(),
|
|
114
|
+
Term::Binary(BinaryOp::Add, left, right) => number_constant(number(left)? + number(right)?),
|
|
115
|
+
Term::Binary(BinaryOp::Mul, left, right) => number_constant(number(left)? * number(right)?),
|
|
116
|
+
Term::Call {
|
|
117
|
+
callee,
|
|
118
|
+
this: None,
|
|
119
|
+
arguments,
|
|
120
|
+
} => {
|
|
121
|
+
let Term::Get(receiver, method) = &**callee else {
|
|
122
|
+
return None;
|
|
123
|
+
};
|
|
124
|
+
let receiver = value(receiver)?;
|
|
125
|
+
let (Constant::String(text), Term::Const(Constant::String(method))) =
|
|
126
|
+
(&*receiver, &**method)
|
|
127
|
+
else {
|
|
128
|
+
return None;
|
|
129
|
+
};
|
|
130
|
+
let text: Vec<u16> = text.encode_utf16().collect();
|
|
131
|
+
// Negative and fractional indexes never occur in the unpacking.
|
|
132
|
+
let index = |term| {
|
|
133
|
+
let index = number(term)?;
|
|
134
|
+
(index >= 0.0 && index.fract() == 0.0).then_some(index as usize)
|
|
135
|
+
};
|
|
136
|
+
match (method.as_str(), arguments.as_slice()) {
|
|
137
|
+
("charCodeAt", [position]) => {
|
|
138
|
+
number_constant(f64::from(*text.get(index(position)?)?))
|
|
139
|
+
}
|
|
140
|
+
("slice", [start, end]) => {
|
|
141
|
+
let end = index(end)?.min(text.len());
|
|
142
|
+
let start = index(start)?.min(end);
|
|
143
|
+
Some(Rc::new(Constant::String(
|
|
144
|
+
String::from_utf16(&text[start..end]).ok()?,
|
|
145
|
+
)))
|
|
146
|
+
}
|
|
147
|
+
_ => None,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
_ => None,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#[cfg(test)]
|
|
155
|
+
mod tests {
|
|
156
|
+
use super::*;
|
|
157
|
+
use crate::vm::output::{Exit, Instruction, Position};
|
|
158
|
+
|
|
159
|
+
fn string(text: &str) -> Term {
|
|
160
|
+
Term::Const(Constant::String(text.into()))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn number(value: f64) -> Term {
|
|
164
|
+
Term::Const(Constant::Number(value))
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
fn method(receiver: u8, name: &str, arguments: Vec<Term>) -> Term {
|
|
168
|
+
Term::Call {
|
|
169
|
+
callee: Box::new(Term::Get(
|
|
170
|
+
Box::new(Term::Reg(receiver)),
|
|
171
|
+
Box::new(string(name)),
|
|
172
|
+
)),
|
|
173
|
+
this: None,
|
|
174
|
+
arguments,
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fn binary(op: BinaryOp, left: u8, right: u8) -> Term {
|
|
179
|
+
Term::Binary(op, Box::new(Term::Reg(left)), Box::new(Term::Reg(right)))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
fn insert(listing: &mut Listing, pc: u32, effects: Vec<Effect>, exit: Exit) {
|
|
183
|
+
let path = Path {
|
|
184
|
+
conditions: Vec::new(),
|
|
185
|
+
effects,
|
|
186
|
+
exit,
|
|
187
|
+
};
|
|
188
|
+
let instruction = Instruction {
|
|
189
|
+
pc,
|
|
190
|
+
key: 0,
|
|
191
|
+
opcode: 0,
|
|
192
|
+
paths: vec![path],
|
|
193
|
+
};
|
|
194
|
+
listing.instructions.insert(pc, instruction);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#[test]
|
|
198
|
+
fn folds_unpacked_names_to_string_loads() {
|
|
199
|
+
// r1 = packed; r2 = 0; r3 = 1; r4 = 2; r5 = 256;
|
|
200
|
+
// r6 = r1.charCodeAt(r2); r2 = r1.charCodeAt(r3); r6 = r6 * r5 + r2 + r4;
|
|
201
|
+
// r1 = r1.slice(r4, r6); payload[r1] = 0
|
|
202
|
+
let effects = vec![
|
|
203
|
+
vec![Effect::Set(1, string("\0\u{3}abc\0\0\0\0"))],
|
|
204
|
+
vec![
|
|
205
|
+
Effect::Set(2, number(0.0)),
|
|
206
|
+
Effect::Set(3, number(1.0)),
|
|
207
|
+
Effect::Set(4, number(2.0)),
|
|
208
|
+
Effect::Set(5, number(256.0)),
|
|
209
|
+
],
|
|
210
|
+
vec![
|
|
211
|
+
Effect::Let(0, method(1, "charCodeAt", vec![Term::Reg(2)])),
|
|
212
|
+
Effect::Set(6, Term::Temp(0)),
|
|
213
|
+
],
|
|
214
|
+
vec![
|
|
215
|
+
Effect::Let(0, method(1, "charCodeAt", vec![Term::Reg(3)])),
|
|
216
|
+
Effect::Set(2, Term::Temp(0)),
|
|
217
|
+
],
|
|
218
|
+
vec![Effect::Set(6, binary(BinaryOp::Mul, 6, 5))],
|
|
219
|
+
vec![Effect::Set(6, binary(BinaryOp::Add, 6, 2))],
|
|
220
|
+
vec![Effect::Set(6, binary(BinaryOp::Add, 6, 4))],
|
|
221
|
+
vec![
|
|
222
|
+
Effect::Let(0, method(1, "slice", vec![Term::Reg(4), Term::Reg(6)])),
|
|
223
|
+
Effect::Set(1, Term::Temp(0)),
|
|
224
|
+
],
|
|
225
|
+
];
|
|
226
|
+
let count = u32::try_from(effects.len()).unwrap();
|
|
227
|
+
let mut listing = Listing {
|
|
228
|
+
functions: vec![Position::new(0, 0)],
|
|
229
|
+
..Listing::default()
|
|
230
|
+
};
|
|
231
|
+
for (pc, effects) in (0..).zip(effects) {
|
|
232
|
+
let exit = if pc + 1 == count {
|
|
233
|
+
Exit::Return
|
|
234
|
+
} else {
|
|
235
|
+
Exit::Next { pc: pc + 1, key: 0 }
|
|
236
|
+
};
|
|
237
|
+
insert(&mut listing, pc, effects, exit);
|
|
238
|
+
}
|
|
239
|
+
fold(&mut listing);
|
|
240
|
+
let last = &listing.instructions[&(count - 1)].paths[0].effects;
|
|
241
|
+
assert_eq!(
|
|
242
|
+
last,
|
|
243
|
+
&[Effect::Let(0, string("abc")), Effect::Set(1, string("abc"))]
|
|
244
|
+
);
|
|
245
|
+
let first_code = &listing.instructions[&2].paths[0].effects;
|
|
246
|
+
assert_eq!(
|
|
247
|
+
first_code,
|
|
248
|
+
&[Effect::Let(0, number(0.0)), Effect::Set(6, number(0.0))]
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#[test]
|
|
253
|
+
fn catch_names_fold_without_erasing_normal_path_names() {
|
|
254
|
+
let mut listing = Listing {
|
|
255
|
+
functions: vec![Position::new(0, 0)],
|
|
256
|
+
..Listing::default()
|
|
257
|
+
};
|
|
258
|
+
insert(
|
|
259
|
+
&mut listing,
|
|
260
|
+
0,
|
|
261
|
+
vec![
|
|
262
|
+
Effect::Set(1, string("\0\u{3}abc\0")),
|
|
263
|
+
Effect::Set(2, number(2.0)),
|
|
264
|
+
Effect::Set(3, number(5.0)),
|
|
265
|
+
Effect::Set(
|
|
266
|
+
9,
|
|
267
|
+
Term::Array(vec![Term::Array(vec![
|
|
268
|
+
number(100.0),
|
|
269
|
+
number(0.0),
|
|
270
|
+
Term::Cells,
|
|
271
|
+
number(0.0),
|
|
272
|
+
])]),
|
|
273
|
+
),
|
|
274
|
+
],
|
|
275
|
+
Exit::Next { pc: 1, key: 0 },
|
|
276
|
+
);
|
|
277
|
+
insert(
|
|
278
|
+
&mut listing,
|
|
279
|
+
1,
|
|
280
|
+
vec![Effect::Set(
|
|
281
|
+
1,
|
|
282
|
+
method(1, "slice", vec![Term::Reg(2), Term::Reg(3)]),
|
|
283
|
+
)],
|
|
284
|
+
Exit::Return,
|
|
285
|
+
);
|
|
286
|
+
insert(
|
|
287
|
+
&mut listing,
|
|
288
|
+
100,
|
|
289
|
+
vec![
|
|
290
|
+
Effect::Set(1, string("\0\u{3}xyz\0")),
|
|
291
|
+
Effect::Set(2, number(2.0)),
|
|
292
|
+
Effect::Set(3, number(5.0)),
|
|
293
|
+
Effect::Set(4, method(1, "slice", vec![Term::Reg(2), Term::Reg(3)])),
|
|
294
|
+
],
|
|
295
|
+
Exit::Next { pc: 1, key: 0 },
|
|
296
|
+
);
|
|
297
|
+
fold(&mut listing);
|
|
298
|
+
assert_eq!(
|
|
299
|
+
listing.instructions[&1].paths[0].effects[0],
|
|
300
|
+
Effect::Set(1, string("abc"))
|
|
301
|
+
);
|
|
302
|
+
assert_eq!(
|
|
303
|
+
listing.instructions[&100].paths[0].effects[3],
|
|
304
|
+
Effect::Set(4, string("xyz"))
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
}
|