/* reveng.c * Greg Cook, 23/Feb/2019 */ /* CRC RevEng: arbitrary-precision CRC calculator and algorithm finder * Copyright (C) 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, * 2019 Gregory Cook * * This file is part of CRC RevEng. * * CRC RevEng is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * CRC RevEng is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CRC RevEng. If not, see . */ /* 2013-09-16: calini(), calout() work on shortest argument * 2013-06-11: added sequence number to uprog() calls * 2013-02-08: added polynomial range search * 2013-01-18: refactored model checking to pshres(); renamed chkres() * 2012-05-24: efficiently build Init contribution string * 2012-05-24: removed broken search for crossed-endian algorithms * 2012-05-23: rewrote engini() after Ewing; removed modini() * 2011-01-17: fixed ANSI C warnings * 2011-01-08: fixed calini(), modini() caters for crossed-endian algos * 2011-01-04: renamed functions, added calini(), factored pshres(); * rewrote engini() and implemented quick Init search * 2011-01-01: reveng() initialises terminating entry, addparms() * initialises all fields * 2010-12-26: renamed CRC RevEng. right results, rejects polys faster * 2010-12-24: completed, first tests (unsuccessful) * 2010-12-21: completed modulate(), partial sketch of reveng() * 2010-12-19: started reveng */ /* reveng() can in theory be modified to search for polynomials shorter * than the full width as well, but this imposes a heavy time burden on * the full width search, which is the primary use case, as well as * complicating the search range function introduced in version 1.1.0. * It is more effective to search for each shorter width directly. */ #include #define FILE void #include "reveng.h" static poly_t *modpol(const poly_t init, int rflags, int args, const poly_t *argpolys); static void engini(int *resc, model_t **result, const poly_t divisor, int flags, int args, const poly_t *argpolys); static void calout(int *resc, model_t **result, const poly_t divisor, const poly_t init, int flags, int args, const poly_t *argpolys); static void calini(int *resc, model_t **result, const poly_t divisor, int flags, const poly_t xorout, int args, const poly_t *argpolys); static void chkres(int *resc, model_t **result, const poly_t divisor, const poly_t init, int flags, const poly_t xorout, int args, const poly_t *argpolys); static const poly_t pzero = PZERO; model_t * reveng(const model_t *guess, const poly_t qpoly, int rflags, int args, const poly_t *argpolys) { /* Complete the parameters of a model by calculation or brute search. */ poly_t *pworks, *wptr, rem, gpoly; model_t *result = NULL, *rptr; int resc = 0; unsigned long spin = 0, seq = 0; if (~rflags & R_HAVEP) { /* The poly is not known. * Produce a list of differences between the arguments. */ pworks = modpol(guess->init, rflags, args, argpolys); if (!pworks || !plen(*pworks)) { free(pworks); goto requit; } /* Initialise the guessed poly to the starting value. */ gpoly = pclone(guess->spoly); /* Clear the least significant term, to be set in the * loop. qpoly does not need fixing as it is only * compared with odd polys. */ if (plen(gpoly)) pshift(&gpoly, gpoly, 0UL, 0UL, plen(gpoly) - 1UL, 1UL); while (piter(&gpoly) && (~rflags & R_HAVEQ || pcmp(&gpoly, &qpoly) < 0)) { /* For each possible poly of this size, try * dividing all the differences in the list. */ if (!(spin++ & R_SPMASK)) { uprog(gpoly, guess->flags, seq++); } for (wptr = pworks; plen(*wptr); ++wptr) { /* straight divide message by poly, don't multiply by x^n */ rem = pcrc(*wptr, gpoly, pzero, pzero, 0); if (ptst(rem)) { pfree(&rem); break; } else pfree(&rem); } /* If gpoly divides all the differences, it is a * candidate. Search for an Init value for this * poly or if Init is known, log the result. */ if (!plen(*wptr)) { /* gpoly is a candidate poly */ if (rflags & R_HAVEI && rflags & R_HAVEX) chkres(&resc, &result, gpoly, guess->init, guess->flags, guess->xorout, args, argpolys); else if (rflags & R_HAVEI) calout(&resc, &result, gpoly, guess->init, guess->flags, args, argpolys); else if (rflags & R_HAVEX) calini(&resc, &result, gpoly, guess->flags, guess->xorout, args, argpolys); else engini(&resc, &result, gpoly, guess->flags, args, argpolys); } if (!piter(&gpoly)) break; } /* Finished with gpoly and the differences list, free them. */ pfree(&gpoly); for (wptr = pworks; plen(*wptr); ++wptr) pfree(wptr); free(pworks); } else if (rflags & R_HAVEI && rflags & R_HAVEX) /* All parameters are known! Submit the result if we get here */ chkres(&resc, &result, guess->spoly, guess->init, guess->flags, guess->xorout, args, argpolys); else if (rflags & R_HAVEI) /* Poly and Init are known, calculate XorOut */ calout(&resc, &result, guess->spoly, guess->init, guess->flags, args, argpolys); else if (rflags & R_HAVEX) /* Poly and XorOut are known, calculate Init */ calini(&resc, &result, guess->spoly, guess->flags, guess->xorout, args, argpolys); else /* Poly is known but not Init; search for Init. */ engini(&resc, &result, guess->spoly, guess->flags, args, argpolys); requit: if (!(result = realloc(result, ++resc * sizeof(model_t)))) { uerror("cannot reallocate result array"); return NULL; } rptr = result + resc - 1; rptr->spoly = pzero; rptr->init = pzero; rptr->flags = 0; rptr->xorout = pzero; rptr->check = pzero; rptr->magic = pzero; rptr->name = NULL; return (result); } static poly_t * modpol(const poly_t init, int rflags, int args, const poly_t *argpolys) { /* Produce, in ascending length order, a list of differences * between the arguments in the list by summing pairs of arguments. * If R_HAVEI is not set in rflags, only pairs of equal length are * summed. * Otherwise, sums of right-aligned pairs are also returned, with * the supplied init poly added to the leftmost terms of each * poly of the pair. */ poly_t work, swap, *result, *rptr, *iptr; const poly_t *aptr, *bptr, *eptr = argpolys + args; unsigned long alen, blen; if (args < 2) return (NULL); result = calloc(((((args - 1) * args) >> 1) + 1) * sizeof(poly_t), sizeof(char)); if (!result) uerror("cannot allocate memory for codeword table"); rptr = result; for (aptr = argpolys; aptr < eptr; ++aptr) { alen = plen(*aptr); for (bptr = aptr + 1; bptr < eptr; ++bptr) { blen = plen(*bptr); if (alen == blen) { work = pclone(*aptr); psum(&work, *bptr, 0UL); } else if (rflags & R_HAVEI && alen < blen) { work = pclone(*bptr); psum(&work, *aptr, blen - alen); psum(&work, init, 0UL); psum(&work, init, blen - alen); } else if (rflags & R_HAVEI /* && alen > blen */) { work = pclone(*aptr); psum(&work, *bptr, alen - blen); psum(&work, init, 0UL); psum(&work, init, alen - blen); } else work = pzero; if (plen(work)) pnorm(&work); if ((blen = plen(work))) { /* insert work into result[] in ascending order of length */ for (iptr = result; iptr < rptr; ++iptr) { if (plen(work) < plen(*iptr)) { swap = *iptr; *iptr = work; work = swap; } else if (plen(*iptr) == blen && !pcmp(&work, iptr)) { pfree(&work); work = *--rptr; break; } } *rptr++ = work; } } } *rptr = pzero; return (result); } static void engini(int *resc, model_t **result, const poly_t divisor, int flags, int args, const poly_t *argpolys) { /* Search for init values implied by the arguments. * Method from: Ewing, Gregory C. (March 2010). * "Reverse-Engineering a CRC Algorithm". Christchurch: * University of Canterbury. * */ poly_t apoly = PZERO, bpoly, pone = PZERO, *mat, *jptr; const poly_t *aptr, *bptr, *iptr; unsigned long alen, blen, dlen, ilen, i, j; int cy; dlen = plen(divisor); /* Allocate the CRC matrix */ mat = (poly_t *) calloc((dlen << 1) * sizeof(poly_t), sizeof(char)); if (!mat) uerror("cannot allocate memory for CRC matrix"); /* Find arguments of the two shortest lengths */ alen = blen = plen(*(aptr = bptr = iptr = argpolys)); for (++iptr; iptr < argpolys + args; ++iptr) { ilen = plen(*iptr); if (ilen < alen) { bptr = aptr; blen = alen; aptr = iptr; alen = ilen; } else if (ilen > alen && (aptr == bptr || ilen < blen)) { bptr = iptr; blen = ilen; } } if (aptr == bptr) { /* if no arguments are suitable, calculate Init with an * assumed XorOut of 0. Create a padded XorOut */ palloc(&apoly, dlen); calini(resc, result, divisor, flags, apoly, args, argpolys); pfree(&apoly); free(mat); return; } /* Find the potential contribution of the bottom bit of Init */ palloc(&pone, 1UL); piter(&pone); if (blen < (dlen << 1)) { palloc(&apoly, dlen); /* >= 1 */ psum(&apoly, pone, (dlen << 1) - 1UL - blen); /* >= 0 */ psum(&apoly, pone, (dlen << 1) - 1UL - alen); /* >= 1 */ } else { palloc(&apoly, blen - dlen + 1UL); /* > dlen */ psum(&apoly, pone, 0UL); psum(&apoly, pone, blen - alen); /* >= 1 */ } if (plen(apoly) > dlen) { mat[dlen] = pcrc(apoly, divisor, pzero, pzero, 0); pfree(&apoly); } else { mat[dlen] = apoly; } /* Find the actual contribution of Init */ apoly = pcrc(*aptr, divisor, pzero, pzero, 0); bpoly = pcrc(*bptr, divisor, pzero, apoly, 0); /* Populate the matrix */ palloc(&apoly, 1UL); for (jptr = mat; jptr < mat + dlen; ++jptr) *jptr = pzero; for (iptr = jptr++; jptr < mat + (dlen << 1); iptr = jptr++) * jptr = pcrc(apoly, divisor, *iptr, pzero, P_MULXN); pfree(&apoly); /* Transpose the matrix, augment with the Init contribution * and convert to row echelon form */ for (i = 0UL; i < dlen; ++i) { apoly = pzero; iptr = mat + (dlen << 1); for (j = 0UL; j < dlen; ++j) ppaste(&apoly, *--iptr, i, j, j + 1UL, dlen + 1UL); if (ptst(apoly)) ppaste(&apoly, bpoly, i, dlen, dlen + 1UL, dlen + 1UL); j = pfirst(apoly); while (j < dlen && !pident(mat[j], pzero)) { psum(&apoly, mat[j], 0UL); /* pfirst(apoly) > j */ j = pfirst(apoly); } if (j < dlen) mat[j] = apoly; /* pident(mat[j], pzero) || pfirst(mat[j]) == j */ else pfree(&apoly); } palloc(&bpoly, dlen + 1UL); psum(&bpoly, pone, dlen); /* Iterate through all solutions */ do { /* Solve the matrix by Gaussian elimination. * The parity of the result, masked by each row, should be even. */ cy = 1; apoly = pclone(bpoly); jptr = mat + dlen; for (i = 0UL; i < dlen; ++i) { /* Compute next bit of Init */ if (pmpar(apoly, *--jptr)) psum(&apoly, pone, dlen - 1UL - i); /* Toggle each zero row with carry, for next iteration */ if (cy) { if (pident(*jptr, pzero)) { /* 0 to 1, no carry */ *jptr = bpoly; cy = 0; } else if (pident(*jptr, bpoly)) { /* 1 to 0, carry forward */ *jptr = pzero; } } } /* Trim the augment mask bit */ praloc(&apoly, dlen); /* Test the Init value and add to results if correct */ calout(resc, result, divisor, apoly, flags, args, argpolys); pfree(&apoly); } while (!cy); pfree(&pone); pfree(&bpoly); /* Free the matrix. */ for (jptr = mat; jptr < mat + (dlen << 1); ++jptr) pfree(jptr); free(mat); } static void calout(int *resc, model_t **result, const poly_t divisor, const poly_t init, int flags, int args, const poly_t *argpolys) { /* Calculate Xorout, check it against all the arguments and * add to results if consistent. */ poly_t xorout; const poly_t *aptr, *iptr; unsigned long alen, ilen; if (args < 1) return; /* find argument of the shortest length */ alen = plen(*(aptr = iptr = argpolys)); for (++iptr; iptr < argpolys + args; ++iptr) { ilen = plen(*iptr); if (ilen < alen) { aptr = iptr; alen = ilen; } } xorout = pcrc(*aptr, divisor, init, pzero, 0); /* On little-endian algorithms, the calculations yield * the reverse of the actual xorout: in the Williams * model, the refout stage intervenes between init and * xorout. */ if (flags & P_REFOUT) prev(&xorout); /* Submit the model to the results table. * Could skip the shortest argument but we wish to check our * calculation. */ chkres(resc, result, divisor, init, flags, xorout, args, argpolys); pfree(&xorout); } static void calini(int *resc, model_t **result, const poly_t divisor, int flags, const poly_t xorout, int args, const poly_t *argpolys) { /* Calculate Init, check it against all the arguments and add to * results if consistent. */ poly_t rcpdiv, rxor, arg, init; const poly_t *aptr, *iptr; unsigned long alen, ilen; if (args < 1) return; /* find argument of the shortest length */ alen = plen(*(aptr = iptr = argpolys)); for (++iptr; iptr < argpolys + args; ++iptr) { ilen = plen(*iptr); if (ilen < alen) { aptr = iptr; alen = ilen; } } rcpdiv = pclone(divisor); prcp(&rcpdiv); /* If the algorithm is reflected, an ordinary CRC requires the * model's XorOut to be reversed, as XorOut follows the RefOut * stage. To reverse the CRC calculation we need rxor to be the * mirror image of the forward XorOut. */ rxor = pclone(xorout); if (~flags & P_REFOUT) prev(&rxor); arg = pclone(*aptr); prev(&arg); init = pcrc(arg, rcpdiv, rxor, pzero, 0); pfree(&arg); pfree(&rxor); pfree(&rcpdiv); prev(&init); /* Submit the model to the results table. * Could skip the shortest argument but we wish to check our * calculation. */ chkres(resc, result, divisor, init, flags, xorout, args, argpolys); pfree(&init); } static void chkres(int *resc, model_t **result, const poly_t divisor, const poly_t init, int flags, const poly_t xorout, int args, const poly_t *argpolys) { /* Checks a model against the argument list, and adds to the * external results table if consistent. * Extends the result array and updates the external pointer if * necessary. */ model_t *rptr; poly_t xor, crc; const poly_t *aptr = argpolys, *const eptr = argpolys + args; /* If the algorithm is reflected, an ordinary CRC requires the * model's XorOut to be reversed, as XorOut follows the RefOut * stage. */ xor = pclone(xorout); if (flags & P_REFOUT) prev(&xor); for (; aptr < eptr; ++aptr) { crc = pcrc(*aptr, divisor, init, xor, 0); if (ptst(crc)) { pfree(&crc); break; } else { pfree(&crc); } } pfree(&xor); if (aptr != eptr) return; *result = realloc(*result, ++*resc * sizeof(model_t)); if (!*result) { uerror("cannot reallocate result array"); return; } rptr = *result + *resc - 1; rptr->spoly = pclone(divisor); rptr->init = pclone(init); rptr->flags = flags; rptr->xorout = pclone(xorout); rptr->check = pzero; rptr->magic = pzero; rptr->name = NULL; /* compute check value for this model */ mcheck(rptr); /* callback to notify new model */ ufound(rptr); }