node-marshal 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.
@@ -1,5 +1,26 @@
1
1
  require_relative '../ext/node-marshal/nodemarshal.so'
2
- require 'zlib'
2
+ begin
3
+ require 'zlib'
4
+ rescue LoadError
5
+ # If zlib library is absent in the system no support for
6
+ # compression will be provided in
7
+ end
8
+
9
+ # Implementation of Array::to_h method for Ruby 1.9 (and probably 2.0)
10
+ # Don't use for Ruby 2.2.x and Ruby 2.3.x
11
+ if !defined?([].to_h)
12
+ class Array
13
+ def to_h
14
+ h = {}
15
+ a = self
16
+ a.each do |x|
17
+ raise "wrong array length" if x.length != 2
18
+ h[x[0]] = x[1];
19
+ end
20
+ return h
21
+ end
22
+ end
23
+ end
3
24
 
4
25
  class NodeMarshal
5
26
  # call-seq:
@@ -12,7 +33,7 @@ class NodeMarshal
12
33
  # with the command for nodemarshal.so inclusion (default is
13
34
  # require_relative '../ext/node-marshal/nodemarshal.so')
14
35
  def to_compiled_rb(outfile, *args)
15
- compress = false
36
+ compress = true
16
37
  so_path = "require_relative '../ext/node-marshal/nodemarshal.so'"
17
38
  if args.length > 0
18
39
  opts = args[0]
@@ -25,6 +46,9 @@ class NodeMarshal
25
46
  end
26
47
  # Compression
27
48
  if compress
49
+ if !defined?(Zlib)
50
+ raise "Compression is not supported: Zlib is absent"
51
+ end
28
52
  zlib_include = "require 'zlib'"
29
53
  data_txt = NodeMarshal.base85r_encode(Zlib::deflate(self.to_bin))
30
54
  data_bin = "Zlib::inflate(NodeMarshal.base85r_decode(data_txt))"
@@ -61,4 +85,135 @@ EOS
61
85
  node.to_compiled_rb(outfile, *args)
62
86
  return true
63
87
  end
88
+
89
+ # call-seq:
90
+ # obj.replace_symbols(syms_subs)
91
+ #
92
+ # Replaces some symbols inside parsed AST to user-defined aliases.
93
+ # It is designed to make code obfuscation easier. Be careful when using
94
+ # this ability: it is possible to break external libraries calls,
95
+ # operators overloading and some metaprogramming techniques.
96
+ # - +syms_subs+ -- Hash with the table of aliases. Keys are original names,
97
+ # values are aliases. Keys and values MUST BE strings (not symbols!).
98
+ def replace_symbols(syms_subs)
99
+ # Check input data
100
+ # a) type
101
+ if !(syms_subs.is_a?(Hash))
102
+ raise "symb_subs must be a hash"
103
+ end
104
+ # b) uniqueness of values inside the hash
105
+ values = syms_subs.values
106
+ if values.size != values.uniq.size
107
+ raise ArgumentError, "values (new names) must be unique"
108
+ end
109
+ # c) uniqueness of values after replacement
110
+ # TODO: MAKE IT!!!
111
+ # Use NodeMarshal C part to replace the symbols
112
+ self.to_hash # To initialize Hash with preparsed Ruby AST NODE
113
+ syms_subs.each do |key, value|
114
+ change_symbol(key, value)
115
+ end
116
+ self
117
+ end
118
+
119
+ # call-seq:
120
+ # obj.get_safe_symbols
121
+ #
122
+ # Returns an array that contains strings with the names of symbols that are safe
123
+ # to change. It excludes symbols that are present in the table of literals (and their derivatives
124
+ # such as @x and x=). Such operation is useful for attr_readed, attr_writer and another similar
125
+ # metaprogramming techniques handling
126
+ #
127
+ # - +our_symbols+ symbols created during node creation (must be found manually by the user
128
+ # by means of Symbol.all_symbols calling BEFORE and AFTER node creation.
129
+ def get_safe_symbols(our_symbols)
130
+ self.to_hash # To initialize Hash with preparsed Ruby AST NODE
131
+ symbolic_literals = self.literals.select {|x| x.is_a?(Symbol)}.map {|x| x.to_s}
132
+ fixed_symbols = [] + symbolic_literals
133
+ fixed_symbols += symbolic_literals.map {|x| "@#{x}"}
134
+ fixed_symbols += symbolic_literals.map {|x| "#{x}="}
135
+ our_symbols = our_symbols.dup
136
+ our_symbols -= fixed_symbols
137
+ end
138
+
139
+ # call-seq:
140
+ # obj.get_aliases_table(our_symbols)
141
+ #
142
+ # Returns a hash that has {"old_sym_name"=>"new_sym_name",...} format.
143
+ # "new_sym_name" are generated automatically.
144
+ #
145
+ # - +our_symbols+ -- An array that contains the list of symbols (AS STRINGS,
146
+ # NOT AS SYMBOLS) that can be renamed.
147
+ def get_aliases_table(our_symbols)
148
+ symbols_ary = get_safe_symbols(our_symbols)
149
+ pos = 0;
150
+ aliases_ary = symbols_ary.map do |sym|
151
+ pos += 1
152
+ if sym.length > 1 && sym[0..1] == '@@'
153
+ "@@q#{pos}"
154
+ elsif sym[0] == '@'
155
+ "@q#{pos}"
156
+ elsif sym[0] =~ /[A-Z]/
157
+ "Q#{pos}"
158
+ elsif sym[0] =~ /[a-z]/
159
+ "q#{pos}"
160
+ end
161
+ end
162
+ [symbols_ary, aliases_ary].transpose.to_h
163
+ end
164
+
165
+ # call-seq:
166
+ # obj.rename_ivars
167
+ def rename_ivars(*args)
168
+ if args.size == 0
169
+ excl_names = []
170
+ else
171
+ excl_names = args[0]
172
+ end
173
+
174
+ to_hash
175
+ syms = @nodehash[:symbols].select {|x| (x =~ /@[^@]/) == 0}
176
+ pos = 1;
177
+ syms_new = syms.map do |x|
178
+ if excl_names.find_index(x[1..-1]) != nil
179
+ str = x
180
+ else
181
+ str = "@ivar#{pos}"
182
+ end
183
+ pos = pos + 1;
184
+ str
185
+ end
186
+ syms_subs = [syms, syms_new].transpose.to_h
187
+ replace_symbols(syms_subs)
188
+ self
189
+ end
190
+
191
+ # call-seq:
192
+ # obj.rebuld
193
+ #
194
+ # Rebuilds the node by converting it to the binary dump and further restoring
195
+ # of it from this dump. It doesn't change the original node and returns rebuilt
196
+ # node.
197
+ def rebuild
198
+ NodeMarshal.new(:binmemory, to_bin)
199
+ end
200
+ end
201
+
202
+ # Designed for the logging of symbols table changes. When an example of
203
+ # SymbolsLogger is created the current global table of symbols is saved
204
+ # inside it. The created example can be used for finding new symbols in
205
+ # the global table. This is useful for code obfuscation.
206
+ class SymbolsLogger
207
+ def initialize
208
+ @symtbl_old = Symbol.all_symbols
209
+ end
210
+
211
+ def new_symbols
212
+ symtbl_new = Symbol.all_symbols
213
+ symtbl_new - @symtbl_old
214
+ end
215
+
216
+ def update
217
+ @symtbl_old = Symbol.all_symbols
218
+ end
64
219
  end
@@ -125,7 +125,6 @@ a, b = XYPoint.new(0.0, 0.0), XYPoint.new(3.0, 4.0)
125
125
  c, d = PolarPoint.new(1.0, 0.0), PolarPoint.new(2.0, 90.0)
126
126
  [a.distance(b), c.distance(d)]
127
127
  EOS
128
-
129
128
  assert_node_compiler(program)
130
129
  end
131
130
 
@@ -148,6 +147,45 @@ EOS
148
147
  end
149
148
  end
150
149
 
150
+ # Test regular expressions (NODE_MATCH3 node issue)
151
+ def test_node_match3
152
+ program = <<-EOS
153
+ a = " d--abc"
154
+ a =~ Regexp.new("abc")
155
+ EOS
156
+ assert_node_compiler(program)
157
+ end
158
+
159
+ def test_node_block_pass
160
+ program = <<-EOS
161
+ [1, 2, 3, "A"].map(&:class)
162
+ EOS
163
+ assert_node_compiler(program)
164
+ end
165
+
166
+ # Check the reaction on the parsing errors during the node creation
167
+ # In the case of syntax error ArgumentError exception should be generated
168
+ def test_syntax_error
169
+ program = "a = 1; a++" # Contains syntax error
170
+ # a) from memory (string)
171
+ test_passed = false
172
+ begin
173
+ node = NodeMarshal.new(:srcmemory, program)
174
+ rescue ArgumentError
175
+ test_passed = true
176
+ end
177
+ assert_equal(test_passed, true);
178
+ # b) from file
179
+ File.open("_tmp.rb", "w") {|fp| fp << program }
180
+ test_passed = false
181
+ begin
182
+ node = NodeMarshal.new(:srcfile, "_tmp.rb")
183
+ rescue ArgumentError
184
+ test_passed = true
185
+ end
186
+ assert_equal(test_passed, true);
187
+ end
188
+
151
189
  # Part of the tests: compare result of direct usage of eval
152
190
  # for the source code and its
153
191
  def assert_node_compiler(program)
@@ -58,11 +58,11 @@ EOS
58
58
  File.open('lifegame_bin.rb', 'w') {|fp| fp << txt }
59
59
  `ruby lifegame_bin.rb`
60
60
  res_file = File.read('life.res')
61
- `rm life.res`
61
+ `rm life.res`
62
62
  # Run the life game without compilation to the file
63
63
  load('lifegame.rb')
64
64
  res_load = run_game
65
65
  Object.send(:remove_const, :LifeGame)
66
- assert_equal(res_file, res_load)
66
+ assert_equal(res_file, res_load)
67
67
  end
68
68
  end
@@ -0,0 +1,36 @@
1
+ require_relative '../lib/node-marshal.rb'
2
+ require 'test/unit'
3
+
4
+ # Test node-marshal obfuscator abilities
5
+ class TestObfuscator < Test::Unit::TestCase
6
+ def test_sym_replace
7
+ # Create the node and find all new symbols
8
+ symlog = SymbolsLogger.new
9
+ node = NodeMarshal.new(:srcfile, 'lifegame.rb')
10
+ our_symbols = symlog.new_symbols.map(&:to_s)
11
+ node.to_hash
12
+ # Try to exclude symbols used in attr_reader and attr_writer constructions
13
+ our_symbols = node.get_safe_symbols(our_symbols)
14
+ # Prepare hash table for replacement
15
+ reptbl = node.get_aliases_table(our_symbols)
16
+ life_game_name = reptbl["LifeGame"]
17
+ grid_name = reptbl["Grid"]
18
+ make_step_name = reptbl["make_step!"].to_sym
19
+ cfg_glider_gun_name = reptbl["cfg_glider_gun!"].to_sym
20
+ # Replace symbols
21
+ puts "----- Symbols replacement table"
22
+ puts reptbl.to_s
23
+ puts "-------------------------------"
24
+ node.replace_symbols(reptbl)
25
+ # Rebuild node, save it to the file and reload it
26
+ node = node.rebuild
27
+ File.open('life.bin', 'wb') {|fp| fp << node.to_bin};
28
+ node.compile.eval
29
+ # Execute the node
30
+ grid_class = Object.const_get(life_game_name).const_get(grid_name)
31
+ g = grid_class.new(25, 80)
32
+ g.send(cfg_glider_gun_name)
33
+ 75.times {g.send(make_step_name)}
34
+ puts g.to_ascii
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: UTF-8
2
+ require_relative '../lib/node-marshal.rb'
3
+ require 'test/unit'
4
+
5
+
6
+ # This unit tests Ruby 2.3 &. safe navigation operator
7
+ class TestQCALL < Test::Unit::TestCase
8
+ def test_qcall
9
+ qcall_program = %q{
10
+ class Account
11
+ def initialize(owner_name, owner_address)
12
+ @owner_name = owner_name
13
+ @owner_info = (owner_address.nil?) ? nil : OwnerInfo.new(owner_address);
14
+ end
15
+ def owner_name
16
+ @owner_name
17
+ end
18
+ def owner_info
19
+ @owner_info
20
+ end
21
+ end
22
+ class OwnerInfo
23
+ def initialize(address)
24
+ @address = address
25
+ end
26
+ def address
27
+ @address
28
+ end
29
+ end
30
+ a = Account.new("Owner", "Moscow");
31
+ puts "'#{a&.owner_name}'"
32
+ puts "'#{a&.owner_info&.address}'"
33
+ b = Account.new("Owner", nil);
34
+ puts "'#{b&.owner_name}'"
35
+ puts "'#{b&.owner_info&.address}'"
36
+ [a&.owner_name, a&.owner_info&.address, b&.owner_name, b&.owner_info&.address]
37
+ }
38
+
39
+ node = NodeMarshal.new(:srcmemory, qcall_program)
40
+ bindump = node.to_bin
41
+ node = NodeMarshal.new(:binmemory, bindump)
42
+ res_node = node.compile.eval
43
+ res_text = eval(qcall_program)
44
+ assert_equal(res_text, res_node)
45
+ end
46
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: node-marshal
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexey Voskov
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir:
10
10
  - bin
11
11
  cert_chain: []
12
- date: 2015-05-04 00:00:00.000000000 Z
12
+ date: 2015-12-24 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: "This gem is designed for transformation of Ruby source code (eiher in
15
15
  the form of files or strings) to the \nRuby nodes (syntax trees) used by Ruby MRI
@@ -34,6 +34,7 @@ files:
34
34
  - ext/node-marshal/libobj/readme.txt
35
35
  - ext/node-marshal/node193.h
36
36
  - ext/node-marshal/node220.h
37
+ - ext/node-marshal/node230.h
37
38
  - ext/node-marshal/nodedump.c
38
39
  - ext/node-marshal/nodedump.h
39
40
  - ext/node-marshal/nodeinfo.c
@@ -42,6 +43,8 @@ files:
42
43
  - test/test_base.rb
43
44
  - test/test_complex.rb
44
45
  - test/test_lifegame.rb
46
+ - test/test_obfuscator.rb
47
+ - test/test_qcall.rb
45
48
  - test/tinytet.rb
46
49
  homepage:
47
50
  licenses:
@@ -65,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
68
  version: '0'
66
69
  requirements: []
67
70
  rubyforge_project:
68
- rubygems_version: 2.4.5
71
+ rubygems_version: 2.4.5.1
69
72
  signing_key:
70
73
  specification_version: 4
71
74
  summary: Transforms Ruby sources to binary nodes (trees) that can be saved and loaded