Ruby とすてきな難読化

(これは 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 。作り方を整理して別の機会に紹介するかも。しないかも。

*1:正確には Fixnum#to_s と Bignum#to_s かも。

*2:改行の直前にバックスラッシュが来るとまずいので、バックスラッシュも避けた方がよい。

*3:コードにバイナリを許すならわりと簡単です。