Search code examples
winapinsis

Getting info from struct after calling system plugin


I am trying to get some info from windows via the System plugin and the netapi32 library.

I try to call NetWkstaGetInfo() after allocating a struct suitable as a WKSTA_INFO_100.

The NetWkstaGetInfo() prototype states in MSDN:

NET_API_STATUS NetWkstaGetInfo(
  _In_   LPWSTR servername,
  _In_   DWORD level,
  _Out_  LPBYTE *bufptr
);

While the WKSTA_INFO_100 is

typedef struct _WKSTA_INFO_100 {
  DWORD wki100_platform_id;
  LMSTR wki100_computername;
  LMSTR wki100_langroup;
  DWORD wki100_ver_major;
  DWORD wki100_ver_minor;
} WKSTA_INFO_100, *PWKSTA_INFO_100, *LPWKSTA_INFO_100;

For a preliminary test, I try to display the struct members in a messagebox. I first initialize the struct with dummy info to check if the api call replaces the content of my allocated block.

But until now I got barely nothing after the first struct member, I suppose that the struct is not correctly defined, or that I have a struct alignment issue. Unfortunately the weird documentation of the System plugin is driving me nuts does not help me much.

Here is my test script:

outfile "hello.exe"

section

System::Call /NOUNLOAD "*(*i11,t 'some',t 'thing',i22,i44)i .r0"
Dumpstate::debug
System::Call /NOUNLOAD "netapi32::NetWkstaGetInfo(i0, i100, i r0) i.r6"
Dumpstate::debug
System::Call /NOUNLOAD "*$0(*i.r1, t.r2, t.r3, i.r4, i.r5)"
Dumpstate::debug
messagebox MB_OK "Hello, to $2 $3 domain (win $1 - $4.$5) !"
System::Free $0

sectionEnd

The first retrieved value (500) is correct. But the other members keep their initial value. What I am missing?

(Edit) Corollary questions:

  • it seems that following the documentation and MSDN, the first member of the struct should be i and not *i but I did not managed to get a correct returned value without the * (the Dumpstate plugin tends to show it is returned as an address)
  • is the /NOUNLOAD parameter for the plugin mandatory? I have found several examples with it but did not find a precise reason for it. I feared that the allocated struct could have been freed prematurely without the parameter. Could you confirm / infirm?

Solution

  • bufptr is out only so passing r0 as input to NetWkstaGetInfo is pointless, the function does not require input data. You should not use *i with NetApiBufferFree, i alone is enough ($0 already has the address, you don't want the system plugin to play with the pointer, just pass it straight to the API)

    !include LogicLib.nsh
    
    System::Call "netapi32::NetWkstaGetInfo(i0, i100, *i 0 r0) i.r1"
    ${If} 0 = $1
        System::Call "*$0(i.r1, w.r2, w.r3, i.r4, i.r5)"
        DetailPrint "Hello, to $2 $3 domain (win $1 - $4.$5) !"
    ${EndIf}
    System::Call "netapi32::NetApiBufferFree(ir0)"
    

    In the preceding example I used *i 0 r0 for the bufptr parameter so that $0 is NULL before the function starts (If you don't want to use this trick you can just do StrCpy $0 0 before the system call). If you don't do this then it is unclear what happens if the function fails. The documentation does not specify what happens to bufptr when the function fails, hopefully it is set to NULL but you cannot know for sure. If the function fails we end up passing NULL to NetApiBufferFree and that is usually a safe thing to pass to a free function but the documentation does not call this out as OK. To be on the super safe side you should only free a non-NULL pointer:

    System::Call "netapi32::NetWkstaGetInfo(i0, i100, *i 0 r0) i.r1"
    ${If} 0 = $1
        System::Call "*$0(i.r1, w.r2, w.r3, i.r4, i.r5)"
        DetailPrint "Hello, to $2 $3 domain (win $1 - $4.$5) !"
    ${EndIf}
    ${IfThen} $0 <> 0 ${|} System::Call "netapi32::NetApiBufferFree(ir0)" ${|}
    SectionEnd
    

    /NOUNLOAD is no longer required when using the system plugin (Since v2.42). /NOUNLOAD prevents NSIS from unloading the plugin. That is important if the plugin has internal state but in your case the only state is a block of memory allocated by Windows.