5.7 KiB
Reconstruction notes — DES/UNDES V1.1.0
Confidence
The main program logic is now reconstructed with high confidence. The supplied
cryptl99.zip is especially useful because it contains the exact CRYPTLIB 0.99
API and libdes 3.14 source that was linked into the executables.
This is not the original source text. Original variable names, comments,
whitespace and file decomposition were not retained in the executable. Names
such as firstTime, passwordBlock, negativeMarker, and the split into
des_common.c/.h are descriptive reconstruction choices.
Compiler and memory model
Both programs are 16-bit MS-DOS MZ executables and contain the runtime string:
Turbo-C - Copyright (c) 1988 Borland Intl.
The generated code uses far data pointers extensively, and malloc() returns a
segment:offset pointer. That is consistent with a Turbo C large-memory-model
build.
Exact crypto dependency
The executables contain:
libdes v 3.14 - eay
The supplied CRYPTLIB archive contains the same libdes/version.h string, plus
crypt.h, crypt.c, lib_3des.c and the relevant libdes sources. This is the
actual dependency family used by the executables rather than merely a later
compatible library.
Important correction: it is 3DES
The machine code assigns value 3 to both the algorithm and mode variables.
According to the supplied crypt.h:
CRYPT_ALGO_3DES= 3CRYPT_MODE_CBC= 3
Therefore the encryption is two-key 3DES-CBC, despite the executable being
called des.exe.
crypt.c defines the recommended 3DES-CBC parameters as:
- key size: 112 bits = 14 bytes
- IV size: 32 bits = 4 bytes
- DES block size: 64 bits = 8 bytes
The main routine obtains both sizes through queryAlgoModeInformation().
Password handling
The command-line password is copied directly into a 14-byte key buffer and is zero-padded if shorter. Characters after byte 14 do not influence encryption. There is no password hash or KDF in the DES/UNDES layer.
CRYPTLIB's des3InitKey() then constructs the two DES key schedules from that
user-key buffer. des_check_key in the bundled libdes defaults to zero, so
parity/weak-key checking is not enabled by default.
File layout
For a non-empty encrypted input the file is:
Offset Size Meaning
0 4 file version: 0x00002775
4 4 CRYPTLIB-recommended CBC IV
8 256 encrypted password-check block
264 16384 encrypted data block 1
16648 16384 encrypted data block 2
... ...
For an empty input, des.exe writes only the four-byte version. Because no
encryptBuffer() call with data occurs, no IV or password-check block is
written.
Data-block layout before encryption
Each 16384-byte encrypted data block consists of:
0..3 signed 32-bit length marker
4..7 signed 32-bit secondary marker
8..16383 at most 16376 bytes input data
For a partial final block, the first field is the positive plaintext byte count.
For a full block it is a negative value; undes.exe interprets any negative
value as exactly 16376 plaintext bytes.
The full-block negative value is formed from the absolute value of the first
signed 16-bit word of the plaintext and then negated. If that produces zero,
-1 is stored instead. The second field is also a negative marker derived from
a signed 16-bit plaintext word. undes.exe does not use this second field.
The exact purpose of the secondary marker cannot be established from the executables alone; its computation is established, its design intent is not.
CBC ordering on the first block
An unusual but confirmed detail is the first-block ordering.
des.exe performs the cryptographic operations in this order:
- encrypt first 16 KiB data block
- retrieve the now-generated IV
- encrypt the 256-byte password-check block
but writes:
- IV
- encrypted password-check block
- encrypted first data block
undes.exe reads the stored password block and first data block, then decrypts
in cryptographic order:
- decrypt first data block
- decrypt password-check block
This preserves the CBC state exactly. The apparent storage/processing order mismatch is therefore intentional in the program logic, not a decompiler error.
Password-check block
Before encryption, the first 14 bytes of the 256-byte password-check block are the derived 14-byte key. The executable does not initialise the remaining 242 bytes before encrypting the block. During decryption only the first 14 bytes are compared.
Uninitialised bytes in the final data block
des.exe always encrypts the complete 16384-byte buffer. If the final input
chunk is shorter than 16376 bytes, the unused tail is not explicitly cleared
first. This is also visible in the machine code. UNDES writes only the number
of plaintext bytes stored in the first 32-bit field.
Error handling and robustness
Read errors are detected only when read() returns -1; short reads are not
rejected. undes.exe does not authenticate ciphertext and does not validate a
positive decrypted length against 16376 before passing it to fwrite(). These
are normal limitations for small DOS-era utility code, but they matter if old
encrypted files are corrupted or attacker-controlled.
Remaining uncertainty
The following cannot be recovered exactly without original source/debug symbols:
- original variable/function names for the DES/UNDES-specific code;
- original comments and formatting;
- exact source-file decomposition;
- the design rationale for the secondary 32-bit marker;
- the symbolic name/formula originally used for file version
0x2775.
The actual program behaviour no longer depends on guessed CRYPTLIB API names.