吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 725|回复: 7
上一主题 下一主题
收起左侧

[Windows] C++控制台程序模拟SQL注入与逆向分析(含视频)

[复制链接]
跳转到指定楼层
楼主
小菜鸟一枚 发表于 2026-6-24 20:36 回帖奖励

《C++控制台程序模拟SQL注入与逆向分析》

同学们好,欢迎回来,我又来胡说八道了,练习讲课找工作。

之前我们用C++和IDA玩过函数重载、引用和指针的底层分析。今天我们把思路往前推一步——看看程序在真实世界中是怎么被攻击的。

我们今天的目标是:

用C++写一个带SQL注入漏洞的登录程序,看看攻击者是怎么绕过密码的;

用IDA打开这个程序,从汇编层面看看漏洞到底长什么样。

你会发现,无论代码写得多么高级,漏洞的本质,最终都会体现在底层的指令里。准备好了吗?我们开始。

第一部分:写一个有漏洞的登录程序

(打开Dev-C++,新建一个项目,边写边讲)

我们先写一个最简单的控制台登录程序。它的功能是——输入用户名和密码,查询数据库,如果匹配就登录成功。

但为了演示漏洞,我们先不用真实的数据库,而是用内存数据模拟查询逻辑。这样就不需要安装MySQL,省去环境配置的时间。

#include <iostream>
#include <string>
using namespace std;

// 模拟数据库中的用户数据
struct User {
    string username;
    string password;
};

// 模拟数据库表(只有一条测试数据)
User users[] = {
    {"admin", "admin123"}
};

// 有漏洞的登录函数
bool loginLouDong(string username, string password) {
    // 关键漏洞:直接拼接用户输入来构造SQL语句(这里是模拟查询条件)
    string sql = "SELECT * FROM users WHERE username = '" 
                 + username + "' AND password = '" + password + "'";
    cout << "[执行的SQL] " << sql << endl;

    // 模拟查询:遍历内存数据
    for (int i = 0; i < 1; i++) {
        if (users[i].username == username && users[i].password == password) {
            return true;
        }
    }
    return false;
}

int main() {
    string username, password;

    cout << "========== 用户登录系统 ==========" << endl;
    cout << "请输入用户名: ";
    getline(cin, username);   
    cout << "请输入密码: ";
    getline(cin, password);   

    cout << endl << "--- 使用漏洞版本登录 ---" << endl;
    if (loginLouDong(username, password)) {
        cout << "登录成功!欢迎," << username << endl;
    } else {
        cout << "用户名或密码错误" << endl;
    }

    return 0;
}

大家看,关键问题就在这里:我们直接把用户输入的username和password拼接到查询语句里了。

正常用户输入admin和admin123时,SQL语句是:

SELECT * FROM users WHERE username = 'admin' AND password = 'admin123'

这个没问题,能查到数据。但攻击者不会这么老实。

假设攻击者知道系统里有一个用户叫admin,他在密码框里输入这样一串东西:

' OR '1'='1
(输入并回车)

我们来分析一下刚才发生了什么。程序拼接出来的SQL语句变成了:

SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1'

关键点来了:'1'='1'永远为真。整个条件的逻辑变成了“用户名等于admin 且 密码等于空 或者 1=1”。因为1=1恒成立,整个条件恒为真,所以程序返回了成功。

这就是SQL注入——攻击者通过输入特殊字符,改变了程序的执行逻辑。

第二部分:修复漏洞——使用参数化查询

那怎么修复这个漏洞呢?核心原则是:永远不要把用户输入直接拼接到SQL语句里。

在C++里,如果我们使用真实的数据库(比如MySQL Connector/C++),可以使用预处理语句(Prepared Statement)。这里我们模拟一下它的思想:

// 安全的登录函数(使用预处理语句思想)
bool loginSafe(string username, string password) {
    // 先用占位符(?)表示参数位置,不拼接用户输入
    string sql_template = "SELECT * FROM users WHERE username = ? AND password = ?";
    cout << "[SQL模板] " << sql_template << endl;

    // 模拟预处理:将用户输入作为纯数据绑定到参数位置
    cout << "[参数1(用户名)] " << username << endl;
    cout << "[参数2(密码)] " << password << endl;

    // 模拟查询:遍历内存数据(这里仍然使用等值比较)
    for (int i = 0; i < 1; i++) {
        if (users[i].username == username && users[i].password == password) {
            return true;
        }
    }
    return false;
}

注意这里的区别:SQL语句的骨架先用?占位,用户输入作为纯数据绑定进去。攻击者输入的' OR '1'='1在这个场景下,就是一个普通的字符串,不会被当作SQL代码执行。

我们在安全版本里输入同样的攻击字符串:

text
用户名: admin
密码: ' OR '1'='1

因为程序现在拿' OR '1'='1去和数据库里的admin123做等值比较,结果不匹配,所以登录失败。

第三部分:用IDA逆向分析——在底层看漏洞的本质

切换到IDA界面,好,现在关键来了。我们已经看到了漏洞在源代码层面是什么样的。但如果攻击者拿不到你的源代码呢? 他只有一个编译好的exe文件,还能不能找到这个漏洞?

答案是:能。 这就是逆向分析的力量。

我们把有漏洞的那个版本编译成exe,然后用IDA打开它。

先按Ctrl+F搜索main函数。

双击它,IDA帮我们定位到了使用这个字符串的地方。现在我们来看这段汇编代码:

; ============================================================
; 1. 准备参数(从右往左压栈)
; ============================================================
lea     eax, [ebp+p_password]      ; 获取临时密码对象的地址
mov     [esp+4], eax               ; 将密码地址作为第2个参数压栈

lea     eax, [ebp+p_username]      ; 获取临时用户名对象的地址
mov     [esp], eax                 ; 将用户名地址作为第1个参数压栈

; ============================================================
; 2. 调用登录函数
; ============================================================
mov     [ebp+fctx.call_site], 4    ; (异常处理相关,可忽略)
call    __Z12loginLouDongSsSs      ; 调用 loginLouDong(用户名, 密码)
                                    ; 返回值(bool)保存在 al 寄存器中

; ============================================================
; 3. 接收返回值
; ============================================================
mov     byte ptr [ebp+lpuexcpt], al ; 将 al 中的返回值保存到内存变量 lpuexcpt
                                     ; 为什么要保存?因为后续析构函数会污染 eax

双击Z12loginLouDongSsSs这个函数进去

第1步:开头 + 用户名

lea     eax, [ebp+__lhs]                          ; 准备一个临时字符串
mov     edx, [ebp+p_username]                   ; 获取用户输入的用户名
mov     [esp+8], edx                            ; 参数:用户名
mov     dword ptr [esp+4], offset __lhs         ; 参数:SQL开头部分
mov     [esp], eax                              ; 返回值位置
call    __ZStplIcSt11char_traitsIcESaIcEESbIT_T0_T1_EPKS3_RKS6_
                                                 ; 拼接:SQL开头 + 用户名

这一步完成:"SELECT * FROM users WHERE username = '" + 用户名

第2步:追加中间部分

lea     eax, [ebp+var_24]                       ; 新临时字符串
mov     dword ptr [esp+8], offset __rhs         ; 参数:"' AND password = '"
lea     edx, [ebp+__lhs]                        ; 上一步的结果
mov     [esp+4], edx
mov     [esp], eax
call    __ZStplIcSt11char_traitsIcESaIcEESbIT_T0_T1_ERKS6_PKS3_
                                                 ; 拼接:(上一步结果) + "' AND password = '"

这一步完成:"SELECT ... '" + 用户名 + "' AND password = '"

第3步:追加密码

lea     eax, [ebp+var_28]                       ; 新临时字符串
mov     edx, [ebp+p_password]                   ; 获取用户输入的密码
mov     [esp+8], edx
lea     edx, [ebp+var_24]                       ; 上一步的结果
mov     [esp+4], edx
mov     [esp], eax
call    __ZStplIcSt11char_traitsIcESaIcEESbIT_T0_T1_ERKS6_S8_
                                                 ; 拼接:(上一步结果) + 密码

这一步完成:"SELECT ... '" + 用户名 + "' AND password = '" + 密码

第4步:收尾

lea     eax, [ebp+sql]                          ; 最终的SQL字符串
mov     dword ptr [esp+8], offset asc_48903A    ; 参数:"'"
lea     edx, [ebp+var_28]                       ; 上一步的结果
mov     [esp+4], edx
mov     [esp], eax
call    __ZStplIcSt11char_traitsIcESaIcEESbIT_T0_T1_ERKS6_PKS3_
                                                 ; 拼接:(上一步结果) + "'"

这一步完成:"SELECT ... '" + 用户名 + "' AND password = '" + 密码 + "'"

大家注意看,这里的逻辑是:先把用户输入的内容和SQL模板拼在一起,然后再执行。这就是漏洞的根源——输入内容和代码指令在同一个阶段被处理了。

(对比安全版本的汇编)

我们再看安全版本的汇编:

; 输出 "[SQL"
call    __ZStls...    ; cout << "[SQL"

; 输出 sql_template(带 ? 的模板)
call    __ZStls...    ; cout << sql_template

; 输出 "["
call    __ZStls...    ; cout << "["

; 输出 用户名(作为纯数据)
call    __ZStls...    ; cout << p_username

; 输出 "["
call    __ZStls...    ; cout << "["

; 输出 密码(作为纯数据)
call    __ZStls...    ; cout << p_password

同学们注意看,在安全版本的整个反汇编代码中,你找不到任何一个 __ZStpl(字符串拼接函数)的调用。 程序没有把用户输入和SQL模板‘粘’在一起,而是分别输出展示。

SQL 模板先被发送到数据库,并且预编译(解析、检查语法、生成执行计划)——但此时参数位置用 ? 占位,没有具体值。

SELECT * FROM users WHERE username = ? AND password = ?

然后,用户输入的数据作为纯参数,在预编译完成之后才发送给数据库:

参数1(用户名):admin
参数2(密码):' OR '1'='1

数据库此时只做一件事:把 ? 替换成对应的参数值,然后执行已经编译好的执行计划。 注意,这个替换是纯数据替换,' OR '1'='1 不会被当作 SQL 语句的一部分来解析,它就是一个普通的字符串。

这就是我们今天要传达的核心思想:在源代码层面,漏洞只是一行拼接代码;在汇编层面,漏洞表现为“指令和数据混在一起处理”。

第四部分:用MySQL数据库演示SQL注入

同学们,我们前面用C++模拟了内存里的“数据库”,但那个毕竟是假的。现在,我们直接操作一个真实的MySQL数据库,看看这条漏洞语句在数据库里到底是怎么被执行的。

-- 1. 创建一个测试数据库,如果存在就删除重建,确保环境干净

DROP DATABASE IF EXISTS sql_injection_demo;
CREATE DATABASE sql_injection_demo;
USE sql_injection_demo;

-- 2. 创建一个简单的用户表
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    password VARCHAR(50) NOT NULL
);

-- 3. 插入一条正常用户的测试数据
INSERT INTO users (username, password) VALUES ('admin', 'admin123');

-- 4. 确认数据已经插入成功
SELECT * FROM users;

如果是一个正常用户,输入了用户名admin和密码admin123,拼接后的语句是这样的:

-- 模拟C++漏洞版拼接后的SQL(正常情况)
SELECT * FROM users WHERE username = 'admin' AND password = 'admin123';

我们在密码框里输入' OR '1'='1。

-- 模拟C++漏洞版拼接后的SQL(攻击情况)
SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1';

大家看结果!即使我们输入了错误的密码,这条语句依然返回了admin的记录。

同学们,接下来我们模拟安全版代码——演示__ZStpl的替代方案

参数化查询的核心是“先发模板,后传数据”。第一步,我们把SQL语句的骨架发给MySQL,让它先进行预编译。

-- 1. 准备(PREPARE)语句模板:执行命令
PREPARE stmt FROM 'SELECT * FROM users WHERE username = ? AND password = ?';

-- 2. 将用户输入(包含注入代码)作为纯文本数据保存到变量中
SET @UserName = 'admin';
SET @password = ''' OR ''1''=''1';  -- 这就是攻击者的输入

-- 3. 执行预编译好的语句,把纯数据填入占位符
EXECUTE stmt USING @username, @password;

-- 4.关闭模板
DEALLOCATE PREPARE stmt;

结果是什么?Empty set,空结果!没有数据返回!

说明这条注入语句没有生效,因为' OR '1'='1在预编译执行时,被当成了password字段的一个值去进行比较,而不是作为SQL逻辑去执行。它不是一个改变了查询逻辑的指令,而只是一个没有匹配到任何人的密码字符串。

第五部分:总结——攻防一体的思维方式

好,我们来快速回顾一下今天的内容。

第一,我们看到了SQL注入的本质——程序把用户输入当代码执行了。根本原因是“拼接”,解决方案是“参数化查询”。

第二,我们用IDA看到了漏洞在底层的样子——漏洞版代码中连续调用ZStpl进行字符串拼接,而安全版没有ZStpl调用,SQL模板和参数是分开处理的。这让我们从汇编层面理解了漏洞的本质。

第三,我们在真实的MySQL数据库中验证了两种写法的区别——拼接版返回了数据(登录成功),参数化查询版返回空结果(登录失败)。

第四,需要特别强调的是:C++本身并不具备“防注入”能力——字符串就是字符串,它只管拼接和传递。真正保护我们的是MySQL自己的预处理(PREPARE)机制。编程语言通过MySQL的C API接口,把这个“先编译模板、后绑定参数”的能力调用过来而已。所以,是数据库的预处理机制在底层挡住了注入,不是编程语言。

今天的课就到这里。

附录:本课核心概念速查表

  1. SQL注入(SQL Injection)
项目 说明
定义 攻击者通过输入特殊字符,篡改SQL语句的原始逻辑,从而绕过验证或获取未授权数据。
根本原因 程序将用户输入直接拼接到SQL语句中,导致用户输入被当作代码执行。
经典攻击载荷 ' OR '1'='1 —— 使WHERE条件恒为真,绕过密码验证。
核心本质 数据与指令混在一起。
  1. 参数化查询(Prepared Statement)
项目 说明
定义 先将SQL语句的“骨架”(带?占位符)发送给数据库进行预编译,再将用户输入作为纯数据绑定到参数位置。
为什么安全 数据库先固定了SQL语句的结构,用户输入只作为纯数据填入,不会被当作代码解析。
核心本质 数据与指令分离。
  1. C++底层函数名对照表
汇编中看到的符号 实际含义 对应C++代码
__ZStpl std::operator+(字符串拼接) string sql = "SELECT " + username;
__ZStls std::operator<<(流输出) cout << "Hello";
__ZStrs std::operator>>(流输入) cin >> username;
__ZNSsC1Ev std::string::string()(构造函数) string s;
__ZNSsD1Ev std::string::~string()(析构函数) 对象生命周期结束时自动调用
__ZNSs7compareERKSs std::string::compare()(字符串比较) if (s1 == s2)

命名规则:__Z 是C++编译器的标准前缀;St 代表 std 命名空间;pl = plus(+),ls = left shift(<<),rs = right shift(>>)。

  1. IDA反汇编关键指令速查表 指令 含义 在课程中的作用
    lea eax, [ebp+var] 取局部变量的地址 准备参数的地址
    mov [esp+4], eax 将值存入栈中(压参数) 函数调用前压参
    call __Z... 调用函数 执行函数调用
    mov byte ptr [ebp+lpuexcpt], al 保存返回值 从al寄存器取回返回值
    cmp byte ptr [ebp+lpuexcpt], 0 比较 判断登录是否成功
    jz loc_xxxxx 条件跳转(如果相等则跳转) 根据比较结果决定执行路径

通过网盘分享的文件:sql注入
链接: https://pan.baidu.com/s/1CRGkjlDjFOe8PzXis6XuRA?pwd=ht87 提取码: ht87

免费评分

参与人数 3吾爱币 +2 热心值 +3 收起 理由
52pojieplayer + 1 谢谢@Thanks!
laozhang4201 + 1 + 1 热心回复!
汉江龙王 + 1 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
schoolyears 发表于 2026-6-24 22:43
这种老古董漏洞不会有的
3#
52pojieplayer 发表于 2026-6-25 07:09
感谢分享!写得很详细,学到新知识点了【' OR '1'='1】
4#
ys1312 发表于 2026-6-25 08:27
5#
Huanxian 发表于 2026-6-25 09:24
无论怎么样都是学习到了
6#
ailmail 发表于 2026-6-25 10:54
继续学习  ,谢谢楼主
头像被屏蔽
7#
小腾子 发表于 2026-6-25 11:19
提示: 该帖被管理员或版主屏蔽
8#
kulouxiaohai 发表于 2026-6-25 11:55
这方面怎么学习啊?
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-6-26 07:20

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表