IORCC Deobfuscation: NegaPosi by SASADA Koichi

First, let's have a look at the complete code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:

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
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:

The initialize method

2
3
  def 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.

The mysterious +@ and -@ methods

5
6
7
8
9
10
11
12
13
  def +@
    @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.

The b2c (bits-to-code?) method

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
  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

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:

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.

The grand finale

34
35
36
37
38
39
40
41
42
43
44
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.

Florian Groß