The patterns are definitely interesting. If we assume the final operation in the hash algorithm is to add C6EA to first two response words and E64C to the last two response words, then:
Challenge Response before Add Challenge in binary Response before addition in binary
8000 0000 0000 0000 + 8000 0000 8000 8000 : 1000000000000000 0000000000000000 0000000000000000 0000000000000000 + 1000000000000000 0000000000000000 1000000000000000 1000000000000000
C000 0000 0000 0000 + 4000 8000 C000 C000 : 1100000000000000 0000000000000000 0000000000000000 0000000000000000 + 0100000000000000 1000000000000000 1100000000000000 1100000000000000
E000 0000 0000 0000 + A000 C000 6000 6000 : 1110000000000000 0000000000000000 0000000000000000 0000000000000000 + 1010000000000000 1100000000000000 0110000000000000 0110000000000000
F000 0000 0000 0000 + 1000 2000 D000 D000 : 1111000000000000 0000000000000000 0000000000000000 0000000000000000 + 0001000000000000 0010000000000000 1101000000000000 1101000000000000
F800 0000 0000 0000 + 0800 1000 0800 0800 : 1111100000000000 0000000000000000 0000000000000000 0000000000000000 + 0000100000000000 0001000000000000 0000100000000000 0000100000000000
FC00 0000 0000 0000 + 4400 4800 BC00 BC00 : 1111110000000000 0000000000000000 0000000000000000 0000000000000000 + 0100010000000000 0100100000000000 1011110000000000 1011110000000000
FE00 0000 0000 0000 + 4200 4400 F600 F600 : 1111111000000000 0000000000000000 0000000000000000 0000000000000000 + 0100001000000000 0100010000000000 1111011000000000 1111011000000000
FF00 0000 0000 0000 + 6900 6A00 0D00 0D00 : 1111111100000000 0000000000000000 0000000000000000 0000000000000000 + 0110100100000000 0110101000000000 0000110100000000 0000110100000000
FF80 0000 0000 0000 + 0A80 0B00 0780 0780 : 1111111110000000 0000000000000000 0000000000000000 0000000000000000 + 0000101010000000 0000101100000000 0000011110000000 0000011110000000
FFC0 0000 0000 0000 + 1640 1680 7C40 7C40 : 1111111111000000 0000000000000000 0000000000000000 0000000000000000 + 0001011001000000 0001011010000000 0111110001000000 0111110001000000
FFE0 0000 0000 0000 + 7820 7840 2120 2120 : 1111111111100000 0000000000000000 0000000000000000 0000000000000000 + 0111100000100000 0111100001000000 0010000100100000 0010000100100000
FFF0 0000 0000 0000 + 2710 2720 5C30 5C30 : 1111111111110000 0000000000000000 0000000000000000 0000000000000000 + 0010011100010000 0010011100100000 0101110000110000 0101110000110000
FFF8 0000 0000 0000 + 18A8 18B0 DCC8 DCC8 : 1111111111111000 0000000000000000 0000000000000000 0000000000000000 + 0001100010101000 0001100010110000 1101110011001000 1101110011001000
FFFC 0000 0000 0000 + 8644 8648 04AC 04AC : 1111111111111100 0000000000000000 0000000000000000 0000000000000000 + 1000011001000100 1000011001001000 0000010010101100 0000010010101100
FFFE 0000 0000 0000 + E222 E224 D7C2 D7C2 : 1111111111111110 0000000000000000 0000000000000000 0000000000000000 + 1110001000100010 1110001000100100 1101011111000010 1101011111000010
FFFF 0000 0000 0000 + 6A25 6A26 2CC9 2CC9 : 1111111111111111 0000000000000000 0000000000000000 0000000000000000 + 0110101000100101 0110101000100110 0010110011001001 0010110011001001
If you have a bash shell (and basic utilities like sed, mktemp, awk), you can save the following as say gen.sh and run it via ./gen.sh dump*.txt to generate various files (including known.h for experimentation in C). This is what I use for initial mangling.
#!/bin/bash
# SPDX-License-Identifier: CC0-1.0
export LANG=C LC_ALL=C
# Usage
if [ $# -lt 1 ] || [ ":$*" = ":-h" ] || [ ":$*" = ":--help" ]; then
exec >&2
printf '\n'
printf 'Usage: %s [ -h | --help ]\n' "$0"
printf ' %s BYTE-PATTERN-FILE(s)...\n' "$0"
printf '\n'
printf 'This reads in hexadecimal input consisting of 16 bytes,\n'
printf 'whitespace-separated, forming a challenge-response pair.\n'
printf '\n'
printf 'After reading all inputs, the three following files are generated:\n'
printf ' bytes.txt, containing all unique patterns of\n'
printf ' "HH HH HH HH HH HH HH HH = HH HH HH HH HH HH HH HH"\n'
printf ' words.txt, above reformatted to little-endian 16-bit words,\n'
printf ' "HHHH HHHH HHHH HHHH = HHHH HHHH HHHH HHHH"\n'
printf ' words-sub.txt, the above assuming final substraction by zero response,\n'
printf ' "HHHH HHHH HHHH HHHH - HHHH HHHH HHHH HHHH : BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB - BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB\n'
printf ' words-add.txt, the above assuming final addition by zero response,\n'
printf ' "HHHH HHHH HHHH HHHH + HHHH HHHH HHHH HHHH : BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB + BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB\n'
printf ' words-xor.txt, the above assuming final exclusive-or by zero response,\n'
printf ' "HHHH HHHH HHHH HHHH ^ HHHH HHHH HHHH HHHH : BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB ^ BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBB\n'
printf ' known.h, a C header file exposing these patterns\n'
printf '\n' >&2
exit 0
fi
# Auto-deleted temporary directory for work files
Work=$(mktemp -d) || exit 1
trap "rm -rf '$Work'" EXIT
# Transmogrify inputs into hex bytes only
sed -e 's|[\t\r\v\f ][\t\r\v\f ]*| |g' \
-e '/^[#;]/ d' \
-e 's|^ *| |; s| *$||' \
-e 's| 00*\([0-9A-Fa-f][0-9A-Fa-f]\)| \1|g' \
"$@" | sort -g > "$Work/input" || exit 1
# Reformat to 'HH HH HH HH HH HH HH HH = HH HH HH HH HH HH HH HH'
awk '(NF >= 15) {
challenge = $1 " " $2 " " $3 " " $4 " " $5 " " $6 " " $7 " " $8
response = $9 " " $10 " " $11 " " $12 " " $13 " " $14 " " $15 " " $16
if (challenge in known) {
if (known[challenge] != response) {
printf "Warning: Challenge %s has conflicting responses %s and %s!\n", challenge, known[challenge], response > "/dev/stderr"
}
} else {
known[challenge] = response
printf "%s = %s\n", challenge, response
}
}' < "$Work/input" | sort -n > "$Work/bytes" || exit 1
# Reformat to 'HHHH HHHH HHHH HHHH = HHHH HHHH HHHH HHHH'
awk '(NF >= 16) {
printf "%s%s %s%s %s%s %s%s = %s%s %s%s %s%s %s%s\n", $2,$1, $4,$3, $6,$5, $8,$7, $11,$10, $13,$12, $15,$14, $17,$16
}' < "$Work/bytes" | sort -n > "$Work/words"
# Ensure the first challenge is all ones.
first=($(sed -ne '1 p' "$Work/words"))
if [ "${first[0]}:${first[1]}:${first[2]}:${first[3]}" != "0000:0000:0000:0000" ]; then
printf 'There is no response for a zero challenge in the input dataset!\n' >&2
exit 1
else
zero1="${first[5]}"
zero2="${first[6]}"
zero3="${first[7]}"
zero4="${first[8]}"
fi
# Generate known.h
(
sed -ne 's|^ ||p; s|^ *$||p' <<' END'
// SPDX-License-Identifier: CC0-1.0
#ifndef KNOWN_H
#define KNOWN_H
#include <stdint.h>
/* DO NOT MODIFY!
*
* This file is autogenerated by gen.sh script.
*/
#ifdef HELPER_FUNC
#define KNOWN_HELPER HELPER_FUNC
#else
#define KNOWN_HELPER __attribute__((__unused__)) static
#endif
// String returned by format_() functions if the buffer is too short.
static const char BUFFER_TOO_SHORT[] = "...";
// Hexadecimal digits
static const char hex_digit[16] = {
'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
};
// Challenge or response token.
typedef struct {
union {
uint64_t val;
uint64_t u64[1];
uint32_t u32[2];
uint16_t u16[4];
uint8_t u8[8];
char c[8];
};
} token;
static const struct {
union {
const uint64_t cval;
const token c;
const uint64_t c64[1];
const uint32_t c32[2];
const uint16_t c16[4];
const uint8_t c8[8];
const char cc[8];
};
union {
const uint64_t rval;
const token r;
const uint64_t r64[1];
const uint32_t r32[2];
const uint16_t r16[4];
const uint8_t r8[8];
const char rc[8];
};
} known[] = {
// First dataset corresponds to the zero challenge.
END
# Zero challenge.
printf ' { .c16 = { 0x0000, 0x0000, 0x0000, 0x0000 }, .r16 = { 0x%s, 0x%s, 0x%s, 0x%s } },\n' $zero1 $zero2 $zero3 $zero4
# All other challenges.
awk '($1 != "0000" || $2 != "0000" || $3 != "0000" || $4 != "0000") {
printf " { .c16 = { 0x%s, 0x%s, 0x%s, 0x%s }, .r16 = { 0x%s, 0x%s, 0x%s, 0x%s } },\n", $1, $2, $3, $4, $6, $7, $8, $9
}' "$Work/words" | sort -n -k 5
sed -ne 's|^ ||p; s|^ *$||p' <<' END'
};
#undef knowns
#define knowns (sizeof known / sizeof known[0])
// Return the index of the matching challenge, or -1 if not found.
KNOWN_HELPER int index_of_challenge(const token c)
{
int k = knowns;
while (k-->0)
if (known[k].cval == c.val)
return k;
return -1;
}
// Return the index of the matching response, or -1 if not found.
KNOWN_HELPER int index_of_response(const token r)
{
int k = knowns;
while (k-->0)
if (known[k].rval == r.val)
return k;
return -1;
}
// Return 1 if the specified challenge matches the response,
// 0 if the challenge does not match the response or vice versa,
// -1 if the specified challenge and response are unknown
KNOWN_HELPER int verify_challenge_response(const token c, const token r)
{
int k = knowns;
while (k-->0) {
if (known[k].cval == c.val) {
return (known[k].rval == r.val);
} else
if (known[k].rval == r.val) {
return 0;
}
}
return -1;
}
// Format token t as binary bytes into n-character buffer b.
KNOWN_HELPER const char *format_bin8(char *const b, const size_t n, const token t)
{
char *p = b;
// Eight items, each eight characters plus delimiter (space or NUL)
if (n < 8*(8+1))
return BUFFER_TOO_SHORT;
for (int i = 0; i < 8; i++) {
if (i)
*(p++) = ' ';
for (unsigned int m = 0x80; m != 0; m >>= 1)
*(p++) = '0' + !!(t.u8[i] & m);
}
*p = '\0';
return (const char *)b;
}
// Format token t as hexadecimal bytes into n-character buffer b.
KNOWN_HELPER const char *format_hex8(char *const b, const size_t n, const token t)
{
char *p = b;
// Eight items, each two characters plus delimiter (space or NUL)
if (n < 8*(2+1))
return BUFFER_TOO_SHORT;
for (int i = 0; i < 8; i++) {
if (i)
*(p++) = ' ';
*(p++) = hex_digit[ (t.u8[i] >> 4) & 15 ];
*(p++) = hex_digit[ t.u8[i] & 15 ];
}
*p = '\0';
return (const char *)b;
}
// Format token t as binary 16-bit words into n-character buffer b.
KNOWN_HELPER const char *format_bin16(char *const b, const size_t n, const token t)
{
char *p = b;
// Four items, each sixteen characters plus delimiter (space or NUL)
if (n < 4*(16+1))
return BUFFER_TOO_SHORT;
for (int i = 0; i < 4; i++) {
if (i)
*(p++) = ' ';
for (unsigned int m = 0x8000; m != 0; m >>= 1)
*(p++) = '0' + !!(t.u16[i] & m);
}
*p = '\0';
return (const char *)b;
}
// Format token t as hexadecimal 16-bit words into n-character buffer b.
KNOWN_HELPER const char *format_hex16(char *const b, const size_t n, const token t)
{
char *p = b;
// Four items, each four characters plus delimiter (space or NUL)
if (n < 4*(4+1))
return BUFFER_TOO_SHORT;
for (int i = 0; i < 4; i++) {
if (i)
*(p++) = ' ';
*(p++) = hex_digit[ (t.u16[i] >> 12) & 15 ];
*(p++) = hex_digit[ (t.u16[i] >> 8) & 15 ];
*(p++) = hex_digit[ (t.u16[i] >> 4) & 15 ];
*(p++) = hex_digit[ t.u16[i] & 15 ];
}
*p = '\0';
return (const char *)b;
}
#undef KNOWN_HELPER
#endif /* KNOWN_H */
END
) > "known.h" || exit 1
printf 'Generated "known.h" successfully.\n' >&2
cat "$Work/bytes" > "bytes.txt" || exit 1
printf 'Generated "bytes.txt" successfully.\n' >&2
cat "$Work/words" > "words.txt" || exit 1
printf 'Generated "words.txt" successfully.\n' >&2
awk -v z1=$[0x$zero1] -v z2=$[0x$zero2] -v z3=$[0x$zero3] -v z4=$[0x$zero4] \
-v outadd="$Work/words-add" \
-v outsub="$Work/words-sub" \
-v outxor="$Work/words-xor" \
'function unadd(v_, z_) { return sprintf("%04X", and(65536 + strtonum("0x" v_) - z_, 65535)) }
function unsub(v_, z_) { return sprintf("%04X", and(65536 - strtonum("0x" v_) + z_, 65535)) }
function unxor(v_, z_) { return sprintf("%04X", and(xor(strtonum("0x" v_), z_), 65535)) }
function binary(h_) {
r_ = ""
v_ = and(65535, int(strtonum("0x" h_)))
for (i_ = 0; i_ < 16; i_++) {
r_ = and(v_, 1) r_
v_ = int(v_ / 2)
}
return r_
}
{
a1 = unadd($6, z1) ; a2 = unadd($7, z2) ; a3 = unadd($8, z3) ; a4 = unadd($9, z4)
s1 = unsub($6, z1) ; s2 = unsub($7, z2) ; s3 = unsub($8, z3) ; s4 = unsub($9, z4)
x1 = unxor($6, z1) ; x2 = unxor($7, z2) ; x3 = unxor($8, z3) ; x4 = unxor($9, z4)
printf "%s %s %s %s + %s %s %s %s : %s %s %s %s + %s %s %s %s\n", $1, $2, $3, $4, a1, a2, a3, a4, binary($1), binary($2), binary($3), binary($4), binary(a1), binary(a2), binary(a3), binary(a4) >> outadd
printf "%s %s %s %s - %s %s %s %s : %s %s %s %s - %s %s %s %s\n", $1, $2, $3, $4, s1, s2, s3, s4, binary($1), binary($2), binary($3), binary($4), binary(s1), binary(s2), binary(s3), binary(s4) >> outsub
printf "%s %s %s %s ^ %s %s %s %s : %s %s %s %s ^ %s %s %s %s\n", $1, $2, $3, $4, x1, x2, x3, x4, binary($1), binary($2), binary($3), binary($4), binary(x1), binary(x2), binary(x3), binary(x4) >> outxor
}' < "$Work/words" || exit 1
for name in add sub xor ; do
cat "$Work/words-$name" > "words-$name.txt" || exit 1
printf 'Generated "%s" successfully.\n' "words-$name.txt" >&2
done
(Let me know if you'd prefer to see that in Python or C instead.)