Found link to RubyEncoder on InfoQ (
RubyEncoder: Obfuscation and Code Protection for Ruby ), 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:
···
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.
Protecting Ruby code - Ruby - Ruby-Forum
You have not made any changes or corrections to the website since.
----------------
$ 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
----------------