Reconstruction of des.exe/undes.exe
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user