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





5.00/5 (5投票s)
小版本更新,改进了 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_DWORD
和 REG_SZ
设置。您只能获取 REG_DWORD
和 REG_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日:初始版本