jsobfu 0.2.1 → 0.3.0
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 +5 -13
- data/lib/jsobfu.rb +25 -12
- data/lib/jsobfu/obfuscator.rb +24 -8
- data/spec/jsobfu_spec.rb +95 -7
- data/spec/spec_helper.rb +1 -0
- metadata +13 -13
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
ZTY0ZDAyNDllMmRiNDRjZDkyYTFjOWYyNzllZGM3ZGYxNTRkMGVlYQ==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d61cd6788963eb7f3079f126f4fc74ec831a7dd5
|
4
|
+
data.tar.gz: a7de9413eda46fe02f80eba2916006113e7bff25
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
M2VkMzdhZDE3ZjI0MjA2OThkOGFkZWM4NGZhM2E3OTdmMWVmYzcxNDljMTYw
|
11
|
-
YTM0Zjg5NzJhZTU4MzA5NjA3OTc2NDdiNzY1MzgzMjRmYzJmNDk=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
MjRkMmJhMDhjZDVjZjBjNjIzYWFlMjUwYmU0Y2VjYTgzZTdiODcwZDhmMDQ5
|
14
|
-
YjNjNTRlNWUwOWJiYmE4ZDVmZWMxNjVmZTg1ZTFmYjkwZDVjYThjNGViYTY2
|
15
|
-
YjhlOTgyOGQ2MDNjZTA5ZWIwOGJkZDljN2IxYTNlNjlmNGEwZGQ=
|
6
|
+
metadata.gz: d45a69e75aa3bb6f73e7d3cc361e631917f32147f278118c08567312ddf6aae589449dde9e0f1af8efbf8a1db3d0112fbc612927bee49d2eeca244195d795c73
|
7
|
+
data.tar.gz: 9ca1927799eea1eccbdc64616dcb9fb9425d432d8b0500a19ad3a20aa492830fe4d9480d6207d45693f8d06481624b42e93d8d2ae15500ef72128399aee6baeb
|
data/lib/jsobfu.rb
CHANGED
@@ -19,9 +19,12 @@ class JSObfu
|
|
19
19
|
|
20
20
|
# Saves +code+ for later obfuscation with #obfuscate
|
21
21
|
# @param code [#to_s] the code to obfuscate
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
# @param opts [Hash] an options hash
|
23
|
+
# @option opts [JSObfu::Scope] a pre-existing scope. This is useful for preserving
|
24
|
+
# variable rename maps between separate obfuscations of different scripts.
|
25
|
+
def initialize(code=nil, opts={})
|
26
|
+
self.code = code
|
27
|
+
@scope = opts.fetch(:scope) { Scope.new }
|
25
28
|
end
|
26
29
|
|
27
30
|
# Add +str+ to the un-obfuscated code.
|
@@ -37,7 +40,14 @@ class JSObfu
|
|
37
40
|
|
38
41
|
# @return [RKelly::Nodes::SourceElementsNode] the abstract syntax tree
|
39
42
|
def ast
|
40
|
-
@ast
|
43
|
+
@ast ||= parse
|
44
|
+
end
|
45
|
+
|
46
|
+
# Sets the code that this obfuscator will transform
|
47
|
+
# @param [String] code
|
48
|
+
def code=(code)
|
49
|
+
@ast = nil # invalidate any previous parses
|
50
|
+
@code = code
|
41
51
|
end
|
42
52
|
|
43
53
|
# Parse and obfuscate
|
@@ -49,9 +59,13 @@ class JSObfu
|
|
49
59
|
# obfuscator on this code (1)
|
50
60
|
# @option opts [String] :global the global object to rewrite unresolved lookups to.
|
51
61
|
# Depending on the environment, it may be `window`, `global`, or `this`.
|
62
|
+
# @option opts [Boolean] :memory_sensitive the execution environment is sensitive
|
63
|
+
# to changes in memory usage (e.g. a heap spray). This disables string transformations
|
64
|
+
# and other "noisy" obfuscation tactics. (false)
|
52
65
|
# @return [self]
|
53
66
|
def obfuscate(opts={})
|
54
67
|
return self if JSObfu.disabled?
|
68
|
+
raise ArgumentError.new("code must be present") if @code.nil?
|
55
69
|
|
56
70
|
iterations = opts.fetch(:iterations, 1).to_i
|
57
71
|
strip_whitespace = opts.fetch(:strip_whitespace, true)
|
@@ -65,15 +79,12 @@ class JSObfu
|
|
65
79
|
@code.delete!("\r")
|
66
80
|
end
|
67
81
|
|
68
|
-
new_renames = obfuscator.renames.dup
|
69
82
|
if @renames
|
70
83
|
# "patch up" the renames after each iteration
|
71
|
-
@renames.
|
72
|
-
@renames[key] = new_renames[prev_rename]
|
73
|
-
end
|
84
|
+
@renames.merge! (obfuscator.renames)
|
74
85
|
else
|
75
86
|
# on first iteration, take the renames as-is
|
76
|
-
@renames =
|
87
|
+
@renames = obfuscator.renames.dup
|
77
88
|
end
|
78
89
|
|
79
90
|
unless i == iterations-1
|
@@ -82,6 +93,9 @@ class JSObfu
|
|
82
93
|
end
|
83
94
|
end
|
84
95
|
|
96
|
+
# Enter all of the renames into current scope
|
97
|
+
@scope.renames.merge!(@renames || {})
|
98
|
+
|
85
99
|
self
|
86
100
|
end
|
87
101
|
|
@@ -96,11 +110,10 @@ class JSObfu
|
|
96
110
|
|
97
111
|
protected
|
98
112
|
|
99
|
-
#
|
100
113
|
# Generate an Abstract Syntax Tree (#ast) for later obfuscation
|
101
|
-
#
|
114
|
+
# @return [RKelly::Nodes::SourceElementsNode] the abstract syntax tree
|
102
115
|
def parse
|
103
|
-
|
116
|
+
RKelly::Parser.new.parse(@code)
|
104
117
|
end
|
105
118
|
|
106
119
|
end
|
data/lib/jsobfu/obfuscator.rb
CHANGED
@@ -21,9 +21,13 @@ class JSObfu::Obfuscator < JSObfu::ECMANoWhitespaceVisitor
|
|
21
21
|
# @option opts [JSObfu::Scope] :scope the optional scope to save vars to
|
22
22
|
# @option opts [String] :global the global object to rewrite unresolved lookups to.
|
23
23
|
# Depending on the environment, it may be `window`, `global`, or `this`.
|
24
|
+
# @option opts [Boolean] :memory_sensitive the execution environment is sensitive
|
25
|
+
# to changes in memory usage (e.g. a heap spray). This disables string transformations
|
26
|
+
# and other "noisy" obfuscation tactics. (false)
|
24
27
|
def initialize(opts={})
|
25
|
-
@scope = opts.fetch(:scope
|
28
|
+
@scope = opts.fetch(:scope) { JSObfu::Scope.new }
|
26
29
|
@global = opts.fetch(:global, DEFAULT_GLOBAL).to_s
|
30
|
+
@memory_sensitive = !!opts.fetch(:memory_sensitive, false)
|
27
31
|
@renames = {}
|
28
32
|
super()
|
29
33
|
end
|
@@ -99,7 +103,7 @@ class JSObfu::Obfuscator < JSObfu::ECMANoWhitespaceVisitor
|
|
99
103
|
o.value = JSObfu::Utils::random_var_encoding(new_val)
|
100
104
|
super
|
101
105
|
else
|
102
|
-
if o.value.to_s == global.to_s
|
106
|
+
if @memory_sensitive || o.value.to_s == global.to_s
|
103
107
|
# if the ref is the global object, don't obfuscate it on itself. This helps
|
104
108
|
# "shimmed" globals (like `window=this` at the top of the script) work reliably.
|
105
109
|
super
|
@@ -112,8 +116,12 @@ class JSObfu::Obfuscator < JSObfu::ECMANoWhitespaceVisitor
|
|
112
116
|
|
113
117
|
# Called on a dot lookup, like X.Y
|
114
118
|
def visit_DotAccessorNode(o)
|
115
|
-
|
116
|
-
|
119
|
+
if @memory_sensitive
|
120
|
+
super
|
121
|
+
else
|
122
|
+
obf_str = JSObfu::Utils::transform_string(o.accessor, scope, :quotes => false)
|
123
|
+
"#{o.value.accept(self)}[(#{obf_str})]"
|
124
|
+
end
|
117
125
|
end
|
118
126
|
|
119
127
|
# Called when a parameter is declared. "Shadowed" parameters in the original
|
@@ -127,20 +135,28 @@ class JSObfu::Obfuscator < JSObfu::ECMANoWhitespaceVisitor
|
|
127
135
|
# A property node in an object "{}"
|
128
136
|
def visit_PropertyNode(o)
|
129
137
|
# if it is a non-alphanumeric property, obfuscate the string's bytes
|
130
|
-
|
131
|
-
|
138
|
+
unless @memory_sensitive
|
139
|
+
if o.name =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
140
|
+
o.instance_variable_set :@name, '"'+JSObfu::Utils::random_string_encoding(o.name)+'"'
|
141
|
+
end
|
132
142
|
end
|
133
143
|
|
134
144
|
super
|
135
145
|
end
|
136
146
|
|
137
147
|
def visit_NumberNode(o)
|
138
|
-
|
148
|
+
unless @memory_sensitive
|
149
|
+
o.value = JSObfu::Utils::transform_number(o.value)
|
150
|
+
end
|
151
|
+
|
139
152
|
super
|
140
153
|
end
|
141
154
|
|
142
155
|
def visit_StringNode(o)
|
143
|
-
|
156
|
+
unless @memory_sensitive
|
157
|
+
o.value = JSObfu::Utils::transform_string(o.value, scope)
|
158
|
+
end
|
159
|
+
|
144
160
|
super
|
145
161
|
end
|
146
162
|
|
data/spec/jsobfu_spec.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'execjs'
|
2
3
|
|
3
4
|
describe JSObfu do
|
4
5
|
|
5
|
-
|
6
|
+
let(:js) { 'var x; function y() {};' }
|
6
7
|
|
7
8
|
subject(:jsobfu) do
|
8
|
-
described_class.new(
|
9
|
+
described_class.new(js)
|
9
10
|
end
|
10
11
|
|
11
|
-
|
12
|
+
describe '#sym' do
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
let(:iterations) { 1 }
|
15
|
+
|
16
|
+
before do
|
17
|
+
jsobfu.obfuscate(iterations: iterations)
|
18
|
+
end
|
16
19
|
|
17
|
-
describe '#sym' do
|
18
20
|
context 'when given the string "x"' do
|
19
21
|
it 'returns some string' do
|
20
22
|
expect(jsobfu.sym('x')).not_to be_nil
|
@@ -44,4 +46,90 @@ describe JSObfu do
|
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
49
|
+
describe '#obfuscate' do
|
50
|
+
|
51
|
+
describe 'the :iterations option' do
|
52
|
+
|
53
|
+
describe 'when :iterations is 1' do
|
54
|
+
|
55
|
+
let(:js) { 'this.test = function() { return 5; }' }
|
56
|
+
|
57
|
+
it 'evaluates to the same result as when :iterations is 5' do
|
58
|
+
obfu1 = described_class.new(js).obfuscate(iterations: 1).to_s
|
59
|
+
obfu5 = described_class.new(js).obfuscate(iterations: 5).to_s
|
60
|
+
expect(obfu1).to evaluate_to(obfu5)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'the :memory_sensitive option' do
|
67
|
+
|
68
|
+
let(:match) { "ABCDEFG" }
|
69
|
+
let(:js) { "var x = '#{match}'" }
|
70
|
+
|
71
|
+
describe 'when true' do
|
72
|
+
10.times do
|
73
|
+
|
74
|
+
it 'does not obfuscate String literals' do
|
75
|
+
expect(jsobfu.obfuscate(memory_sensitive: true).to_s).to include(match)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe 'when false' do
|
82
|
+
10.times do
|
83
|
+
|
84
|
+
it 'obfuscates String literals' do
|
85
|
+
expect(jsobfu.obfuscate(memory_sensitive: false).to_s).not_to include(match)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
describe 'preserving the variable map across calls' do
|
94
|
+
|
95
|
+
let(:code1) { 'var Blah = 1;' }
|
96
|
+
let(:code2) { 'this.test = function(){ return Blah + 1; };' }
|
97
|
+
|
98
|
+
describe 'when calling obfuscate again after changing the code' do
|
99
|
+
|
100
|
+
it 'preserves the variable map' do
|
101
|
+
js = JSObfu.new(code1)
|
102
|
+
obf1 = js.obfuscate.to_s
|
103
|
+
js.code = code2
|
104
|
+
obf2 = js.obfuscate.to_s
|
105
|
+
|
106
|
+
expect(obf1+obf2).to evaluate_to(code1+code2)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
describe 'when calling obfuscate twice after changing the code' do
|
112
|
+
|
113
|
+
let(:code2) { 'var Foo = 2;' }
|
114
|
+
let(:code3) { 'this.test = function(){ return Blah + Foo + 1; };' }
|
115
|
+
|
116
|
+
it 'preserves the variable map' do
|
117
|
+
js = JSObfu.new
|
118
|
+
|
119
|
+
js.code = code1
|
120
|
+
obf1 = js.obfuscate.to_s
|
121
|
+
js.code = code2
|
122
|
+
obf2 = js.obfuscate.to_s
|
123
|
+
js.code = code3
|
124
|
+
obf3 = js.obfuscate.to_s
|
125
|
+
|
126
|
+
expect(obf1+obf2+obf3).to evaluate_to(code1+code2+code3)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
47
135
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsobfu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Lee
|
@@ -29,70 +29,70 @@ dependencies:
|
|
29
29
|
name: rspec
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
|
-
- - ~>
|
32
|
+
- - "~>"
|
33
33
|
- !ruby/object:Gem::Version
|
34
34
|
version: '3.1'
|
35
35
|
type: :development
|
36
36
|
prerelease: false
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
38
38
|
requirements:
|
39
|
-
- - ~>
|
39
|
+
- - "~>"
|
40
40
|
- !ruby/object:Gem::Version
|
41
41
|
version: '3.1'
|
42
42
|
- !ruby/object:Gem::Dependency
|
43
43
|
name: simplecov
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
45
45
|
requirements:
|
46
|
-
- -
|
46
|
+
- - ">="
|
47
47
|
- !ruby/object:Gem::Version
|
48
48
|
version: '0'
|
49
49
|
type: :development
|
50
50
|
prerelease: false
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
52
52
|
requirements:
|
53
|
-
- -
|
53
|
+
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: '0'
|
56
56
|
- !ruby/object:Gem::Dependency
|
57
57
|
name: execjs
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
59
59
|
requirements:
|
60
|
-
- -
|
60
|
+
- - ">="
|
61
61
|
- !ruby/object:Gem::Version
|
62
62
|
version: '0'
|
63
63
|
type: :development
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
|
-
- -
|
67
|
+
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
69
|
version: '0'
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: rake
|
72
72
|
requirement: !ruby/object:Gem::Requirement
|
73
73
|
requirements:
|
74
|
-
- -
|
74
|
+
- - ">="
|
75
75
|
- !ruby/object:Gem::Version
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
|
-
- -
|
81
|
+
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: '0'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: yard
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
|
-
- -
|
88
|
+
- - ">="
|
89
89
|
- !ruby/object:Gem::Version
|
90
90
|
version: '0'
|
91
91
|
type: :development
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
|
-
- -
|
95
|
+
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
98
|
description:
|
@@ -131,12 +131,12 @@ require_paths:
|
|
131
131
|
- lib
|
132
132
|
required_ruby_version: !ruby/object:Gem::Requirement
|
133
133
|
requirements:
|
134
|
-
- -
|
134
|
+
- - ">="
|
135
135
|
- !ruby/object:Gem::Version
|
136
136
|
version: '0'
|
137
137
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
138
138
|
requirements:
|
139
|
-
- -
|
139
|
+
- - ">="
|
140
140
|
- !ruby/object:Gem::Version
|
141
141
|
version: '0'
|
142
142
|
requirements: []
|