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
----------------