IORCC Deobfuscation: Lost-key XOR-Vigenere decryptor by Cash Erler

The code for this entry originally came in one single overlong line — had I tried to render that here your browser would probably start hating me so I have decided to insert escaped newlines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
require'zlib';eval(Zlib::Inflate.inflate("x\332\355WKo\333F\020\276\367W\250\
\262\001\222\tM\357\246M\017\242\211\242h\200\036\212`\201\026\350\205`\f=h\233\
\301Zt%\273A-2\277\275\363\315\222\334\241,#v\214\366T\331\262\326\303y\3177\
\263\243M\371\347]\265)\203UuYnoO\257Wo\203\364>[T\353U\265\276L\257\353\325\
\235-'\277\226\233ui\323Uy1\251\027\027\341\253\371\346r\e\245u\366\216\205f\
\263\367\357\336&\353\362S\010zr=\277\3315w\315]r[\237o\333\344c]\255#>\343O\
\025\352\037\334\177\341\367\364\271\t\003\245\337|\027\304\364aM@:\363\260\316\
>\237\232\323(\326\252(\327\253\t\275\323\332h\253\224V\306d\247\037\362\371\
\311}\321\314f\356\363C\016\311\342\365\361ij\026\037\313\345\355\3577\363e\231\
\224\363\345\325y\315\204]\263l\3620\177\317\241\024M\376\263\235o\267Et\222/\
\223%\037\213\374D\323\373M\3214Kv-\373<\361\026\233&\\\304\253,\354\270\263\
\314)\232\3748\311\247]z\216v\3136\235\306\323\243\035\262\263\214\332\f\024\
\342\257\327\345\264\230\205\313o36\3122\254e2\260\236\2610\202\354\037\260\256\
 (f=/\313:Z\024\245\313\244Zoo\347\353ey~]\336^\325\253-\a\273k\252fqv6\235\333\
j\276\355\236tV\252\230\377F\276\n\333\277\257\241\345\206\262\323\306G\273\352\
\340\203t\332\246\2441`'\316\316\266\245\275H\0032\377l\253\017,=42E\002\360\
\236\246\345_s;Y\274^\305\367Q\233\036\233\276\016\312\2450=\256=\305U\202\230\
\254\"\222\265\004\217\237~\373\345\017\"h\243\210\307j\235\251\205V8\353\304X\
\372!1CGc-\251\240\337\020\317\361#\036\023\n\2556\254Cg3\002}\265\356s\235\202\
K[K\022\020 \243\206\216\241p3\33255\350\232\036\030q$\233\344!\363\204^},$\023\
Xg\235:\364r1\"1\344\277\261\207\031(\301DE\260\344\026Y\177\345\036\221\204mP\
\263\266Mk\305\366\210%3\220\302S\322\306IR\316\377!\203 S\336\310\216\215\203\
\315\002-\211 5D2\257\210\302\321p\234\364\205\222Jj\220\022E\321h\347\223RQ*94\
K\022\243\314H`4{LV\003\021N\f\333\364I\347l\327UR\305t\340\332i>\241x=Mu4R\245\
\373\223\244\251NB\211\247\236\3465\253^bx\332Yc\263\252M\220b\253\220\310\004\
\331\242\020,`\005T\021Y\251P@\020\365Ax\310z\364\264\240\265vj2\037?0\v\"en\
\244\374\251\032\225\253v\346\253\3712\215\032\322(o\206~A\006\010\f\324\22357\
\026\"\316\024\365\021\360@\277:\363.$\f\342\016$\200\v\341\302\230\020\340\341\
\201K\017\270+i\326-\312\313j\235\n[\376({\330u\254\266\334\034\031\367%:CK\210\
{\311h\aQH\333Q\023\250\210;e\360\322\362\213\202\247\216\266\340C&(p\274HT7\
\336&B\352\300\036z\206\204\375 \032z\304\233\217\034\267AK\207R\363\213\324u\
\334\203\272h\234 \304&\364S\302]|\024\233b\000\023E\034\005\300!\330\2274\026\
\205\316\363\203\364\"\316\245!\242\360Y?4\204b\023.\2009\036X\300\213p\200]\
\304\324\200$^\204\025\222D\325X \363\324\004\223\205\207\241M\245\352\341(s\
\3415\260w\226\313=\2422 \200\177\344\355\211\3350\004\341\217\207\215r%x\030\
\302\304\230\335{#\250#o\204h\327;\220\242\275B%j&\343e\005\226/\r\200\035\035\
\206K\243\027\216Z\230\323.\335\356^!\vF\002K\366\246kG\321\364E\301\362\250\
\275a\f\031\207i%\216\342&ie\205\260\324}\272\252ho\222\306\370\362!}6\364C\003\
\2717\206'!.\315\036mhMm\370\252\241\365\221g\275\326A\302\254\270X,\371\353\
\232:\222\321\253\025\217v%\222\023!\243r\272\364(\376\177\236\374\233\363\3048\
\330b\241xdTp\325\321\377\3428F\234\214\263\357\255f\324\306\226\257\022\"\000\
\354\003\024C\207\na\353\240&O\305\376\004ncy\350\f\276\357+Q|\201bBi\206\277\
\345u\251\273\310\367\242\303*\204d\n\271}\016\2345r8\034\201[\343:>\364*\242\
\266\025+HZ\263e\212\0247q\357\310X\267[\333(9_o}P\201\324>\266\364\000\217hh\
\352\225a\213q\260\031\334\022sg\360\e\206\234B=\246\2421\341e\364\270\321\224\
\347\0056L\267\227)\244\210\307\027\257<\343\257\000\303\264u{\235\326\352i\303\
^\332\200\n\236\243a\277\034J#~S\335'2\371\001q\3745$\356\027^\371\325\344\331\
\036\362\004\267\330\251<\212\237\257\345kr\371\302d\362r\376\344d\252C\311\374\
R6\017e\375\005\271yAV\363/\257\345\261(\340hW\020\222\a\027k)60\354\217\363\
\3501\263rt\0364\025\025|\265\031\355\276d\357\3159\367\225\025\223U\273n\027\
\324\321H\031\030\036\357\356\377\010\266\337\374\003\3375Q\335"))

As you see there's a huge string which gets passed through Zlib::Inflate.inflate and then executed via eval. That string contains lots of octal escapes which means that this likely is a string representation that was created by Ruby's String#inspect.

Zlib::Inflate.inflate takes a zip-encoded string and decodes it.

Let's have a look at the actual code hidden in the string (again, rewrapped at the 80 character boundary):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
require'digest/md5';z=binding;module Kernel;def obf(*args);o=Digest::MD5.new(
args.map{|u|u.to_s}.join).to_s.to_i(16).to_s(4).tr('0123','01lO');o[(o=~/O/),10]
end end;oO1l0010OO=/^[a-z]|::[a-z]|^[01lO]+$/;ObjectSpace.each_object{|c|[([
Module]|[Class])-[c.class]][-1][-2]||c.to_s=~ oO1l0010OO||(b,d=(
c.class==Module ? ["module #{c};","#{obf(c)}=#{c}.clone"]:(c!=Class ? [
"class #{obf(c)}<#{c};end;class #{c};",'']:["class Class;",'']));
c.instance_methods.each{|i|b<<"alias_method #{obf(i,obf(i)).to_s.to_sym.
inspect},#{i.to_s.to_sym.inspect};"};b<<'class<<self;';c.methods.each{|i|b<<
"alias_method #{obf(i,obf(i)).inspect},#{i.to_sym.inspect};"};b<<"end;end;";eval
b+d,z)};$OO1l0010O0=Class;$oO1l0010O0=Module;$Ol0l00llOO=ARGV;$O1O0001l11=
0b1000011.OlOlO0O0O1.OOll010010(0b101010.OlOlO0O0O1);ol1OO1O001=:define_method
OlOl1ll101.OlO00lO101($OO1l0010O0){|oO00l1O10O|oO00l1O10O.OO0ll0010O(ol1OO1O001,
$OO1l0010O0.O0O0lO00l1(oO00l1O10O,O000O011Ol.O0O0lO00l1(oO00l1O10O)).
O0l01l1ll1){|*oO00l1011l|self}};o01OO1O1l1=O0l1Ol101l.O1O0000101($Ol0l00llOO.
O1Ol110lOl,'rb');o0lO11101l=o01OO1O1l1.OO11l00O0l(o01OO1O1l1.O0l0lO1000.
O101l1ll00);o01OO1O1l1.O1OO11lO0l.OO0ll0010O('O11011llOl'.O0l01l1ll1,
o0lOll11ll={0b100000,0b1100101,0b1110100,0b1100001,0b1101111,0b1101001,
0b1101110,0b1110011,0b1101000,0b1110010,0b1100100,0b1101100,0b1110101,0b1001010
}.O00ll0Ol10.Ol1110OlO1.Oll01l0O00($O1O0001l11).O0O110l101).OO0ll0010O(
'O11011llOl'.O0l01l1ll1,o0111l0O00=o0lOll11ll.OOl1l0OlO0($O1O0001l11)).
OO0ll0010O('O11011llOl'.O0l01l1ll1,o0101O0000=o0lO11101l.OOl1l0OlO0(
$O1O0001l11)).OO0ll0010O('O11011llOl'.O0l01l1ll1,o1OOl11ll0=1);o110O00OO0=
ol0100O0O1=0;ol0l0ll0l1=o0101O0000.O0lO0O1000.Ol0lOO1lOO(1.O0010OO100(0b10))
begin;o1OOl11ll0=o1OOl11ll0.OOll010010(1);ol11l0lO00=OlO00O10O1.O1O0000101(
o1OOl11ll0){OOllOll0ll.O1O0000101(0)};o0101O0000.Ollllll011{|olO1O01101,
o111110ll1|ol11l0lO00.O0l1l1ll00(o111110ll1.O00O1l000l(o1OOl11ll0)).O0l1lO11Ol(
olO1O01101,ol11l0lO00.O0l1l1ll00(o111110ll1.O00O1l000l(o1OOl11ll0)).O0l1l1ll00(
olO1O01101).OOll010010(1))};ol11l0lO00.OlOOOlllll{|o0l1110lOO|o0l1110lOO.
O00ll0Ol10};ol0100O0O1,o110O00OO0=[[ol0100O0O1,o110O00OO0],[ol11l0lO00.
OOO01l0lO1{|o0l1110lOO|o0l11010O1=o1l1100l10=0;o0l1110lOO.OlOOOlOO1O{
|o00000Ol0O|ol0ll1100l=o00000Ol0O.O1l11O10Ol;o0l11010O1=o0l11010O1.OOll010010(
ol0ll1100l.OOO0l1O000(ol0ll1100l.O00001l001(1))).OO0ll0010O('O11011llOl'.
O0l01l1ll1,o1l1100l10=o1l1100l10.OOll010010(ol0ll1100l))};oOO1O11l00=o1l1100l10.
OOO0l1O000(o1l1100l10.O00001l001(1));oOO1O11l00.Ol0lOOOOO0 ? 0.0:o0l11010O1.
O1l110l1Ol.OOllO1lOO1(oOO1O11l00)}.OOO10l1110.O0O0l001Ol(o1OOl11ll0.Ol0lOO1lOO(
0b10)),o1OOl11ll0]].OOO1O0O1Ol{|o0OO0l1lO0|o0OO0l1lO0.O1Ol110lOl}.O1l11O10Ol
end while o1OOl11ll0.O0llOOO0Ol(ol0l0ll0l1).OOOOl0O00O(ol0100O0O1.Oll1OO0l0l(
'00111111'.OOOl0lO0ll(0b000010).OOllO1lOO1(0d001000.O1l110l1Ol)));o1OOl11ll0=
o110O00OO0;ol11l0lO00=OlO00O10O1.O1O0000101(o1OOl11ll0){OOllOll0ll.O1O0000101(0)
};o0101O0000.Ollllll011{|olO1O01101,o111110ll1|ol11l0lO00.O0l1l1ll00(o111110ll1.
O00O1l000l(o1OOl11ll0)).O0l1lO11Ol(olO1O01101,ol11l0lO00.O0l1l1ll00(o111110ll1.
O00O1l000l(o1OOl11ll0)).O0l1l1ll00(olO1O01101).OOll010010(1))};ol11l0lO00.
OlOOOlllll{|o0l1110lOO|o0l1110lOO.O00ll0Ol10};oOOOOOO01l=ol11l0lO00.OOO01l0lO1{
|oOOl0OOOlO|oOOl0OOOlO.OOO1O0O1Ol{|olO1O01101,ol0ll1100l|(ol0ll1100l.OlOO100l0l)
}.O1Ol110lOl(10).OOO01l0lO1{|olO1O01101,ol0ll1100l|olO1O01101}};oO01lOl00l=
oOOOOOO01l.OOO01l0lO1{|olO0O0001O|ol1l1lO0ll=o0111l0O00.OOO01l0lO1{|oO1O1lll01|
olO0O0001O.OOO01l0lO1{|oO101l01OO|oO101l01OO.O1lOlOOOOO(oO1O1lll01)}}.Ol1110OlO1
olO0ll10l0=ol1l1lO0ll.OOO01l0lO1{|o1l00O0OO1|olO0O0001O.OOO01l0lO1{|oO101l01OO|
oO101l01OO.O1lOlOOOOO(o1l00O0OO1)}.Oll01l0O00($O1O0001l11).O11OlO0Ol1(o0lOll11ll
)};ol1l1lO0ll.O0O0l001Ol(olO0ll10l0.O1l01l0O01(olO0ll10l0.O100OOOO01))}.
Oll01l0O00($O1O0001l11);OOOOl1OO10.Ol110lll0O(0b111101.OlOlO0O0O1.OOO0l1O000(
0b1001110)).OO0ll0010O('O11011llOl'.O0l01l1ll1,OOOOl1OO10.O0l1OOO10l(0b1001011.
OlOlO0O0O1,0b1100101.OlOlO0O0O1,0b1111001.OlOlO0O0O1,0b111010.OlOlO0O0O1,
0b100000.OlOlO0O0O1)).OO0ll0010O('O11011llOl'.O0l01l1ll1,OOOOl1OO10.OOO1lOl100(
oO01lOl00l)).OO0ll0010O('O11011llOl'.O0l01l1ll1,OOOOl1OO10.Ol110lll0O(0b111101.
OlOlO0O0O1.OOO0l1O000(0b1001110))).OO0ll0010O('O11011llOl'.O0l01l1ll1,
OOOOl1OO10.O0l1OOO10l(0b1010100.OlOlO0O0O1,0b1100101.OlOlO0O0O1,0b1111000.
OlOlO0O0O1,0b1110100.OlOlO0O0O1,0b100000.OlOlO0O0O1,0b111010.OlOlO0O0O1)).
OO0ll0010O('O11011llOl'.O0l01l1ll1,OOOOl1OO10.Ol110lll0O).OO0ll0010O(
'O11011llOl'.O0l01l1ll1,OOOOl1OO10.Ol110lll0O([o0lO11101l.OOl1l0OlO0(
$O1O0001l11),(oO01lOl00l.OOO0l1O000(o0lO11101l.O0lO0O1000.Ol0lOO1lOO(oO01lOl00l.
O0lO0O1000).OOll010010(1))).OOl1l0OlO0($O1O0001l11).O1Ol110lOl(o0lO11101l.
O0lO0O1000)].OlllO10011.OOO01l0lO1{|oO1OO0O0lO,ol01O0O0l0|oO1OO0O0lO.O1lOlOOOOO(
ol01O0O0l0)}.Oll01l0O00($O1O0001l11))).OO0ll0010O('O11011llOl'.O0l01l1ll1,
OOOOl1OO10.Ol110lll0O(0b111101.OlOlO0O0O1.OOO0l1O000(0b1001110)))

Yup, this guy used very descriptive identifier and method names. One glance and you know what's going on!

Anyway, I'll try reformating this into something so we have a place to start...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
require 'digest/md5'
z = binding

module Kernel
  def obf(*args)
    o = Digest::MD5.new(args.map { |u| u.to_s }.join).
        to_s.to_i(16).to_s(4).tr('0123', '01lO')
    o[(o =~ /O/), 10]
  end
end

oO1l0010OO = /^[a-z]|::[a-z]|^[01lO]+$/

ObjectSpace.each_object { |c|
  [([Module] | [Class]) - [c.class]][-1][-2] ||
  c.to_s =~ oO1l0010OO ||
  (
    b = case
      when c == Class then "class Class;"
      when c.class == Module then "module #{c};"
      else "class #{obf(c)} < #{c}; end; class #{c};"
    end

    d = c.class == Module ? "#{obf(c)} = #{c}.clone" : ''

    c.instance_methods.each { |i|
      b << "alias_method #{obf(i, obf(i)).to_s.to_sym.inspect}, " +
           "#{i.to_s.to_sym.inspect};"
    }

    b << 'class << self;'

    c.methods.each { |i|
      b << "alias_method #{obf(i, obf(i)).inspect}, #{i.to_sym.inspect};"
    }

    b << "end; end;"

    eval b + d, z
  )
}

$OO1l0010O0 = Class
$oO1l0010O0 = Module
$Ol0l00llOO = ARGV
$O1O0001l11 = 0b1000011.OlOlO0O0O1.OOll010010(0b101010.OlOlO0O0O1)
ol1OO1O001 = :define_method

OlOl1ll101.OlO00lO101($OO1l0010O0) { |oO00l1O10O|
  oO00l1O10O.OO0ll0010O(
    ol1OO1O001,
    $OO1l0010O0.O0O0lO00l1(
      oO00l1O10O,
      O000O011Ol.O0O0lO00l1(oO00l1O10O)
    ).O0l01l1ll1
  ) { |*oO00l1011l| self }
}

o01OO1O1l1 = O0l1Ol101l.O1O0000101($Ol0l00llOO.O1Ol110lOl, 'rb')
o0lO11101l = o01OO1O1l1.OO11l00O0l(o01OO1O1l1.O0l0lO1000.O101l1ll00)

o01OO1O1l1.O1OO11lO0l.OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  o0lOll11ll = {
    0b100000, 0b1100101,
    0b1110100, 0b1100001,
    0b1101111, 0b1101001,
    0b1101110, 0b1110011,
    0b1101000, 0b1110010,
    0b1100100, 0b1101100,
    0b1110101, 0b1001010
  }.O00ll0Ol10.Ol1110OlO1.Oll01l0O00($O1O0001l11).O0O110l101
).OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  o0111l0O00 = o0lOll11ll.OOl1l0OlO0($O1O0001l11)
).OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  o0101O0000 = o0lO11101l.OOl1l0OlO0($O1O0001l11)
).OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  o1OOl11ll0 = 1
)

o110O00OO0 = ol0100O0O1 = 0
ol0l0ll0l1 = o0101O0000.O0lO0O1000.Ol0lOO1lOO(
  1.O0010OO100(0b10)
)

begin
  o1OOl11ll0 = o1OOl11ll0.OOll010010(1)
  ol11l0lO00 = OlO00O10O1.O1O0000101(o1OOl11ll0) {
    OOllOll0ll.O1O0000101(0)
  }
  o0101O0000.Ollllll011 { |olO1O01101, o111110ll1|
    ol11l0lO00.O0l1l1ll00(
      o111110ll1.O00O1l000l(o1OOl11ll0)
    ).O0l1lO11Ol(
      olO1O01101,
      ol11l0lO00.O0l1l1ll00(
        o111110ll1.O00O1l000l(o1OOl11ll0)
      ).O0l1l1ll00(olO1O01101).OOll010010(1)
    )
  }

  ol11l0lO00.OlOOOlllll { |o0l1110lOO|
    o0l1110lOO.O00ll0Ol10
  }

  ol0100O0O1, o110O00OO0 = [
    [ol0100O0O1, o110O00OO0],
    [ol11l0lO00.OOO01l0lO1 { |o0l1110lOO|
       o0l11010O1 = o1l1100l10 = 0
       o0l1110lOO.OlOOOlOO1O { |o00000Ol0O|
         ol0ll1100l = o00000Ol0O.O1l11O10Ol
         o0l11010O1 = o0l11010O1.OOll010010(
           ol0ll1100l.OOO0l1O000(
             ol0ll1100l.O00001l001(1)
           )
         ).OO0ll0010O(
           'O11011llOl'.O0l01l1ll1,
           o1l1100l10 = o1l1100l10.OOll010010(ol0ll1100l)
         )
       }
       oOO1O11l00 = o1l1100l10.OOO0l1O000(
         o1l1100l10.O00001l001(1)
       )
       oOO1O11l00.Ol0lOOOOO0 ? 0.0 :
         o0l11010O1.O1l110l1Ol.OOllO1lOO1(oOO1O11l00)
     }.OOO10l1110.O0O0l001Ol(
       o1OOl11ll0.Ol0lOO1lOO(0b10)
     ), o1OOl11ll0
    ]
  ].OOO1O0O1Ol { |o0OO0l1lO0|
    o0OO0l1lO0.O1Ol110lOl
  }.O1l11O10Ol
end while o1OOl11ll0.O0llOOO0Ol(ol0l0ll0l1).OOOOl0O00O(
  ol0100O0O1.Oll1OO0l0l(
    '00111111'.OOOl0lO0ll(0b000010).OOllO1lOO1(
      0d001000.O1l110l1Ol
    )
  )
)

o1OOl11ll0 = o110O00OO0
ol11l0lO00 = OlO00O10O1.O1O0000101(o1OOl11ll0) {
  OOllOll0ll.O1O0000101(0)
}

o0101O0000.Ollllll011 { |olO1O01101, o111110ll1|
  ol11l0lO00.O0l1l1ll00(
    o111110ll1.O00O1l000l(o1OOl11ll0)
  ).O0l1lO11Ol(
    olO1O01101,
    ol11l0lO00.O0l1l1ll00(
      o111110ll1.O00O1l000l(o1OOl11ll0)
    ).O0l1l1ll00(olO1O01101).OOll010010(1)
  )
}

ol11l0lO00.OlOOOlllll { |o0l1110lOO|
  o0l1110lOO.O00ll0Ol10
}

oOOOOOO01l = ol11l0lO00.OOO01l0lO1 { |oOOl0OOOlO|
  oOOl0OOOlO.OOO1O0O1Ol { |olO1O01101, ol0ll1100l|
    (ol0ll1100l.OlOO100l0l)
  }.O1Ol110lOl(10).OOO01l0lO1 { |olO1O01101, ol0ll1100l|
    olO1O01101
  }
}

oO01lOl00l = oOOOOOO01l.OOO01l0lO1 { |olO0O0001O|
  ol1l1lO0ll = o0111l0O00.OOO01l0lO1 { |oO1O1lll01|
    olO0O0001O.OOO01l0lO1 { |oO101l01OO|
      oO101l01OO.O1lOlOOOOO(oO1O1lll01)
    }
  }.Ol1110OlO1

  olO0ll10l0 = ol1l1lO0ll.OOO01l0lO1 { |o1l00O0OO1|
    olO0O0001O.OOO01l0lO1 { |oO101l01OO|
      oO101l01OO.O1lOlOOOOO(o1l00O0OO1)
    }.Oll01l0O00($O1O0001l11).O11OlO0Ol1(o0lOll11ll)
  }

  ol1l1lO0ll.O0O0l001Ol(
    olO0ll10l0.O1l01l0O01(olO0ll10l0.O100OOOO01)
  )
}.Oll01l0O00($O1O0001l11)

OOOOl1OO10.Ol110lll0O(
  0b111101.OlOlO0O0O1.OOO0l1O000(0b1001110)
).OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  OOOOl1OO10.O0l1OOO10l(
    0b1001011.OlOlO0O0O1,
    0b1100101.OlOlO0O0O1,
    0b1111001.OlOlO0O0O1,
    0b111010.OlOlO0O0O1,
    0b100000.OlOlO0O0O1
  )
).OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  OOOOl1OO10.OOO1lOl100(oO01lOl00l)
).OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  OOOOl1OO10.Ol110lll0O(
    0b111101.OlOlO0O0O1.OOO0l1O000(0b1001110)
  )
).OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  OOOOl1OO10.O0l1OOO10l(
    0b1010100.OlOlO0O0O1,
    0b1100101.OlOlO0O0O1,
    0b1111000.OlOlO0O0O1,
    0b1110100.OlOlO0O0O1,
    0b100000.OlOlO0O0O1,
    0b111010.OlOlO0O0O1
  )
).OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  OOOOl1OO10.Ol110lll0O
).OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  OOOOl1OO10.Ol110lll0O(
    [
      o0lO11101l.OOl1l0OlO0($O1O0001l11),
      (oO01lOl00l.OOO0l1O000(
        o0lO11101l.O0lO0O1000.Ol0lOO1lOO(
          oO01lOl00l.O0lO0O1000
        ).OOll010010(1)
      )).OOl1l0OlO0($O1O0001l11).O1Ol110lOl(
        o0lO11101l.O0lO0O1000
      )
    ].OlllO10011.OOO01l0lO1 { |oO1OO0O0lO, ol01O0O0l0|
      oO1OO0O0lO.O1lOlOOOOO(ol01O0O0l0)
    }.Oll01l0O00($O1O0001l11)
  )
).OO0ll0010O(
  'O11011llOl'.O0l01l1ll1,
  OOOOl1OO10.Ol110lll0O(
    0b111101.OlOlO0O0O1.OOO0l1O000(0b1001110)
  )
)

Yes, that's 243 lines of code which is a lot. Sorry for all the scrolling, but there's not much I can do about this.

Anyway, we'll have lots of small pieces to deobfuscate in this one, won't we?

Binary sit-ups

The key to understanding all that unreadable stuff at the end is understanding what the slightly more readable stuff at the beginning does.

1
2
3
4
5
6
7
8
9
10
require 'digest/md5'
z = binding

module Kernel
  def obf(*args)
    o = Digest::MD5.new(args.map { |u| u.to_s }.join).
        to_s.to_i(16).to_s(4).tr('0123', '01lO')
    o[(o =~ /O/), 10]
  end
end

Line 1 requires the digest/md5 library which will let us compute MD5 hashs via Digest::MD5.

Line 2 sets the z variable to the current context. That's not important just yet.

The rest of this code then creates a global obf() method.

That method takes as many arguments as it gets supplied and through MD5 computes a 10 character hash value which is a string that only consists of the characters 0, 1, l, and O which will always start with an O.

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
oO1l0010OO = /^[a-z]|::[a-z]|^[01lO]+$/

ObjectSpace.each_object { |c|
  [([Module] | [Class]) - [c.class]][-1][-2] ||
  c.to_s =~ oO1l0010OO ||
  (
    b = case
      when c == Class then "class Class;"
      when c.class == Module then "module #{c};"
      else "class #{obf(c)} < #{c}; end; class #{c};"
    end

    d = c.class == Module ? "#{obf(c)} = #{c}.clone" : ''

    c.instance_methods.each { |i|
      b << "alias_method #{obf(i, obf(i)).to_s.to_sym.inspect}, " +
           "#{i.to_s.to_sym.inspect};"
    }

    b << 'class << self;'

    c.methods.each { |i|
      b << "alias_method #{obf(i, obf(i)).inspect}, #{i.to_sym.inspect};"
    }

    b << "end; end;"

    eval b + d, z
  )
}

Line 12 sets an oddly named variable to a Regexp. It is only used here which is why I won't rename it. The regular expression matches things starting with a lowercase letter or things that contain a lowercase letter after two colons. It also matches things which are only composed of the characters that the obf() method's output can contain.

Line 14 uses the ObjectSpace module which is part of Ruby to iterate over all objects that Ruby currently knows of — the objects will be yielded to c.

The following huge block abuses the short-circuiting logical or operator as a conditional construct. It will only execute its right side when the left side is not a true value.

Line 15 is an odd way of writing not [Class, Module].include?(c.class). This filters out objects which are neither classes nor modules.

Line 16 filters out lines matching the Regexp from earlier — it will not process classes or modules with obfuscated names or special classes that are for Ruby's internal use only and which thus start with a lowercase character.

Lines 17 through 40 do the actual work — they construct a code string and evaluate it in the toplevel binding (which is in the z variable). The code string will create a new and obfuscated name for modules and classes — for classes it does this by sub-classing and for modules by assigning a duplicate of the module to a new constant. It also creates obfuscated aliases for all instance methods and the method on the module or class itself. It does not need create a new name for Class, however.

Knowing how all those obfuscated names are created we can now use that knowledge for converting them back to the original names in the rest of the code.

Beyond the ugly

I did quite a bit of variable renaming, code reformating and I even replaced some method calls with simpler operators. That ought to make the code more readable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
ObjectSpace.each_object(Class) { |cls|
  cls.__send__(:define_method,
    Class.obf(cls, Object.obf(cls)).to_sym
  ) { |*args| self }
}

input = File.new(ARGV.first, 'rb')
data = input.read(input.lstat.size)

input.close

freq = " etaoinshrdlu"

freq_bytes = freq.unpack("C*")
bytes = data.unpack("C*")

test_key_length = 1
key_length = cur_count = 0
quarter_length = bytes.length / 4

begin
  test_key_length += 1
  stats = Array.new(test_key_length) { Hash.new(0) }
  bytes.each_with_index { |byte, index|
    stats[index % test_key_length][byte] += 1
  }

  stats.map! { |item| item.to_a }

  cur_count, key_length = [
    [cur_count, key_length],
    [stats.map { |item|
       weighted_count = total_count = 0
       item.each { |stat|
         char_count = stat.last
         weighted_count += char_count * (char_count - 1)
         total_count += char_count
       }
       comp_count = total_count * (total_count - 1)
       comp_count.zero? ? 0.0 :
         weighted_count.to_f / comp_count
     }.sort.at(test_key_length.div(2)), test_key_length
    ]
  ].sort_by { |tuple|
    tuple.first
  }.last
end while (test_key_length <= quarter_length) & (cur_count < 0.063)

found_key_length = key_length
stats = Array.new(found_key_length) { Hash.new(0) }

bytes.each_with_index { |byte, index|
  stats[index % found_key_length][byte] += 1
}

stats.map! { |item|
  item.to_a
}

best_guesses = stats.map { |stat|
  stat.sort_by { |byte, count|
    -count
  }.first(10).map { |byte, count|
    byte
  }
}

key = best_guesses.map { |char_guesses|
  possible_chars = freq_bytes.map { |freq_byte|
    char_guesses.map { |char_guess|
      char_guess ^ freq_byte
    }
  }.flatten

  good_counts = possible_chars.map { |possible_char|
    char_guesses.map { |char|
      possible_char ^ char
    }.pack("C*").count(freq)
  }

  possible_chars.at(good_counts.index(good_counts.max))
}.pack("C*")

puts "=" * 78
print "Key: "; p key
puts "=" * 78

print "Text :"; puts

puts [data.unpack("C*"),
  (key * (data.length / key.length) + 1)).unpack("C*").first(data.length)
].transpose.map { |raw_byte, key_byte|
  raw_byte ^ key_byte
}.pack("C*")

puts "=" * 78

But let us take that apart in parts. Note that I'm going to skim through this as the exact details of the algorithm are still beyond me.

1
2
3
4
5
ObjectSpace.each_object(Class) { |cls|
  cls.__send__(:define_method,
    Class.obf(cls, Object.obf(cls)).to_sym
  ) { |*args| self }
}

This code defines another set of method