Hacking an old ZIP file with crypto programs of the South African underground
It's not often that we get to study code that only a few people have seen before; a code that was an important part of the destruction of the apartheid system in South Africa; a code used for secure communications with one-time ciphers smuggled into South Africa on floppy disks by a flight attendant. But I experienced this one morning shortly after decrypting a thirty-year-old PKZIP file whose password had long been forgotten.
Recently I became interested in secure communications that were used African National Congress within Operation Voulacarried out in the late 1980s. Operation Vula involved the infiltration of ANC leaders (and transfer of equipment) into South Africa to prepare a secret network that carried out various elements of ANC political activity within the country.
The success of the operation required secure communications based on 8-bit computers, DTMF signals, acoustic transducers and various other equipment for exchanging messages with one-time encryption, using programs written in PowerBASIC.
I won't go into details about how all this works, since the main developer of the encryption system
posted the source code in
. Tim's article on the encryption system can be found
. I highly recommend reading it if you are interested in details.
The code was not made open source earlier for one simple reason: while flying from the UK to South Africa in 1991, he packaged all the source code in a zip file and set a password. In subsequent years, he simply forgot the password! Therefore, when I wrote him a letter asking if it was possible to open source it, he replied:
I still have the source code for Voola, but unfortunately it is mostly inaccessible because when I returned from the UK to South Africa in 1991, I zip-zipped all the files with a password. I was able to decrypt and unpack one of the files, but, alas, it was a very early version of the software. I can't retrieve the rest because I forgot the password. When I returned to South Africa, there was no need for code. I thought I would never forget the password, but when I tried to decrypt it several years later, I couldn't remember it.
If you can figure out how to decode zipped files, I'll be happy to publish them. I have already tried to crack the code several times, but have not yet achieved success.
I readily agreed and he sent me two files:
ALLBAS.ZIP
And
CODMAY93.ZIP
. They were created in an old version of PKZIP and are password protected. Fortunately, there is
to the ZipCrypto scheme, which was used in the ZIP format of that era. And the open source implementation of this attack is called
.
That is, it was enough to “simply” predict 12 bytes of plaintext at a known location inside the ZIP file. Here's a snippet of what was inside the ZIP file:
$ 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
Tim had several unencrypted files
.BAS
but their versions were different from what was in the file, and attempted attacks
bkcrack
using them (after running from the original PKZIP to
) were unsuccessful; I decided that before carrying out further attacks I needed to think a little.
IN ALLBAS.ZIP
contained a lot of uncompressed files because they were already binary and therefore not worth compressing. These files are marked as 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
Files stored as
Store
are promising for plaintext prediction because they are not compressed and do not need to compress the source file to obtain the plaintext. Examining ZIP files, since ZIP files begin with a PK header, seemed like a suitable way to find predictable plaintext at a known position. Here are the standard PK header fields at the very beginning of the ZIP file:
I decided that a realistic attack would be to predict the name of the first file in the archive. If the file name consists of at least eight characters (which is quite likely, since at least four characters are used for
.BAS
,
.INC
and so on), then at least 12 characters of plaintext will be available if you add up the size of the file name (offset 0x1A, 0x1B) and the length of the additional field (which in all ZIPs sent by Tim turned out to be equal to 0x00, 0x00).
In the worst case, we will be able to brute force potential filenames, given that they are all combinations of capital letters and numbers with a maximum length of eight characters plus the extension. But it turned out that this was not required.
Fortunately, Tim discovered another version OLDCOD.ZIP
(one of the ZIP files inside ALLBAS.ZIP
), and he told me that the first file in it is called COMKEY.BAS
. I wrote a small Perl program to generate the required plaintext in the hope that OLDCOD.ZIP
inside ALLBAS.ZIP
starts with COMKEY.BAS
$ cat maken.pl
use strict;
use warnings;
my $outfile = "hexname-$$.txt";
while (<>) {
chomp;
my $bas = $_;
print("$bas / $outfile\n");
my $n = sprintf("%c\x00\x00\x00$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");
}
In 23 minutes
bkcrack
gave the key to the file
ALLBAS.ZIP
and I managed to decipher it. The same key went to
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
After finding the key
bkcrack
performs decryption:
$ 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)
And so we finally received the long-lost source code used to organize ANC secure communications!
If I didn't succeed, I would attack one of the other ZIP files using the same method (and then just start trying out the file names). I would guess that TECOD5.ZIP
was probably a ZIP of just one file TECOD.BAS
(or maybe TECOD5.BAS
), judging by the compressed size TECOD.BAS
V ALLBAS.ZIP
. It turned out that if I started with this, I wouldn't have to wait 23 minutes:
$ 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
Given the correct plaintext, the known plaintext attack on ZipCrypto is fast. If you ever have to do something like this, it's worth spending time thinking about plaintext. In particular, files marked as
Store
in a ZIP file since they are uncompressed and their contents may be easier to predict (instead of having to find the original file and compress it to match what's in the ZIP).
Running the code
I compiled two programs and ran them in DOSBox. First (
) was used to create disks with random numbers, which were used as a one-time cipher, and the second (
) was used to encrypt and decrypt messages sent by e-mail. The compiled code and generated executables can be found in
.
To compile, it was enough to run the PowerBASIC compiler as follows:
C:\>EXE\PBC 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:\>EXE\PBC 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:\>
The first step is to create random data on disk that will be used as a one-time cipher. RANDOM.EXE uses three randomness generation algorithms (one of which uses a user-entered random key).
Encryption and decryption are performed through TECOD.EXE, which is password protected.
Although the password is built into the program and is quite simple, Tim Jenkin obfuscated it as follows:
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)
In this particular version of the program, the main menu is accessed after entering the TIMBOBIMBO password. It is worth noting that each version of these programs was distributed to different ANC members and had different passwords.
If you want to run these programs yourself, you can use
.
Here are screenshots from three short videos demonstrating the creation of random data in RANDATA.1 for the key using RANDOM.EXE and then encrypting the message saved in PLAIN.TXT on the RAM disk (all cryptographic operations had to occur on the RAM disk), which was converted to PLAIN.BIN (and vice versa). The videos themselves can be viewed at original articles.
Generating random data to use as an encryption key
File encryption
Here the programs (TECOD.EXE/TECOD.CNF) are on floppy disk A:, the data disk (containing the key file created above) is on disk B:; There is also a RAM disk for R:. For this to work, the RANDATA.1 file created in the previous step must be renamed SNUM.
File decryption
Here the programs (TECOD.EXE/TECOD.CNF) are on floppy disk A:, the data disk (containing the key file created above) is on B:; There is also a RAM disk for R:. The RANDATA.1 file should be named RNUM on B:.
There are many more interesting details about how these programs work that deserve a separate long post. For example, key material is destroyed after use, and the RANDOM.EXE program can create randomness in various ways; there is also code to check the distribution of the generated random bytes. When performing all cryptographic operations, the emphasis is on using the RAM disk.