65.9K
CodeProject 正在变化。 阅读更多。
Home

mscript 2.0.2:DLL 编写、注册表 DLL、exec() 错误处理

starIconstarIconstarIconstarIconstarIcon

5.00/5 (5投票s)

2022年4月6日

Apache

2分钟阅读

viewsIcon

9098

downloadIcon

119

小版本更新,改进了 DLL 安全性,添加了注册表 DLL,增强了 exec() 错误处理

引言

本文涵盖了 mscript 脚本语言和运行时的 2.0.2 小版本更新

如果您是 mscript 的新手,请访问 mscript.io 以获取最新的文档和下载。

对于开发者的视角,您还可以查看 关于 1.0 版本的原始文章关于 2.0 版本的第二篇文章

该项目在 GitHub 上是开源的。

DLL 安全性

当我为 mscript 添加 DLL 集成时,我接受了您提供的 DLL 路径,并使用了 LoadLibrary() 并希望一切顺利。在这个版本中,我要求 DLL 与 mscript EXE 位于同一目录中,在大多数系统上位于 C:\Program Files (x86)\mscript 中,并且不包含斜杠,不包含 ..,就在该文件夹中。此外,我要求 DLL 由与 mscript EXE 相同的主体和发布者签名。我希望所有的 DLL 开发都在 GitHub mscript 解决方案中进行,在那里我可以构建它、签名它并将其作为安装程序的一部分。如果您不关心成为安装程序的一部分或进行任何签名,请注意,如果 EXE 未签名,则它加载的 DLL 可以未签名。DLL 目录限制仍然有效。

以下是确定 EXE 或 DLL 的主体和发布者的代码

bin_crypt.h

#pragma once

#include <string>

namespace mscript
{
    struct bin_crypt_info
    {
        std::wstring publisher;
        std::wstring subject;
    };
    bin_crypt_info getBinCryptInfo(const std::wstring& filePath);
}
bin_crypt.cpp

#include "pch.h"
#include "bin_crypt.h"
#include "utils.h"

#include <wincrypt.h>
#include <wintrust.h>
#pragma comment(lib, "crypt32.lib")

#define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)

mscript::bin_crypt_info mscript::getBinCryptInfo(const std::wstring & filePath)
{
    mscript::bin_crypt_info crypt_info;

    // Error handling with Win32 "objects" is fun and easy!
    std::string exp_msg;
#define BIN_CRYPT_ERR(msg) { exp_msg = (msg); goto Cleanup; }

    // Pre-declare *everything*
    DWORD dwEncoding = 0, dwContentType = 0, dwFormatType = 0, dwSignerInfo = 0, dwData = 0;
    HCERTSTORE hStore = NULL;
    HCRYPTMSG hMsg = NULL;
    BOOL fResult = FALSE;
    PCMSG_SIGNER_INFO pSignerInfo = NULL;
    PCCERT_CONTEXT pCertContext = NULL;
    LPTSTR szName = NULL;

    // Get the certificate from the file
    fResult =
        CryptQueryObject
        (
            CERT_QUERY_OBJECT_FILE,
            filePath.c_str(),
            CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
            CERT_QUERY_FORMAT_FLAG_BINARY,
            0,
            &dwEncoding,
            &dwContentType,
            &dwFormatType,
            &hStore,
            &hMsg,
            NULL
        );
    if (!fResult)
        return crypt_info;

    // Get signer info
    fResult = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, NULL, &dwSignerInfo);
    if (!fResult)
        BIN_CRYPT_ERR("Getting binary security signer info failed");
    pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo);
    if (!fResult)
        BIN_CRYPT_ERR("Allocating signer info memory failed");
    fResult = CryptMsgGetParam(hMsg, CMSG_SIGNER_INFO_PARAM, 0, pSignerInfo, &dwSignerInfo);
    if (!fResult)
        BIN_CRYPT_ERR("Getting signer info failed");

    // Get the cert from the store
    CERT_INFO CertInfo;
    CertInfo.Issuer = pSignerInfo->Issuer;
    CertInfo.SerialNumber = pSignerInfo->SerialNumber;
    pCertContext = 
        CertFindCertificateInStore
        (
            hStore,
            ENCODING,
            0,
            CERT_FIND_SUBJECT_CERT,
            (PVOID)&CertInfo,
            NULL
        );
    if (!fResult)
        BIN_CRYPT_ERR("Finding signing certificate failed");

    // Get the issuer
    dwData =
        CertGetNameString
        (
            pCertContext,
            CERT_NAME_SIMPLE_DISPLAY_TYPE,
            CERT_NAME_ISSUER_FLAG,
            NULL,
            NULL,
            0
        );
    if (dwData == 0)
        BIN_CRYPT_ERR("Getting certificate issuer length failed");
    szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));
    if (!szName)
        BIN_CRYPT_ERR("Allocating memory for certificate issuer failed");
    if (!(CertGetNameString(pCertContext,
        CERT_NAME_SIMPLE_DISPLAY_TYPE,
        CERT_NAME_ISSUER_FLAG,
        NULL,
        szName,
        dwData)))
    {
        BIN_CRYPT_ERR("Getting certificate issuer failed");
    }
    crypt_info.publisher = szName;
    LocalFree(szName);
    szName = NULL;

    // Get the subject
    dwData =
        CertGetNameString
        (
            pCertContext,
            CERT_NAME_SIMPLE_DISPLAY_TYPE,
            0,
            NULL,
            NULL,
            0
        );
    if (dwData == 0)
        BIN_CRYPT_ERR("Getting certificate subject length failed");
    szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));
    if (!szName)
        BIN_CRYPT_ERR("Allocating memory for certificate subject failed");
    if (!(CertGetNameString(pCertContext,
        CERT_NAME_SIMPLE_DISPLAY_TYPE,
        0,
        NULL,
        szName,
        dwData)))
    {
        BIN_CRYPT_ERR("Getting certificate subject failed");
    }
    crypt_info.subject = szName;
    LocalFree(szName);
    szName = NULL;

Cleanup:
    if (pSignerInfo != NULL) LocalFree(pSignerInfo);
    if (szName != NULL) LocalFree(szName);
    if (pCertContext != NULL) CertFreeCertificateContext(pCertContext);
    if (hStore != NULL) CertCloseStore(hStore, 0);
    if (hMsg != NULL) CryptMsgClose(hMsg);

    // raiseError is an mscript utility function
    // you might throw std::runtime_error(exp_msg.c_str())
    if (!exp_msg.empty())
        raiseError(exp_msg.c_str()); 
    else
        return crypt_info;
}

注册表 DLL

在这个版本中,我添加了一个新的 DLL - mscript-registry.dll - 用于处理注册表键

msreg_create_key(key)
 - ensure that a registry key exists

msreg_delete_key(key)
 - ensure that a registry key no longer exists

msreg_get_sub_keys(key)
 - get a list of the names of the sub-keys of a key
 - just the names of the sub-keys, not full keys

msreg_put_settings(key, settings_index)
 - add settings to a key with the name-values in an index
 - you can send in number and string settings
 - to remove a setting, pass null as the index value

msreg_get_settings(key)
 - get the settings on a key in a name-values index

注意:您只能放置 REG_DWORDREG_SZ 设置。您只能获取 REG_DWORDREG_SZ 设置,其他设置将被忽略。

以下是为该 DLL 提供支持的源代码

registry.h

#pragma once
#include "pch.h"

using namespace mscript;

class registry
{
public:
	registry(const object::list& params)
		: m_params(params)
		, m_root_key(nullptr)
		, m_local_key(nullptr)
	{
		if (m_params.empty() || m_params[0].type() != object::STRING)
			raiseError("Registry functions take a first registry key string parameter");
		m_input_path = m_params[0].stringVal();

		size_t first_slash = m_input_path.find('\\');
		if (first_slash == std::wstring::npos)
			raiseWError(L"Invalid registry key (no slash): " + m_input_path);

		m_path = m_input_path.substr(first_slash + 1);
		if (m_path.empty())
			raiseWError(L"Invalid registry key (empty key): " + m_input_path);

		std::wstring reg_root;
		reg_root = toUpper(m_input_path.substr(0, first_slash));
		if (reg_root.empty())
			raiseWError(L"Invalid registry key (invalid m_root_key): " + m_input_path);

		if (reg_root == L"HKCR" || reg_root == L"HKEY_CLASSES_ROOT")
			m_root_key = HKEY_CLASSES_ROOT;
		else if (reg_root == L"HKCC" || reg_root == L"HKEY_CURRENT_CONFIG")
			m_root_key = HKEY_CURRENT_CONFIG;
		else if (reg_root == L"HKCU" || reg_root == L"HKEY_CURRENT_USER")
			m_root_key = HKEY_CURRENT_USER;
		else if (reg_root == L"HKLM" || reg_root == L"HKEY_LOCAL_MACHINE")
			m_root_key = HKEY_LOCAL_MACHINE;
		else if (reg_root == L"HKU" || reg_root == L"HKEY_USERS")
			m_root_key = HKEY_USERS;
		else
			raiseWError(L"Invalid registry key (unknown root): " + 
                          m_input_path + L" (" + reg_root + L")");
	}

	~registry()
	{
		if (m_local_key != nullptr)
			::RegCloseKey(m_local_key);
	}

	void createKey()
	{
		DWORD dwError = ::RegCreateKey(m_root_key, m_path.c_str(), &m_local_key);
		if (dwError != ERROR_SUCCESS)
			raiseWError(L"Creating key failed: " + m_input_path + L": " + 
                          getLastErrorMsg(dwError));
	}

	void deleteKey()
	{
		{
			DWORD dwError = ::RegOpenKey(m_root_key, m_path.c_str(), &m_local_key);
			if (dwError != ERROR_SUCCESS)
				raiseWError(L"Opening key failed: " + m_input_path + L": " + 
                              getLastErrorMsg(dwError));
		}

		{
			DWORD dwError = ::RegDeleteTree(m_local_key, m_path.c_str());
			if (dwError != ERROR_SUCCESS && dwError != ERROR_FILE_NOT_FOUND && 
			    dwError != ERROR_TOO_MANY_OPEN_FILES)
				raiseWError(L"Deleting key failed: " + m_input_path + L": " + 
                              getLastErrorMsg(dwError));
		}
	}

	object::list getSubKeys()
	{
		DWORD dwError = ::RegOpenKey(m_root_key, m_path.c_str(), &m_local_key);
		if (dwError != ERROR_SUCCESS)
			raiseWError(L"Opening key failed: " + m_input_path + L": " + 
                          getLastErrorMsg(dwError));

		const size_t MAX_VALUE_LEN = 16 * 1024;
		std::unique_ptr<wchar_t[]> value_name(new wchar_t[MAX_VALUE_LEN + 1]);
		value_name[MAX_VALUE_LEN] = '\0';

		object::list retVal;
		DWORD result = ERROR_SUCCESS;
		for (DWORD e = 0; ; ++e)
		{
			result = ::RegEnumKey(m_local_key, e, value_name.get(), MAX_VALUE_LEN);
			if (result == ERROR_NO_MORE_ITEMS)
				break;
			else if (result == ERROR_SUCCESS)
				retVal.push_back(std::wstring(value_name.get()));
			else
				raiseWError(L"Enumerating key failed: " + m_input_path + L": " + 
                              getLastErrorMsg(dwError));
		}
		return retVal;
	}

	void putKeySettings()
	{
		if (m_params.size() != 2 || m_params[1].type() != object::INDEX)
			raiseError("msreg_put_settings takes a registry key 
                        and an index of settings to put");
		object::index index = m_params[1].indexVal();

		DWORD dwError = ::RegOpenKey(m_root_key, m_path.c_str(), &m_local_key);
		if (dwError != ERROR_SUCCESS)
			raiseWError(L"Opening key failed: " + m_input_path + L": " + 
                          getLastErrorMsg(dwError));

		for (const auto& name : index.keys())
		{
			if (name.type() != object::STRING)
			{
				raiseWError(L"msreg_put_settings index key is not a string: " 
                            + name.toString());
			}

			object val = index.get(name);
			if (val.type() == object::NOTHING)
			{
				dwError = ::RegDeleteValue(m_local_key, name.stringVal().c_str());
				if (dwError != ERROR_SUCCESS)
					break;
			}
			else if (val.type() == object::NUMBER)
			{
				int64_t num_val = int64_t(round(val.numberVal()));
				if (num_val < 0)
					raiseWError(L"msreg_put_settings value must be a positive integer: " 
                                  + name.toString());
				else if (num_val > MAXDWORD)
					raiseWError(L"msreg_put_settings value must not exceed DWORD capacity: " 
                                + name.toString());
				DWORD dw_val = DWORD(num_val);
				dwError = ::RegSetKeyValue(m_local_key, nullptr, name.stringVal().c_str(), 
				REG_DWORD, LPCWSTR(&dw_val), sizeof(dw_val));
				if (dwError != ERROR_SUCCESS)
					raiseWError(L"Setting number value failed: " + m_input_path + L": " + 
					name.stringVal() + L": " + getLastErrorMsg(dwError));
			}
			else if (val.type() == object::STRING)
			{
				dwError = ::RegSetKeyValue(m_local_key, nullptr, name.stringVal().c_str(), 
				REG_SZ, val.stringVal().c_str(), 
                (val.stringVal().length() + 1) * sizeof(wchar_t));
				if (dwError != ERROR_SUCCESS)
					raiseWError(L"Setting string value failed: " + m_input_path + L": " + 
					name.stringVal() + L": " + getLastErrorMsg(dwError));
			}
			else
				raiseWError(L"msreg_put_settings value is not null, number, or string: " 
                              + val.toString());
		}
	}

	object::index getKeySettings()
	{
		object::index ret_val;
		DWORD dwError = ::RegOpenKey(m_root_key, m_path.c_str(), &m_local_key);
		if (dwError != ERROR_SUCCESS)
			raiseWError(L"Opening key failed: " + m_input_path + L": " 
                        + getLastErrorMsg(dwError));

		const size_t MAX_VALUE_LEN = 16 * 1024;
		std::unique_ptr<wchar_t[]> value_name(new wchar_t[MAX_VALUE_LEN + 1]);
		value_name[MAX_VALUE_LEN] = '\0';
		for (DWORD i = 0; ; ++i)
		{
			DWORD val_len = MAX_VALUE_LEN;
			DWORD dwValRet =
				::RegEnumValue(m_local_key, i, value_name.get(), &val_len, 
                               nullptr, nullptr, nullptr, nullptr);
			if (dwValRet != ERROR_SUCCESS)
				break;

			const DWORD flags = RRF_RT_REG_DWORD | RRF_RT_REG_SZ;
			DWORD type = 0;
			DWORD data_len = 0;
			dwError =
				::RegGetValue(m_local_key, nullptr, value_name.get(), 
                              flags, &type, nullptr, &data_len);
			if (dwError != ERROR_SUCCESS && dwError != ERROR_MORE_DATA)
				break;
			if (type == REG_DWORD)
			{
				DWORD data_val = 0;
				dwError =
					::RegGetValue(m_local_key, nullptr, value_name.get(), 
                                  flags, &type, &data_val, &data_len);
				if (dwError != ERROR_SUCCESS)
					raiseWError(L"Getting DWORD value failed: " + 
                                m_input_path + L": " + getLastErrorMsg(dwError));
				ret_val.set(std::wstring(value_name.get()), double(data_val));
			}
			else if (type == REG_SZ)
			{
				std::unique_ptr<wchar_t[]> value(new wchar_t[data_len + 1]);
				dwError =
					::RegGetValue(m_local_key, nullptr, value_name.get(), 
                                  flags, &type, value.get(), &data_len);
				if (dwError != ERROR_SUCCESS)
					raiseWError(L"Getting string value failed: " + 
                                m_input_path + L": " + getLastErrorMsg(dwError));
				ret_val.set(std::wstring(value_name.get()), std::wstring(value.get()));
			}
			//else - omitted
		}

		return ret_val;
	}

private:
	object::list m_params;

	std::wstring m_input_path;

	HKEY m_root_key;
	std::wstring m_path;

	HKEY m_local_key;
};

exec() 错误处理

exec() 现在如果被调用程序的退出代码不为零,则会引发错误。exec() 接受第二个参数,即选项的索引,其中之一是“ignore_errors”。当设置为 true 时,非零退出代码不会引发错误。您仍然可以在返回的索引中获取错误代码,键为“exit_code”。

作为一个实际的 mscript 示例,处理 .bat 文件很麻烦,那么如何创建一个 mscript 来...构建 mscript 呢?(mscript-builder.exe 是最近的 mscript EXE 的副本,总得从某个地方开始)。

? arguments.length() != 1
	>> Usage: mscript-builder.exe build.ms version
	>> version should be like "2.0.2"
	* exit(0)
}

$ version = arguments.get(0)
> "Finalizing version " + version

$ ignore_errors = index("ignore_errors", true)

>> Binaries...
* exec("rmdir /S /Q binaries", ignore_errors)
* exec("mkdir binaries")
* exec("xcopy /Y ..\mscript\Win32\Release\*.exe binaries\.")
* exec("xcopy /Y ..\mscript\Win32\Release\*.dll binaries\.")

>> Samples...
* exec("rmdir /S /Q samples", ignore_errors)
* exec("mkdir samples")
* exec("xcopy /Y ..\mscript\mscript-examples\*.* samples\.")

>> Resource hacking and signing...
$ modules = list("mscript2")
* modules.add("mscript-dll-sample")
* modules.add("mscript-timestamp")
* modules.add("mscript-registry")
@ module : modules
	> "..." + module
	
	$ ext = "dll"
	? module = "mscript2"
		& ext = "exe"
	}
	
	$ filename = module + "." + ext

	* exec("del resources.res", ignore_errors)
	* exec("ResourceHacker.exe -open resources-" + module + 
           ".rc -save resources.res -action compile")
	* exec("ResourceHacker.exe -open binaries\" + filename + 
           " -save binaries\" + filename + " -action addoverwrite -resource resources.res")
	* exec("signtool sign /f mscript.pfx /p foobar binaries\" + filename)
}

>> Building installer
* exec("AdvancedInstaller.com /rebuild mscript.aip")

>> Building site
* exec("rmdir /S /Q ..\mscript.io\releases\" + version, ignore_errors)
* exec("mkdir ..\mscript.io\releases\" + version)

* exec("mkdir ..\mscript.io\releases\" + version  + "\samples")
* exec("xcopy samples\*.* ..\mscript.io\releases\" + version + "\samples\.")

@ module : modules
	$ ext = "dll"
	? module = "mscript2"
		& ext = "exe"
	}
	
	$ filename = module + "." + ext
	* exec("xcopy /Y binaries\" + filename + " ..\mscript.io\releases\" + version + "\.")
}

>> Finalizing installer
* exec("xcopy /Y mscript-SetupFiles\mscript.msi ..\mscript.io\releases\" + version + "\.")
* exec("signtool sign /f mscript.pfx /p foobar ..\mscript.io\releases\" + 
        version + "\mscript.msi")

>> All done!

结论和关注点

DLL 安全性得到了显著提高,核心 exec() 例程现在支持合理的错误处理,并且您拥有一个用于处理注册表的简单 DLL。

尽情享用!

历史

  • 2022年4月5日:初始版本
© . All rights reserved.