Files
des_undes/reconstruction-notes.md

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 = 3
  • CRYPT_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:

  1. encrypt first 16 KiB data block
  2. retrieve the now-generated IV
  3. encrypt the 256-byte password-check block

but writes:

  1. IV
  2. encrypted password-check block
  3. encrypted first data block

undes.exe reads the stored password block and first data block, then decrypts in cryptographic order:

  1. decrypt first data block
  2. 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.