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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.