IORCC Deobfuscation: Pretty numbers by Vincent Foley

This time we'll start with the usage as this entry takes arguments:

ruby entry.rb <integer> [<character>]
If character is specified, it must be a single character; don't forget to escape characters such as * or ..
Non-digits in integer will be treated as 0.

Okay, now let's have a look at all the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
        d=[30644250780,9003106878,
    30636278846,66641217692,4501790980,
 671_24_603036,131_61973916,66_606629_920,
   30642677916,30643069058];a,s=[],$*[0]
      s.each_byte{|b|a<<("%036b"%d[b.
         chr.to_i]).scan(/\d{6}/)}
          a.transpose.each{ |a|
            a.join.each_byte{\
             |i|print i==49?\
               ($*[1]||"#")\
                 :32.chr}
                   puts
                    }

What a pretty Ruby gem! I almost don't dare destroying this by reformating, but it will have to be:

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
d = [
  30644250780,
  9003106878,
  30636278846,
  66641217692,
  4501790980,
  671_24_603036,
  131_61973916,
  66_606629_920,
  30642677916,
  30643069058
];

a, s = [], $*[0]

s.each_byte { |b|
  a << ("%036b" % d[b.chr.to_i]).scan(/\d{6}/)
}

a.transpose.each { |a|
  a.join.each_byte { |i|
    print i == 49 ? ($*[1] || "#") : 32.chr
  }

  puts
}

Now that we have the code nicely unwrapped in front of us we can take it apart piece by piece.

Setting up a few variables

1
2
3
4
5
6
7
8
9
10
11
12
d = [
  30644250780,
  9003106878,
  30636278846,
  66641217692,
  4501790980,
  671_24_603036,
  131_61973916,
  66_606629_920,
  30642677916,
  30643069058
];

This sets d to an array with exactly ten numbers as its content. Note that underscores in numbers are ignored by Ruby so we can safely ignore them as well.

14
a, s = [], $*[0]

This sets a to an empty Array and s to the first element of the perlish $* which happens to be the same as ARGV. So s will actually be the integer string specified on the command line.

16
17
18
s.each_byte { |b|
  a << ("%036b" % d[b.chr.to_i]).scan(/\d{6}/)
}

Ah, this would then be the first piece of complex code involved in all this. s.each_byte will iterate over all characters that compose the integer string that was specified on the command line and yield their respective ASCII values.

d[b.chr.to_i] first converts that ASCII value back into the character it was refering to (e. g. "5") and then converts that character into the number it represents (e. g. 5). It then uses that number as an index into the d array which contains exactly 10 elements. There's 10 different digits in good old decimal number notation so that maps quite nicely.

"%036b" % d[b.chr.to_i] formats the element of d into a 36 character string where each character is one binary bit of the number. Note that one single digit on output has the size of exactly 6×6 characters. For the digit 0 this produces the string "011100100010100010100010100010011100".

("%036b" % d[b.chr.to_i]).scan(/\d{6}/) takes that binary string and extracts slices of six binary digits into an array. For the digit 0 we now have this array: ["011100", "100010", "100010", "100010", "100010", "011100"].

Let's space that out over multiple lines, remove the punctuation and replace the zeros with spaces:

1
2
3
4
5
6
 111  
1   1 
1   1 
1   1 
1   1 
 111  

Hey, doesn't that look a lot like our old friend the zero up close?

The other numbers in the d digits array are digit representation strings encoded in the same manner. Decoding them is left as an exercise to the reader.

Anyway, back to the code:

17
  a << ("%036b" % d[b.chr.to_i]).scan(/\d{6}/)

This then appends the array of digit representation lines to a.

So after all this code a will be an array of digit line arrays with as many entries as there are digits in the input number.

Into your face

Now that we have that nice array we only need to figure out a way of putting the digits aside each other and making it all visible to the user who is already waiting way too long while we're here discussing code!

20
21
22
23
24
25
26
a.transpose.each { |a|
  a.join.each_byte { |i|
    print i == 49 ? ($*[1] || "#") : 32.chr
  }

  puts
}

Ah, to understand this we first need to know about Array#transpose. That method will swap the dimensions of an Array which sounds tricky, but is really simple when we explain it with a few samples:

[["a1", "b1"], ["a2", "b2"]].transpose will produce [["a1", "a2"], ["b1", "b2"]].
[["a1", "b1", "c1"], ["a2", "b2", "c2"]].transpose will produce [["a1", "a2"], ["b1", "b2"], ["c1", "c2"]].
[["a1", "b1"], ["a2", "b2"], ["a3", "b3"]].transpose will produce [["a1", "a2", "a3"], ["b1", "b2", "b3"]].

When we apply that method to our a array it will magically put all digit parts that should go onto the same line next to each other in the same subarray. So it will have a subarray for every final output line which will have one item per digit part.

The outer loop will then iterate over all lines and yield them into the a variable — the a variable can be safely reused for the lines as Ruby itself will execute methods on objects and not on variables.

The inner loop joins all the parts of the different digits together into one single string and iterates over the ASCII values of the characters in it.

The print statement on line 22 prints the second argument or "#" in case it wasn't supplied in case the character's ASCII value is 49 (ASCII value of "1") and in all other cases 32.chr (same as " ") in all other cases.

The puts on line 25 gets executed once after each line was printed — puts without an argument will only emit a newline.

All that in combination will then print out a nice big rendering of the supplied number. I liked how this entry was similar to an older Ruby Quiz and even if it was quite simple it was at the same time very elegant. Well done!

Florian Groß