Wednesday, January 7, 2015

Practical Reverse Engineering p. 35 #8

Question number 8 on page 35 of Practical Reverse Engineering is as follows:

Sample H. Decompile sub_11732 and explain the most likely programming construct used in the original code.

The function's disassembly looks like the following:

sub_1172E:
    push    esi
    mov     esi, [esp+8]
    dec     esi
    jz      short loc_1175F
    dec     esi
    jz      short loc_11755
    dec     esi
    jz      short loc_1174B
    sub     esi, 9
    jnz     short loc_1176B
    mov     esi, [eax+8]
    shr     esi, 1
    add     eax, 0Ch
    jmp     short loc_11767 

loc_1174B:                             
    mov     esi, [eax+3Ch]
    shr     esi, 1
    add     eax, 5Eh
    jmp     short loc_11767 

loc_11755:                            
    mov     esi, [eax+3Ch]
    shr     esi, 1
    add     eax, 44h
    jmp     short loc_11767 

loc_1175F:                          
    mov     esi, [eax+3Ch]
    shr     esi, 1
    add     eax, 40h

loc_11767:                         
                        
    mov     [ecx], esi
    mov     [edx], eax

loc_1176B:                              
    pop     esi
    retn    4 

The function contains a switch. The calling convention is odd for x86 as there are three variables passed in a register, which means it was probably compiled as a static unit. There is a lot of repeated code, which means either the programmer or the compiler couldn't optimize the size in a logical way. Here is a more natural way to write the function:

static struct *sub_1172E(struct *arg1, struct *arg2, 
                         struct *arg3, int unknown_enum)
{
    DWORD dwUnknown;

    /* mov esi, [esp+8] */
    switch (unknown_enum)
    {
        case 1:                 /* dec esi */
            arg1 += 64;         /* add eax, 40h */
        break;

        case 2:                 /* dec esi */
            arg1 += 68          /* add eax, 44h */
        break;

        case 3:                 /* dec esi */
            arg1 += 94;         /* add eax, 5Eh */
        break;

        case 12:                /* sub esi, 9 */
            arg1 += 12;         /* add eax, 0Ch */
        break;

        default:
            return arg1;
    }
     
    dwUnknown = (unknown_enum == 12) ? 
            arg1->Unknown0x8 :  /* mov esi, [eax+8] */
            arg1->Unknown0x3c;  /* mov esi, [eax+3Ch] */

    dwUnknown /= 2;             /* shr esi, 1 */

    *arg2 = dwUnknown;          /* mov [ecx], esi */
    *arg3 = arg1;               /* mov [edx], eax */

    return arg1;
}

We can infer some of the struct in the first argument by where offsets were accessed:

struct arg1 {
    BYTE Unknown0x0[0x8];   /* 0x0 */
    DWORD bigTwiceVal;      /* 0x8 */
    BYTE Unknown0xc[0x30];  /* 0xc */
    DWORD smallTwiceVal;    /* 0x3c */
};

No comments :

Post a Comment