feat(loader): randomize injector window title and jitter its size

Anti-fingerprint hardening for the loader window:

- The OS-level window title (read by GetWindowTextW / window scanners) is
  now a fresh random alphanumeric string on every launch instead of the
  fixed "OpenZen Loader" text. The visible custom title bar keeps showing
  the branded name, so the UI is unchanged.
- The window size gets a per-launch random jitter of +/-10px on each axis
  (base 760x500), with the minimum size lowered by the jitter so a negative
  jitter isn't clamped.

Note: the Win32 *class* name is intentionally not randomized here — a Qt6
top-level window's class atom is fixed at CreateWindow time and Qt 6.10
exposes no hook to customize it (SetClassLongPtr cannot rename a class),
so randomizing it would require replacing the Qt window with a hand-rolled
native window. Title + size are the practical fingerprint vectors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Shirona1337
2026-06-20 01:18:08 +08:00
parent 941b69203b
commit 8f53eda70e
+38 -6
View File
@@ -15,6 +15,7 @@
#include <QPainterPath> #include <QPainterPath>
#include <QParallelAnimationGroup> #include <QParallelAnimationGroup>
#include <QPropertyAnimation> #include <QPropertyAnimation>
#include <QRandomGenerator>
#include <QString> #include <QString>
#include <QTimer> #include <QTimer>
#include <QVBoxLayout> #include <QVBoxLayout>
@@ -32,6 +33,28 @@ namespace loader {
namespace { namespace {
constexpr int kCornerRadius = 12; constexpr int kCornerRadius = 12;
constexpr int kBaseWidth = 760;
constexpr int kBaseHeight = 500;
constexpr int kSizeJitter = 10; // ± px of random size jitter applied per launch
// Random alphanumeric identifier (first char always a letter). Used to give the
// loader window a non-constant Win32 title each launch so a scanner can't match
// it against a fixed window-text string.
QString randomIdent(int minLen, int maxLen) {
static const char kAlphabet[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
constexpr int kLetters = 52; // leading a-zA-Z only
constexpr int kAll = int(sizeof(kAlphabet)) - 1;
QRandomGenerator* rng = QRandomGenerator::global();
const int len = minLen + int(rng->bounded(quint32(maxLen - minLen + 1)));
QString s;
s.reserve(len);
s.append(QLatin1Char(kAlphabet[rng->bounded(kLetters)]));
for (int i = 1; i < len; ++i) {
s.append(QLatin1Char(kAlphabet[rng->bounded(kAll)]));
}
return s;
}
QString fromW(const std::wstring& w) { QString fromW(const std::wstring& w) {
return QString::fromWCharArray(w.c_str(), static_cast<int>(w.size())); return QString::fromWCharArray(w.c_str(), static_cast<int>(w.size()));
@@ -60,8 +83,14 @@ MainWindow::MainWindow(QWidget* parent)
setWindowFlags(Qt::FramelessWindowHint | Qt::Window); setWindowFlags(Qt::FramelessWindowHint | Qt::Window);
setAttribute(Qt::WA_TranslucentBackground); setAttribute(Qt::WA_TranslucentBackground);
setAttribute(Qt::WA_NoSystemBackground); setAttribute(Qt::WA_NoSystemBackground);
setMinimumSize(760, 500); // Base size with a small random jitter (±kSizeJitter px) on each axis so the
resize(760, 500); // window dimensions aren't a constant fingerprint. Minimum is lowered by the
// jitter so a negative jitter isn't clamped back to the base size.
setMinimumSize(kBaseWidth - kSizeJitter, kBaseHeight - kSizeJitter);
QRandomGenerator* rng = QRandomGenerator::global();
const int jitterW = int(rng->bounded(quint32(2 * kSizeJitter + 1))) - kSizeJitter;
const int jitterH = int(rng->bounded(quint32(2 * kSizeJitter + 1))) - kSizeJitter;
resize(kBaseWidth + jitterW, kBaseHeight + jitterH);
styleApp(); styleApp();
buildUi(); buildUi();
@@ -75,12 +104,15 @@ MainWindow::MainWindow(QWidget* parent)
void MainWindow::buildUi() { void MainWindow::buildUi() {
#ifdef OPENZEN_BUILD_REVISION #ifdef OPENZEN_BUILD_REVISION
const QString winTitle = QStringLiteral("OpenZen Loader · build %1") const QString displayTitle = QStringLiteral("OpenZen Loader · build %1")
.arg(QString::fromLatin1(OPENZEN_BUILD_REVISION).left(7)); .arg(QString::fromLatin1(OPENZEN_BUILD_REVISION).left(7));
#else #else
const QString winTitle = QStringLiteral("OpenZen Loader"); const QString displayTitle = QStringLiteral("OpenZen Loader");
#endif #endif
setWindowTitle(winTitle); // The OS-level window title (what GetWindowTextW and window scanners read) is
// randomised on every launch so it can't be matched against a fixed string.
// The visible custom title bar below still shows the branded name.
setWindowTitle(randomIdent(8, 16));
auto* central = new QWidget(this); auto* central = new QWidget(this);
central->setAttribute(Qt::WA_TranslucentBackground); central->setAttribute(Qt::WA_TranslucentBackground);
@@ -90,7 +122,7 @@ void MainWindow::buildUi() {
root->setSpacing(0); root->setSpacing(0);
titleBar_ = new TitleBar(central); titleBar_ = new TitleBar(central);
titleBar_->setTitleText(winTitle); titleBar_->setTitleText(displayTitle);
root->addWidget(titleBar_); root->addWidget(titleBar_);
auto* body = new QWidget(central); auto* body = new QWidget(central);