Search code examples
chookreverse-engineeringdetoursusercall

Hooking usercall function?


I have a virtual machine, which on VM_Create passes the address of a function (systemCalls) to the virtual machine.

So I hook VM_Create and steal the syscalls address, put it into a backup function pointer, and the address of my modified systemCalls function pass to the original VM_Create, from which I can alter arguments, add or remove calls, and then call the backed-up syscalls function. That worked well, until a new release of the game.

I believe to have found the problem:

This is the beginning of the unmodified systemCalls function:

intptr_t CL_CgameSystemCalls(intptr_t *args) {

    switch (args[0]) {

        case CG_PRINT:
            Com_Printf( "%s", (const char*)VMA(1));
            return 0;

        case CG_ERROR:
            Com_Error(ERR_DROP, "%s", (const char*)VMA(1));
            return 0;

This is my modifed syscall function:

intptr_t modified_CL_CgameSystemCalls (intptr_t *args)
{
    switch (*args)
    {
        case CG_GETSNAPSHOT:

            mysnap = mysnap ;
            mynextSnap = (snapshot_t*) (CG_QVM2NATIVE(args[2]));
            mysnap = mynextSnap;

            retval = original_CL_CgameSystemCalls(args);
            break ;

The problem is calling the original function from the modified one:

    intptr_t modified_CL_CgameSystemCalls(intptr_t *args)
    {
        retval = original_CL_CgameSystemCalls(args);
        return retval;
    }

already fails.

As you can see from the pseudocode of the disassembly, the new definition of CL_CgameSystemCalls seems to be:

char __usercall sub_4017B0<al>(int a1<ebx>, int a2)

Which means they changed the function by adding the __usercall attribute and by putting the first argument into register ebx, if I interpret the decompilation right.

Now my question:

How can I retrieve *args (args[0]) into a variable?

And how can I call the unmodified function from the modified one, which now uses __usercall?

This is the disassembly of systemcalls with usercall:

.text:004017B0 ; =============== S U B R O U T I N E =======================================
.text:004017B0
.text:004017B0
.text:004017B0 sub_4017B0      proc near               ; DATA XREF: sub_402670+5Co
.text:004017B0
.text:004017B0 var_18          = dword ptr -18h
.text:004017B0 var_4           = dword ptr -4
.text:004017B0 arg_0           = dword ptr  4
.text:004017B0
.text:004017B0 ; FUNCTION CHUNK AT .text:00401430 SIZE 00000026 BYTES
.text:004017B0 ; FUNCTION CHUNK AT .text:00401459 SIZE 00000013 BYTES
.text:004017B0 ; FUNCTION CHUNK AT .text:00410E90 SIZE 00000006 BYTES
.text:004017B0 ; FUNCTION CHUNK AT .text:00412AC0 SIZE 00000006 BYTES
.text:004017B0
.text:004017B0                 push    esi
.text:004017B1                 mov     esi, [esp+0Ch+var_4]
.text:004017B5                 mov     eax, [esi]
.text:004017B7                 cmp     eax, 73h        ; switch 116 cases
.text:004017BA                 push    edi
.text:004017BB                 ja      loc_402486      ; default
.text:004017BB                                         ; jumptable 004017C1 cases 21,90-99,109,110
.text:004017C1                 jmp     ds:off_40249C[eax*4] ; switch jump
.text:004017C8
.text:004017C8 loc_4017C8:                             ; DATA XREF: .text:off_40249Co
.text:004017C8                 mov     eax, [esi+4]    ; jumptable 004017C1 case 0
.text:004017CB                 push    eax
.text:004017CC                 call    VM_ArgPtr
.text:004017D1                 push    eax             ; char
.text:004017D2                 push    offset aS_5     ; "%s"
.text:004017D7                 call    Com_Printf
.text:004017DC                 add     esp, 0Ch
.text:004017DF                 pop     edi
.text:004017E0                 xor     eax, eax
.text:004017E2                 pop     esi
.text:004017E3                 retn
.text:004017E4 ; ---------------------------------------------------------------------------
.text:004017E4
.text:004017E4 loc_4017E4:                             ; CODE XREF: sub_4017B0+11j
.text:004017E4                                         ; DATA XREF: .text:off_40249Co
.text:004017E4                 mov     ecx, [esi+4]    ; jumptable 004017C1 case 1
.text:004017E7                 push    ecx
.text:004017E8                 call    VM_ArgPtr
.text:004017ED                 push    eax             ; char
.text:004017EE                 push    offset aS_5     ; "%s"
.text:004017F3                 push    1               ; int
.text:004017F5                 call    Com_Error
.text:004017F5 ; --------------------------------------------------------------

And this is the pseudocode created from the hexrays decompiler:

char __usercall sub_4017B0<al>(int a1<ebx>, int a2)
{
    int v2;           // ST34_4@1
    char result;      // al@2
    int v4;           // ST34_4@2
    int v5;           // eax@2
    int v6;           // ST34_4@3
    int v7;           // eax@3
    int v8;           // ST34_4@5
    int v9;           // ST24_4@5
    int v10;          // ST20_4@5
    int v11;          // ST1C_4@5
    int v12;          // eax@5
    int v13;          // ST34_4@6
    int v14;          // eax@6
    int v15;          // ST34_4@7
    int v16;          // ST24_4@7
    int v17;          // eax@7
    int v18;          // ST34_4@8
    signed int v19;   // ST24_4@8
    int v20;          // ST20_4@8
    int v21;          // eax@8
    int v22;          // ST34_4@10
    signed int v23;   // ST24_4@10
    int v24;          // eax@10
    int v25;          // ST34_4@11
    signed int v26;   // ST24_4@11
    int v27;          // eax@11
    int v28;          // ST34_4@12
    int v29;          // ST24_4@12
    int v30;          // ST20_4@12
    int v31;          // eax@12
    int v32;          // ST34_4@13
    int v33;          // ST24_4@13
    int v34;          // ST20_4@13
    int v35;          // eax@13
    int v36;          // ST34_4@14
    int v37;          // ST24_4@14
    size_t v38;       // ST20_4@14
    int v39;          // eax@14
    int v40;          // ST34_4@15
    int v41;          // ST34_4@16
    int v42;          // ST34_4@17
    int v43;          // eax@17
    int v44;          // ST34_4@18
    int v45;          // eax@18
    int v46;          // ST34_4@19
    int v47;          // eax@19
    int v48;          // ST34_4@20
    int v49;          // eax@20
    int v50;          // ST34_4@22
    int v51;          // eax@22
    void *v52;        // ecx@22
    int v53;          // ST34_4@24
    int v54;          // ST20_4@24
    int v55;          // eax@24
    int v56;          // ST34_4@25
    int v57;          // ST20_4@25
    int v58;          // eax@25
    int v59;          // ST34_4@26
    signed int v60;   // ST24_4@26
    int v61;          // eax@26
    int v62;          // ST34_4@27
    int v63;          // ST24_4@27
    int v64;          // ST20_4@27
    signed int v65;   // ST1C_4@27
    int v66;          // eax@27
    int v67;          // ST34_4@28
    int v68;          // eax@29
    int v69;          // ST34_4@30
    int v70;          // ST34_4@32
    int v71;          // ST20_4@32
    int v72;          // ST1C_4@32
    int v73;          // ST18_4@32
    signed int v74;   // ST14_4@32
    int v75;          // ST10_4@32
    int v76;          // ST0C_4@32
    int v77;          // ST08_4@32
    int v78;          // ST04_4@32
    int v79;          // eax@32
    int v80;          // ST34_4@34
    int v81;          // ST24_4@34
    int v82;          // ST20_4@34
    int v83;          // ST1C_4@34
    int v84;          // ST18_4@34
    int v85;          // ST14_4@34
    int v86;          // eax@34
    int v87;          // ST34_4@35
    int v88;          // ST24_4@35
    int v89;          // ST20_4@35
    int v90;          // ST1C_4@35
    int v91;          // eax@35
    int v92;          // ST34_4@36
    int v93;          // ST34_4@37
    int v94;          // ST34_4@38
    int v95;          // ST24_4@38
    int v96;          // ST20_4@38
    int v97;          // eax@38
    int v98;          // ST34_4@39
    int v99;          // ST24_4@39
    int v100;         // ST20_4@39
    int v101;         // eax@39
    int v102;         // ST34_4@40
    int v103;         // ST34_4@41
    int v104;         // eax@41
    int v105;         // ST34_4@42
    int v106;         // ST20_4@42
    int v107;         // eax@42
    int v108;         // ST34_4@43
    int v109;         // eax@43
    int v110;         // ST34_4@44
    int v111;         // ST24_4@44
    int v112;         // eax@44
    int v113;         // ST34_4@45
    int v114;         // eax@45
    int v115;         // ST34_4@46
    int v116;         // eax@46
    int v117;         // ST34_4@47
    int v118;         // ST34_4@48
    int v119;         // ST34_4@49
    int v120;         // ST34_4@50
    int v121;         // ST24_4@50
    int v122;         // ST20_4@50
    int v123;         // eax@50
    int v124;         // ST34_4@52
    int v125;         // eax@52
    int v126;         // ST34_4@53
    int v127;         // eax@53
    int v128;         // ST34_4@54
    int v129;         // ST24_4@54
    int v130;         // eax@54
    int v131;         // ST34_4@55
    int v132;         // ST24_4@55
    int v133;         // ST20_4@55
    int v134;         // ST1C_4@55
    int v135;         // eax@55
    int v136;         // ST34_4@56
    float v137;       // ST24_4@56
    float v138;       // ST20_4@56
    float v139;       // ST1C_4@56
    float v140;       // ST18_4@56
    int v141;         // eax@56
    int v142;         // ST34_4@57
    float v143;       // ST24_4@57
    float v144;       // ST20_4@57
    float v145;       // ST1C_4@57
    float v146;       // ST18_4@57
    int v147;         // eax@57
    int v148;         // ST34_4@58
    int v149;         // eax@58
    int v150;         // ST34_4@59
    int v151;         // eax@59
    int v152;         // ST34_4@60
    int v153;         // ST34_4@61
    int v154;         // ST24_4@61
    int v155;         // eax@61
    int v156;         // ST34_4@62
    int v157;         // ST24_4@62
    float v158;       // ST20_4@62
    int v159;         // ST1C_4@62
    int v160;         // ST18_4@62
    int v161;         // ST14_4@62
    int v162;         // eax@62
    int v163;         // ST34_4@63
    int v164;         // eax@63
    int v165;         // ST34_4@64
    int v166;         // eax@64
    int v167;         // ST34_4@65
    int v168;         // edi@65
    int v169;         // ST34_4@66
    int v170;         // eax@66
    int v171;         // ST34_4@68
    int v172;         // eax@68
    int v173;         // edx@69
    int v174;         // ST34_4@69
    float v175;       // ST34_4@69
    int v176;         // ST34_4@72
    int v177;         // ST34_4@73
    int v178;         // ST34_4@74
    unsigned int v179;// ST24_4@74
    int v180;         // ST20_4@74
    int v181;         // eax@74
    int v182;         // ST34_4@75
    signed int v183;  // ST24_4@75
    int v184;         // ST20_4@75
    int v185;         // eax@75
    int v186;         // ST34_4@76
    size_t v187;      // ST24_4@76
    int v188;         // ST20_4@76
    int v189;         // eax@76
    int v190;         // ST34_4@77
    float v191;       // ST34_4@77
    int v192;         // ST34_4@78
    float v193;       // ST34_4@78
    double v194;      // st7@79
    int v195;         // ST34_4@79
    float v196;       // ST34_4@79
    int v197;         // ST34_4@80
    float v198;       // ST34_4@80
    int v199;         // ST34_4@81
    float v200;       // ST34_4@81
    int v201;         // ST34_4@82
    float v202;       // ST34_4@82
    int v203;         // ST34_4@83
    float v204;       // ST34_4@83
    int v205;         // ST34_4@84
    int v206;         // edi@84
    int v207;         // eax@84
    int v208;         // ST34_4@85
    int v209;         // edi@85
    int v210;         // eax@85
    int v211;         // ST34_4@87
    int v212;         // edi@87
    int v213;         // eax@87
    int v214;         // ST34_4@88
    int v215;         // edi@88
    int v216;         // ST24_4@88
    int v217;         // eax@88
    int v218;         // ST34_4@90
    int v219;         // ST34_4@91
    int v220;         // eax@91
    int v221;         // ST34_4@92
    unsigned int v222;// ST24_4@92
    int v223;         // ST20_4@92
    int v224;         // ST1C_4@92
    int v225;         // ST18_4@92
    int v226;         // ST14_4@92
    int v227;         // eax@92
    int v228;         // ST34_4@95
    int v229;         // ST34_4@96
    int v230;         // ST34_4@97
    int v231;         // ST24_4@97
    int v232;         // ST20_4@97
    int v233;         // eax@97
    int v234;         // ST34_4@98
    int v235;         // ST24_4@98
    int v236;         // eax@98
    int v237;         // ST34_4@99
    int v238;         // ST24_4@99
    int v239;         // eax@99
    int v240;         // ST34_4@100
    int v241;         // eax@100
    int v242;         // ST34_4@101
    int v243;         // eax@101
    int v244;         // ST34_4@102
    int v245;         // eax@102
    int v246;         // ST34_4@103
    int v247;         // ST20_4@103
    int v248;         // eax@103
    int v249;         // ST34_4@104
    int v250;         // [sp-18h] [bp-30h]@28
    int v251;         // [sp-14h] [bp-2Ch]@28
    int v252;         // [sp-10h] [bp-28h]@28
    int v253;         // [sp-Ch] [bp-24h]@28
    int v254;         // [sp-8h] [bp-20h]@28
    signed int v255;  // [sp-4h] [bp-1Ch]@28
    int v256;         // [sp+0h] [bp-18h]@28
    int v257;         // [sp+4h] [bp-14h]@28
    int v258;         // [sp+4h] [bp-14h]@31

    switch (*(_DWORD *)v2)
    {
        case 0:
            v5 = VM_ArgPtr(*(_DWORD *)(v4 + 4));
            Com_Printf("%s", v5);
            return 0;

        case 1:
            v7 = VM_ArgPtr(*(_DWORD *)(v6 + 4));
            Com_Error(1, "%s", v7);
            return result;

        case 2:
            return sub_447700();

        case 3:
            v9 = *(_DWORD *)(v8 + 16);
            v10 = VM_ArgPtr(*(_DWORD *)(v8 + 12));
            v11 = VM_ArgPtr(*(_DWORD *)(v8 + 8));
            v12 = VM_ArgPtr(*(_DWORD *)(v8 + 4));
            sub_4213C0(v12, (const char *)v11, v10, v9);
            return 0;

        case 4:
         ...

        default:
            Com_Error(1, "Bad cgame system trap: %i", *(_DWORD *)v249);
            return result;
    }
    return result;
}

You can find the full (last known official) source of the CL_CgameSystemCalls function here (too much to copy-paste):

http://ioqsrc.vampireducks.com/da/d3b/cl__cgame_8c-source.html

And this here is the disassembly of the old version, in which calling orig_syscall from modified syscall worked:

.text:00402B40 ; =============== S U B R O U T I N E =======================================
.text:00402B40
.text:00402B40
.text:00402B40 sub_402B40      proc near               ; CODE XREF: sub_40B380+Bp
.text:00402B40                                         ; sub_40E3B0+Bp ...
.text:00402B40                 mov     edx, dword_BBE104
.text:00402B46                 mov     eax, dword_CB60EC
.text:00402B4B                 and     edx, 0FFFFFFF7h
.text:00402B4E                 test    eax, eax
.text:00402B50                 mov     dword_BBE104, edx
.text:00402B56                 mov     dword_BBE21C, 0
.text:00402B60                 jz      short locret_402B82
.text:00402B62                 push    1
.text:00402B64                 push    eax
.text:00402B65                 call    sub_43E360
.text:00402B6A                 mov     eax, dword_CB60EC
.text:00402B6F                 push    eax
.text:00402B70                 call    sub_43E270
.text:00402B75                 add     esp, 0Ch
.text:00402B78                 mov     dword_CB60EC, 0
.text:00402B82
.text:00402B82 locret_402B82:                          ; CODE XREF: sub_402B40+20j
.text:00402B82                 retn
.text:00402B82 sub_402B40      endp
.text:00402B82
.text:00402B82 ; ---------------------------------------------------------------------------
.text:00402B83                 align 10h
.text:00402B90
.text:00402B90 loc_402B90:                             ; DATA XREF: sub_403AA0+5Co
.text:00402B90                 push    ecx
.text:00402B91                 push    ebx
.text:00402B92                 push    esi
.text:00402B93                 push    edi
.text:00402B94                 mov     edi, [esp+14h]
.text:00402B98                 mov     eax, [edi]
.text:00402B9A                 cmp     eax, 6Fh        ; switch 112 cases
.text:00402B9D                 ja      loc_4038C7      ; default
.text:00402B9D                                         ; jumptable 00402BA3 cases 21,90-99,109,110
.text:00402BA3                 jmp     ds:off_4038E0[eax*4] ; switch jump
.text:00402BAA
.text:00402BAA loc_402BAA:                             ; DATA XREF: .text:off_4038E0o
.text:00402BAA                 mov     eax, [edi+4]    ; jumptable 00402BA3 case 0
.text:00402BAD                 push    eax
.text:00402BAE                 call    sub_43E300
.text:00402BB3                 push    eax
.text:00402BB4                 push    offset aS_7     ; "%s"
.text:00402BB9                 call    sub_41BB90
.text:00402BBE                 add     esp, 0Ch
.text:00402BC1                 pop     edi
.text:00402BC2                 pop     esi
.text:00402BC3                 xor     eax, eax
.text:00402BC5                 pop     ebx
.text:00402BC6                 pop     ecx
.text:00402BC7                 retn
.text:00402BC8 ; ---------------------------------------------------------------------------
.text:00402BC8
.text:00402BC8 loc_402BC8:                             ; CODE XREF: .text:00402BA3j
.text:00402BC8                                         ; DATA XREF: .text:off_4038E0o
.text:00402BC8                 mov     ecx, [edi+4]    ; jumptable 00402BA3 case 1
.text:00402BCB                 push    ecx
.text:00402BCC                 call    sub_43E300
.text:00402BD1                 push    eax
.text:00402BD2                 push    offset aS_7     ; "%s"
.text:00402BD7                 push    1
.text:00402BD9                 call    sub_41D850
.text:00402BDE ; ---------------------------------------------------------------------------

Solution

  • There is no such thing as __usercall. Hex-rays uses this to represent a non-standard calling convention. This can happen in three ways:

    1. The programmer used assembly
    2. The programmer is using Open Watcom's #pragma aux to define a custom convention
    3. The programmer is using Visual C++ 7+, which optimizes register allocation to prevent thrashing

    In cases like this, there are only two ways to go, use assembly or use Open Watcom (which just got an update after years of abandonment). Inline assembly works best IMO, but you'll need a wrapper going in (to call the original) and a wrapper coming out (to hook the original).