Die Programmiersprache Ruby

Blog| Forum| Wiki  

Die Artikel HowToObfuscate und HowToObfuscate2 überschneiden sich inhaltlich. Bitte hilf mit, das zu verbessern, indem du die Artikel unter einem Namen zusammenführst.
Bitte entferne anschließend den Redundanz-Baustein.

Inhaltsverzeichnis

Vorgeschichte

Die hier erläuterte Technik wurde von mir (murphy) benutzt, um das Witz-Script Chunky-Bacon-Server (Version ohne Port 203) zu erstellen, das es sogar auf RedHanded geschafft hat; die Idee ist nicht neu, aber immer wieder witzig.

Begriffe

Obfuscating (Vernebeln, Verschleiern) bedeutet, einen Code lauffähig, aber für Menschen unlesbar zu machen. ASCII-Art ist der Versuch, nur mit den grundlegendsten Schriftzeichen Grafiken zu basteln; sogar Star Wars kann man damit herstellen.

Zurück zum Eigentlichen:

Wie kann ich mein Script in ein ASCII-Kunstwerk verwandeln?

Fragen hierzu am besten im Ruby-Forum stellen.

Du brauchst:

  1. das Script
  2. die Grafik (zB Schwarz-Weiß-PNG)
  3. RMagick
  4. rb2art:
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
# murphy's rb2art
# version 2005.august.11
# Licence: LGPL

if ARGV.empty?
  puts <<-INFO 
    Usage: ruby rb2art.rb <script> <graphic>
  INFO
  exit
end

require 'RMagick'
graphic = Magick::ImageList.new ARGV.pop
c, r = graphic.columns, graphic.rows
graphic.resize! c * 0.8, r * 0.5
c, r = graphic.columns, graphic.rows
a = graphic.export_pixels 0, 0, c, r, 'I'

colors = '#####           '
require 'enumerator'
ascii = ''
a.each_slice(c) do |row|
  ascii << row.map { |x| colors[x / 16,1] }.join << "\n"
end

program = File.read ARGV.pop

module AsciiMask
  ENCODE = {
    "\s" => '=s',
    "\t" => '=t',
    "\n" => '=n',
    "\r" => '=r',
    "=" => '==',
    "#" => '=#',
  }
  DECODE = ENCODE.invert

  def encode s
    s.gsub!(/[\s#=]/) { |c| ENCODE[c] }
  end
  
  def decode s
    s.gsub!(/=./m) { |c| DECODE[c] }
  end
end

include AsciiMask

program = encode program

i = -1
art = ascii.split('').map do |a|
  if a == '#'
    if i + 1 < program.size
      program[i += 1,1]
    else
      '#'
    end
  else
    a
  end
end.join
$stderr.puts 'Warning: Graphic is too small for script!' if i + 1 != program.size

puts <<-'ART' % art
eval <<-'RUBY'.gsub(/\s/,'').gsub(/=./){|c|{'=s'=>"\s",'=t'=>"\t",'=n'=>"\n",'=r'=>"\r",'=='=>'=','=#' => '#'}[c]}
%sRUBY
ART


Beispiel

  1. Speichere folgendes Script unter small.rb:
1
2
3
 # A small program
 
 puts 'Hello ASCII-art world!'
  1. Lade das Rails-Icon herunter und speichere es in rails.ico.
  2. Kopiere alles zusammen mit rb2art.rb in einen Ordner.
  3. Starte es, zum Beispiel mit:
ruby -rubygems rb2art.rb small.rb rails.ico > small.obf.rb

Ergebnis:

1
2
3
4
5
6
7
8
9
10
eval <<-'RUBY'.gsub(/\s/,'').gsub(/=./){|c|{'=s'=>"\s",'=t'=>"\t",'=n'=>"\n",'=r'=>"\r",'=='=>'=','=#' => '#'}[c]}
=#=sA    =ss
mal       l=
sp       rog
ra    m=n=np
ut    s=s'He
ll    o=sASC
II    -art=s
wor  ld!'=n#
RUBY

Obwohl das jetzt keiner mehr lesen kann, ist es ausführbar und tut dasselbe wie das Originalscript:

 % ruby small.obf.rb
 Hello ASCII-art world!

Anpassen

Bildgröße

In der Zeile

graphic.resize! c * 0.8, r * 0.5

wird das Bild verkleinert und ein wenig platt gedrückt; da Buchstaben normalerweise höher als weit sind, wirkt das Bild am Schluss wieder normal.

Wenn man die Faktoren anpasst, kann man das Bild natürlich vergrößern und verkleinern, bis es zum Code passt.

Dunkelheit

Die Zeile

colors = '#####           '

steuert, wie die Grauwerte des Bildes übersetzt werden: Links = schwarz bis rechts = weiß. Mit

colors = '##############  '

würde das Bild dunkler, die Linien breiter, und du hast mehr Platz für Code. Mit

colors = '#               '

bewirkst du das Gegenteil.

Erläuterung

Bild --> ASCII-Grafik

Das Bild wird also gelesen:

1
2
 require 'RMagick'
 graphic = Magick::ImageList.new ARGV.pop

und in die richtige Größe umgewandelt:

1
2
 c, r = graphic.columns, graphic.rows
 graphic.resize! c * 0.8, r * 0.5

Dann wird es in ein Array von Grauwerten (mit 'I') konvertiert:

1
2
 c, r = graphic.columns, graphic.rows
 a = graphic.export_pixels 0, 0, c, r, 'I'

Näheres unter http://www.simplesystems.org/RMagick/doc/usage.html.

Als nächstes werden die Grauwerte in Zeichen umgewandelt, genauer in ' ' (Leerstelle) und '#'. Leider sind die Grauwerte in einem eindimensionalen Array gespeichert, Pixel an Pixel. Wir brauchen ein zweidimensionales Array, also ein Array von Arrays:

1
2
3
4
5
6
 colors = '#####           '
 require 'enumerator'  # weil uns each_slice eine Menge Arbeite abnimmt
 ascii = ''
 a.each_slice(c) do |row|  # jetzt haben wir jede Reihe einzeln
   ascii << row.map { |x| colors[x / 16,1] }.join << "\n"
 end

Mehr zu each_slice unter Undokumentierte_Methoden. Jetzt soll jedes '#' in der Grafik durch ein Zeichen des Scripts ersetzt werden.

ASCII maskieren und demaskieren

Nachdem wir die Grafik im String ascii haben, der so aussieht:

1
2
3
4
5
6
7
8
9
 "#####    ###
 ###       ##
 ##       ###
 ##    ######
 ##    ######
 ##    ######
 ##    ######
 ###  #######
 "

müssen wir noch das Script ein wenig umformen; speziell dürfen die Zeichen '#', ' ' sowie Zeilenwechsel und jede andere Art Whitespace nicht verloren gehen. Das Modul AsciiMask übernimmt diese Funktion durch einen simplen Abbildungs-Hash (man könnte es mapping nennen):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 module AsciiMask
   ENCODE = {
     "\s" => '=s',
     "\t" => '=t',
     "\n" => '=n',
     "\r" => '=r',
     "=" => '==',
     "#" => '=#',
   }
   DECODE = ENCODE.invert
 
   def encode s
     s.gsub!(/[\s#=]/) { |c| ENCODE[c] }
   end
   
   def decode s
     s.gsub!(/=./m) { |c| DECODE[c] }
   end
 end

Der Vorteil dieser Codierung ist, dass der ursprüngliche Text auch wiederhergestellt werden kann, wenn man beliebig '#' und Whitespace einfügt. Einzige Ausnahme: '##' würde zu '#', aber das ist kein Problem, da dies in Ruby nur einen Kommentar einleitet. AsciiMask.decode ist netterweise mit drin, wird aber nicht gebraucht.

1
2
 include AsciiMask
 program = encode program

Das Programm wird verschlüsselt. Nun muss es nur noch "eingewoben" werden.

Script in die Grafik einweben

Die Grafik wird durchlaufen, um jedes Zeichen durch das nächste (i-te) Zeichen im maskierten Script zu ersetzen. Ist das Script vorzeitig zuende, wird eine Warnung ausgegeben:

1
2
3
4
5
6
7
8
9
10
11
12
13
i = -1
art = ascii.split('').map do |a|
  if a == '#'
    if i + 1 < program.size
      program[i += 1,1]
    else
      '#'
    end
  else
    a
  end
end.join
$stderr.puts 'Warning: Graphic is too small for script!' if i + 1 != program.size

Ausgabe

Zuletzt wird das fertige unlesbare Script ausgegeben:

1
2
3
4
 puts <<-'ART' % art
 eval <<-'RUBY'.gsub(/\s/,'').gsub(/=./){|c|{'=s'=>"\s",'=t'=>"\t",'=n'=>"\n",'=r'=>"\r",'=='=>'=','=#' => '#'}[c]}
 %sRUBY
 ART

Es besteht einfach aus einer eval-Anweisung, einem gekürzten AsciiMask.decode und der ASCII-Grafik in einem Heredoc.

Viel Spaß damit!