First, let's have a look at the complete code:
class NP def initialize a=@p=[], b=@b=[]; end def +@;@b<<1;b2c end;def-@;@b<<0;b2c end def b2c;if @b.size==8;c=0;@b.each{|b|c<<=1;c|=b};send( 'lave'.reverse,(@p.join))if c==0;@p<<c.chr;@b=[] end self end end ; begin _ = NP.new end # The Programming Language `NegaPosi' +-+--++----+--+-+++--+-------+--++--+++---+-+++-+-+-+++-----+++-_ +--++++--+---++-+-+-+++--+--+-+------+--++++-++---++-++---++-++-_ +++--++-+-+--++--+++--+------+----+--++--+++-++-+----++------+--_ -+-+----+++--+--+----+--+--+-++-++--+++-++++-++-----+-+-+----++-_ ---------+-+---- _
Let's reformat it so that it's easier to see what's happening here. I've only touched whitespace and removed superflous punctuation like trailing semicolons:
class NP def initialize a=@p=[], b=@b=[] end def +@ @b << 1 b2c end def -@ @b << 0 b2c end def b2c if @b.size == 8 c = 0 @b.each { |b| c <<= 1 c |= b } send('lave'.reverse, @p.join) if c == 0 @p << c.chr @b = [] end self end end begin _ = NP.new end # The Programming Language `NegaPosi' +-+--++----+--+-+++--+-------+--++--+++---+-+++-+-+-+++-----+++-_ +--++++--+---++-+-+-+++--+--+-+------+--++++-++---++-++---++-++-_ +++--++-+-+--++--+++--+------+----+--++--+++-++-+----++------+--_ -+-+----+++--+--+----+--+--+-++-++--+++-++++-++-----+-+-+----++-_ ---------+-+----_
The code first declares the NP class with the four instance methods initialize, +@,
-@ and b2c. At the end of the code there's lots of funky stuff going on, but an instance of the
class that has been assigned to the variable _ seems to be involved.
Let's take apart the NP class method per method:
initialize methoddef initialize a=@p=[], b=@b=[] end
This looks fairly simply, but there's a small bonus involved: The instance variables @p and @b
are actually initialized from within the argument list! The initialize method takes an argument a whose default
value is determined by evaluating @p=[] and an argument b whose default value is determined by
evaluating @b=[].
Note that it would not have made a difference to set the instance variables to blank arrays from outside the argument list, but it is a nice touch anyway.
+@ and -@ methodsdef +@ @b << 1 b2c end def -@ @b << 0 b2c end
These are the most important part of this obfuscation as they allow the syntactical cleverness at the bottom of the code to happen.
The +@ and -@ methods are actually special in that Ruby calls them when you do -obj or
+obj. In Ruby's standard library they are implemented by all Numeric classes.
In this entry unary plus pushes 1 and unary minus pushes 0 to the end of the
@b array. Both methods then pass control over to the b2c and return self because the
b2c method call is the last statement and returns self as well.
b2c (bits-to-code?) methoddef b2c if @b.size == 8 c = 0 @b.each { |b| c <<= 1 c |= b } send('lave'.reverse, @p.join) if c == 0 @p << c.chr @b = [] end self end
This method only actually does something when the @b array contains eight elements.
In that case it interprets the array as an eight bit sequence (lines 17 to 22) and appends the resulting byte's
ASCII character to the @p array and clears the @b array.
If the current byte is zero then the code that has been collected up to that point is evaluated in line 24:
send('lave'.reverse, @p.join) if c == 0
'lave'.reverse is the same as 'eval' and send('eval', @p.join) is the same
as eval(@p.join). @p.join converts the array of characters to an actual String.
begin _ = NP.new end # The Programming Language `NegaPosi' +-+--++----+--+-+++--+-------+--++--+++---+-+++-+-+-+++-----+++-_ +--++++--+---++-+-+-+++--+--+-+------+--++++-++---++-++---++-++-_ +++--++-+-+--++--+++--+------+----+--++--+++-++-+----++------+--_ -+-+----+++--+--+----+--+--+-++-++--+++-++++-++-----+-+-+----++-_ ---------+-+----_
Now that we have had a look at the infrastructure and know that calls to +obj will be mapped to bit 1 and that
calls to -obj will be mapped to bit 0 we only have to find out what the negaposi-transliterated binary string
at the end of the script actually means. (Note that the bits are actually in reverse order in the source code.)
Line 40 is "puts 'He",
line 41 is "llo Ruby",
line 42 is " and Neg",
line 43 is "aPosi!'\n",
line 44 is "\n\000".
Chaining this together yields the null-terminated string "puts 'Hello Ruby and NegaPosi!'\n\n\000" which is
evaluated without the null byte at line 24 of the b2c method.