Search code examples
assemblyx86reverse-engineeringlcg

Reverse multiplication with unknown dx - solve for original AX after multiple mul/add constant steps


I have this repeating iterative process in 16-bit x86 asm

rec_loop:
  mul bx          ; constant value of 0x11bf
  add ax, 0x4743
  loop rec_loop

it's running several times but for our case let's say it runs 3 times, now after it is finished I get dx:ax and need to recover the initial ax of this program (the initial dx is 0). From first sight, we can see that information about dx is being lost in this process because mul override the last dx with a new one.

I'm looking for a way to reverse this process if it's even possible, of course with the assumption the code above can't be changed.

If I forgot to explain something or didn't give enough details about the problem just tell me and I'll answer.


Solution

  • The computation xn+1 := (a * xn + c) mod m, with a = 0x11bf, c = 0x4743, m= 216 forms a pseudo-random number generator. Specifically, a linear congruential generator with period 211. So clearly not a high-quality PRNG, but it may be sufficient for use cases that merely require data that looks kind of random. I note that the question does not state the context in which this code is used.

    The sequence of xi (here successively generated in AX) can be reversed by using the modular multiplicative inverse ainverse like so: xn-1 = ainverse* (xn - c) mod m. In this case the modular multiplicative inverse is 0xde3f.

    Note that the value of DX is not needed in the backwards computation, and at any point in the sequence can readily be determined from AX if needed. The determination of the multiplicative inverse below uses a naive method based on the definition. For larger operand sizes this is hugely inefficient, one would want to use the Extended Euclidean algorithm instead.

    Simple C99 code for experimenting with the computation of the multiplicative inverse and the reversing of the sequence of values in AX:

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    
    #define X_0   (0xc001)
    #define ITERS (3)
    
    uint32_t modular_inverse (uint32_t a, uint32_t m)
    {
        for (uint32_t x = 1; x < m; x++) {
            if (((a % m) * (x % m)) % m == 1) {
                return x;
            }
        }
        printf ("no inverse found\n");
        return 0;
    }
    
    uint16_t umul16_lo (uint16_t a, uint16_t b)
    {
        return (uint16_t)(a * b);
    }
    
    uint16_t umul16_hi (uint16_t a, uint16_t b)
    {
        return (uint16_t)(((uint32_t)a * b) >> 16);
    }
    
    int main (void)
    {
        const uint16_t a = 0x11bf;   // multiplier of PRNG
        const uint16_t c = 0x4743;   // additive offset of PRNG
        const uint32_t m = 0x10000U; // modulo of PRNG
        uint16_t a_inverse;
    
        a_inverse = modular_inverse (a, m);
    
        printf ("a_inverse = %04x\n", a_inverse);
    
        const uint16_t bx = a; 
        uint16_t dx = 0;
        uint16_t ax = X_0;
    
        printf ("starting values:    ax=%04x  dx=%04x  bx=%04x\n", ax, dx, bx);
        printf ("forward computation\n");
    
        for (int i = 0; i < ITERS; i++) {
            dx = umul16_hi (ax, bx);
            ax = umul16_lo (ax, bx) + c;
        }
        printf ("after %d iterations: ax=%04x  dx=%04x  bx=%04x\n", ITERS, ax, dx, bx);
        printf ("backward computation\n");
    
        for (int i = 0; i < ITERS; i++) {
            ax = umul16_lo (ax - c, a_inverse);
        }
    
        printf ("after %d iterations: ax=%04x\n", ITERS, ax);
    
        return EXIT_SUCCESS;
    }