2

What is a way to lookup a COM method offset with an image, just based on the module name and method name.

For example want to find "Exec" method in "WScript.Shell" In this scenario we know the method is in wshom.ocx. In this case it is relatively easy to find with the public symbols.

We can find the vtable :

.text:7B471070 ; const CWshShell::`vftable'{for `IWshShell3'} .text:7B471070 ??_7CWshShell@@6BIWshShell3@@@ dd offset ?QueryInterface@CWshExec@@UAGJABU_GUID@@PAPAX@Z .text:7B471070 ; DATA XREF: CWshShell::Create(IUnknown *)+74↓o .text:7B471070 ; CWshShell::`scalar deleting destructor'(uint)+B↓o .text:7B471070 ; CWshExec::QueryInterface(_GUID const &,void * *) .text:7B471074 dd offset ?AddRef@CWebPreviewDispatch@@EAGKXZ ; CWebPreviewDispatch::AddRef(void) .text:7B471078 dd offset ?Release@?$CAggregatedUnknown@$02@@UAGKXZ ; CAggregatedUnknown<3>::Release(void) .text:7B47107C dd offset ?GetTypeInfoCount@CWshExec@@UAGJPAI@Z ; CWshExec::GetTypeInfoCount(uint *) .text:7B471080 dd offset ?GetTypeInfo@CWshShell@@UAGJIKPAPAUITypeInfo@@@Z ; CWshShell::GetTypeInfo(uint,ulong,ITypeInfo * *) .text:7B471084 dd offset ?GetIDsOfNames@CWshShell@@UAGJABU_GUID@@PAPAGIKPAJ@Z ; CWshShell::GetIDsOfNames(_GUID const &,ushort * *,uint,ulong,long *) .text:7B471088 dd offset ?Invoke@CWshShell@@UAGJJABU_GUID@@KGPAUtagDISPPARAMS@@PAUtagVARIANT@@PAUtagEXCEPINFO@@PAI@Z ; CWshShell::Invoke(long,_GUID const &,ulong,ushort,tagDISPPARAMS *,tagVARIANT *,tagEXCEPINFO *,uint *) .text:7B47108C dd offset ?get_SpecialFolders@CWshShell@@UAGJPAPAUIWshCollection@@@Z ; CWshShell::get_SpecialFolders(IWshCollection * *) .text:7B471090 dd offset ?get_Environment@CWshShell@@UAGJPAUtagVARIANT@@PAPAUIWshEnvironment@@@Z ; CWshShell::get_Environment(tagVARIANT *,IWshEnvironment * *) .text:7B471094 dd offset ?Run@CWshShell@@UAGJPAGPAUtagVARIANT@@1PAH@Z ; CWshShell::Run(ushort *,tagVARIANT *,tagVARIANT *,int *) .text:7B471098 dd offset ?Popup@CWshShell@@UAGJPAGPAUtagVARIANT@@11PAH@Z ; CWshShell::Popup(ushort *,tagVARIANT *,tagVARIANT *,tagVARIANT *,int *) .text:7B47109C dd offset ?CreateShortcut@CWshShell@@UAGJPAGPAPAUIDispatch@@@Z ; CWshShell::CreateShortcut(ushort *,IDispatch * *) .text:7B4710A0 dd offset ?ExpandEnvironmentStringsA@CWshShell@@UAGJPAGPAPAG@Z ; CWshShell::ExpandEnvironmentStringsA(ushort *,ushort * *) .text:7B4710A4 dd offset ?RegRead@CWshShell@@UAGJPAGPAUtagVARIANT@@@Z ; CWshShell::RegRead(ushort *,tagVARIANT *) .text:7B4710A8 dd offset ?RegWrite@CWshShell@@UAGJPAGPAUtagVARIANT@@1@Z ; CWshShell::RegWrite(ushort *,tagVARIANT *,tagVARIANT *) .text:7B4710AC dd offset ?RegDelete@CWshShell@@UAGJPAG@Z ; CWshShell::RegDelete(ushort *) .text:7B4710B0 dd offset ?LogEvent@CWshShell@@UAGJPAUtagVARIANT@@PAG1PAF@Z ; CWshShell::LogEvent(tagVARIANT *,ushort *,ushort *,short *) .text:7B4710B4 dd offset ?AppActivate@CWshShell@@UAGJPAUtagVARIANT@@0PAF@Z ; CWshShell::AppActivate(tagVARIANT *,tagVARIANT *,short *) .text:7B4710B8 dd offset ?SendKeys@CWshShell@@UAGJPAGPAUtagVARIANT@@@Z ; CWshShell::SendKeys(ushort *,tagVARIANT *) .text:7B4710BC dd offset ?Exec@CWshShell@@UAGJPAGPAPAUIWshExec@@@Z ; CWshShell::Exec(ushort *,IWshExec * *) .text:7B4710C0 dd offset ?get_CurrentDirectory@CWshShell@@UAGJPAPAG@Z ; CWshShell::get_CurrentDirectory(ushort * *) .text:7B4710C4 dd offset ?put_CurrentDirectory@CWshShell@@UAGJPAG@Z ; CWshShell::put_CurrentDirectory(ushort *) 

In this case offset at 7B4710BC points to the method I'm looking into. However I'm trying to more generally resolve this when there are no symbols available.

Analyzing the following with WinDbg:

"C:\WINDOWS\syswow64\WindowsPowerShell\v1.0\powershell.exe" -Command "$obj = New-Object -ComObject WScript.Shell;$obj.Exec('notepad')" 

Found that C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35\System.Management.Automation.dll has a method System.Management.Automation.ComInterop.ComTypeDesc.TryGetFunc(System.String, System.Management.Automation.ComInterop.ComMethodDesc ByRef)

Which is taking "Exec" as the first parameter, and seems to internally use a hash table to find the method, but couldn't see how the hash table is initially populated.

Eventually the function is called via Oleaut32!DispCallFunc which seems to have a function definition like:

HRESULT __stdcall DispCallFunc( void *pvInstance, ULONG_PTR oVft, CALLCONV cc, VARTYPE vtReturn, UINT cActuals, VARTYPE *prgvt, VARIANTARG **prgpvarg, VARIANT *pvargResult) 

In this case pvInstance is pointing to the vfTable in the COM object, and oVft is the offset for the specific target method.

Essentially I am trying to work out how do I calculate the vftable offset for "WScript.Shell" (with the module name already known)(, and oVft offset for method i.e. "Exec", even without symbols for a COM method.

1

1 Answer 1

2

In your example, the Exec method is actually implemented in IWshShell3 as can be seen in the typelib:

[Guid("41904400-be18-11d3-a28b-00104bd35090")] interface IWshShell3 { /* Methods */ int Run(string Command, [Optional] Object& WindowStyle, [Optional] Object& WaitOnReturn); int Popup(string Text, [Optional] Object& SecondsToWait, [Optional] Object& Title, [Optional] Object& Type); object CreateShortcut(string PathLink); string ExpandEnvironmentStrings(string Src); object RegRead(string Name); void RegWrite(string Name, Object& Value, [Optional] Object& Type); void RegDelete(string Name); bool LogEvent(Object& Type, string Message, [Optional] string Target); bool AppActivate(Object& App, [Optional] Object& Wait); void SendKeys(string Keys, [Optional] Object& Wait); WshExec Exec(string Command); /* Properties */ IWshCollection SpecialFolders { get; } IWshEnvironment Environment([Optional] Object& Type) { get; } string CurrentDirectory { get; set; } } 

With the following quick test code I was able to get an offset for the methods:

procedure Main; const Filename: String = 'C:\Windows\SysWOW64\wshom.ocx'; MethodName: PChar = 'Exec'; CLSID_WshShell3: TGuid = '{41904400-BE18-11D3-A28B-00104BD35090}'; var tlib: ITypeLib; hr: HRESULT; i: Integer; tinfo: ITypeInfo; typeattrib: PTypeAttr; j: Integer; FuncDesc: PFuncDesc; Name: WideString; OffSet: DWORD; begin CoInitialize(nil); OleCheck(LoadTypeLibEx(PChar(Filename), REGKIND_NONE, tlib)); for i := 0 to tlib.GetTypeInfoCount-1 do begin hr := tlib.GetTypeInfo(i, tinfo); if Succeeded(hr) then begin hr := tinfo.GetTypeAttr(typeattrib); if Succeeded(hr) then begin if IsEqualGuid(CLSID_WshShell3, typeattrib.guid) then begin WriteLn('Found IWshShell3'); for j := 0 to typeattrib.cFuncs-1 do begin hr := tinfo.GetFuncDesc(j, FuncDesc); if Succeeded(hr) then begin hr := tinfo.GetDocumentation(FuncDesc.memid, @Name, nil, nil, nil); if Succeeded(hr) then begin OffSet := j * SizeOf(Pointer); WriteLn(Format('Found Method %s at Offset: %d', [Name, Offset])); end; tinfo.ReleaseFuncDesc(FuncDesc); end; end; end; tinfo.ReleaseTypeAttr(typeattrib); end; end; end; end; 

Output:

Found IWshShell3 Found Method QueryInterface at Offset: 0 Found Method AddRef at Offset: 4 Found Method Release at Offset: 8 Found Method GetTypeInfoCount at Offset: 12 Found Method GetTypeInfo at Offset: 16 Found Method GetIDsOfNames at Offset: 20 Found Method Invoke at Offset: 24 Found Method SpecialFolders at Offset: 28 Found Method Environment at Offset: 32 Found Method Run at Offset: 36 Found Method Popup at Offset: 40 Found Method CreateShortcut at Offset: 44 Found Method ExpandEnvironmentStrings at Offset: 48 Found Method RegRead at Offset: 52 Found Method RegWrite at Offset: 56 Found Method RegDelete at Offset: 60 Found Method LogEvent at Offset: 64 Found Method AppActivate at Offset: 68 Found Method SendKeys at Offset: 72 Found Method Exec at Offset: 76 Found Method CurrentDirectory at Offset: 80 Found Method CurrentDirectory at Offset: 84 

Probably need to do more checking but hopefully this gives you a start and of course, a typelib is needed. The Offset is the offset from the Interface pointer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.