(これは Ruby Advent Calendar jp: 2009 の 16 日目の記事です。)
Ruby は読みやすいプログラムを簡単に書ける言語ですが、読みにくいプログラムも簡単に書けます。
今回は、Ruby でできるかんたんな難読化のテクニックを 4 つ紹介します。
1. Integer#to_s を使う方法
Integer#to_s *1 は、何進数として文字列化するかを引数で指定できます。普通は 2 、10 、16 くらいしか使わないと思いますが、実は 36 まで指定できます。
0.to_s(36) #=> "0" 1.to_s(36) #=> "1" ... 8.to_s(36) #=> "8" 9.to_s(36) #=> "9" 10.to_s(36) #=> "a" 11.to_s(36) #=> "b" ... 34.to_s(36) #=> "y" 35.to_s(36) #=> "z" 36.to_s(36) #=> "10"
このように、0-9 か a-z だけの文字列を生成することができます。例えば、
p "helloworld".to_i(36) #=> 1767707668033969
なので、
puts 1767707668033969.to_s(36) #=> helloworld
などと書けます。ぱっと見では何が出るかわからないでしょう。
実例。
puts "Ruby Advent Calendar jp: 2009"
↓
puts "R" + "uby" + " A" + "dvent" + " C" + "alendar" + "jp" + ": " + "2009"
↓
a = ["uby", "dvent", "alendar", "jp", "2009"] puts ["R", " A", " C", " ", ": "].zip(a).join
↓
a = [37169,20855234,19507626802,690,85759].map{|s|s.to_s(35)} puts %w(R \ A \ C \ :\ ).zip(a).join
↓
puts %w(R \ A \ C \ :\ ).zip([109* 11*31,2*103*3491* 29,178*109593409, 690,85759 ].map{| c|c.to_s(35)})*""
to_s を使う他のアプローチでは、こんなのも書けます。読み解いてみてください。
puts"ujf9z30ia ehch14v8sfrqw1 h0fyq".gsub(/[ ]/,"").to_i(36 ).to_s(35).tr\ "13456","R AC:"
2. %w() リテラルを使う方法
あまり知られていませんが、%w() というリテラルがあります。文字列の配列を生成します。
%w(foo bar baz) #=> ["foo","bar","baz"]
これを使うと、プログラムをアスキーアート化して難読化できます。まずは
puts "Ruby Advent Calendar jp: 2009"
を変えて、スペースを使わないでプログラムを書きます *2 。" " がほしければ 32.chr (スペースの ASCII コード) などで作れます。改行はセミコロンなどで代用してください。
puts"Ruby_Advent_Calendar_jp:_2009".tr("_",32.chr)
これを eval でくるみます。
eval('puts"Ruby_Advent_Calendar_jp:_2009".tr("_",32.chr)')
コード部分を、%w() リテラル + join に置き換えます。
eval(%w(puts"Ruby_Advent_Calendar_jp:_2009".tr("_",32.chr)).join)
あとは、%w の中に空白入れ放題、改行し放題です。キーワードの途中でもメソッド名の途中でも OK です。自由に整形してください。
eval(%w( put s"R ub y_ Adve nt_C al en da r_ jp :_ 20 09 ".tr ("_" ,3 2. chr )). join)#xx
eval( %w(pu ts"Ru by_Ad vent_ Calen dar_j p:_20 09".g sub(" _",32 .chr) )*"")
とか。
3. 意外な名前のメソッドを使う方法
メソッドであることを忘れがちなメソッドを使うと、読み手を混乱させられると思います。
alias ` puts `Ruby Advent Calendar jp: 2009`
とか、
alias / send "Ruby Advent Calendar jp: 2009\n" / "display"
とか。Integer#to_s と組み合わせると、見ただけでは何が起こるのか全くわからなくなるでしょう。
alias / send "Ruby Advent Calendar jp: 2009 " / 24885962359.to_s(35)
1.9 専用ですが、こんなのも書けます。
# coding: UTF-8 (ゆきひろ ="Ruby Advent Calendar jp: 2009"; alias/send;alias|display;alias まつもと puts まつもと ゆきひろ /:|)
(↑もちろんこの signature は偽物です。matz とは関係ありません。念のため。)
4. ツールを使う方法
_ を使えば、任意の Ruby プログラムを _ だけにできます。とても手軽に意味不明にできます。
ref: http://d.hatena.ne.jp/ku-ma-me/20091115/p1
$ gem19 install _ $ ruby19 -r_ -e 'puts __script__("puts \"Ruby Advent Calendar jp: 2009\"")' > rac2009.rb
こうなります。
require "_" ____ _ _____ ____ __ ____ ____ __ ___ ____ __ __ _ ______ ___ _ ______ _____ ___ __ _____ ____ __ ____ ___ _____ ___ ____ ___ __ _ ______ ___ __ _____ ______ ___ _____ _____ ____ __ _____ ___ _____ ______ ____ _ ___ ____ __ ___ _ ______ ___ __ ______ __ ___ _____ __ ____ _ _ ___ _____ ______ ____ _ ___ ___ _____ _____ ___ _____ __ ____ __ _ _ ______ ___ ___ ______ _____ ____ _ _____ __ ____ _____ _ ______ ___ __ ___ ___ __ ___ _ __ ___ _ __ ____ ____ _ ______ _____
実行するとちゃんと動きます。
$ ruby19 rac2009.rb Ruby Advent Calendar jp: 2009
ほかに、任意の Ruby プログラムを記号だけにするプログラム (id:kurimura さん)や任意の Ruby プログラムをアルファベットと数字だけにするプログラム (id:shinichiro_h さん)も使えると思います。
まとめ
かんたんな Ruby コードの難読化方法を紹介しました。
どれも単純な方法なので、知っていればすぐに読み解けてしまいます (解読ツールが作れてしまう) が、Ruby に詳しくない人にはそれなりに効果があると思います。
Ruby Advent Calendar jp: 2009 ではみんな本当に役に立ちそうな tips ばっかり紹介していたので、こういう tips もよいかなと。
おまけ: Array#pack を使う方法
元の文字列が 64 種類以下の文字で構成されている場合は、こんな方法もあります。
puts ["Fj2T'oz{UPI^91+V:{Wm4}UU"].pack("m").tr("VEmCTUOfo7", " auAdCn9b:").strip
今回のために書いてみたのですが、全然簡単ではなかったので紹介だけ *3 。作り方を整理して別の機会に紹介するかも。しないかも。