jsobfu 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: []