163 lines
5.7 KiB
Markdown
163 lines
5.7 KiB
Markdown
# 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:
|
|
|
|
```text
|
|
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:
|
|
|
|
```text
|
|
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.
|