2011年11月6日星期日

Windows API操作WMI Namespace Security

因为项目里用通过WMI查询remote machine的information,但默认WMI必须要Admin的权限,所以找了non-Admin的solution:

Q 8. How do I set WMI namespace security?

Maintaining WMI Security

手动测试很快就过了,只要相应地把WMI Control的priviledge和DCOM的priviledge设置正确即可。

但下一个问题就是怎么把这个setting deploy到remote machine上,显然得派上GPO了,最方便的当然是vbs咯,即不像PowerShell那样要runtime,又不想C++那样难以操作,msdn就有:Setting Namespace Security Descriptor。用脚本搞定似乎很容易了,但是,且慢:

GetSecurityDescriptor

Gets the security descriptor that controls access to the WMI namespace associated with the instance of __SystemSecurity. The security descriptor is returned as an instance of__SecurityDescriptor.

Windows Server 2003, Windows XP, Windows 2000, and Windows NT 4.0:  This method is not available.

SetSecurityDescriptor

Writes an updated version of the security descriptor that controls access to the printer. The security descriptor is represented by an instance of __SecurityDescriptor.

Windows Server 2003, Windows XP, Windows 2000, and Windows NT 4.0:  This method is not available.

晕菜了,这两个强大的api竟然在xp/2000/2003上都不能用,那怎么能适应不同客户端呢~~~

现在唯一的解决方法就只能是用C/C++来写native application了,C#由于需要.net framework也被抛弃(有现成的呢)~~~

很不幸的是,唯一的能找到的实现只有C#的,决定自己写一个,本以为1-2个小时的事情,结果竟然折腾了2个晚上,网上相关的资料太少了,M$的参考资料里很多的WMI的例子也都是用vbs或C#写成的,而用C/C++操作WMI namespace security的就灰常难找咯,一个简单的如何获得WMI里的__SystemSecurity就折腾了半天

自己折腾,最终终于搞定,几点体会:

1. Windows下用C/C++对COM/DCOM编程实在是痛苦,而牵涉到IDispatch,dual interface等,实在想彻底抛弃C/C++,投入script的怀抱

2. 可惜的script对Windows Security的支持比较有限

3. Windows下的error handling也是梦魇,满眼的HRESULT/FAILED/…眼花缭乱

唉,看来Linux玩久了再回头看Windows很多很多的不习惯啊~~~

里面牵涉到的技术点包括:

1. 如何获得WMI namespace的__SystemSecurity class(GetObject)

2. 如何invoke __SystemSecurity的方法(GetMethod/ExecMethod)

3. 如何对security descriptor操作,包括如何把security descriptor和SDDL(Security Descriptor Definition Language)相互转换,以及如何对SDDI作修改

具体技术细节不想细细展开了,直接贴代码,万一有人用到可以copy-paste;-)

// WmiSecurity.cpp : Defines the entry point for the console application.
//
#define _WIN32_DCOM
#include <iostream>
using namespace std;
#include <comdef.h>
#include <Wbemidl.h>

#pragma comment(lib, "wbemuuid.lib")

#include "stdafx.h"
#ifdef WIN32
#include "XGetopt.h"
#endif

#include <atlbase.h>
#include <Sddl.h>

int parseSecurityFlag(const LPTSTR sec_flag)
{
 if (_tcsicmp(sec_flag, _T("read")) == 0)
  return WBEM_ENABLE;
 if (_tcsicmp(sec_flag, _T("remoteaccess")) == 0)
  return WBEM_ENABLE | WBEM_REMOTE_ACCESS;
 if (_tcsicmp(sec_flag, _T("providerwrite")) == 0)
  return WBEM_ENABLE | WBEM_WRITE_PROVIDER;
 if (_tcsicmp(sec_flag, _T("partialwrite")) == 0)
  return WBEM_ENABLE | WBEM_PARTIAL_WRITE_REP;
 if (_tcsicmp(sec_flag, _T("fullwrite")) == 0)
  return WBEM_ENABLE | WBEM_FULL_WRITE_REP;
 if (_tcsicmp(sec_flag, _T("full")) == 0)
  return WBEM_REMOTE_ACCESS | WBEM_METHOD_EXECUTE | WBEM_FULL_WRITE_REP | WBEM_ENABLE | READ_CONTROL | WRITE_DAC;
 return 0;
}

void usage(const LPTSTR cmd)
{
 _tprintf(_T("Usage: %s -n namespace -u user_account -s security_flag [-r]\n"), cmd);
 _tprintf(_T("Where -n: Namespace to target\n"));
 _tprintf(_T("Where -u: User account to grant\n"));
 _tprintf(_T("Where -s: WMI security flag to grant\n"));
 _tprintf(_T("Where -r: grant all subsequent WMI namespaces as well as present one"));
 exit(-1);
}

#ifdef UNICODE
#define tstring wstring
#else
#define tstring string
#endif

class AceString
{
public:
 AceString() : recurse_(false), accessAllowed_(true) {}
 void addRight(tstring s)
 {
  if (_tcsstr(rights_.c_str(), s.c_str()) == NULL)
   rights_.append(s);
 }
 void reset()
 {
  rights_ = _T("");
  aceString_ = _T("");
  recurse_ = false;
  accessAllowed_ = true;
 }
 tstring getAceString()
 {
  return aceString_;
 }
 void setRecursive(bool b)
 {
  recurse_ = b;
 }
 void setAccessAllowed(bool b)
 {
  accessAllowed_ = b;
 }
 void createFinalAceString(LPTSTR sid)
 {
  if (accessAllowed_)
   aceString_.append(_T("A;"));  // sddl access allowed
  else
   aceString_.append(_T("D;"));  // sddl access denied

  if (recurse_)
   aceString_.append(_T("CI;")); // recurse through subcontainers
  else
   aceString_.append(_T(";"));  // initial container only

  // Now add the rights...
  if (rights_.size() == 0)
   throw "AceString.CreateFinalSidString: empty rights string";
  else
  {
   aceString_.append(rights_);
   aceString_.append(_T(";"));
  }

  // We don't do anything for Object Guid or Inherit Object Guid 
  // in this version...
  aceString_.append(_T(";;"));

  if (sid == NULL || _tcslen(sid) == 0)
   throw "AceString.CreateFinalSidString: no Trustee specified";
  else
   aceString_.append(sid);
 }
 void createAceStringFromWmiRight(int sec_flag)
 {
  switch (sec_flag)
  {
  case WBEM_REMOTE_ACCESS:
   addRight(_T("WP"));
   break;
  case WBEM_METHOD_EXECUTE:
   addRight(_T("DC"));
   break;
  case WBEM_FULL_WRITE_REP:
   addRight(_T("LC"));
   addRight(_T("SW"));
   addRight(_T("RP"));
   break;
  case WBEM_PARTIAL_WRITE_REP:
   addRight(_T("SW"));
   break;
  case WBEM_WRITE_PROVIDER:
   addRight(_T("BP"));
   break;
  case WBEM_ENABLE:
   addRight(_T("CC"));
   break;
  case READ_CONTROL:
   addRight(_T("RC"));
   break;
  case WRITE_DAC:
   addRight(_T("WD"));
   break;
  default:
   throw "AceString.CreateAceStringFromRight: Invalid Wmi right specified";
  }
 }
private:
 tstring rights_;
 tstring aceString_;
 bool recurse_;
 bool accessAllowed_;
};

class AceStringManager : public AceString
{
private:
 LPTSTR sid_;
public:
 AceStringManager(LPTSTR sid, bool accessAllowed, bool recursive)
 {
  AceString::AceString();
  setAccessAllowed(accessAllowed);
  setRecursive(recursive);
  sid_ = sid;
 }
 tstring returnAceString(int sec_flag)
 {
  if ((WBEM_ENABLE & sec_flag) > 0)
   createAceStringFromWmiRight(WBEM_ENABLE);
  if ((WBEM_METHOD_EXECUTE & sec_flag) > 0)
   createAceStringFromWmiRight(WBEM_METHOD_EXECUTE);
  if ((WBEM_FULL_WRITE_REP & sec_flag) > 0)
   createAceStringFromWmiRight(WBEM_FULL_WRITE_REP);
  if ((WBEM_PARTIAL_WRITE_REP & sec_flag) > 0)
   createAceStringFromWmiRight(WBEM_PARTIAL_WRITE_REP);
  if ((WBEM_WRITE_PROVIDER & sec_flag) > 0)
   createAceStringFromWmiRight(WBEM_WRITE_PROVIDER);
  if ((WBEM_REMOTE_ACCESS & sec_flag) > 0)
   createAceStringFromWmiRight(WBEM_REMOTE_ACCESS);
  if ((READ_CONTROL & sec_flag) > 0)
   createAceStringFromWmiRight(READ_CONTROL);
  if ((WRITE_DAC & sec_flag) > 0)
   createAceStringFromWmiRight(WRITE_DAC);

  createFinalAceString(sid_);
  return getAceString();
 }
};

BOOL getSidStringFromName(const LPTSTR account, LPTSTR* pszSid)
{
 BYTE sidBuffer[4096] = {0};
 PSID psid = (PSID)&sidBuffer;
 DWORD sidBufferSize = sizeof(sidBuffer) / sizeof(sidBuffer[0]);
 DWORD domainSize = 255;
 LPTSTR domain = new TCHAR[domainSize];
 SID_NAME_USE snu;
 BOOL rtn = LookupAccountName(NULL, account, psid, &sidBufferSize, domain, &domainSize, &snu);
 delete[] domain;
 if (!rtn)
 {
  return FALSE;
 }
 rtn = ConvertSidToStringSid(psid, pszSid);
 if (!rtn)
 {
  return FALSE;
 }
 return TRUE;
}

int _tmain(int argc, LPTSTR argv[])
{
 USES_CONVERSION;
 int oc;
 LPTSTR name_space = NULL;
 LPTSTR account = NULL;
 int sec_flag = 0;
 bool recursive = false;
 while ((oc = getopt(argc, argv, _T("n:u:s::r"))) != -1)
 {
  switch (oc)
  {
  case 'n':
   name_space = optarg;
   break;
  case 'u':
   account = optarg;
   break;
  case 's':
   sec_flag = parseSecurityFlag(optarg);
   break;
  case 'r':
   recursive = true;
   break;
  }
 }
 if (name_space == NULL || account == NULL || sec_flag == 0)
 {
  usage(argv[0]);
 }

 HRESULT hres;

 IWbemLocator *pLoc = NULL;
 IWbemServices *pSvc = NULL;

 BSTR ClassPath = SysAllocString(L"__SystemSecurity");
 BSTR methodGetSD = SysAllocString(L"GetSD");
 BSTR methodSetSD = SysAllocString(L"SetSD");

 IWbemClassObject* pClass = NULL;
 IWbemClassObject * pGetSD_InClass = NULL;
 IWbemClassObject * pGetSD_OutClass = NULL;
 IWbemClassObject * pGetSD_OutInst = NULL;
 IWbemClassObject * pSetSD_InClass = NULL;
 IWbemClassObject * pSetSD_OutClass = NULL;
 IWbemClassObject * pSetSD_InInst = NULL;
 IWbemClassObject * pSetSD_OutInst = NULL;

 VARIANT varRes;

 // Step 1: --------------------------------------------------
 // Initialize COM. ------------------------------------------

 hres =  CoInitializeEx(0, COINIT_MULTITHREADED); 
 if (FAILED(hres))
 {
  cout << "Failed to initialize COM library. Error code = 0x" 
   << hex << hres << endl;
  return 1;                  // Program has failed.
 }

 // Step 2: --------------------------------------------------
 // Set general COM security levels --------------------------
 // Note: If you are using Windows 2000, you need to specify -
 // the default authentication credentials for a user by using
 // a SOLE_AUTHENTICATION_LIST structure in the pAuthList ----
 // parameter of CoInitializeSecurity ------------------------

 hres =  CoInitializeSecurity(
  NULL, 
  -1,                          // COM authentication
  NULL,                        // Authentication services
  NULL,                        // Reserved
  RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication 
  RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation  
  NULL,                        // Authentication info
  EOAC_NONE,                   // Additional capabilities 
  NULL                         // Reserved
  );
 if (FAILED(hres))
 {
  cout << "Failed to initialize security. Error code = 0x" 
   << hex << hres << endl;
  CoUninitialize();
  return 1;
 }

 LPTSTR pszSid = NULL;
 BOOL rtn = getSidStringFromName(account, &pszSid);
 if (!rtn)
 {
  cout << "Failed to get SID for account." << endl;
  CoUninitialize();
  return 1;
 }
 _tprintf(_T("Account: %s\nSID: %s\n\n"), account, pszSid);

 AceStringManager aceStringManager(pszSid, true, recursive);
 tstring sddl = aceStringManager.returnAceString(sec_flag);
 LocalFree(pszSid);
 pszSid = NULL;

 // Step 3: ---------------------------------------------------
 // Obtain the initial locator to WMI -------------------------

 hres = CoCreateInstance(
  CLSID_WbemLocator,             
  0, 
  CLSCTX_INPROC_SERVER, 
  IID_IWbemLocator, (LPVOID *) &pLoc);

 if (FAILED(hres))
 {
  cout << "Failed to create IWbemLocator object."
   << " Err code = 0x"
   << hex << hres << endl;
  goto _clean;
 }

 // Step 4: -----------------------------------------------------
 // Connect to WMI through the IWbemLocator::ConnectServer method

 // Connect to the root\cimv2 namespace with
 // the current user and obtain pointer pSvc
 // to make IWbemServices calls.
 hres = pLoc->ConnectServer(
  _bstr_t(T2W(name_space)), // Object path of WMI namespace
  NULL,                     // User name. NULL = current user
  NULL,                     // User password. NULL = current
  0,                        // Locale. NULL indicates current
  NULL,                     // Security flags.
  0,                        // Authority (e.g. Kerberos)
  0,                        // Context object 
  &pSvc                     // pointer to IWbemServices proxy
  );
 if (FAILED(hres))
 {
  cout << "Could not connect. Error code = 0x" 
   << hex << hres << endl;
  goto _clean;
 }

 cout << "Connected to WMI namespace: " << T2A(name_space) << endl;

 // Get system class of __SystemSecurity
 hres = pSvc->GetObject(ClassPath, 0, NULL, &pClass, NULL);
 if (FAILED(hres))
 {
  cout << "Failed to get __SystemSecurity. Error code = 0x"
   << hex << hres << endl;
  goto _clean;
 }

 // Get method GetSD
 hres = pClass->GetMethod(methodGetSD, 0, &pGetSD_InClass, &pGetSD_OutClass);
 if (FAILED(hres))
 {
  cout << "Failed to GetMethod of GetSD. Error code = 0x"
   << hex << hres << endl;
  goto _clean;
 }
 // Execute method GetSD, to get original SD
 hres = pSvc->ExecMethod(ClassPath, methodGetSD, 0, NULL, pGetSD_InClass, &pGetSD_OutInst, NULL);
 if (FAILED(hres))
 {
  cout << "Failed to ExecMethod of GetSD. Error code = 0x"
   << hex << hres << endl;
  goto _clean;
 }
 hres = pGetSD_OutInst->Get(L"SD", 0, &varRes, NULL, 0);
 if (FAILED(hres))
 {
  cout << "Failed to get SD property of GetSD result. Error Code = 0x"
   << hex << hres << endl;
  goto _clean;
 }

 SAFEARRAY* pArray = varRes.parray;
 BYTE* pszOriginalSD = NULL;
 hres = SafeArrayAccessData(pArray, (void**)&pszOriginalSD);
 if (FAILED(hres))
 {
  cout << "Failed to lock SafeArray of returned SD. Error code = 0x"
   << hex << hres << endl;
  goto _clean;
 }

 LPTSTR pszSDDL = NULL;
 ULONG sizeSDDL = 0;
 rtn = ConvertSecurityDescriptorToStringSecurityDescriptor(pszOriginalSD,
  1,
  DACL_SECURITY_INFORMATION |
  OWNER_SECURITY_INFORMATION |
  GROUP_SECURITY_INFORMATION |
  SACL_SECURITY_INFORMATION,
  &pszSDDL, &sizeSDDL);
 if (!rtn)
 {
  cout << "Failed to ConvertSecurityDescriptorToStringSecurityDescriptor. Error code = 0x"
   << hex << GetLastError() << endl;
  SafeArrayUnaccessData(pArray);
  goto _clean;
 }
 _tprintf(_T("\nOriginal SD: \n%s\n"), pszSDDL);
 hres = SafeArrayUnaccessData(pArray);
 if (FAILED(hres))
 {
  cout << "Failed to unlock SafeArray of returned SD. Error code = 0x"
   << hex << hres << endl;
  LocalFree(pszSDDL);
  pszSDDL = NULL;
  goto _clean;
 }

 // change pszSDDL (for new SDDL)
 int len = _tcslen(pszSDDL) + sddl.size() + 100;
 LPTSTR newSDDL = new TCHAR[len];
 wsprintf(newSDDL, _T("%s(%s)"), pszSDDL, sddl.c_str());
 LocalFree(pszSDDL);
 pszSDDL = NULL;
 _tprintf(_T("\nNew SD: \n%s\n\n"), newSDDL);

 PSECURITY_DESCRIPTOR pSystemSD = NULL;
 ULONG iSystemSDSize = 0;
 rtn = ConvertStringSecurityDescriptorToSecurityDescriptor(newSDDL, 1, &pSystemSD, &iSystemSDSize);
 delete[] newSDDL;
 newSDDL = NULL;
 if (!rtn)
 {
  cout << "Failed to ConvertStringSecurityDescriptorToSecurityDescriptor. Error code = 0x"
   << hex << GetLastError() << endl;
  goto _clean;
 }

 BYTE * securityDescriptor = NULL;
 SAFEARRAY* pNewArray = SafeArrayCreateVector(VT_UI1, 0, iSystemSDSize);
 hres = SafeArrayAccessData(pNewArray, (void**)&securityDescriptor);
 if (FAILED(hres))
 {
  cout << "Failed to lock SD for SetSD. Error code = 0x"
   << hex << hres << endl;
  LocalFree(pSystemSD);
  pSystemSD = NULL;
  goto _clean;
 }
 memcpy(securityDescriptor, pSystemSD, iSystemSDSize);
 LocalFree(pSystemSD);
 pSystemSD = NULL;
 hres = SafeArrayUnaccessData(pNewArray);
 if (FAILED(hres))
 {
  cout << "Failed to unlock SD for SetSD. Error code = 0x"
   << hex << hres << endl;
  goto _clean;
 }
 VariantClear(&varRes);
 varRes.vt = VT_ARRAY | VT_BOOL;
 varRes.parray = pNewArray;

 hres = pClass->GetMethod(methodSetSD, 0, &pSetSD_InClass, &pSetSD_OutClass);
 if (FAILED(hres))
 {
  cout << "Failed to GetMethod of SetSD. Error code = 0x"
   << hex << hres << endl;
  goto _clean;
 }
 hres = pSetSD_InClass->SpawnInstance(0, &pSetSD_InInst);
 if (FAILED(hres))
 {
  cout << "Failed to SpawnInstance for SetSD. Error code = 0x"
   << hex << hres << endl;
  goto _clean;
 }
 hres = pSetSD_InInst->Put(L"SD", 0, &varRes, 0);
 if (FAILED(hres))
 {
  cout << "Failed to put property of SD for SetSD. Error code = 0x"
   << hex << hres << endl;
  goto _clean;
 }
 VariantClear(&varRes);
 hres = pSvc->ExecMethod(ClassPath, methodSetSD, 0, NULL, pSetSD_InInst, &pSetSD_OutInst, NULL);
 if (FAILED(hres))
 {
  cout << "Failed to SetSD. Error code = 0x"
   << hex << hres << endl;
  goto _clean;
 }
 hres = pSetSD_OutInst->Get(L"ReturnValue", 0, &varRes, NULL, 0);
 if (FAILED(hres))
 {
  cout << "Failed to get result of SetSD. Error code = 0x"
   << hex << hres << endl;
  goto _clean;
 }
 if (varRes.intVal != 0)
  cout << "SetSD failed. Error cdoe = 0x" << hex << varRes.intVal << endl;
 else
  cout << "SetSD successfully." << endl;

 // Cleanup
 // ========
_clean:
 VariantClear(&varRes);

 SysFreeString(ClassPath);
 SysFreeString(methodGetSD);
 SysFreeString(methodSetSD);

 if (pClass) pClass->Release();
 if (pGetSD_InClass) pGetSD_InClass->Release();
 if (pGetSD_OutClass) pGetSD_OutClass->Release();
 if (pGetSD_OutInst) pGetSD_OutInst->Release();
 if (pSetSD_InClass) pSetSD_InClass->Release();
 if (pSetSD_OutClass) pSetSD_OutClass->Release();
 if (pSetSD_InInst) pSetSD_InInst->Release();

 pSvc->Release();
 pLoc->Release();
 CoUninitialize();

 return 0;   // Program successfully completed.
}

没有评论:

发表评论

Test Ads
Hello World