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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZjMwNzdiYThlZjA5YzdlZDVhZmUwYWE5MThhNzY1MDBiOGQ2ZTAxMg==
5
- data.tar.gz: !binary |-
6
- ZTY0ZDAyNDllMmRiNDRjZDkyYTFjOWYyNzllZGM3ZGYxNTRkMGVlYQ==
2
+ SHA1:
3
+ metadata.gz: d61cd6788963eb7f3079f126f4fc74ec831a7dd5
4
+ data.tar.gz: a7de9413eda46fe02f80eba2916006113e7bff25
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- ZDA0Mjg0Y2YyODg1YTAxYmE2YzlmNDQ3NDllMzI0ZDE5YTgxZjdiYjM0OGU2
10
- M2VkMzdhZDE3ZjI0MjA2OThkOGFkZWM4NGZhM2E3OTdmMWVmYzcxNDljMTYw
11
- YTM0Zjg5NzJhZTU4MzA5NjA3OTc2NDdiNzY1MzgzMjRmYzJmNDk=
12
- data.tar.gz: !binary |-
13
- MjRkMmJhMDhjZDVjZjBjNjIzYWFlMjUwYmU0Y2VjYTgzZTdiODcwZDhmMDQ5
14
- YjNjNTRlNWUwOWJiYmE4ZDVmZWMxNjVmZTg1ZTFmYjkwZDVjYThjNGViYTY2
15
- YjhlOTgyOGQ2MDNjZTA5ZWIwOGJkZDljN2IxYTNlNjlmNGEwZGQ=
6
+ metadata.gz: d45a69e75aa3bb6f73e7d3cc361e631917f32147f278118c08567312ddf6aae589449dde9e0f1af8efbf8a1db3d0112fbc612927bee49d2eeca244195d795c73
7
+ data.tar.gz: 9ca1927799eea1eccbdc64616dcb9fb9425d432d8b0500a19ad3a20aa492830fe4d9480d6207d45693f8d06481624b42e93d8d2ae15500ef72128399aee6baeb
@@ -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
- def initialize(code)
23
- @code = code.to_s
24
- @scope = Scope.new
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 || parse
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.each do |key, prev_rename|
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 = new_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
- @ast = RKelly::Parser.new.parse(@code)
116
+ RKelly::Parser.new.parse(@code)
104
117
  end
105
118
 
106
119
  end
@@ -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, JSObfu::Scope.new)
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
- obf_str = JSObfu::Utils::transform_string(o.accessor, scope, :quotes => false)
116
- "#{o.value.accept(self)}[(#{obf_str})]"
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
- if o.name =~ /^[a-zA-Z_][a-zA-Z0-9_]*$/
131
- o.instance_variable_set :@name, '"'+JSObfu::Utils::random_string_encoding(o.name)+'"'
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
- o.value = JSObfu::Utils::transform_number(o.value)
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
- o.value = JSObfu::Utils::transform_string(o.value, scope)
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
 
@@ -1,20 +1,22 @@
1
1
  require 'spec_helper'
2
+ require 'execjs'
2
3
 
3
4
  describe JSObfu do
4
5
 
5
- TEST_STRING = 'var x; function y() {};'
6
+ let(:js) { 'var x; function y() {};' }
6
7
 
7
8
  subject(:jsobfu) do
8
- described_class.new(TEST_STRING)
9
+ described_class.new(js)
9
10
  end
10
11
 
11
- let(:iterations) { 1 }
12
+ describe '#sym' do
12
13
 
13
- before do
14
- jsobfu.obfuscate(iterations: iterations)
15
- end
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
@@ -2,6 +2,7 @@ require 'simplecov'
2
2
  require 'rspec/core'
3
3
  require 'rspec/mocks'
4
4
  require 'jsobfu'
5
+ require 'pathname'
5
6
 
6
7
  # Requires supporting ruby files with custom matchers and macros, etc,
7
8
  # in spec/support/ and its subdirectories.
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.2.1
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: []