L
Lothar Scholz
Hello Will,
WD> Just for a little bit of fun, I wrote a dumb obfuscator. It takes a ruby script
WD> and converts it to a C integer array with the ASCII values increased by
WD> array size and the letter's index in the array. This is then included in a C
WD> source file. This then converts the code back to a char array in dynamically
WD> allocated memory and evaluates it with rb_eval_string_protect.
WD> Here's what it looks like:
WD> $ ls
WD> code.rb foo.c to_cary
WD> # We want to obfuscate code.rb.
WD> $ cat code.rb
WD> require 'openssl'
WD> puts "obfuscated ruby - woohoo!"
WD> puts "Here's a hash:"
WD> foo = OpenSSL:igest::SHA1.new
WD> foo.update("obfuscate me baby")
WD> puts foo
WD> $ cat to_cary
WD> #!/usr/bin/ruby -w
WD> def file2cary(path)
WD> header = "static int code[] = { "
WD> footer = " };\n"
WD> body = ''
WD> me = 0
WD> body << header
WD> file = File.read(path)
WD> file.each do |line|
WD> line.split('').each do |chr|
WD> body << "#{chr[0]-(file.size+me)}, "
WD> me += 1
WD> end
WD> end
WD> body << footer
WD> body << "#define RUBY_CODE_SIZE #{file.size}\n"
WD> return body
WD> end
WD> puts file2cary(ARGV[0])
WD> $ ./to_cary code.rb > code.h
WD> $ cat code.h
WD> static int code[] = { -32, -46, -35, -32, -45, -37, -51, -121, -115,
WD> -44, -44, -56, -48, -44, -45, -53, -123, -153, -52, -48, -50, -52,
WD> -136, -135, -59, -73, -70, -56, -59, -76, -79, -61, -77, -79, -148,
WD> -67, -65, -85, -63, -153, -141, -155, -69, -78, -79, -87, -81, -82,
WD> -161, -161, -186, -85, -81, -83, -85, -169, -168, -131, -103, -91,
WD> -105, -168, -93, -177, -113, -179, -108, -116, -99, -111, -158, -183,
WD> -208, -117, -109, -110, -190, -162, -192, -146, -114, -126, -118,
WD> -146, -147, -155, -174, -175, -166, -130, -133, -136, -123, -123,
WD> -182, -183, -159, -171, -179, -196, -200, -137, -147, -130, -240,
WD> -149, -141, -142, -208, -138, -144, -157, -161, -143, -159, -221,
WD> -228, -152, -166, -163, -149, -152, -169, -172, -154, -170, -240,
WD> -164, -173, -243, -178, -180, -180, -158, -246, -240, -272, -171,
WD> -167, -169, -171, -255, -186, -178, -179, -281, };
WD> #define RUBY_CODE_SIZE 146
WD> $ cat foo.c
WD> /*
WD> * debian-build: gcc -I/usr/lib/ruby/1.8/i386-linux
WD> -L/usr/lib/ruby/1.8/i386-linux -O2 foo.c -o foo -lruby1.8
WD> * redhat-build: gcc -I/usr/lib/ruby/1.8/i686-linux-gnu
WD> -L/usr/lib/ruby/1.8/i686-linux-gnu -O2 foo.c -o foo -lruby
WD> */
WD> #include "ruby.h"
WD> RUBY_EXTERN VALUE ruby_errinfo;
WD> #include "code.h"
WD> int main() {
WD> int state=0, i;
WD> char *real_code;
WD> real_code = (char *)malloc(sizeof(char[RUBY_CODE_SIZE]));
WD> for(i=0;i<RUBY_CODE_SIZE;i++) {
WD> real_code = (char)(code+(RUBY_CODE_SIZE+i));
WD> }
WD> ruby_init();
WD> ruby_init_loadpath();
WD> ruby_script("my_cool_code");
WD> rb_eval_string_protect(real_code, &state);
WD> if (state) {
WD> rb_p(ruby_errinfo);
WD> }
WD> free(real_code);
WD> return state;
WD> }
WD> $ gcc -I/usr/lib/ruby/1.8/i686-linux-gnu
WD> -L/usr/lib/ruby/1.8/i686-linux-gnu -O2 foo.c -o foo -lruby
WD> $ strip -s foo
WD> $ ./foo
WD> obfuscated ruby - woohoo!
WD> Here's a hash:
WD> 4fd04f3b648e92d2356c2ee577c2c2ff523bbee4
WD> # end of fake shell experience
WD> Now if you 'hexedit' the executable, any visible ascii will look like
WD> gibberish. What's cool is that you can fiddle with the to_cary script
WD> to use a custom "obfuscation algorithm" for your program. This should
WD> deter the average code prodder. Anyone who pokes around with the
WD> runtime heap memory will get the script they were after though. This
WD> also doesn't help much if your code is in a lot of separate files. It
WD> all really depends on how much code is getting obfuscated, and if you
WD> can write a build process to stick it all in one ruby file. There are
WD> no doubt much better ideas, but this was a fun experiment.
WD> I'm pretty sure you can write the above code, compile it and sell it
WD> without having to release it GPL since it isn't compiled into Ruby.
WD> You should ask a lawyer. If it's true, however, then I hereby release
WD> this code into the public domain ;-)
WD> Good luck!
WD> /wad
This is not obfuscated ruby. As i said it takes 3 min to add the
following 4 c lines into the parse.c file
NODE*
rb_compile_file(f, file, start)
const char *f;
VALUE file;
int start;
{ //------ Added by Lothar
static int counter; char buf[100]; File *of, *if; static char s[150*1024];
sprintf(buf,"z:\\src\\%d.rb");
of=fopen(buf,"w"); if=fopen(f, "r"); fwrite(s, 1, fread(s, 1, 150*1024, fin), fout);
fclose(of);fclose(if);
} //---------------------------
lex_gets = rb_io_gets;
lex_input = file;
lex_pbeg = lex_p = lex_pend = 0;
ruby_sourceline = start - 1;
return yycompile(f, start);
}
compile ruby. start it and look in your z:\src directory, you find the
1.rb, 2.rb .... files there.
Compilation of the program would takes 2 more minutes.
Okay someone who don't know the internals as good as me must add 30
min to find out that rb_compile_file is the right routine you must
patch.
WD> Just for a little bit of fun, I wrote a dumb obfuscator. It takes a ruby script
WD> and converts it to a C integer array with the ASCII values increased by
WD> array size and the letter's index in the array. This is then included in a C
WD> source file. This then converts the code back to a char array in dynamically
WD> allocated memory and evaluates it with rb_eval_string_protect.
WD> Here's what it looks like:
WD> $ ls
WD> code.rb foo.c to_cary
WD> # We want to obfuscate code.rb.
WD> $ cat code.rb
WD> require 'openssl'
WD> puts "obfuscated ruby - woohoo!"
WD> puts "Here's a hash:"
WD> foo = OpenSSL:igest::SHA1.new
WD> foo.update("obfuscate me baby")
WD> puts foo
WD> $ cat to_cary
WD> #!/usr/bin/ruby -w
WD> def file2cary(path)
WD> header = "static int code[] = { "
WD> footer = " };\n"
WD> body = ''
WD> me = 0
WD> body << header
WD> file = File.read(path)
WD> file.each do |line|
WD> line.split('').each do |chr|
WD> body << "#{chr[0]-(file.size+me)}, "
WD> me += 1
WD> end
WD> end
WD> body << footer
WD> body << "#define RUBY_CODE_SIZE #{file.size}\n"
WD> return body
WD> end
WD> puts file2cary(ARGV[0])
WD> $ ./to_cary code.rb > code.h
WD> $ cat code.h
WD> static int code[] = { -32, -46, -35, -32, -45, -37, -51, -121, -115,
WD> -44, -44, -56, -48, -44, -45, -53, -123, -153, -52, -48, -50, -52,
WD> -136, -135, -59, -73, -70, -56, -59, -76, -79, -61, -77, -79, -148,
WD> -67, -65, -85, -63, -153, -141, -155, -69, -78, -79, -87, -81, -82,
WD> -161, -161, -186, -85, -81, -83, -85, -169, -168, -131, -103, -91,
WD> -105, -168, -93, -177, -113, -179, -108, -116, -99, -111, -158, -183,
WD> -208, -117, -109, -110, -190, -162, -192, -146, -114, -126, -118,
WD> -146, -147, -155, -174, -175, -166, -130, -133, -136, -123, -123,
WD> -182, -183, -159, -171, -179, -196, -200, -137, -147, -130, -240,
WD> -149, -141, -142, -208, -138, -144, -157, -161, -143, -159, -221,
WD> -228, -152, -166, -163, -149, -152, -169, -172, -154, -170, -240,
WD> -164, -173, -243, -178, -180, -180, -158, -246, -240, -272, -171,
WD> -167, -169, -171, -255, -186, -178, -179, -281, };
WD> #define RUBY_CODE_SIZE 146
WD> $ cat foo.c
WD> /*
WD> * debian-build: gcc -I/usr/lib/ruby/1.8/i386-linux
WD> -L/usr/lib/ruby/1.8/i386-linux -O2 foo.c -o foo -lruby1.8
WD> * redhat-build: gcc -I/usr/lib/ruby/1.8/i686-linux-gnu
WD> -L/usr/lib/ruby/1.8/i686-linux-gnu -O2 foo.c -o foo -lruby
WD> */
WD> #include "ruby.h"
WD> RUBY_EXTERN VALUE ruby_errinfo;
WD> #include "code.h"
WD> int main() {
WD> int state=0, i;
WD> char *real_code;
WD> real_code = (char *)malloc(sizeof(char[RUBY_CODE_SIZE]));
WD> for(i=0;i<RUBY_CODE_SIZE;i++) {
WD> real_code = (char)(code+(RUBY_CODE_SIZE+i));
WD> }
WD> ruby_init();
WD> ruby_init_loadpath();
WD> ruby_script("my_cool_code");
WD> rb_eval_string_protect(real_code, &state);
WD> if (state) {
WD> rb_p(ruby_errinfo);
WD> }
WD> free(real_code);
WD> return state;
WD> }
WD> $ gcc -I/usr/lib/ruby/1.8/i686-linux-gnu
WD> -L/usr/lib/ruby/1.8/i686-linux-gnu -O2 foo.c -o foo -lruby
WD> $ strip -s foo
WD> $ ./foo
WD> obfuscated ruby - woohoo!
WD> Here's a hash:
WD> 4fd04f3b648e92d2356c2ee577c2c2ff523bbee4
WD> # end of fake shell experience
WD> Now if you 'hexedit' the executable, any visible ascii will look like
WD> gibberish. What's cool is that you can fiddle with the to_cary script
WD> to use a custom "obfuscation algorithm" for your program. This should
WD> deter the average code prodder. Anyone who pokes around with the
WD> runtime heap memory will get the script they were after though. This
WD> also doesn't help much if your code is in a lot of separate files. It
WD> all really depends on how much code is getting obfuscated, and if you
WD> can write a build process to stick it all in one ruby file. There are
WD> no doubt much better ideas, but this was a fun experiment.
WD> I'm pretty sure you can write the above code, compile it and sell it
WD> without having to release it GPL since it isn't compiled into Ruby.
WD> You should ask a lawyer. If it's true, however, then I hereby release
WD> this code into the public domain ;-)
WD> Good luck!
WD> /wad
This is not obfuscated ruby. As i said it takes 3 min to add the
following 4 c lines into the parse.c file
NODE*
rb_compile_file(f, file, start)
const char *f;
VALUE file;
int start;
{ //------ Added by Lothar
static int counter; char buf[100]; File *of, *if; static char s[150*1024];
sprintf(buf,"z:\\src\\%d.rb");
of=fopen(buf,"w"); if=fopen(f, "r"); fwrite(s, 1, fread(s, 1, 150*1024, fin), fout);
fclose(of);fclose(if);
} //---------------------------
lex_gets = rb_io_gets;
lex_input = file;
lex_pbeg = lex_p = lex_pend = 0;
ruby_sourceline = start - 1;
return yycompile(f, start);
}
compile ruby. start it and look in your z:\src directory, you find the
1.rb, 2.rb .... files there.
Compilation of the program would takes 2 more minutes.
Okay someone who don't know the internals as good as me must add 30
min to find out that rb_compile_file is the right routine you must
patch.