之前大佬要我做一款windows一键激活工具。基本原理是利用我们的license,从后台请求一个微软的正版license,然后调用slmgr进行windows激活。这样就可以把license的控制权留在自己这里,而不用传递给工厂,避免license流失。(所以期望从本文找破解方案的同学可以散了233)所以记录下对slmgr的研究。


slmgr是一个vba脚本,整体的结构如下图所示。可以看到整个结构基本都是面向功能的,十分容易理解。

其主要功能就是通过指定的命令进入分支语句,然后调各个功能模块方法

1572054939331

1572055003312

image-20191026105047932

我们先来看看主要的入口函数ExecCommandLine。归结起来就2句话,1是执行Connect,获取WMI服务和注册表;2是根据命令参数执行不同子模块功能

Private Sub ExecCommandLine
    Dim intOption, indexOption
    Dim strOption, chOpt
    Dim remoteInfo(3)

    
    '省略中间的参数检测代码
    
    '连接至本地计算机或者远端计算机,以获取WMI服务和注册表
    Call Connect()

    If intUnknownOption = intOption Then
        LineOut GetResource("L_MsgInvalidOptions")
        LineOut ""
        Call DisplayUsage()
    End If
	'针对不同的命令参数执行子函数
    intOption = ParseCommandLine(indexOption)

    If intUnknownOption = intOption Then
        LineOut GetResource("L_MsgUnrecognizedOption") & WScript.Arguments.Item(indexOption)
        LineOut ""
        Call DisplayUsage()
    End If
End Sub

Connect的源码如下,其主要功能是从参数中判断是否是对本地计算机处理,针对远端计算机和本地计算机,采用不同方式获取WMI服务和注册表

Private Sub Connect
    Dim objLocator, strOutput
    Dim objServer, objService
    Dim strErr, strVersion

    On Error Resume Next

    '如果是本地计算机,则直接获取WMI服务和注册表
    If g_strComputer = "." Then
        Set g_objWMIService = GetObject("winmgmts:\\" & g_strComputer & "\root\cimv2")
        QuitIfError2("L_MsgErrorLocalWMI")

        Set g_objRegistry = GetObject("winmgmts:\\" & g_strComputer & "\root\default:StdRegProv")
        QuitIfError2("L_MsgErrorLocalRegistry")

        Exit Sub
    End If

            '否则建立远端连接

    ' Create Locator object to connect to remote CIM object manager
    Set objLocator = CreateObject("WbemScripting.SWbemLocator")
    QuitIfError2("L_MsgErrorWMI")

                ' 获取远端WMI服务
    Set g_objWMIService = objLocator.ConnectServer (g_strComputer, "\root\cimv2", g_strUserName, g_strPassword)
    QuitIfError2("L_MsgErrorConnection")

    g_IsRemoteComputer = True

    g_objWMIService.Security_.impersonationlevel = wbemImpersonationLevelImpersonate
    QuitIfError2("L_MsgErrorImpersonation")

    g_objWMIService.Security_.AuthenticationLevel = wbemAuthenticationLevelPktPrivacy
    QuitIfError2("L_MsgErrorAuthenticationLevel")

    ' Get the SPP service version on the remote machine
    set objService = GetServiceObject("Version")
    strVersion = objService.Version

    ' The Windows 8 version of SLMgr.vbs does not support remote connections to Vista/WS08 and Windows 7/WS08R2 machines
    if (Not IsNull(strVersion)) Then
        strVersion = Left(strVersion, 3)
        If (strVersion = "6.0") Or (strVersion = "6.1") Then
            LineOut GetResource("L_MsgRemoteWmiVersionMismatch")
            ExitScript 1
        End If
    End If

    Set objServer = objLocator.ConnectServer(g_strComputer, "\root\default:StdRegProv", g_strUserName, g_strPassword)
    QuitIfError2("L_MsgErrorConnectionRegistry")

    objServer.Security_.ImpersonationLevel = 3
    Set g_objRegistry = objServer.Get("StdRegProv")
    '获取注册表对象
    QuitIfError2("L_MsgErrorConnectionRegistry")
End Sub

接下来是ParseCommandLine函数,其就是简单的一个switchcase,根据输入的命令参数不同,执行不同的激活命令。

Private Function ParseCommandLine(index)
    Dim strOption, chOpt

    ParseCommandLine = intKnownOption

    strOption = LCase(WScript.Arguments.Item(index))

    chOpt = Left(strOption, 1)

    If (chOpt <> "-") And (chOpt <> "/") Then
        ParseCommandLine = intUnknownOption
        Exit Function
    End If

    strOption = Right(strOption, Len(strOption) - 1)

    If strOption = GetResource("L_optInstallLicense") Then

        If HandleOptionParam(index+1, True, GetResource("L_optInstallLicense"), GetResource("L_ParamsLicenseFile")) Then
            InstallLicense WScript.Arguments.Item(index+1)
        End If

    ElseIf strOption = GetResource("L_optInstallProductKey") Then

        If HandleOptionParam(index+1, True, GetResource("L_optInstallProductKey"), GetResource("L_ParamsProductKey")) Then
            InstallProductKey WScript.Arguments.Item(index+1)
        End If

    ElseIf strOption = GetResource("L_optUninstallProductKey") Then

        If HandleOptionParam(index+1, False, GetResource("L_optUninstallProductKey"), GetResource("L_ParamsActivationIDOptional")) Then
            UninstallProductKey WScript.Arguments.Item(index+1)
        Else
            UninstallProductKey ""
        End If

       '中间还有一堆else if省略
    Else

        ParseCommandLine = intUnknownOption

    End If

End Function

我们选取其中最重要的一个函数ActivateProduct来分析下。源码有部分删减,其中过程是获得一个叫“Version”的服务对象,从所有产品列表中检查是否是指定的产品,进行激活并更新状态。

Private Sub ActivateProduct(strActivationID)
    Dim objService, objProduct
    Dim iIsPrimaryWindowsSku, bFoundAtLeastOneKey
    Dim strOutput
    Dim bCheckProductForCommand

    strActivationID = LCase(strActivationID)

    bFoundAtLeastOneKey = False
    '获取“Version”服务
    set objService = GetServiceObject("Version")
'获取激活产品对象
    For Each objProduct in GetProductCollection("ID, ApplicationId, PartialProductKey, LicenseIsAddon, Description, Name, LicenseStatus, VLActivationTypeEnabled ", "PartialProductKey <> null")

        bCheckProductForCommand = CheckProductForCommand(objProduct, strActivationID)

        If (bCheckProductForCommand) Then
            '省略部分输出
            If (Not(IsMAK(objProduct.Description)) Or (objProduct.LicenseStatus <> 1)) Then
                '激活产品
                objProduct.Activate()
                QuitIfError()
                '更新状态
                objService.RefreshLicenseStatus()
                objProduct.refresh_
            End If
            DisplayActivatedStatus objProduct

            bFoundAtLeastOneKey = True
            If (strActivationID <> "") Or (iIsPrimaryWindowsSku = 1) Then
                Exit Sub
            End If
        End If
    Next
    '省略部分输出
End Sub

那我们再来看看GetServiceObject方法,实际就是执行了一个WMI的SQL查询,从SoftwareLicensingService获取Version

Function GetServiceObject(strQuery)
    Dim objService
    Dim colServices

    On Error Resume Next

    Set colServices = g_objWMIService.ExecQuery("SELECT " & strQuery & " FROM SoftwareLicensingService" )
    For each objService in colServices
        QuitIfError()
        Exit For
    Next
    set GetServiceObject = objService
End Function

而GetProductCollection则是从SoftwareLicensingProduct获取指定的产品状态

Function GetProductCollection(strSelect, strWhere)
    Dim colProducts
    Dim objProduct

    On Error Resume Next

    If strWhere = EmptyWhereClause Then
        Set colProducts = g_objWMIService.ExecQuery("SELECT " & strSelect & " FROM SoftwareLicensingProduct")
        QuitIfError()
    Else
        Set colProducts = g_objWMIService.ExecQuery("SELECT " & strSelect & " FROM SoftwareLicensingProduct WHERE " & strWhere)
        QuitIfError()
    End If

    For each objProduct in colProducts
    Next

    QuitIfError()

    set GetProductCollection = colProducts
End Function

OK,这里已经大致清除激活的代码原理了,其他的功能基本也是依赖于WMI查询的对象。

而这些WMI对象则是通过对应的COMAPI进行方法调用,以执行进一步的命令。


参考文献:Calling a WMI Method - Windows applications - Microsoft Docs


本文会经常更新,请阅读原文: https://xinyuehtx.github.io/post/slmgr%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名黄腾霄(包含链接: https://xinyuehtx.github.io ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系