mirror of
https://github.com/tnodir/fort
synced 2024-11-15 08:46:03 +00:00
DriverPayload: Add payload signature.
This commit is contained in:
parent
df815f4ddf
commit
a6f63a1237
@ -1,8 +1,14 @@
|
||||
#include "driverpayload.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QCryptographicHash>
|
||||
#include <QFile>
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <qt_windows.h>
|
||||
|
||||
#include <bcrypt.h>
|
||||
|
||||
#include <util/fileutil.h>
|
||||
|
||||
namespace {
|
||||
@ -17,6 +23,11 @@ constexpr quint32 readUInt32(const char *cp, int offset)
|
||||
return *((quint32 *) (cp + offset));
|
||||
}
|
||||
|
||||
constexpr void writeUInt16(char *cp, int offset, quint16 v)
|
||||
{
|
||||
*((quint16 *) (cp + offset)) = v;
|
||||
}
|
||||
|
||||
constexpr void writeUInt32(char *cp, int offset, quint32 v)
|
||||
{
|
||||
*((quint32 *) (cp + offset)) = v;
|
||||
@ -56,85 +67,47 @@ void adjustPayloadPadding(QByteArray &data)
|
||||
}
|
||||
}
|
||||
|
||||
const char *getCoffHeader(const QByteArray &data)
|
||||
bool getCoffHeaderOffset(const QByteArray &data, int &outCoffHeaderOffset)
|
||||
{
|
||||
const char *cp = data.data();
|
||||
|
||||
// Check the input DOS header: "MZ"
|
||||
if (cp[0] != 'M' || cp[1] != 'Z') {
|
||||
qCritical() << "DOS Header error: Invalid signature";
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the input PE header offset
|
||||
const quint32 peOffset = readUInt32(cp, 0x3C);
|
||||
if (peOffset + 64 > data.size()) {
|
||||
qCritical() << "DOS Header error: Invalid PE Header Offset" << peOffset;
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the input PE header: "PE\0\0"
|
||||
const char *pe = cp + peOffset;
|
||||
if (*pe++ != 'P' || *pe++ != 'E' || *pe++ != '\0' || *pe++ != '\0') {
|
||||
qCritical() << "PE Header error: Invalid signature at offset:" << peOffset;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
qDebug() << "PE Header offset:" << peOffset << "COFF Header offset" << (pe - cp);
|
||||
|
||||
return pe;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DriverPayload::processArguments(const QStringList &args)
|
||||
{
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("Append payload to the signed executable file."
|
||||
"The result is stored in the output file.");
|
||||
|
||||
const QCommandLineOption inputOption(QStringList() << "i"
|
||||
<< "input",
|
||||
"Input file.", "input");
|
||||
parser.addOption(inputOption);
|
||||
|
||||
const QCommandLineOption outputOption(QStringList() << "o"
|
||||
<< "output",
|
||||
"Output file.", "output");
|
||||
parser.addOption(outputOption);
|
||||
|
||||
const QCommandLineOption payloadOption(QStringList() << "p"
|
||||
<< "payload",
|
||||
"Payload file.", "payload");
|
||||
parser.addOption(payloadOption);
|
||||
|
||||
parser.addHelpOption();
|
||||
|
||||
parser.process(args);
|
||||
|
||||
if (!parser.isSet(inputOption) || !parser.isSet(outputOption) || !parser.isSet(payloadOption)) {
|
||||
parser.showHelp(1);
|
||||
return;
|
||||
}
|
||||
|
||||
m_inputFilePath = parser.value(inputOption);
|
||||
m_outputFilePath = parser.value(outputOption);
|
||||
m_payloadFilePath = parser.value(payloadOption);
|
||||
}
|
||||
|
||||
bool DriverPayload::createOutputFile()
|
||||
{
|
||||
// Read input & payload files
|
||||
QByteArray inData = readFile(m_inputFilePath, 1 * 1024 * 1024);
|
||||
QByteArray payloadData = readFile(m_payloadFilePath, 3 * 1024 * 1024);
|
||||
if (inData.isEmpty() || payloadData.isEmpty())
|
||||
return false;
|
||||
}
|
||||
|
||||
outCoffHeaderOffset = pe - cp;
|
||||
|
||||
qDebug() << "PE Header offset:" << peOffset << "COFF Header offset" << outCoffHeaderOffset;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getCertTableSize(const QByteArray &data, int &outCertEntrySizeOffset, int &outCertTableOffset,
|
||||
int &outCertTableSize)
|
||||
{
|
||||
// Get a pointer to COFF Header
|
||||
const char *coffHeader = getCoffHeader(inData);
|
||||
if (!coffHeader)
|
||||
int coffHeaderOffset;
|
||||
if (!getCoffHeaderOffset(data, coffHeaderOffset))
|
||||
return false;
|
||||
|
||||
const char *coffHeader = data.data() + coffHeaderOffset;
|
||||
|
||||
// Get the COFF magic number
|
||||
constexpr int COFF_MAGIC_OFFSET = 20;
|
||||
|
||||
@ -158,7 +131,7 @@ bool DriverPayload::createOutputFile()
|
||||
const quint32 certTableOffset = readUInt32(coffHeader, CERTIFICATE_ENTRY_OFFSET);
|
||||
const quint32 certTableSize = readUInt32(coffHeader, CERTIFICATE_ENTRY_SIZE_OFFSET);
|
||||
|
||||
if (certTableSize == 0 || certTableOffset + certTableSize != inData.size()) {
|
||||
if (certTableSize == 0 || certTableOffset + certTableSize != data.size()) {
|
||||
qCritical().nospace() << "Certificate table error: Not at the end of input file (offset: "
|
||||
<< certTableOffset << " size:" << certTableSize
|
||||
<< "). Expected file size: " << (certTableOffset + certTableSize);
|
||||
@ -166,25 +139,243 @@ bool DriverPayload::createOutputFile()
|
||||
}
|
||||
|
||||
// Check Certificate table's size from its table
|
||||
if (certTableSize != readUInt32(inData.constData(), certTableOffset)) {
|
||||
if (certTableSize != readUInt32(data.constData(), certTableOffset)) {
|
||||
qCritical() << "Certificate table error: Size mismatch";
|
||||
return false;
|
||||
}
|
||||
|
||||
outCertEntrySizeOffset = coffHeaderOffset + CERTIFICATE_ENTRY_SIZE_OFFSET;
|
||||
outCertTableOffset = certTableOffset;
|
||||
outCertTableSize = certTableSize;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool signHash(BCRYPT_KEY_HANDLE keyHandle, const QByteArray &digest, QByteArray &outSignature)
|
||||
{
|
||||
NTSTATUS status;
|
||||
|
||||
BCRYPT_PKCS1_PADDING_INFO padInfo;
|
||||
padInfo.pszAlgId = BCRYPT_SHA256_ALGORITHM;
|
||||
|
||||
ULONG blobLen;
|
||||
status = BCryptSignHash(keyHandle, &padInfo, (PUCHAR) digest.data(), digest.size(), NULL, 0,
|
||||
&blobLen, BCRYPT_PAD_PKCS1);
|
||||
if (status) {
|
||||
qCritical() << "Sign Hash error: Size:" << status;
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray blob(blobLen, Qt::Uninitialized);
|
||||
|
||||
status = BCryptSignHash(keyHandle, &padInfo, (PUCHAR) digest.data(), digest.size(),
|
||||
(PUCHAR) blob.data(), blobLen, &blobLen, BCRYPT_PAD_PKCS1);
|
||||
if (status) {
|
||||
qCritical() << "Sign Hash error:" << status;
|
||||
return false;
|
||||
}
|
||||
|
||||
outSignature = blob;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool exportKey(BCRYPT_KEY_HANDLE keyHandle, LPCWSTR blobType, const QString &filePath)
|
||||
{
|
||||
NTSTATUS status;
|
||||
|
||||
ULONG blobLen;
|
||||
status = BCryptExportKey(keyHandle, NULL, blobType, NULL, 0, &blobLen, 0);
|
||||
if (status) {
|
||||
qCritical() << "Export Key error: Size:" << status << filePath;
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray blob(blobLen, Qt::Uninitialized);
|
||||
|
||||
status = BCryptExportKey(keyHandle, NULL, blobType, (PUCHAR) blob.data(), blobLen, &blobLen, 0);
|
||||
if (status) {
|
||||
qCritical() << "Export Key error:" << status << filePath;
|
||||
return false;
|
||||
}
|
||||
|
||||
return writeFile(filePath, { blob });
|
||||
}
|
||||
|
||||
bool createKeyPair(
|
||||
BCRYPT_KEY_HANDLE &keyHandle, BCRYPT_ALG_HANDLE algHandle, const QString &secretFilePath)
|
||||
{
|
||||
NTSTATUS status;
|
||||
|
||||
status = BCryptGenerateKeyPair(algHandle, &keyHandle, 4096, 0);
|
||||
if (status) {
|
||||
qCritical() << "Create Key error: Generate:" << status;
|
||||
return false;
|
||||
}
|
||||
|
||||
status = BCryptFinalizeKeyPair(keyHandle, 0);
|
||||
if (status) {
|
||||
qCritical() << "Create Key error: Finalize:" << status;
|
||||
return false;
|
||||
}
|
||||
|
||||
return exportKey(keyHandle, BCRYPT_RSAPRIVATE_BLOB, secretFilePath)
|
||||
&& exportKey(keyHandle, BCRYPT_RSAPUBLIC_BLOB, secretFilePath + ".pub");
|
||||
}
|
||||
|
||||
bool importKeyPair(
|
||||
BCRYPT_KEY_HANDLE &keyHandle, BCRYPT_ALG_HANDLE algHandle, const QString &secretFilePath)
|
||||
{
|
||||
NTSTATUS status;
|
||||
|
||||
const QByteArray blob = readFile(secretFilePath, 128 * 1024);
|
||||
if (blob.isEmpty())
|
||||
return false;
|
||||
|
||||
status = BCryptImportKeyPair(algHandle, NULL, BCRYPT_RSAPRIVATE_BLOB, &keyHandle,
|
||||
(PUCHAR) blob.data(), blob.size(), 0);
|
||||
if (status) {
|
||||
qCritical() << "Import Key error:" << status;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool openSecretFile(
|
||||
BCRYPT_KEY_HANDLE &keyHandle, BCRYPT_ALG_HANDLE algHandle, const QString &secretFilePath)
|
||||
{
|
||||
if (!FileUtil::fileExists(secretFilePath)) {
|
||||
return createKeyPair(keyHandle, algHandle, secretFilePath);
|
||||
} else {
|
||||
return importKeyPair(keyHandle, algHandle, secretFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
bool createPayloadSignature(
|
||||
const QByteArray &data, const QString &secretFilePath, QByteArray &outSignature)
|
||||
{
|
||||
NTSTATUS status;
|
||||
|
||||
BCRYPT_ALG_HANDLE algHandle;
|
||||
status = BCryptOpenAlgorithmProvider(&algHandle, BCRYPT_RSA_ALGORITHM, NULL, 0);
|
||||
if (status) {
|
||||
qCritical() << "Payload Sign error: Open Algorithm:" << status;
|
||||
return false;
|
||||
}
|
||||
|
||||
BCRYPT_KEY_HANDLE keyHandle;
|
||||
bool ok = openSecretFile(keyHandle, algHandle, secretFilePath);
|
||||
if (ok) {
|
||||
const QByteArray digest = QCryptographicHash::hash(data, QCryptographicHash::Sha256);
|
||||
|
||||
ok = signHash(keyHandle, digest, outSignature);
|
||||
|
||||
BCryptDestroyKey(keyHandle);
|
||||
}
|
||||
|
||||
BCryptCloseAlgorithmProvider(algHandle, 0);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DriverPayload::processArguments(const QStringList &args)
|
||||
{
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("Append payload to the signed executable file."
|
||||
"The result is stored in the output file.");
|
||||
|
||||
const QCommandLineOption inputOption(QStringList() << "i"
|
||||
<< "input",
|
||||
"Input file path.", "input");
|
||||
parser.addOption(inputOption);
|
||||
|
||||
const QCommandLineOption outputOption(QStringList() << "o"
|
||||
<< "output",
|
||||
"Output file path.", "output");
|
||||
parser.addOption(outputOption);
|
||||
|
||||
const QCommandLineOption payloadOption(QStringList() << "p"
|
||||
<< "payload",
|
||||
"Payload file path.", "payload");
|
||||
parser.addOption(payloadOption);
|
||||
|
||||
const QCommandLineOption secretOption(QStringList() << "s"
|
||||
<< "secret",
|
||||
"Secret file path.", "secret");
|
||||
parser.addOption(secretOption);
|
||||
|
||||
parser.addHelpOption();
|
||||
|
||||
parser.process(args);
|
||||
|
||||
if (!parser.isSet(inputOption) || !parser.isSet(outputOption) || !parser.isSet(payloadOption)
|
||||
|| !parser.isSet(secretOption)) {
|
||||
parser.showHelp(1);
|
||||
return;
|
||||
}
|
||||
|
||||
m_inputFilePath = parser.value(inputOption);
|
||||
m_outputFilePath = parser.value(outputOption);
|
||||
m_payloadFilePath = parser.value(payloadOption);
|
||||
m_secretFilePath = parser.value(secretOption);
|
||||
}
|
||||
|
||||
bool DriverPayload::createOutputFile()
|
||||
{
|
||||
// Read input & payload files
|
||||
QByteArray inData = readFile(m_inputFilePath, 1 * 1024 * 1024);
|
||||
QByteArray payloadData = readFile(m_payloadFilePath, 3 * 1024 * 1024);
|
||||
if (inData.isEmpty() || payloadData.isEmpty())
|
||||
return false;
|
||||
|
||||
// Get the Certificate entry section's info
|
||||
int certEntrySizeOffset;
|
||||
int certTableOffset;
|
||||
int certTableSize;
|
||||
if (!getCertTableSize(inData, certEntrySizeOffset, certTableOffset, certTableSize))
|
||||
return false;
|
||||
|
||||
// Payload's empty certificate header
|
||||
const QByteArray payloadHeader(8, '\0');
|
||||
|
||||
// Adjust padding of payload by required alignment
|
||||
adjustPayloadPadding(payloadData);
|
||||
|
||||
// Payload Signature
|
||||
QByteArray payloadSignature;
|
||||
if (!createPayloadSignature(payloadData, m_secretFilePath, payloadSignature))
|
||||
return false;
|
||||
|
||||
const int signatureSize = payloadSignature.size();
|
||||
adjustPayloadPadding(payloadSignature);
|
||||
|
||||
// Payload Info
|
||||
QByteArray payloadInfo(8, '\0');
|
||||
{
|
||||
char *cp = payloadInfo.data();
|
||||
writeUInt16(cp, 0, signatureSize);
|
||||
writeUInt16(cp, 2, payloadSignature.size());
|
||||
writeUInt32(cp, 4, payloadData.size());
|
||||
}
|
||||
|
||||
// Update the Certificate entry
|
||||
{
|
||||
char *cp = const_cast<char *>(coffHeader);
|
||||
const int newCertTableSize = certTableSize + payloadHeader.size() + payloadData.size();
|
||||
writeUInt32(cp, CERTIFICATE_ENTRY_SIZE_OFFSET, newCertTableSize);
|
||||
const int newCertTableSize = certTableSize + payloadHeader.size() + payloadData.size()
|
||||
+ payloadSignature.size() + payloadInfo.size();
|
||||
char *cp = inData.data();
|
||||
writeUInt32(cp, certEntrySizeOffset, newCertTableSize);
|
||||
writeUInt32(cp, certTableOffset, newCertTableSize);
|
||||
}
|
||||
|
||||
// Write the input & payload data into output file
|
||||
return writeFile(m_outputFilePath, { inData, payloadHeader, payloadData });
|
||||
if (!writeFile(m_outputFilePath,
|
||||
{ inData, payloadHeader, payloadSignature, payloadData, payloadInfo }))
|
||||
return false;
|
||||
|
||||
qDebug() << "Success:" << m_outputFilePath;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ private:
|
||||
QString m_inputFilePath;
|
||||
QString m_outputFilePath;
|
||||
QString m_payloadFilePath;
|
||||
QString m_secretFilePath;
|
||||
};
|
||||
|
||||
#endif // DRIVERPAYLOAD_H
|
||||
|
Loading…
Reference in New Issue
Block a user