Нечасто нам доводится изучать код, который до нас видели только считанное количество людей; код, который был важной частью разрушения системы апартеида в ЮАР; код, который использовался для защищённых коммуникаций с одноразовыми шифрами, контрабандой передававшихся в ЮАР на дискетах бортпроводником. Но мне довелось испытать это одним утром вскоре после того, как я расшифровал тридцатилетний файл PKZIP, пароль к которому давно забыли.
Недавно я заинтересовался защищёнными коммуникациями, которые использовались Африканским национальным конгрессом в рамках операции «Вула», проводившейся в конце 1980-х годов. Операция «Вула» заключалась в проникновении лидеров АНК (и передаче снаряжения) в ЮАР для подготовки тайной сети, реализующей различные элементы политической активности АНК внутри страны.
Для успеха операции требовались защищённые коммуникации, организованные на основе 8-битных компьютеров, DTMF-сигналов, акустических преобразователей и различного другого оборудования для обмена сообщений с одноразовым шифрованием, использующих программы, написанные на PowerBASIC.
Я не буду вдаваться в подробности работы всего этого, поскольку главный разработчик системы шифрования Тим Дженкин выложил исходный код в опенсорс. Статью Тима о системе шифрования можно найти здесь. Я крайне рекомендую прочитать её, если вам интересны подробности.
Код не был выложен в опенсорс раньше по одной простой причине: улетая из Великобритании в ЮАР в 1991 году, он упаковал весь исходный код в файл zip и установил пароль. В последующие годы он просто забыл пароль! Поэтому когда я написал ему письмо с вопросом, можно ли выложить его в опенсорс, он ответил:
У меня всё ещё хранится исходный код «Вула», но, к сожалению, по большей мере он недоступен, потому что возвращаясь из Великобритании в ЮАР в 1991 году, я запаковал все файлы в zip с паролем. Мне удалось расшифровать и распаковать один из файлов, но, увы, это была очень ранняя версия ПО. Остальное я не могу извлечь, потому что забыл пароль. Когда я вернулся в ЮАР, потребности в коде не было. Я думал, что никогда не забуду пароль, но когда попытался расшифровать его спустя несколько лет, не смог его вспомнить.
Если вы сможете разобраться, как декодировать запакованные файлы, то я буду счастлив опубликовать их. Я уже несколько раз пытался взломать код, но пока успеха не достиг.
Я с готовностью согласился, и он отправил мне два файла: ALLBAS.ZIP
и CODMAY93.ZIP
. Они были созданы в старой версии PKZIP и защищены паролем. К счастью, существует известная атака на основе открытых текстов на схему ZipCrypto, которая использовалась в формате ZIP той эпохи. А опенсорсная реализация этой атаки называется bkcrack.
То есть достаточно было «просто» спрогнозировать 12 байтов открытого текста в известном месте внутри файла ZIP. Вот фрагмент того, что находилось внутри файла ZIP:
$ bkcrack -L ALLBAS.ZIP | head -n 20
bkcrack 1.7.0 - 2024-05-26
Archive: ALLBAS.ZIP
Index Encryption Compression CRC32 Uncompressed Packed size Name
----- ---------- ----------- -------- ------------ ------------ ----------------
0 ZipCrypto Shrink b0f86b1d 163 117 A1PSW.BAS
1 ZipCrypto Shrink 8fa662d4 163 118 A2PSW.BAS
2 ZipCrypto Shrink 0c5a7295 163 119 A3PSW.BAS
3 ZipCrypto Shrink 49907f86 179 125 A4PSW.BAS
4 ZipCrypto Shrink 3d20eb7a 163 120 A5PSW.BAS
5 ZipCrypto Shrink f8b558f0 136 128 BIOS.INC
6 ZipCrypto Implode 799074ed 377 278 CHKERR.INC
7 ZipCrypto Implode c44ea0a5 17906 5401 CODSUBS.INC
8 ZipCrypto Implode 7bd7e23d 27287 8297 COMAID.BAS
9 ZipCrypto Implode 03dc63da 2109 1001 COMKEY.BAS
10 ZipCrypto Store 3500d320 2372 2384 CONFIG.TIM
11 ZipCrypto Shrink 35a85089 147 111 CONPSW.BAS
12 ZipCrypto Implode 55be75ce 2094 825 DOS.INC
13 ZipCrypto Shrink 3387d043 134 127 DOSVER.INC
14 ZipCrypto Implode 28a32efa 1304 535 DOSX.INC
15 ZipCrypto Implode 6578a66c 3196 966 EDDY.BAS
У Тима было несколько незашифрованных файлов .BAS
, но их версии отличались от того, что было в файле, и попытки атак bkcrack
с их помощью (после прогона их через оригинальный PKZIP в DOSBox) оказались безуспешными; я решил, что прежде чем проводить дальнейшие атаки, нужно немного поразмыслить.
В ALLBAS.ZIP
содержалось множество файлов без сжатия, потому что они и так были двоичными, а значит, не стоили сжатия. Эти файлы помечены как Store
:
$ bkcrack -L ALLBAS.ZIP | grep Store
10 ZipCrypto Store 3500d320 2372 2384 CONFIG.TIM
23 ZipCrypto Store 14a285ac 2 14 KEYCOD.EXE
25 ZipCrypto Store d6343ce1 4767 4779 KEYONE.ZIP
26 ZipCrypto Store 650778b7 6523 6535 KEYTHREE.ZIP
30 ZipCrypto Store 12a711cd 58172 58184 OLDCOD.ZIP
41 ZipCrypto Store 00000000 0 12 TAPCOD.EXE
44 ZipCrypto Store 55000714 12716 12728 TECOD5.ZIP
45 ZipCrypto Store f4f4366c 9230 9242 TECOD6.ZIP
Файлы, хранящиеся как Store
, перспективны для прогнозирования открытого текста, потому что их не сжимали и для получения открытого текста не нужно сжимать исходный файл. Изучение файлов ZIP, поскольку файлы ZIP начинаются с заголовка PK, показалось мне подходящим способом поиска предсказуемого открытого текста в известной позиции. Вот поля стандартного заголовка PK в самом начале файла ZIP:
Я решил, что реалистичным способом атаки будет прогнозирование имени первого файла в архиве. Если имя файла состоит как минимум из восьми символов (что достаточно вероятно, ведь как минимум четыре символа использовано для .BAS
, .INC
и так далее), то будет доступно не менее 12 символов открытого текста, если сложить размер имени файла (смещение 0x1A, 0x1B) и длину дополнительного поля (которое во всех отправленных Тимом ZIP оказалось равным 0x00, 0x00).
В наихудшем случае у нас будет возможность перебором подобрать потенциальные имена файлов, учитывая, что все они представляют собой сочетания заглавных букв и цифр с максимальной длиной восемь символов плюс расширение. Но оказалось, что этого не требуется.
К счастью, у Тима обнаружилась другая версия OLDCOD.ZIP
(одного из файлов ZIP внутри ALLBAS.ZIP
), и он сообщил мне, что первый файл в нём называется COMKEY.BAS
. Я написал небольшую программу на Perl для создания необходимого открытого текста в надежде, что OLDCOD.ZIP
внутри ALLBAS.ZIP
начинается с COMKEY.BAS
$ cat maken.pl
use strict;
use warnings;
my $outfile = "hexname-$$.txt";
while (<>) {
chomp;
my $bas = $_;
print("$bas / $outfilen");
my $n = sprintf("%cx00x00x00$bas",length($bas));
open G, ">$outfile";
print G $n;
close G;
system("bkcrack -C ALLBAS.ZIP -c OLDCOD.ZIP -p $outfile -o 26 -j 8");
}
Через 23 минуты bkcrack
выдал ключ к файлу ALLBAS.ZIP
, и мне удалось его расшифровать. Тот же самый ключ подошёл и к CODMAY93.ZIP
.
$ time echo "COMKEY.BAS" | perl maken.pl
COMKEY.BAS / hexname-41227.txt
bkcrack 1.7.0 - 2024-05-26
[07:49:38] Z reduction using 6 bytes of known plaintext
100.0 % (6 / 6)
[07:49:38] Attack on 925073 Z values at index 33
Keys: 98e0f009 48a0b11a c70f8499
80.6 % (745571 / 925073)
Found a solution. Stopping.
You may resume the attack with the option: --continue-attack 745571
[18:13:49] Keys
98e0f009 48a0b11a c70f8499
real 23m4.371s
user 162m3.520s
sys 0m37.752s
После нахождения ключа bkcrack
выполняет расшифровку:
$ bkcrack -C ALLBAS.ZIP -k 98e0f009 48a0b11a c70f8499 -D ALLBAS-DECRYPTED.ZIP
bkcrack 1.7.0 - 2024-05-26
[07:52:22] Writing decrypted archive ALLBAS-DECRYPTED.ZIP
100.0 % (81 / 81)
$ bkcrack -C CODMAY93.ZIP -k 98e0f009 48a0b11a c70f8499 -D CODMAY93-DECRYPTED.ZIP
bkcrack 1.7.0 - 2024-05-26
[07:58:31] Writing decrypted archive CODMAY93-DECRYPTED.ZIP
100.0 % (40 / 40)
И так мы наконец получили давно утерянный исходный код, использовавшийся для организации защищённых коммуникаций АНК!
Если бы у меня не получилось, я бы атаковал один из остальных файлов ZIP при помощи того же способа (а потом бы просто начал перебирать имена файлов). Я бы предположил, что TECOD5.ZIP
, вероятно, был ZIP всего одного файла TECOD.BAS
(или может быть TECOD5.BAS
), судя по сжатому размеру TECOD.BAS
в ALLBAS.ZIP
. Оказалось, что если бы я начал с этого, то мне не пришлось бы ждать 23 минуты:
$ time echo "TECOD5.BAS" | perl maken.pl
TECOD5.BAS / hexname-41544.txt
bkcrack 1.7.0 - 2024-05-26
[18:14:51] Z reduction using 6 bytes of known plaintext
100.0 % (6 / 6)
[18:14:51] Attack on 880113 Z values at index 33
Keys: 98e0f009 48a0b11a c70f8499
2.4 % (20737 / 880113)
Found a solution. Stopping.
You may resume the attack with the option: --continue-attack 20737
[18:15:29] Keys
98e0f009 48a0b11a c70f8499
real 0m38.152s
user 4m35.318s
sys 0m0.897s
При правильном открытом тексте атака на ZipCrypto с известным открытым текстом работает быстро. Если вам когда-нибудь придётся делать что-то подобное, то стоит потратить время на размышления об открытом тексте. В частности, для изучения полезны файлы, отмеченные как Store
в файле ZIP, поскольку они не сжаты и их содержимое предсказать может быть проще (вместо того, чтобы искать исходный файл и сжимать его, чтобы он соответствовал тому, что находится в ZIP).
Запускаем код
Я скомпилировал две программы и запустил их в DOSBox. Первая (RANDOM.BAS) использовалась для создания дисков со случайными числами, которые применялись в качестве одноразового шифра, а вторая (TECOD.BAS) использовалась для шифрования и дешифровки отправляемых по электронной почте сообщений. Скомпилированный код и сгенерированные исполняемые файлы можно найти в GitHub.
Для компиляции достаточно было запустить компилятор PowerBASIC следующим образом:
C:>EXEPBC TECOD.BAS PowerBASIC Compiler Version 3.00b Copyright (c) 1989-1993 by Robert S. Zale Spectra Publishing, Sunnyvale, CA, USA C:TECOD.BAS 2575 statements, 2329 lines Compile time: 00:12.0 Compilation speed: 12600 stmts/minute 45984 bytes code, 4880 bytes data, 2048 bytes stack Segments(1): 46k C:>EXEPBC RANDOM.BAS PowerBASIC Compiler Version 3.00b Copyright (c) 1989-1993 by Robert S. Zale Spectra Publishing, Sunnyvale, CA, USA C:RANDOM.BAS 2194 statements, 1940 lines Compile time: 00:10.1 Compilation speed: 12600 stmts/minute 33328 bytes code, 4704 bytes data, 3072 bytes stack Segments(1): 34k C:>
Первый этап заключается в создании случайных данных на диске, которые будут использоваться как одноразовый шифр. RANDOM.EXE использует три алгоритма генерации случайности (один из них задействует вводимый пользователем случайный ключ).
Шифрование и дешифровка выполняются через TECOD.EXE, который защищён паролем.
Хотя пароль встроен в программу и достаточно прост, Тим Дженкин обфусцировал его следующим образом:
DIM PW$(PL)
PW$(9)=CHR$(66):PW$(4)=CHR$(66):PW$(1)=CHR$(84):PW$(5)=CHR$(79):PW$(2) = CHR$(73)
PW$(3)=CHR$(77):PW$(6)=CHR$(66):PW$(8)=CHR$(77):PW$(10)=CHR$(79):PW$(7)=CHR$(73)
В этой конкретной версии программы переход в главное меню выполняется после ввода пароля TIMBOBIMBO. Стоит отметить, что каждая версия этих программ передавалась разным членам АНК и имела разные пароли.
Если вам захочется запустить эти программы самостоятельно, то можно воспользоваться руководством.
Вот скриншоты из трёх коротких видео, демонстрирующих создание случайных данных в RANDATA.1 для ключа при помощи RANDOM.EXE с последующим шифрованием сообщения, сохранённого в PLAIN.TXT на RAM-диске (все криптографические операции должны были происходить на RAM-диске), которое преобразовывалось в PLAIN.BIN (и наоборот). Сами видео можно посмотреть в оригинале статьи.
Создание случайных данных для использования в качестве ключа шифрования
Шифрование файла
Здесь программы (TECOD.EXE/TECOD.CNF) находятся на дискете A:, диск с данными (содержащий созданный выше файл ключа) находится на B:; также есть RAM-диск на R:. Чтобы всё это работало, файл RANDATA.1, созданный на предыдущем этапе, нужно переименовать в SNUM.
Дешифровка файла
Здесь программы (TECOD.EXE/TECOD.CNF) находятся на дискете A:, диск с данными (содержащий созданный выше файл ключа) на B:; также есть RAM-диск на R:. Файл RANDATA.1 должен называться RNUM на B:.
Есть ещё много интересных деталей работы этих программ, заслуживающих отдельного длинного поста. Например, материал для ключа уничтожается после использования, а программа RANDOM.EXE может создавать случайность разными способами; также есть код для проверки распределения созданных случайных байтов. При выполнении всех криптографических операций упор делается на использовании RAM-диска.
Автор: PatientZero