Misja Gynvaela 002

MISJA 002                                        DIFFICULTY: ███░░░░░░░ [3/10]

Przechwyciliśmy fragment komunikacji pomiędzy dwoma podejrzanymi. Sądzimy, że
to hasło administratora do jednego z systemów, na których mogą znajdować się
kluczowe dla sprawy dane.

                 ╭┈┈                                     ┈┈╮  
                 ┊ QW== QT== QT== QQ== QU== Qd== QU== Qd== ┊
                   QX== QV== QW== Qe== QT== QR== QU== QT==
                   QT== QU== QX== QU== QT== QR== QT== QQ==
                 ┊ QW== Qe==               »goo.gl/4Iuxdi« ┊ 
                 ╰┈┈                                     ┈┈╯   

Niestety, pomimo, iż wiemy, że użyte zostało kodowanie Base64, nie jesteśmy w
stanie zdekodować ukrytej wiadomości. Nasi technicy uparcie twierdzą, że po
zdekodowaniu wychodzi „AAAAAAAAAAAAAAAAAAAAAAAAAA”, ale sądzimy, że nie mają
racji.

Zwracamy się więc do Ciebie z prośbą o pomoc - zrzuć okiem na powyższą
wiadomość i sprawdź czy nie ma czasem drugiego dna.

Jak widać, zakodowane części wiadomości nie są identyczne. Jednak przy próbie zdekodowania faktycznie wychodzą same litery A:

TEXT = <<COMM
QW== QT== QT== QQ== QU== Qd== QU== Qd==
QX== QV== QW== Qe== QT== QR== QU== QT==
QT== QU== QX== QU== QT== QR== QT== QQ==
QW== Qe==
COMM
 
require 'base64'
p TEXT.split.map{ |n| Base64.decode64 n }.join
"AAAAAAAAAAAAAAAAAAAAAAAAAA"

Po zmianie funkcji decode64 na strict_decode64 wywołanie kończy się wyjątkiem `unpack’: invalid base64 (ArgumentError). Dzieje się tak, ponieważ zakodowane wiadomości nie są zgodne ze specyfikacją: każdy znak (poza =) enkoduje 6 bitów, standardowo bajt ma 8 bitów, więc 4 bity są nadmiarowe. Specyfikacja oczekuje, że będą wyzerowane, ale tutaj wyraźnie są różne. Zapewne w nich ukryta jest “steganograficzna” wiadomość.

Można to sprawdzić w dość prosty sposób:

def decode_letter(l)
	case l
	when /[A-Z]/
		l.ord - 'A'.ord
	when /[a-z]/
		l.ord - 'a'.ord + 26
	when /\d/
		l.ord - '0'.ord + 52
	when '+'
		62
	when '/'
		63
	else
		nil
	end
end
 
def decode(str)
	first = (decode_letter(str[0]) << 2) + (decode_letter(str[1]) >> 4)
	second = decode_letter(str[1]) & 0xF
	second
end
 
 
p TEXT.split.map{ |s| decode s }
[6, 3, 3, 0, 4, 13, 4, 13, 7, 5, 6, 14, 3, 1, 4, 3, 3, 4, 7, 4, 3, 1, 3, 0, 6, 14]

Tak jest. Pierwsze co sprawdziłem to bezpośrednie mapowanie na indeksy liter alfabetu:

p TEXT.split.map{ |s| decode s }.map{ |n| (n + 'A'.ord).chr }.join
"GDDAENENHFGODBEDDEHEDBDAGO"

Nope.

A co jeśli kolejne 4-bitowe wartości są kolejnymi nibblami ukrytej wiadomości?

p TEXT.split.map{ |s| decode s }.each_slice(2).map{|a,b| (a << 4) + b }.map(&:chr).join
"c0MMun1C4t10n"

Yay! Udało się rozpykać zadanie o trudności 3/10 (pytanie tylko czy to nie jest przypadkiem 1064)

Pełen kod można znaleźć tutaj.

2 thoughts on “Misja Gynvaela 002

Leave a Reply

Your email address will not be published.