On Fri, Oct 17, 2008 at 1:55 AM, Mike Gold <mike.gold.4433 / gmail.com> wrote: > Ade Inovica wrote: >> Interesting solution. May I also suggest that you try >> www.rubyencoder.com as this protects Ruby source code also. I am >> involved in this project (disclaimer!) but thought it was appropriate to >> mention it > > The last time you advertised this product here, we had proven the claims > on your website to be false. > > http://www.ruby-forum.com/topic/166458#731051 > > You have not made any changes or corrections to the website since. Found link to RubyEncoder on InfoQ ( http://www.infoq.com/news/2008/10/rubyencoder ), and just for fun, decided to look how difficult would it be to crack it :) It turns out, that RubyEncoder uses following scheme: modified Ruby-1.8.7 interpreter, that stores encoded AST nodes along with encoding/restriction options, while rgloader simply decodes it back to AST and executes. So, using just a few quick and dirty hacks it is possible to get source back: 1) one-byte change in library to call external ruby_exic instead of ruby_exec: ---------------- $ cmp -l rgloader.linux.so.original rgloader.linux.so 4616 145 151 ---------------- 2) A bit patched ruby, ruby-1.8.6/eval.c to keep injected AST: ---------------- NODE *ruby_eval_hack; int ruby_exic(){ volatile NODE *tmp; int state; Init_stack((void*)&tmp); ruby_eval_hack = ruby_eval_tree; state = ruby_exec_internal(); return state; } ---------------- 3) Patch for RawParseTree in ParseTree-3.0.1/lib to retrieve sexp from intercepted tree: ---------------- builder.prefix " #{extern_mode} NODE *ruby_eval_hack; " builder.c %Q{ static VALUE parse_tree_full() { VALUE result = rb_ary_new(); add_to_parse_tree(self, result, ruby_eval_hack, NULL); return result; } ---------------- 4) And, finally, simple environment to get source code back from RubyEncoder: ---------------- require 'rubygems' require 'parse_tree' require 'ruby2ruby' require 'encoded_script' # protected code, you say? RawParseTree.new.parse_tree_full().each do |sexp| puts Ruby2Ruby.new.process(Unifier.new.process(sexp)) end ---------------- Example: Original: ---------------- class EncodedHelloWorld ENCODER_VERSION = "1.0" def initialize puts "Hello, world!" end end ---------------- Encoded: ---------------- # RubyEncoder v1.0 evaluation _d = _d0 = File.expand_path(File.dirname(__FILE__)); while 1 do _f = _d + '/rgloader/loader.rb'; break if File.exist?(_f); _d1 = File.dirname(_d); if _d1 == _d then raise "Ruby script '"+__FILE__+"' is protected by RubyEncoder and requires the RubyEncoder loader. Please visit the http://www.rubyencoder.com/loaders/ RubyEncoder site to download the required loader and unpack it into '"+_d0+"/rgloader/' directory to run this protected script."; break; else _d = _d1; end; end; require _f; RGLoader::load('AAEAAAAEaAAAAIAAAAAA/1nu5hlzvK93ynRezwoJSWaAXO0XWYMyqYojzdIsXeg/n3sTUToqkcdtx9wMbCcidZy4WpqIq2fj9tHsyREq8dCcvPsiWYISiwZ2jFHadIF3FhHZ9eLhZWJTZuRZDYG3Zk0nttbBzuP6EgAAAPgAAACl6rEqW0Dbrjuf0Nl2ehDd4mtpWkb9bP504YjdDfJj1ZM0tqmLXWMXpXnXL1kFNqoEfnws38xmo1J0E/Ziw4typ+51d572ijDg17Xz7NWj9xEykyN4uXEKn/Dt1mKExla1mnX4eAKxbnOJrNqZPDmpIJdOEqOO+/CLfQIGvvKYt11MIyTZK9I2R4J+/oNK2RGwbmzynpFKV32zxdILn4thrQx3gDLbD5ZPbfR6qWsmtJT6pyxccj7RtwGSat4BetCUKmcHR6b/qvp6rvPtaA1m/1JuuGNLUzg3tHHLkA/U14GfF4af9VyqtLQy5ww+jHB6wz4BkFe06gAAAAA='); ---------------- Output: ---------------- class EncodedHelloWorld ENCODER_VERSION = "1.0" def initialize puts("Hello, world!") end end ----------------