Wiki: Example 2

WARNING: Wiki content is an archive, no promise about quality!

Please choose a tutorial page:


This example is the first step in Starcraft's CDKey Decode. This shuffles the characters in the key in a predictable way. I made this the second example because it's a little trickier, but it's not terribly difficult.

As in the previous example, try figuring this out on your own first!

   lea     edi, [esi+0Bh]
   mov     ecx, 0C2h
top:
   mov     eax, ecx
   mov     ebx, 0Ch
   cdq
   idiv    ebx
   mov     al, [edi]
   sub     ecx, 11h
   dec     edi
   cmp     ecx, 7
   mov     bl, [edx+esi]
   mov     [edi+1], bl
   mov     [edx+esi], al
   jge     top

Annotated Code

Here, comments have been added explaining each line a little bit. These comments are added in an attempt to understand what the code's doing.

   ; This is actually continued from the last example, so esi contains the verified CDKey. 
   lea     edi, [esi+0Bh]   ; edi is a pointer to the 12th character in the cdkey (0x0b = 11, and arrays start at 0).
   mov     ecx, 0C2h        ; Set ecx to 0xC2. Recall that ecx is often a loop counter. 
top:
   mov     eax, ecx         ; Move the loop counter to eax.
   mov     ebx, 0Ch         ; Set ebx to 0x0C (0x0C = 12, and arrays are indexed from 0, so the CDKey string goes from 0 to 12).
   cdq                      ; Get ready for a signed division.
   idiv    ebx              ; Divide the loop counter by 0x0C. It isn't clear yet whether this is division or modulus, but
                            ; because an accumulator is being divided by the CDKey length, it's logical to assume that 
                            ; this is modular division. edx will likely be used, and eax will likely be overwritten. 

   mov     al, [edi]        ; Move the value that edi points to (which is a character in the CDKey) to al. Recall that al is the
                            ; right-most byte in eax. This confirms two things: that edi points to a character in the CDKey
                            ; (since a character is 1 byte) and that the division above is modulus (because eax is overwritten). 
   sub     ecx, 11h         ; Subtract 0x11 from ecx. Recall that ecx is often a loop counter, and likely is in this case. 
   dec     edi              ; Decrement the pointer into the CDKey. edi started at the 12th character and is moving backwards. 
   cmp     ecx, 7           ; Compare ecx to 7, which confirms that ecx is the loop counter. The jump corresponding to this
                            ; comparison is a few lines below. 
   mov     bl, [edx+esi]    ; Recall that edx is the remainder from the above division, which is (accumulator % 12), and that 
                            ; esi points to the CDKey. So this takes the character corresponding to the accumulator and moves
                            ; it into bl, which is the right-most byte of ebx. 
   mov     [edi+1], bl      ; Overwrite the character that edi pointed to at the top of the loop. Recall that [edi] is moved 
                            ; into al, then decremented above, which is why this is +1 (to offset the decrement). 
   mov     [edx+esi], al    ; Move al into the string corresponding to the modular division. 
   jge     top              ; Jump as long as the ecx counter is greater than or equal to 7
                            ;
                            ; Note that the loop here is simply a swap. edi decrements, starting from the 12th character and
                            ; moving backwards. ecx is reduced by 0x11 each time, and the character at (ecx % 12) is swapped
                            ; with the character pointed at by edi. 

C Code

Please, try this yourself first!

This is an absolutely direct conversion from the annotated assembly to C. I added a main function that sends a bunch of test keys through the function to print out the results.

Now that a driver function can test the CDKey shuffler, the code can be reduced and condensed.

#include <stdio.h>

/* Prototype */
void shuffleCDKey(char *key);

int main(int argc, char *argv[])
{
    /* A series of test cases (I'm using fake keys here obviously, but real ones work even better) */
    char keys[3][14] = { "1212121212121", /* Valid */
                        "1234567890123", /* Valid */
                        "4962883551538" /* Valid */
                   };
    char *results[]  = { "1112222221111",
                        "7130422865193",
                        "3461239588558" };


    int i;

    for(i = 0; i < 3; i++)
    {
        printf("Original:  %s\n", keys[i]);
        shuffleCDKey(keys[i]);
        printf("Shuffled:  %s\n", keys[i]);
        printf("Should be: %s\n\n", results[i]);
    }

    return 0;
}

void shuffleCDKey(char *key)
{
    int  eax,  ebx, ecx, edx;
    char *esi;
    char *edi;

    esi = key;

        //   ; This is actually continued from the last example, so esi contains the verified CDKey.
        //   lea     edi, [esi+0Bh]   ; edi is a pointer to the 12th character in the cdkey (0x0b = 11, and arrays start at 0).
    edi = (esi + 0x0b);
        //   mov     ecx, 0C2h        ; Set ecx to 0xC2. Recall that ecx is often a loop counter.
    ecx = 0xc2;
        //top:
    do
    {
            //   mov     eax, ecx         ; Move the loop counter to eax.
        eax = ecx;
            //   mov     ebx, 0Ch         ; Set ebx to 0x0C (0x0C = 12, an arrays are indexed from 0, so the CDKey string goes from 0 to 12).
        ebx = 0x0c;
            //   cdq                      ; Get ready for a signed division.
            //   idiv    ebx              ; Divide the loop counter by 0x0C. It isn't clear yet whether this is division or modulus, but
            //                            ; because an accumulator is being divided by the CDKey length, it's logical to assume that
            //                            ; this is modular division. edx will likely be used, and eax will likely be overwritten.
        edx = eax % ebx;
            //
            //   mov     al, [edi]        ; Move the value that edi points to (which is a character in the CDKey) to al. Recall that al is the
            //                            ; right-most byte in eax. This confirms two things: that edi points to a character in the CDKey
            //                            ; (since a character is 1 byte) and that the division above is modulus (because eax is overwritten).
        eax = (char) *edi;

            //   sub     ecx, 11h         ; Subtract 0x11 from ecx. Recall that ecx is often a loop counter, and likely is in this case.
        ecx = ecx - 0x11;
            //   dec     edi              ; Decrement the pointer into the CDKey. edi started at the 12th character and is moving backwards.
        edi--;
            //   cmp     ecx, 7           ; Compare ecx to 7, which confirms that ecx is the loop counter. The jump corresponding to this
            //                            ; comparison is a few lines below.
        /* will handle this compare later */
            //   mov     bl, [edx+esi]    ; Recall that edx is the remainder from the above division, which is (accumulator % 12), and that
            //                            ; esi points to the CDKey. So this takes the character corresponding to the accumulator and moves
            //                            ; it into bl, which is the right-most byte of ebx.
        ebx = (char) *(edx + esi);
            //   mov     [edi+1], bl      ; Overwrite the character that edi pointed to at the top of the loop. Recall that [edi] is moved
            //                            ; into al, then decremented above, which is why this is +1 (to offset the decrement).
        *(edi + 1) = (char) ebx;
            //   mov     [edx+esi], al    ; Move al into the string corresponding to the modular division.
        *(edx + esi) = (char) eax;
            //   jge     top              ; Jump as long as the ecx counter is greater than or equal to 7
    }
    while(ecx >= 7);
            //                            ;
            //                            ; Note that the loop here is simply a swap. edi decrements, starting from the 12th character and
            //                            ; moving backwards. ecx is reduced by 0x11 each time, and the character at (ecx % 12) is swapped
            //                            ; with the character pointed at by edi.
            //
}

Cleaned up C Code

Here's the code after removing the assembly, fixing up the variable declarations, and changing hex values to decimal:

void shuffleCDKey(char *key)
{
    char *esi = key;
    char *edi = esi + 11;

    int ecx = 0xc2;
    int  eax,  ebx, edx;

    do
    {
        eax = ecx;
        ebx = 12;
        edx = eax % ebx;
        eax = (char) *edi;
        ecx = ecx - 17;
        edi--;
        ebx = (char) *(edx + esi);
        *(edi + 1) = (char) ebx;
        *(edx + esi) = (char) eax;
    }
    while(ecx >= 7);
}

Reduced C Code

This code works, and can be used. However, for this exercise, reducing it all the way helps.

Some variables are substituted where they aren't necessary:

void shuffleCDKey(char *key)
{
    char *esi = key;
    char *edi = esi + 11;

    int ecx = 0xc2;
    int  eax,  ebx, edx;

    do
    {
        edx = ecx % 12;
        eax = (char) *edi;
        ecx = ecx - 17;
        edi--;
        *(edi + 1) = (char) *(edx + esi);
        *(edx + esi) = (char) eax;
    }
    while(ecx >= 7);
}

Change the do loop to a for loop, rename ecx, and change the decrement of edi to the bottom of the loop:

void shuffleCDKey(char *key)
{
    char *esi = key;
    char *edi = esi + 11;

    int i;
    int  eax,  ebx, edx;

    for(i = 0xc2; i >= 7; i -= 17)
    {
        edx = i % 12;
        eax = (char) *edi;
        *edi = (char) *(edx + esi);
        *(edx + esi) = (char) eax;
        edi--;
    }
}

Replace esi with key, change pointers to arrays, declare eax as a char (to remove typecasting):

void shuffleCDKey(char *key)
{
    char *edi = key + 11;

    int i;
    char eax;
    int  ebx, edx;

    for(i = 0xc2; i >= 7; i -= 17)
    {
        edx = i % 12;
        eax = *edi;
        *edi = key[edx];
        key[edx] = eax;
        edi--;
    }
}

Replacing the variable swap with a swap() function cleans things up significantly:

void swap (char *a, char *b)
{
    char temp = *a;
    *a = *b;
    *b = temp;
}

void shuffleCDKey(char *key)
{
    char *edi = key + 11;

    int i;
    char eax;
    int  ebx, edx;

    for(i = 0xc2; i >= 7; i -= 17)
    {
        edx = i % 12;
        swap(edi, key + edx);
        edi--;
    }
}

Finished Code

Finally, rename some variables, eliminate unused variables, and combine where possible, leaving this slick little function:

void shuffleCDKey(char *key)
{
    char *position = key + 11;
    int i;
    for(i = 194; i >= 7; i -= 17)
        swap(position--, key + (i % 12));
}

And that's it! Testing it with the driver function verifies that it still works.

Questions

Feel free to edit this section and post questions, I'll do my best to answer them. But you may need to contact me to let me know that a question exists.

Category: Assembly Examples