ソース隠蔽手法
昨日の続き。隠蔽といってもアセンブラレベルでは見れてしまうので頑張れば解析可能なわけだが。Rubyのアセンブラは高レベルなので読みやすいというのが難点か。
ともあれ、このようなファイルを作る。名前はiseqloader.rbとしよう。
require 'fiddle' class RubyVM class InstructionSequence addr = Fiddle.dlopen(nil)['rb_iseq_load'] fn = Fiddle::Function.new( addr, [Fiddle::TYPE_VOIDP] * 3, Fiddle::TYPE_VOIDP) define_singleton_method(:load) do |dat, par=nil, opt=nil| fn.call(Fiddle.dlwrap(dat), par, opt).to_value end end end alias old_require_relative require_relative def require_relative(filename) ext = File.extname(filename) base = File.basename(filename) if ext.empty? rbname = filename + ".rb" rbcname = filename + ".rbc" elsif ext == ".rb" rbname = filename rbcname = filename + "c" else old_require_relative(filename) return end if File.exists?(rbname) if !File.exists?(rbcname) or File.mtime(rbname) > File.mtime(rbcname) open(rbcname, "wb") do |fh| Marshal.dump(RubyVM::InstructionSequence.compile_file(rbname).to_a, fh) end end end if File.exists?(rbcname) open(rbcname, "rb") do |fh| RubyVM::InstructionSequence.load(Marshal.load(fh)).eval end else old_require_relative(filename) end end
このコードをrequireするとrequire_relativeが置き換えられて、実行時にrbと同名のrbcファイルというコンパイル済みファイルを生成し、そっちを読んで実行するようになる。
rbのほうが新しければrbcを再生成するし、無ければ存在するrbcを読んで実行する。rbもrbcも無ければ通常のrequire_relativeが実行される。
例えばこのようなファイルを作る。rbctest.rbとしよう。
class RBCTest def initialize(v) @v = v end def v @v end end
んで、メインのファイルをtest.rbとして作る。
require_relative 'iseqloader' require_relative 'rbctest' p RBCTest.new(10).v #=> 10
test.rbを実行するとrbctest.rbcが生成されて、10が表示される。このあと、rbctest.rbを削除してtest.rbを実行してもエラーにならずに10が表示される。
問題点はrequire_relativeと違って同じファイルを2回以上読み込んでしまうところで、このへんは絶対パスを生成して配列にでも入れておいて同じファイルを読まないようにチェックを入れる必要があるだろう。
開発中はiseqloaderのrequireをはずしておかないとエラーの箇所がわからないだろうが、これを入れて動作するならそのままrbファイルを削除しても動くはずである。その状態でocraでexe化してやれば生のソースを含まないexeの完成、となる。かもしれない。rbcファイルはocraのexeには入ってくれないだろうけど、まあ、問題は無い。か?
誰かきちんと作ってgemにでもしてくれないかしらん。