小白初识C++逆向

CMakeLists.txt(Qt6 + Widgets)入门逆向最舒服的一条路就是先写一个你完全理解的程序,再用 IDA + x64dbg 去“拆自己”,学习成本最低、反馈最快。下面我将用一个自己写的Qt6登录/注册 Demo,后面再介绍怎么在 IDA 和 x64dbg 里一步步定位关键逻辑。

目标1:做一个 “可逆向练习”的登录/注册程序

功能设计:

  • 注册:用户名 + 密码 → 保存到本地(JSON 或 SQLite)
  • 登录:读本地数据 → 校验
  • UI:两个页面(QStackedWidget):Login / Register
  • 校验函数:bool checkCredentials(user, pass)

1) 目录结构

创建文件夹 QtLoginLab/,里面放这些文件:

QtLoginLab/ CMakeLists.txt main.cpp mainwindow.h mainwindow.cpp auth.h auth.cpp storage.h storage.cpp 

CMakeLists.txt

cmake_minimum_required(VERSION 3.20) project(QtLoginLab LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) find_package(Qt6 REQUIRED COMPONENTS Widgets) add_executable(QtLoginLab main.cpp mainwindow.h mainwindow.cpp auth.h auth.cpp storage.h storage.cpp ) target_link_libraries(QtLoginLab PRIVATE Qt6::Widgets) # 逆向学习友好:Debug 更好看,Release 更接近真实 if (MSVC) target_compile_options(QtLoginLab PRIVATE $<$<CONFIG:Debug>:/Od /Zi> $<$<CONFIG:Release>:/O2>) target_link_options(QtLoginLab PRIVATE $<$<CONFIG:Debug>:/DEBUG>) else() target_compile_options(QtLoginLab PRIVATE $<$<CONFIG:Debug>:-O0 -g> $<$<CONFIG:Release>:-O2>) endif() 

main.cpp

#include <QApplication> #include "mainwindow.h" int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); } 

auth.h

#pragma once #include <QString> QString weakHash(const QString& s); // 练习用弱hash bool timingSafeEqual(const QString& a, const QString& b); 

auth.cpp

#include "auth.h" #include <QByteArray> QString weakHash(const QString& s) { QByteArray b = s.toUtf8(); quint32 x = 0x12345678; for (auto ch : b) { x = (x << 5) ^ (x >> 27) ^ quint8(ch); x += 0x9e3779b9; } return QString::number(x, 16); } bool timingSafeEqual(const QString& a, const QString& b) { QByteArray ba = a.toUtf8(); QByteArray bb = b.toUtf8(); if (ba.size() != bb.size()) return false; quint8 diff = 0; for (int i = 0; i < ba.size(); ++i) diff |= quint8(ba[i]) ^ quint8(bb[i]); return diff == 0; } 

storage.h

#pragma once #include <QString> #include <QMap> class Storage { public: explicit Storage(QString path); bool load(); bool save() const; bool hasUser(const QString& user) const; bool addUser(const QString& user, const QString& passHash); QString getHash(const QString& user) const; private: QString m_path; QMap<QString, QString> m_users; // user -> passHash }; 

storage.cpp

#include "storage.h" #include <QFile> #include <QJsonDocument> #include <QJsonObject> Storage::Storage(QString path) : m_path(std::move(path)) {} bool Storage::load() { QFile f(m_path); if (!f.exists()) return true; if (!f.open(QIODevice::ReadOnly)) return false; auto doc = QJsonDocument::fromJson(f.readAll()); if (!doc.isObject()) return false; m_users.clear(); auto obj = doc.object(); for (auto it = obj.begin(); it != obj.end(); ++it) m_users[it.key()] = it.value().toString(); return true; } bool Storage::save() const { QJsonObject obj; for (auto it = m_users.begin(); it != m_users.end(); ++it) obj[it.key()] = it.value(); QFile f(m_path); if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) return false; f.write(QJsonDocument(obj).toJson(QJsonDocument::Indented)); return true; } bool Storage::hasUser(const QString& user) const { return m_users.contains(user); } bool Storage::addUser(const QString& user, const QString& passHash) { if (m_users.contains(user)) return false; m_users[user] = passHash; return true; } QString Storage::getHash(const QString& user) const { return m_users.value(user); } 

mainwindow.h

#pragma once #include <QMainWindow> #include <QStackedWidget> #include <QLineEdit> #include <QLabel> #include "storage.h" class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); private slots: void gotoLogin(); void gotoRegister(); void onRegisterClicked(); void onLoginClicked(); private: void buildUi(); void setStatus(const QString& msg, bool ok); QStackedWidget* m_stack = nullptr; // Login page QWidget* m_loginPage = nullptr; QLineEdit* m_loginUser = nullptr; QLineEdit* m_loginPass = nullptr; // Register page QWidget* m_regPage = nullptr; QLineEdit* m_regUser = nullptr; QLineEdit* m_regPass = nullptr; QLabel* m_status = nullptr; Storage m_storage; }; 

mainwindow.cpp

#include "mainwindow.h" #include "auth.h" #include <QVBoxLayout> #include <QHBoxLayout> #include <QPushButton> #include <QStandardPaths> #include <QDir> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_storage([&]{ // 把 users.json 放到可写目录:AppData / ~/.local/share 等 const auto base = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QDir().mkpath(base); return base + "/users.json"; }()) { buildUi(); if (!m_storage.load()) { setStatus("LOAD_JSON_FAIL", false); } else { setStatus("READY", true); } } void MainWindow::buildUi() { setWindowTitle("QtLoginLab"); resize(420, 240); auto central = new QWidget(this); auto root = new QVBoxLayout(central); m_stack = new QStackedWidget(central); // ---- Login Page ---- m_loginPage = new QWidget(m_stack); { auto v = new QVBoxLayout(m_loginPage); auto title = new QLabel("Login", m_loginPage); title->setStyleSheet("font-size:18px; font-weight:600;"); m_loginUser = new QLineEdit(m_loginPage); m_loginUser->setPlaceholderText("Username"); m_loginPass = new QLineEdit(m_loginPage); m_loginPass->setPlaceholderText("Password"); m_loginPass->setEchoMode(QLineEdit::Password); auto btnRow = new QHBoxLayout(); auto loginBtn = new QPushButton("Login", m_loginPage); auto toRegBtn = new QPushButton("Go Register", m_loginPage); btnRow->addWidget(loginBtn); btnRow->addWidget(toRegBtn); v->addWidget(title); v->addWidget(m_loginUser); v->addWidget(m_loginPass); v->addLayout(btnRow); connect(loginBtn, &QPushButton::clicked, this, &MainWindow::onLoginClicked); connect(toRegBtn, &QPushButton::clicked, this, &MainWindow::gotoRegister); } // ---- Register Page ---- m_regPage = new QWidget(m_stack); { auto v = new QVBoxLayout(m_regPage); auto title = new QLabel("Register", m_regPage); title->setStyleSheet("font-size:18px; font-weight:600;"); m_regUser = new QLineEdit(m_regPage); m_regUser->setPlaceholderText("New Username"); m_regPass = new QLineEdit(m_regPage); m_regPass->setPlaceholderText("New Password"); m_regPass->setEchoMode(QLineEdit::Password); auto btnRow = new QHBoxLayout(); auto regBtn = new QPushButton("Create Account", m_regPage); auto toLoginBtn = new QPushButton("Go Login", m_regPage); btnRow->addWidget(regBtn); btnRow->addWidget(toLoginBtn); v->addWidget(title); v->addWidget(m_regUser); v->addWidget(m_regPass); v->addLayout(btnRow); connect(regBtn, &QPushButton::clicked, this, &MainWindow::onRegisterClicked); connect(toLoginBtn, &QPushButton::clicked, this, &MainWindow::gotoLogin); } m_stack->addWidget(m_loginPage); m_stack->addWidget(m_regPage); m_status = new QLabel("STATUS", central); m_status->setTextInteractionFlags(Qt::TextSelectableByMouse); root->addWidget(m_stack); root->addWidget(m_status); setCentralWidget(central); m_stack->setCurrentWidget(m_loginPage); } void MainWindow::setStatus(const QString& msg, bool ok) { m_status->setText(msg); m_status->setStyleSheet(ok ? "color: #1a7f37;" : "color: #c62828;"); } void MainWindow::gotoLogin() { m_stack->setCurrentWidget(m_loginPage); setStatus("READY_LOGIN", true); } void MainWindow::gotoRegister() { m_stack->setCurrentWidget(m_regPage); setStatus("READY_REGISTER", true); } void MainWindow::onRegisterClicked() { const QString user = m_regUser->text().trimmed(); const QString pass = m_regPass->text(); if (user.isEmpty() || pass.isEmpty()) { setStatus("REG_EMPTY_INPUT", false); return; } if (m_storage.hasUser(user)) { setStatus("USER_EXISTS", false); return; } const QString h = weakHash(pass); if (!m_storage.addUser(user, h)) { setStatus("ADD_USER_FAIL", false); return; } if (!m_storage.save()) { setStatus("WRITE_JSON_FAIL", false); return; } setStatus("WRITE_JSON_OK", true); gotoLogin(); } void MainWindow::onLoginClicked() { const QString user = m_loginUser->text().trimmed(); const QString pass = m_loginPass->text(); if (user.isEmpty() || pass.isEmpty()) { setStatus("LOGIN_EMPTY_INPUT", false); return; } if (!m_storage.hasUser(user)) { setStatus("NO_SUCH_USER", false); return; } const QString inputHash = weakHash(pass); const QString savedHash = m_storage.getHash(user); if (timingSafeEqual(inputHash, savedHash)) { setStatus("AUTH_OK", true); } else { setStatus("AUTH_FAIL", false); } } 

2) 编译运行

这里直接用Qt Creator打开CMakeLists.txt

第一次打开时会弹 Configure Project。需要你自己去配置一下。

配置完之后点击点左下角 绿色 ▶ Run。如果一切正常,你应该会看到一个窗口底部有状态字符串(READY / AUTH_OK / AUTH_FAIL)



目标2:逆向程序

逆向就是一个不断尝试,推理,反向证明的过程。我们不能盲目的去尝试,一个程序都有它自己本身的特征,常见的特征有:系统函数,字符串以及一些通用业务所有的特征。我们可以根据这些去下断点。X64动态调试,IDA静态分析去证明我们找对了地方。

1. 先用x64dbg调试

1. 用 x64dbg(x64) 打开 QtLoginLab.exe(64 位就用 x64dbg)

2. 先按 F9 跑到程序主界面稳定显示,如下图所示:

3.非常明显可以看到弹出来的是一个对话框,那么它大概率会调用系统函数MessageBox。我们就根据这个来下断点。打开x64dbg, 在CPU窗口里,按住CTRL+G然后分别输入MessageBoxW,MessageBoxA

然后鼠标移动到这一行按F2 下断点,同理,对MessageBoxA下断点也是如此。

4.下完断点后我们可以点这个窗口,就可以看到我们自己下的断点位置

5.然后我们输入随便的用户名密码点登录按钮,看他会不会出来弹窗来告诉我们登录成功还是失败。如果出现弹窗且如果断到了 MessageBox,我们就看调用栈往上两三层,通常就是“判断成功/失败”的函数。

可惜的是我们自己写的程序,并没有弹窗来告诉我我们登录成功与否。那对于密码校验,很可能用的就是ucrtbase.memcmp, msvcrt.memcmp,ucrtbase.strcmp,msvcrt.strcmp这些函数,我们不确定哪些存在的话就都试一遍,能下成功就会生效。但在这之前,我们先把之前的断点删掉以免影响我们的判断,右键那一行,点击删除即可。


 

我们接着下断点,这里笔者在strcmp下了一个断点,可以看到已经成功断住。

但是他是不是校验我们账号密码的函数呢,我们要接着分析。我们先来观察寄存器窗口。

此时RCX = 第一个字符串,RDX = 第二个字符串。且截图里已经说明

RCX -> "QMenuBar" RDX -> "QPaintDevice" 

这说明程序正在比较两个字符串。翻译成C++代码就是

strcmp("QMenuBar", "QPaintDevice")

好了,可以证明这次strcmp 和“登录密码”无关。我们再尝试别的路子,我们通过刚刚那个窗口其实还能得到一个信息就是登录结果是通过 QLabel::setText() 显示的,字符串 NO_SUCH_USER 一定是在“登录判断结果之后”才被用到。所以我们的策略是:从 NO_SUCH_USER 这个字符串反向,找到“是谁决定用它的”。这时我们就要用到IDA去分析了。

2. 用IDA去分析

打开IDA后,打开 Strings 窗口(快捷键:Shift + F12),在字符串列表里搜索:NO_SUCH_USER

搜索完之后如图所示:


然后双击这一行会跳到如下图所示的地方:

从上图我们得到重要信息 DATA XREF: sub_1400026C0+85↑o。这说明了这个sub_1400026C0函数用了这个字符串,我们直接搜这个函数,操作如下图所示:

双击函数后显示如下

接着我们按F5直接看他的伪代码,如下图所示:

发现这个函数就是判断登录是否成功的一个重要函数,这就是一个关键点。接着我们拿着这个地址去x64dbg里面,我们看他是否能断到,这里作者的函数的地址是 “140002910”,我们尝试一下在x64dbg里能不能断到。


结果发现他是无效地址,哈哈。这是因为IDA里我们没有加上执行文件模块的偏移,我们现在x64dbg下找一下,先点击视图再点模块


这里作者的qtloginlab.exe的基址是00007FF6C1870000,可以用下面操作复制


拿到了基址,我们在IDA里面修正一下


在这个输入框里输入0x7FF6C1870000,然后点击OK让他修正


修正完之后我们在用找字符串的方式在IDA里找一下那个关键函数。


笔者又回到这里了,可以看到函数地址跟之前已经是不一样的了。我们把这串地址拿到放到x64dbg里看一下能不能找到,笔者这里是有效的。


我们把断点打到这,点击登录,看会不会断进来。断成功了说明我们找的没错。
我们在结合反汇编代码找一下判断登录逻辑的函数。可以找到这个下面这个关键的判断:


这里就是输入了已经注册的用户名,接着来判断密码正不正确的位置。我们输一个已经注册过的用户名,但输入一个错误的密码,点击登录


发现确实断到了这里,同时也证明了我们的猜想,说明我们找对了。同时在此时我们观察一下寄存器窗口哦,发现ZF是1,ZF = 1 ⇒ 条件“为 0 / 为假”。这样再往下运行是不会成功的,我们改一下ZF的值看一下效果会怎样。右键ZF,点击切换,再按F9运行。


可以看到效果,我们输入了正确的值,但是输入了错误的密码,但这样我们还是登录成功了,下方已经显示AUTH_OK。说明我们的努力是有效的。


但是如何让他永久生效呢?可以参考一下下面的文章x64dbg使用详解 - CharyGao - 博客园
作者也简单阐述一下
1.鼠标选中需要修改的汇编代码(可选中多行),选择“二进制” -> “编辑”(或者使用快捷键Ctrl + E)



2、修改十六进制代码后,点击确定。

3、修改十六进制代码后,按下快捷键CTRL+ P,或者右击,选择“补丁”,弹出“补丁对话框”


4、选择“修补文件”按钮,另存为


5. 接着命名一个exe,然后保存,这样就永久修改好了

Read more

2026最新|GitHub 启用双因素身份验证 2FA 教程:TOTP.app 一键生成动态验证码(新手小白图文实操)

2026最新|GitHub 启用双因素身份验证 2FA 教程:TOTP.app 一键生成动态验证码(新手小白图文实操)

2026最新|GitHub 启用双因素身份验证 2FA 教程:TOTP.app 一键生成动态验证码(新手小白图文实操) 如果你最近登录 GitHub 时被提示“启用双因素身份验证(2FA)”,别慌——这就是在你输入密码后,再增加一道“动态验证码”的安全锁。本文用TOTP.app(可下载/可在线) 带你从 0 到 1 完成 GitHub 的 2FA 配置,全程保留原图与链接,按步骤照做就能成功。 关键词:GitHub 2FA、GitHub 双因素身份验证、GitHub 启用 2FA、GitHub TOTP、GitHub 动态验证码、GitHub 账号安全、GitHub 登录保护、

By Ne0inhk
GitHub 热榜项目 - 日榜(2026-1-10)

GitHub 热榜项目 - 日榜(2026-1-10)

GitHub 热榜项目 - 日榜(2026-1-10) 生成于:2026-1-10 统计摘要 共发现热门项目: 12 个 榜单类型:日榜 本期热点趋势总结 本期GitHub热榜显示AI智能体开发工具正席卷开发者社区,Claude Code、opencode等项目通过自然语言交互极大提升编码效率,Chrome DevTools MCP和UI-TARS-desktop则推动多模态智能体与开发工具深度集成,同时TailwindCSS持续领跑前端工具链,NetBird提供现代化安全网络方案,反映出开发者正积极采用AI助手优化工作流,并重点关注智能体工具链集成、实用型开发工具及基础设施安全三大趋势,这些高质量开源方案切实提升了开发体验与工程效率。 1. ChromeDevTools/chrome-devtools-mcp * 🏷️ 项目名称:ChromeDevTools/chrome-devtools-mcp * 🔗 项目地址: https://github.com/ChromeDevTools/chrome-devtools-mcp * ⭐ 当前 Star 数:

By Ne0inhk

飞书机器人通知:任务完成自动推送消息提醒用户查收结果

飞书机器人通知:任务完成自动推送消息提醒用户查收结果 在档案馆管理员老李的日常工作中,有一项重复而繁琐的任务——接收家属寄来的黑白老照片扫描件,手动上传到修复工具,等待几十分钟处理完成后,再逐一截图回复:“您的照片已修复,请查收。”这样的流程不仅效率低下,还容易因遗忘或延迟导致用户体验下降。直到他所在单位接入了一个新系统:照片一上传,AI自动修复着色,完成后飞书机器人立刻弹出一条带预览链接的消息:“【老照片修复完成】您提交的照片已成功上色!”整个过程无需人工干预。 这背后并非魔法,而是DDColor图像着色模型 + ComfyUI可视化工作流 + 飞书机器人自动化通知三者协同构建的一套“智能处理—状态感知—即时反馈”闭环系统的落地实践。这套方案正悄然改变着AI应用的传统交互模式。 从“无感运行”到“主动告知”:为什么需要自动化通知? 当前大多数AI图像处理系统仍停留在“执行即结束”的阶段。用户点击“开始”,然后盯着进度条猜测何时完成;或者干脆切换窗口去做别的事,结果忘了回来查看输出文件夹。这种被动式交互极大削弱了AI本应带来的便捷性。 更深层次的问题在于,当多个任务并行时,缺

By Ne0inhk

本地使用ComfyUI运行Stable Diffusion 3.5

本地使用 ComfyUI 运行 Stable Diffusion 3.5-FP8 你有没有试过用一张消费级显卡,在不到两分钟内生成一张细节拉满的 1024×1024 分辨率图像?现在,这已经不是幻想。随着 Stable Diffusion 3.5-FP8 的发布,开源文生图模型正式迈入“高效推理”时代——不仅画质不输原版,速度更快、显存更省,甚至能在 RTX 3060 上流畅跑起来。 而搭配 ComfyUI 这个高度模块化的前端工具,整个部署过程变得异常轻量且可控。本文将带你从零开始,一步步在本地搭建这套高性能量化系统,并避开国内用户最头疼的网络和路径问题。 硬件要求没你想的那么高 很多人一听到 SD3.5 就下意识觉得“得上专业卡”,其实那是针对未量化的大模型版本。FP8 版本通过 8-bit 浮点精度压缩,大幅降低了计算负载和内存占用。 实测表明:

By Ne0inhk