Requête WMI distante

Codes specifiques à Windows
tatanas
Messages : 39
Inscription : mar. 05/nov./2019 18:40

Requête WMI distante

Message par tatanas »

Bonjour à tous !

Brève présentation puisqu'il s'agit de mon premier post :
Je développe de façon ponctuelle depuis une quinzaine d'années pour m'aider dans mon quotidien d'admin réseaux. Jusqu'à présent j'utilisais AutoIt et pour des raisons de compilation et détection Antivirus (faux positif) j'ai décidé de tester PureBasic.

Pour en revenir à mon soucis, je bascule à l'heure actuel un outil qui me permet d'interroger des postes distants via des requêtes WMI. Sous Autoit, une partie des fonctions nécessaires sont intégrées et invisibles, ce qui fait que très peu de code est nécessaire pour exécuter ces requêtes. Mais sous PureBasic j'ai dû pas mal fouiner sur le forum, et je n'ai pas trouver grand chose de "frais".
J'ai testé un code fonctionnel pour des requêtes locales mais dès que je modifie les paramètres de la méthode ConnectServer en y ajoutant l'identifiant et mot de passe du compte admin local distant, les requêtes (pas la connexion) sont en erreur.
Il y a beaucoup de paramètres qui me dépasse dans les fonctions CoInitializeSecurity_ ou CoSetProxyBlanket_ et j'ai l'impression que, même si l'erreur vient de ExecQuery, il s'agit malgré tout d'un problème de sécurité. Après tout en local, la requête fonctionne.
J'ai aussi récupéré la librairie COMate.pbi mais cette dernière ne permet pas de se connecter en indiquant un compte utilisateur.

Le code que j'utilise :

Code : Tout sélectionner

; #WBEM_INFINITE = $FFFFFFFF 
#COINIT_MULTITHREADED               = 0
#RPC_C_AUTHN_LEVEL_CONNECT          = 2     ;Authenticates the credentials of the client only when the client establishes a relationship with the server.
#RPC_C_AUTHN_LEVEL_CALL             = 3     ;Authenticates only at the beginning of each remote procedure call when the server receives the request.
#EOAC_NONE                          = 0     ;No Authentication capability flags are set
#RPC_C_AUTHN_WINNT                  = 10    ;Use the Microsoft NT LAN Manager (NTLM) SSP.
#RPC_C_AUTHZ_NONE                   = 0     ;The server performs no authorization. Currently, RPC_C_AUTHN_WINNT, RPC_C_AUTHN_GSS_SCHANNEL, And RPC_C_AUTHN_GSS_KERBEROS all use only RPC_C_AUTHZ_NONE.
#RPC_C_IMP_LEVEL_IMPERSONATE        = 3     ;The server process can impersonate the client's security context while acting on behalf of the client. When impersonating at this level, the impersonation token can only be passed across one machine boundary (can be used to access local resources such as files).
#wbemFlagReturnImmediately          = 16    ;Causes the call to return immediately.
#wbemFlagForwardOnly                = 32    ;Causes a forward-only enumerator to be returned. Forward-only enumerators are generally much faster and use less memory than conventional enumerators, but they do not allow calls to SWbemObject.Clone_.
#CLSCTX_INPROC_SERVER               = $1

CompilerIf #PB_Compiler_OS = #PB_Compiler_Unicode
    Macro ptr(String) : @String : EndMacro
CompilerElse
    Macro ptr(String) : ansi2bstr(String) : EndMacro
CompilerEndIf

;=======================================
Procedure.i ansi2bstr(String$)
   size.i = MultiByteToWideChar_(#CP_ACP, 0, String$, Len(String$), 0, 0)
   If Size
    Dim unicode.w(size)
    MultiByteToWideChar_(#CP_ACP, 0, String$, Len(String$), unicode(), size)   ;#CP_ACP
    For Counter.i = 0 To size
        tmp.s + Hex(unicode(Counter), #PB_Unicode)
    Next
    EndIf
    ProcedureReturn SysAllocString_(@unicode())
EndProcedure
;=======================================
Procedure.i bstr2string(bstr.i)
    Shared result.s
    result = PeekS(bstr, -1, #PB_Unicode)
    ProcedureReturn @result
EndProcedure
;=======================================
Procedure.s WMI_GetProcessOwner(Servername$, Processname$, user$, pass$)
    Result$ = ""
    Select Servername$
        Case "" : Namespace$ = "\\.\root\CIMV2"
        Default : Namespace$ = "\\" + RemoveString(Servername$, "\") + "\root\CIMV2"
    EndSelect       
    ;
    If CoInitializeEx_(0, #COINIT_MULTITHREADED) = 0         
        If CoInitializeSecurity_(0, -1, 0, 0, #RPC_C_AUTHN_LEVEL_CONNECT, #RPC_C_IMP_LEVEL_IMPERSONATE, 0, #EOAC_NONE, 0) = 0             
        	If CoCreateInstance_(?CLSID_WbemLocator, 0, #CLSCTX_INPROC_SERVER, ?IID_IWbemLocator, @pLocator.IWbemLocator) = 0
        		
        		If pLocator\ConnectServer(ptr(Namespace$), @user$, @pass$, 0, 0, 0, 0, @pService.IWbemServices) = 0
        			
                    If CoSetProxyBlanket_(pService, #RPC_C_AUTHN_WINNT, #RPC_C_AUTHZ_NONE, 0, #RPC_C_AUTHN_LEVEL_CALL, #RPC_C_IMP_LEVEL_IMPERSONATE, 0, #EOAC_NONE) = 0
                        pService\queryinterface(?IID_IUnknown,@pUnknown.IUnknown) 
                        If CoSetProxyBlanket_(pUnknown, #RPC_C_AUTHN_WINNT, #RPC_C_AUTHZ_NONE, 0, #RPC_C_AUTHN_LEVEL_CALL, #RPC_C_IMP_LEVEL_IMPERSONATE, 0, #EOAC_NONE) = 0
                        	
                            If pService\ExecQuery(ptr("WQL"), ptr("SELECT * FROM Win32_Process"), #wbemFlagReturnImmediately|#wbemFlagForwardOnly, 0, @pEnumerator.IEnumWbemClassObject) = 0
                                pEnumerator\reset() 
                                NbrReturned.i = 1
                                While NbrReturned 
                                    pEnumerator\Next(#WBEM_INFINITE, 1, @pclsObj.IWbemClassObject, @NbrReturned)
                                    ;Get Processname
                                    If pclsObj\Get(ptr("Name"), 0, @vtReturn.VARIANT, 0, 0) = 0          ;Debug PeekS(bstr2string(vtReturn\bstrVal))
                                        Found.i = #False
                                        If LCase(PeekS(bstr2string(vtReturn\bstrVal))) = Processname$   
                                            Found = #True
                                            ;Get Processpath
                                            If pclsObj\Get(ptr("__PATH"), 0, @vtPath.VARIANT, 0, 0) = 0  ;Debug PeekS(bstr2string(vtPath\bstrVal))
                                                ;Get Processowner
                                                If pService\ExecMethod(vtPath\bstrVal, ptr("GetOwner"), 0, 0, 0, @pResult.IWbemClassObject, 0) = 0
                                                    If pResult\Get(ptr("User"), 0, @vtUser.VARIANT, 0, 0) = 0
                                                        Result$ = PeekS(bstr2string(vtUser\bstrVal))
                                                        VariantClear_(vtUser)
                                                    EndIf
                                                    pResult\release()   
                                                EndIf   
                                                VariantClear_(vtPath)       
                                            EndIf 
                                            Break
                                        EndIf     
                                        VariantClear_(vtReturn)
                                        If Found = #False : Result$ = "Error - Unable to find the process" : EndIf
                                    EndIf
                                Wend
                                pclsObj\release()   
                            Else
                                Result$ = "Error - Unable to retrieve the objects"
                            EndIf
                            pUnknown\release() 
                        Else
                            Result$ = "Error - Unable to set the IUnknown security"                           
                        EndIf
                    Else
                        Result$ = "Error - Unable to set the proxy security"
                    EndIf   
                    pService\release()
                Else
                    Result$ = "Error - Unable to connect to CIMV2"
                EndIf 
                pLocator\release()
            Else
                Result$ = "Error - Unable to create a WbemLocator"
            EndIf
        Else
            Result$ = "Error - Unable to set the security values for the process"
        EndIf
        CoUninitialize_()
    Else
        Result$ = "Error - Unable to launch COM"
    EndIf   
    ProcedureReturn Result$
    DataSection 
    	CLSID_IEnumWbemClassObject:
    	Data.l $1B1CAD8C : Data.w $2DAB, $11D2 : Data.b $B6, $04, $00, $10, $4B, $70, $3E, $FD 
    	IID_IEnumWbemClassObject:
    	Data.l $7C857801 : Data.w $7381, $11CF : Data.b $88, $4D, $00, $AA, $00, $4B, $2E, $24 
    	CLSID_WbemLocator:
    	Data.l $4590F811 : Data.w $1D3A, $11D0 : Data.b $89, $1F, $00, $AA, $00, $4B, $2E, $24 
    	IID_IWbemLocator:
    	Data.l $DC12A687 : Data.w $737F, $11CF : Data.b $88, $4D, $00, $AA, $00, $4B, $2E, $24 
    	IID_IUnknown:
    	Data.l $00000000 : Data.w $0000, $0000 : Data.b $C0, $00, $00, $00, $00, $00, $00, $46 
    	IID_IWbemRefresher:
    	Data.l $49353C99 : Data.w $516B, $11D1 : Data.b $AE, $A6, $00, $C0, $4F, $B6, $88, $20 
    	CLSID_WbemRefresher:
    	Data.l $C71566F2 : Data.w $561E, $11D1 : Data.b $AD, $87, $00, $C0, $4F, $D8, $FD, $FF 
    	IID_IWbemConfigureRefresher:
    	Data.l $49353C92 : Data.w $516B, $11D1 : Data.b $AE, $A6, $00, $C0, $4F, $B6, $88, $20 
    	IID_IWbemObjectAccess:
    	Data.l $49353C9A : Data.w $516B, $11D1 : Data.b $AE, $A6, $00, $C0, $4F, $B6, $88, $20 
    EndDataSection   
EndProcedure
;=======================================
;=======================================
;=======================================
Servername$ = "remote_pc"     
Processname$ = "smss.exe"
Debug WMI_GetProcessOwner(Servername$, Processname$, "user", "password")
Ollivier
Messages : 4190
Inscription : ven. 29/juin/2007 17:50
Localisation : Encore ?
Contact :

Re: Requête WMI distante

Message par Ollivier »

Welcome. Alors déjà, je vois, de mes yeux naïfs et empreints d'ignorance, ceci :

Code : Tout sélectionner

CompilerIf #PB_Compiler_OS = #PB_Compiler_Unicode
...
qu'il faut remplacer par cela :

Code : Tout sélectionner

CompilerIf #PB_Compiler_Unicode
...
Sinon, ça peut couiner, même si ça ne semble pas être la cause principale du problème...
Ollivier
Messages : 4190
Inscription : ven. 29/juin/2007 17:50
Localisation : Encore ?
Contact :

Re: Requête WMI distante

Message par Ollivier »

... Après, il y a une petite écharde ici (désolé pour les coupures de ligne, c'est sur smartphone...) :

Code : Tout sélectionner

Procedure.i ansi2bstr(String$)
size.i = MultiByteToWideChar_(#CP_ACP, 0, String$, Len(String$), 0, 0)
If Size
Dim unicode.w(size)
MultiByteToWideChar_(#CP_ACP, 0, String$, Len(String$), unicode(), size) ;#CP_ACP
For Counter.i = 0 To size
tmp.s + Hex(unicode(Counter), #PB_Unicode)
Next
EndIf
ProcedureReturn SysAllocString_(@unicode())
EndProcedure
Dans le 2ème appel de MultiByteToWideChar_(), avant-dernier argument, il faut (il me semble fort) ajouter un signe 'at' (ou 'arobase') comme ceci :

Code : Tout sélectionner

MultiByteToWideChar_(#CP_ACP, 0, String$, Len(String$), @unicode(), size) ;#CP_ACP
(pour récup l'adresse de la variable, et non, juste sa valeur)
tatanas
Messages : 39
Inscription : mar. 05/nov./2019 18:40

Re: Requête WMI distante

Message par tatanas »

Oui comme je le disais ce code est assez ancien. Je ne connais pas l'historique de Purebasic mais j'ai l'impression qu'il est devenu nativement Unicode il n'y a pas si longtemps que ça.
Malheureusement les changements que vous proposez ne règlent pas le problème. D'ailleurs même sans ces changements, le programme fonctionne en local.
Je tiens aussi à préciser que le poste distant sur lequel j'envoie la requête réponds correctement à mon script Autoit (c'est donc bien un soucis dans ce code).

J'ai trouvé un exemple sur msdn de connexion WMI à distance : https://docs.microsoft.com/en-us/window ... e-computer
Mais mes connaissances en C++ ne me permette pas de l'adapter en PB. Il semblerait que le code PB soit malgré tout assez proche...

J'ai l'impression qu'il manque cette partie là à la version PB :

Code : Tout sélectionner

    // Create COAUTHIDENTITY that can be used for setting security on proxy

    COAUTHIDENTITY *userAcct =  NULL ;
    COAUTHIDENTITY authIdent;

    if( !useToken )
    {
        memset(&authIdent, 0, sizeof(COAUTHIDENTITY));
        authIdent.PasswordLength = wcslen (pszPwd);
        authIdent.Password = (USHORT*)pszPwd;

        LPWSTR slash = wcschr (pszName, L'\\');
        if( slash == NULL )
        {
            cout << "Could not create Auth identity. No domain specified\n" ;
            pSvc->Release();
            pLoc->Release();     
            CoUninitialize();
            return 1;               // Program has failed.
        }

        StringCchCopy(pszUserName, CREDUI_MAX_USERNAME_LENGTH+1, slash+1);
        authIdent.User = (USHORT*)pszUserName;
        authIdent.UserLength = wcslen(pszUserName);

        StringCchCopyN(pszDomain, CREDUI_MAX_USERNAME_LENGTH+1, pszName, slash - pszName);
        authIdent.Domain = (USHORT*)pszDomain;
        authIdent.DomainLength = slash - pszName;
        authIdent.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;

        userAcct = &authIdent;

    }
userAcct étant utilisé dans la fonction CoSetProxyBlanket
Marc56
Messages : 2147
Inscription : sam. 08/févr./2014 15:19

Re: Requête WMI distante

Message par Marc56 »

PB est devenu Unicode à partir de la 5.50 (juillet 2016)
https://www.purebasic.com/french/docume ... story.html

Ces codes peuvent peut-être donner des idées:
Par RSBasic https://www.purebasic.fr/english/viewto ... 9&p=519318
Par celtic88 https://www.purebasic.fr/french/viewtop ... 25#p188025

S'il ne contient pas de code confidentiel, peut-être qu'en nous montrant le code AutoIt on pourrait aider ?

:wink:
tatanas
Messages : 39
Inscription : mar. 05/nov./2019 18:40

Re: Requête WMI distante

Message par tatanas »

Ces 2 liens sont intéressants mais le code est toujours pour des requêtes WMI locales :(

Voici un exemple de code Autoit :

Code : Tout sélectionner

Func _Get_Remote_Printer_IP($nom_ordi)
	Local $wbemFlagReturnImmediately = 0x10
	Local $wbemFlagForwardOnly = 0x20
	Local $wbemFlagConnectUseMaxWait = 0x80
	Local $colItems = ""
	Local $resultat = ""

	Local $strUser = GUICtrlRead($s_nom_compte)
	Local $strPass = GUICtrlRead($s_pass_compte)

	Local $strComputer = $nom_ordi

	Local $objSWbemLocator = ObjCreate("WbemScripting.SWbemLocator")
	Local $objSWbemServices = $objSWbemLocator.ConnectServer($strComputer, "Root\CIMv2", $strUser, $strPass, '', '', $wbemFlagConnectUseMaxWait)
	If IsObj($objSWbemServices) Then
		$objSWbemServices.Security_.ImpersonationLevel = 3

		Local $colPrinters = $objSWbemServices.ExecQuery("Select * From Win32_Printer", "WQL", $wbemFlagReturnImmediately + $wbemFlagForwardOnly)
		If IsObj($colPrinters) then
			For $objPrinter in $colPrinters
				$resultat = $resultat & $objPrinter.Name & " : " & $objPrinter.PortName & @CRLF
			Next
			MsgBox(0, "Imprimantes installées sur le poste", $resultat)
		EndIf
	EndIf
EndFunc
Encore un lien expliquant pas mal le fonctionnement WMI mais rien de précis concernant la connexion à distance, comme-ci les seuls paramètres à saisir étaient identifiant/mot de passe/domaine.
https://www.codeproject.com/Articles/10 ... r=76#xx0xx
Ollivier
Messages : 4190
Inscription : ven. 29/juin/2007 17:50
Localisation : Encore ?
Contact :

Re: Requête WMI distante

Message par Ollivier »

Il peut donner de faux positifs en fonctionnant en local si tu testes un logiciel puis un autre. Mon humble avis est qu'il ne fonctionne pas du tout. C'est plus simple à gérer ! En gros, informer si ce qui marche fonctionne avec isolation des tests par réinitialisation.

1) La constante #PB_Compiler_OS retourne une constante d'OS pas autre chose. Là encore, si ça a fonctionné, c'est que c'est un coup de chance.

2) Le arobase absent provoquera forcément un problème. Je dirais même que @unicode() devrait être remplacé par @unicode(0)

3) J'ajoute que Dim Unicode.W(Size), ça cloche. C'est

Code : Tout sélectionner

Dim Unicode.U(Size)
Tu parles d'une partie manquante avec un extrait C++. Ça me semble même vital donc je vais dire une connerie bien grande :

Code : Tout sélectionner

UseToken = ~CoCreateInstance_(taratata)
If UseToken
   etc...
'tention les yeux...
tatanas
Messages : 39
Inscription : mar. 05/nov./2019 18:40

Re: Requête WMI distante

Message par tatanas »

J'ai modifié le type que tu m'as indiqué.
J'ai aussi tenté de remplir la structure :

Code : Tout sélectionner

domain$ = "mondomaine"
#SEC_WINNT_AUTH_IDENTITY_UNICODE = $2
authIdent.COAUTHIDENTITY
authIdent\PasswordLength = Len(password$)
authIdent\Password = @password$
authIdent\Flags = #SEC_WINNT_AUTH_IDENTITY_UNICODE
authIdent\User = @user$
authIdent\UserLength = Len(user$)
authIdent\Domain = @domain$
authIdent\DomainLength = Len(domain$)

*userAcct.COAUTHIDENTITY
*userAcct = @authIdent


...

If CoSetProxyBlanket_(pService, #RPC_C_AUTHN_WINNT, #RPC_C_AUTHZ_NONE, 0, #RPC_C_AUTHN_LEVEL_CALL, #RPC_C_IMP_LEVEL_IMPERSONATE, *userAcct, #EOAC_NONE) = 0
Mais cette fois l'erreur provient de cette dernière fonction... J'ai l'impression que je ne remplis pas correctement la structure COAUTHIDENTITY.
Ollivier
Messages : 4190
Inscription : ven. 29/juin/2007 17:50
Localisation : Encore ?
Contact :

Re: Requête WMI distante

Message par Ollivier »

Déjà désolé de te piquer les yeux avec le numéro (4) : j'aurai dû mettre un nom plus adapté que 'UseToken' puisque l'opération binaire (le tilde x '~x') inverse tous les bits. Je me suis inspiré de l'extrait C++ ("If !UseToken") que tu suspectes de manquer. Je suis hélas sur Smartphone avec un accès limité : tu communiques donc avec un escargot. Cependant, en tâtonnant méthodiquement, la fin risque bien l'aboutissement... Aussi, je n'ai personnellement pas le temps de creuser a priori ces prochaines heures. Si tu peux déjà regarder un peu ce que "retourne" CoCreateInstance_(). De mémoire, un retour nul signifie "pas d'erreur", tandis qu'un retour non nul signifie soit un code d'erreur, soit un identifiant invalide.

Franchement, tu (ou vous, qu'importe, juste "tu" simplification la communication de groupe) n'as pas trop à t'en faire pour le (1), le (2) et le (3) : ce sont juste des définitions propres à PureBasic (le type signé ou pas pour le choix 'W' ou 'U', etc... c'est dans la doc et tu sembles bien maîtriser le sujet).

Par contre, le (4), c'est plus subtile : on doit réussir à bien adapter la configuration orientée objet de la librairie Windows.

5) Alors, pour corser un peu le programme d'adaptation, je rajoute un critère à focaliser aussi : il me semble avoir vu un héritage dans un des codes ci-dessus. Donc, pour définir cette structure, il faudra soit choisir 'extend' (cf la doc), soit faire l'héritage manuellement. (Peut-être que tu avais déjà constaté cet héritage, je ne sais pas)

Dès que j'ai un peu de temps, je regarde ça de plus près (et je ne pense pas être le seul !).
Ollivier
Messages : 4190
Inscription : ven. 29/juin/2007 17:50
Localisation : Encore ?
Contact :

Re: Requête WMI distante

Message par Ollivier »

Hop cet extrait

Code : Tout sélectionner

$objSWbemServices.Security_.ImpersonationLevel = 3
Je déconne ou bien c'est une structure avec héritage ?
tatanas
Messages : 39
Inscription : mar. 05/nov./2019 18:40

Re: Requête WMI distante

Message par tatanas »

Bien le bonjour !

Petit aparté sur "useTOken". Je n'aurais pas dû le mettre dans le bout de code car il n'a pour ainsi dire rien à voir dans le problème de connexion. Il s'agit simplement d'un booléen qui permet de déterminer si, dans l'exemple c++ msdn, l'utilisateur a saisi ou non les informations de credentials. Dans notre cas, on les saisit en dur.

En effet les fonctions/méthodes CoInitializeEx_, CoInitializeSecurity_, CoCreateInstance_, ConnectServer, CoSetProxyBlanket_, ExecQuery renvoient 0 lorsqu'il n'y a pas eu d'erreur.
Mais d'après ce que j'ai pu glaner sur le net, lorsque la commande ExecQuery ne renvoie pas 0 (ce qui est mon cas lorsque je ne renseigne pas la structure AuthIdentity), il ne s'agit pas forcément d'une erreur dans l'appel de celle-ci. La majeur partie du temps, le problème vient de CoSetProxyBlanket_, de l'un de ses paramètres et en particulier de RPC_AUTH_IDENTITY_HANDLE pAuthInfo.
Reste à savoir si je remplis bien cette structure et si je l'utilise correctement (n'étant pas très pointu en terme de gestion/utilisation des pointeurs). Je vois un memset() en C++ pour allouer une taille en mémoire à la structure AuthIdentity, ai-je besoin de m'en préoccuper en PB ?
Je vois aussi des appels _bstr_t() dans plusieurs fonctions qui, corrige moi si je me trompe, n'ont pas d'utilité puisque je travaille en Unicode.

A priori $objSWbemServices.Security_.ImpersonationLevel = 3 correspond à la constante #RPC_C_IMP_LEVEL_IMPERSONATE qu'on retrouve dans CoInitializeSecurity_ ou CoSetProxyBlanket_.

EDIT : si je n'utilise pas le paramètre AuthIdentity (remplacé par 0) l'erreur de ExecQuery est "Access denied". Ce qui tend bien à confirmer qu'il faut absolument remplir cette fichue structure.
Ollivier
Messages : 4190
Inscription : ven. 29/juin/2007 17:50
Localisation : Encore ?
Contact :

Re: Requête WMI distante

Message par Ollivier »

Bien. Je te présente la Coding question. C'est assez sympa et dépaysant surtout pour les problèmes stratosphériques.
tatanas
Messages : 39
Inscription : mar. 05/nov./2019 18:40

Re: Requête WMI distante

Message par tatanas »

Merci pour le lien. En fait j'ai déjà emprunté le topic de quelqu'un d'autre qui cherchait le même chose que moi sur le forum anglais.
Du coup, je ne vais peut être pas en remettre une couche dans cette partie du forum.
(http://forums.purebasic.com/english/vie ... 5e#p544198)
Ollivier
Messages : 4190
Inscription : ven. 29/juin/2007 17:50
Localisation : Encore ?
Contact :

Re: Requête WMI distante

Message par Ollivier »

tatanas a écrit :Merci pour le lien. En fait j'ai déjà emprunté le topic de quelqu'un d'autre qui cherchait le même chose que moi sur le forum anglais.
Du coup, je ne vais peut être pas en remettre une couche dans cette partie du forum.
(http://forums.purebasic.com/english/vie ... 2&p=544198)
Pour de tels sujets, il vaut mieux plus que pas assez.
Répondre