_UNDOCUMENTED CORNER COLUMN_ edited by Andrew Schulman "RINGO: VXDS ON THE FLY" by Alex Shmidt Listing 1 /* CALLGATE.H */ typedef DWORD (FAR PASCAL *GATEPROC)(WORD svc, WORD cnt, DWORD extra); /* SeeYouAtRing0 services */ #define Get386_Svc 0 //get system info #define PhysToLin_Svc Get386_Svc + 1 //map phys to linear #define Register_Hwnd_Svc PhysToLin_Svc + 1 //register HWND #define Unregister_Hwnd_Svc Register_Hwnd_Svc + 1 //unregister HWND #define StopVM_Svc Unregister_Hwnd_Svc + 1 //toggle DOS box exec #define RemapGate_Svc StopVM_Svc + 1 //remap call gate typedef struct { /* call gate procedure parameters */ DWORD G_Dword; // Dword parameter WORD G_Word; // Word parameter WORD G_Svc; // service number }GPARAM; /* RingoInit functions */ #define EXITRINGO 0xFFFF #define INITRINGO 0 Listing 2 /* RINGO.C -- excerpts */ //#define CALLGATE_386 //define CALLGATE_386 to get gates from CALLGATE.386 #include #include "386.h" #include "callgate.h" #ifdef CALLGATE_386 GATEPROC GetFirstCallGateVxD (FARPROC entrypoint,BYTE paramcount); void DestroyInitGateVxD (WORD callgateselector); #endif VOID WINAPI RingoInit(void); VOID WINAPI SeeYouAtRing0(void); VOID WINAPI MakeSureOurSegIsInMemory(void); GATEPROC GetLdtRing0CallGate (FARPROC entrypoint, BYTE paramcount,WORD callgate); GATEPROC GDT_Gate,LDT_Gate; int FAR PASCAL LibMain ( HANDLE hInstance, WORD wDataSeg, WORD cbHeapSize, LPSTR lpszCmdLine ) { FARPROC ri = (FARPROC) RingoInit; if (!(GetWinFlags () & WF_ENHANCED)) /*VxDs exist in enhanced mode only*/ return 0; #ifdef CALLGATE_386 // get the GDT call gate from CALLGATE.386 if (!(LDT_Gate = GetFirstCallGateVxD (ri, sizeof(GPARAM)/4))) #else // get the LDT call gate with INT 2F, AX=168A if (!(LDT_Gate = GetLdtRing0CallGate (ri, sizeof(GPARAM)/4, 0))) #endif return 0; /*** get the main call gate in GDT ***/ GDT_Gate = (GATEPROC)LDT_Gate (INITRINGO, sizeof(GPARAM)/4, (DWORD)SeeYouAtRing0); if (cbHeapSize) UnlockData (0); return (1); } char vendor[] = "MS-DOS"; // Microsoft's signature GATEPROC GetLdtRing0CallGate (FARPROC gproc, BYTE params,WORD gatesel) { #define VENDOR_SPECIFIC_API 0x168a WORD ldt_map; // LDT selector, which maps LDT itself WORD (far * entryp)(void); // entry point to get the above LPCALLGATEDESCRPT CGateDescriptor; // build call gate descriptor with this WORD RW_ldt_map; /* ldt map selector fixes segment read-only problem */ WORD CGateSelector; // to be a call gate selector DWORD initgate_flat; // callgate procedure's linear address _asm { mov si, offset vendor mov ax, VENDOR_SPECIFIC_API int 2fh or al, al jnz no_vendor mov word ptr [entryp], di /* private entry point */ mov word ptr [entryp+2], es mov ax, 100h /* magic number */ } ldt_map = entryp(); /* returns LDT map selector */ _asm jnc vendor_ok no_vendor: return 0; vendor_ok: // When run under SoftICE/W LDT alias returns read_only, give us a good one if (!(RW_ldt_map = AllocSelector(SELECTOROF((void FAR *)&GDT_Gate)))) return 0; SetSelectorBase(RW_ldt_map, GetSelectorBase(ldt_map)); SetSelectorLimit(RW_ldt_map, GetSelectorLimit(ldt_map)); if ((CGateSelector = gatesel) == 0) // we might already have one if (!(CGateSelector = AllocSelector(0))) // Get a selector for the gate { FreeSelector (RW_ldt_map); return 0; } // create a pointer to write into the LDT CGateDescriptor = MAKELP(RW_ldt_map,CGateSelector & SELECTOR_MASK); // build 32-bit ring 3-to-0 call gate #define MK_LIN(x) (GetSelectorBase(SELECTOROF(x)) + (DWORD)OFFSETOF(x)) initgate_flat = MK_LIN(gproc); CGateDescriptor->Offset_O_15 = LOWORD (initgate_flat); CGateDescriptor->Offset_16_31 = HIWORD (initgate_flat); CGateDescriptor->Selector = 0x28; // ring0 flat code seg CGateDescriptor->DWord_Count = params & CALLGATE_DDCOUNT_MASK; CGateDescriptor->Access_Rights = GATE32_RING3; //pres,sys,dpl3,32CallGate FreeSelector (RW_ldt_map); // don't need you any more return ((GATEPROC)MAKELP(CGateSelector,0)); } DWORD WINAPI _export MapPhysToLinear (DWORD physaddr, WORD mapsize) { return (GDT_Gate)(PhysToLin_Svc,mapsize,physaddr); /* DPMI alternative */ } Listing 3 ;;; RINGO.INC -- excerpts GPARAM struc ; parameters G_Dword dd ? G_Word dw ? G_Svc dw ? GPARAM ends CALLGATE_FRAME struc ; stack frame at the time of ring transition CG_pushbp dd ? CG_Old_EIP dd ? ; this is where we came from CG_Old_CS dd ? ; and will get back CG_Params db (type GPARAM) dup (?) ; call gate parameters CG_Old_ESP dd ? ; caller's CG_Old_SS dd ? ; stack CALLGATE_FRAME ends BuildGateStackFrame macro dataseg push ebp mov ebp,esp push gs push ds push es push fs push esi push edi ifidni ,<_DATA> mov ax,ds mov gs,ax ; we'll access our data seg via gs endif mov ax,ss mov ds,ax ; ring 0 flat data delector mov es,ax mov fs,ax ifdifi ,<_DATA> call GetRingoGdtDataSel endif endm ClearGateStackFrame macro cleanup pop edi pop esi pop fs pop es pop ds pop gs pop ebp ret cleanup endm movoffs macro reg,off32 ; run-time fixup mov reg, offset &off32 add reg,gs:[ringo_flat] endm Listing Four ;;; CALLGATE.ASM -- excerpts .386p include vmm.inc include ringo.inc include 386.inc public RingoInit,SeeYouAtRing0,MakeSureOurSegIsInMemory _GATESEG segment dword use32 public 'CODE' assume cs:_GATESEG,gs:_DATA RingoInit proc far BuildGateStackFrame _DATA cmp [ebp].CG_Params.G_Svc,EXITRINGOCALL jnz short @f call RingoExit ; deallocate everything we've got jmp short retini @@: call RelocateRingo ; run-time relocation and fixups jc short init_ret call DynalinkTrick ; get the VxD chain root call InsertRingoDDB ; welcome to the VxD club call CreateRingoGDTGate ; GDT call gate to SeeYouAtRing0 retini: mov edx, eax ; prepare return values for the ring 3 shr edx, 16 ClearGateStackFrame ; clear both ring stack frames RingoInit endp SeeYouAtRing0 proc far ; The callgate service proc BuildGateStackFrame VMMCall Get_Cur_VM_Handle ; always helpful movzx eax, [ebp].CG_Params.G_Svc ; service dispatcher cmp eax,LASTSVC ja @f call gs:Gate_Service_Table[eax*4] @@: mov edx, eax shr edx, 16 ClearGateStackFrame SeeYouAtRing0 endp CreateRingoGDTGate proc movzx edx, word ptr [ebp].CG_Params.G_Dword ; offset16 add edx,gs:[ringo_flat] ; fixup mov ax, cs ; VMM code selector mov cx, [ebp].CG_Params.G_Word ; parameter count and cx, CALLGATE_DDCOUNT_MASK ; make sure it's a reasonable number or cx, GATE32_RING3 ; call gate type call BuildCallGateDWords VMMCall _Allocate_GDT_Selector, ; undocumented flag ror eax,16 ret CreateRingoGDTGate endp BeginProc DestroyGDTCallGate,public movzx eax,[ebp].CG_Params.G_Word VMMCall _Free_GDT_Selector, ret EndProc DestroyGDTCallGate BuildCallGateDWords proc movzx eax, ax shl eax, 16 ; selector mov ax, dx ; offset 0-15 mov dx, cx ; offset 16-31 + type + count ret BuildCallGateDWords endp ;**************************************************************************** ; To get the VxD Base (VMM DDB ptr) we're using the undocumented fact that ; VMM's dynalink handler (considered a 'fault' 20h in DDK spec parlance) ; returns it in ecx. The idea is to hook VMM fault 20h, call any VMM service ; to get our fault handler receive control, call VMM's dynalink directly, ; store ecx in a static variable, and hook fault 20h again, this time ; with fault handlers reversed. ;**************************************************************************** BeginProc DynalinkTrick mov esi, gs:[OurDynalinkHandler] twice: mov eax, 20h VMMCall Hook_VMM_Fault ; install our handler mov gs:[OLD_DYNALINK_HANDLER], esi VMMCall Get_VMM_Version ; need one call get it executed cmp esi, gs:[OurDynalinkHandler] jnz twice mov eax, gs:[VXD_FIRST] ret EndProc DynalinkTrick Ringo_Dynalink_Handler proc call gs:[OLD_DYNALINK_HANDLER] mov gs:[VXD_FIRST], ecx ; DDB pointer ret Ringo_Dynalink_Handler endp PhysToLin proc ; physical to linear address mapping movzx ecx, [ebp].CG_Params.G_Word VMMcall _MapPhysToLinear,<[ebp].CG_Params.G_Dword,ecx,0> ret PhysToLin endp ringo_flat dd 0 ; run-time space base OLD_DYNALINK_HANDLER dd 0 VXD_FIRST dd 0 ; VxD chain root OurDynalinkHandler dd offset Ringo_Dynalink_Handler Ringo_DDB VxD_Desc_Block <,,,1,0,,'Ringo ',,offset RingoControlProc,,,,,,,> Gate_Service_Table label dword dd offset Get386 dd offset PhysToLin dd offset RegisterHWND dd offset UnregisterHWND dd offset StopVM dd offset RemapCallGate _GATESEG ends end