浅析一款非驱动考试网关程序(一)

前言

监考程序需要对网络流量进行过滤,不允许除了考试网站以外的其他网站的访问。其实就是实现了一个小型的网关程序,一般地有驱动实现和非驱动实现两种方式。本文只针对一款简易的非驱动实现的监考程序进行分析。

注意:本文通过对某考试监考网关客户端程序的逆向分析研究,节选其中断网的实现过程进行讲解。所有内容均在本地计算机测试环境中完成,敏感标记均已加码处理,仅供参考,谢绝用于其他用途。

系列文章:

编号文章标题ID
1非驱动考试网关程序(一):断网模块136037076
2非驱动考试网关程序(二):USB热拔插

(暂未发布)


一、初步分析调用树

初步分析时,分析软件是否依赖相应的 Windows 接口,如果有,那么问题就会非常简单。我们使用 API Monitor 程序实现对程序的监视。

分析结果截图 1

在监视时,我们发现了关键的函数:GetIpForwardTable、DeleteIpForwardEntry、GetIpForwardTable2、DeleteIpForwardEntry2、CreateIpForwardEntry。

GetIpForwardTable 函数检索 IPv4 路由表。DeleteIpForwardEntry 函数删除本地计算机的 IPv4 路由表中的现有路由,而 DeleteIpForwardEntry2 则是 IPv6 版本。CreateIpForwardEntry 则用于恢复路由表。

由此,我们可以推测程序通过修改路由表实现断网的功能,这应该是非常简单了。

二、通过 IDA 深入分析

确定了关键的调用接口,我们接下来就要通过 IDA 去分析程序的具体实现。

我们从导入表很快定位到这个调用的具体实现,关键调用在 sub_4028E0 中(记住这个 IDA 标记的函数名)。

首先通过一次调用 GetIpForwardEntry 获取需要为 MIB_IPFORWARDROW 结构分配的内存的大小,随后,再次调用 GetIpForwardEntry 获取 IPv4 路由表。

分析结果截图 2

随后就是遍历这个链表:

分析结果截图 3

在遍历过程中删除路由:

分析结果截图 4

最后通过全局变量 pRoute 将原始的路由表存储起来:

分析结果截图 5

随后通过 sub_402690 检测并尝试删除默认的 IPv6 路由:

分析结果截图 6

sub_402690 的实现:

分析结果截图 7

随后分析 CreateIpForwardEntry,有两次需要用到该函数,一种情况是在删除所有路由后,恢复一部分路由,还有一种就是在考试完成时,完全恢复路由。

恢复部分:

分析结果截图 8

全部恢复(通过全局变量 pRoute): 

分析结果截图 9

至此,我们已经了解了该程序是如何实现考试断网的功能,但是我们知道只修改一次肯定不能防止之后其他程序恢复系统默认路由的。下面我们分析他是如何在考试期间防止路由恢复的。

我们注意到 API 监视日志中的 SetTimer 调用,以及窗口的消息线程中 GetMessageW 以每秒一次的频率测试 WM_TIMER 消息,于是猜测到程序可能通过计时器不停地拉闸删除 IPv4 路由。

然后,我们通过查找 SetTimer 的交叉引用,锁定了来源,这是程序的窗口回调:

我们注意到 SetTimer 设置 ID == 1 的计时器,时间间隔为 1 秒。

分析结果截图 10

在点击关闭客户端时,才会处理 KillTimer 删除计时器。 

分析结果截图 11

继续往下看,我们发现出现 WM_TIMER 消息时候,程序执行删除路由的例程 sub_4028E0,这个函数是前面分析过的:

分析结果截图 12

三、编写测试工具

分析了具体的程序的实现,接下来,我们可以尝试编写一款修改器模拟对抗对吧?

这里的方法就比较多了,毕竟该程序完全依赖 Win32 API,并且不做防护。

提供两种思路如下:

思路1:在程序启动时挂钩 DeleteIpForwardEntry 和 DeleteIpForwardEntry2 ,不做任何操作并返回成功。

思路2:在程序启动前备份路由表,程序启动后任意时间均可挂钩 GetMessageW 并拦截 WM_TIMER 消息(wPararm == 1),在消息中 KillTimer,或者不做任何操作均可。随后通知挂钩程序恢复原始路由表。

提供一份挂钩例程的 Dll 代码(不包含编程恢复原始路由表的处理代码):

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

#include <Windows.h>
#include "detours.h"
#include <ipmib.h>
#include <string>
#include <iphlpapi.h>
#include <atlstr.h>

#pragma comment(lib, "detours.lib")
#pragma comment( lib, "Iphlpapi.lib" )

PVOID pFunGetMessageW = NULL;

typedef BOOL(WINAPI* __GetMessageW)(
    LPMSG lpMsg,
    HWND  hWnd,
    UINT  wMsgFilterMin,
    UINT  wMsgFilterMax
);

BOOL WINAPI HookedGetMessageW(
    LPMSG lpMsg,
    HWND  hWnd,
    UINT  wMsgFilterMin,
    UINT  wMsgFilterMax
);

BOOL APIENTRY InstallHook();
BOOL APIENTRY UnInstallHook();

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        InstallHook();
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        UnInstallHook();
        break;
    }
    return TRUE;
}


BOOL APIENTRY InstallHook()
{
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    pFunGetMessageW = DetourFindFunction("User32.dll", "GetMessageW");
    DetourAttach(&pFunGetMessageW, HookedGetMessageW);
    LONG ret = DetourTransactionCommit();

    return ret == NO_ERROR;
}


BOOL APIENTRY UnInstallHook()
{
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());

    DetourDetach(&pFunGetMessageW, HookedGetMessageW);
    LONG ret = DetourTransactionCommit();
    return ret == NO_ERROR;
}


BOOL WINAPI HookedGetMessageW(
    LPMSG lpMsg,
    HWND  hWnd,
    UINT  wMsgFilterMin,
    UINT  wMsgFilterMax
)
{
    // 判断是否是我们要取消的计时器
    if (lpMsg->wParam == 1 && lpMsg->message == WM_TIMER)
    {
        // 删除计时器,解除路由检查
        lpMsg->message = WM_USER;
        KillTimer(lpMsg->hwnd, lpMsg->wParam);
        return TRUE;
    }

    return ((__GetMessageW)pFunGetMessageW)(
        lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax);
}

路由备份和检查的代码可以参考下面的代码:

CString SwitchStatusStr(int iSwitch)
{
    CString strSwitch;
    strSwitch.Format(_T("%d"), iSwitch);
    return strSwitch;
}

BOOL GetIsRestoreDefaultRoute(DWORD bToDeleteorAdd)
{
    BOOL bRet = FALSE;
    // Declare and initialize variables
    PMIB_IPFORWARDTABLE pIpForwardTable = NULL;
    PMIB_IPFORWARDROW pRow = NULL; // 默认网关,添加时使用
    DWORD dwSize = 0;
    BOOL bOrder = FALSE;
    DWORD dwStatus = 0;

    // 默认路由保存路径
    std::wstring wsFilePath = L"C:\\DefaultRoute.ini";

    // Identify the required size of the buffer.
    if (bToDeleteorAdd == 0)
    {
        dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder);
        if (dwStatus == ERROR_INSUFFICIENT_BUFFER)
        {
            // Allocate memory for the table.
            if (!(pIpForwardTable = (PMIB_IPFORWARDTABLE)malloc(dwSize)))
            {
                printf(("pipForwardTable Malloc failed. Out of memory.\n"));
                goto _END_;
            }
            // Retrieve the table.
            dwStatus = GetIpForwardTable(pIpForwardTable, &dwSize, bOrder);
        }

        if (dwStatus != ERROR_SUCCESS)
        {
            printf(("getIpForwardTable failed.\n"));
            if (pIpForwardTable)
            {
                free(pIpForwardTable);
            }
            goto _END_;
        }

        // Search for the required row in the table. The default gateway has a destination
        // of 0.0.0.0. Be aware the table continues to be searched, but only
        // one row is copied. This is to ensure that, if multiple gateways exist, all of them are deleted.
        for (DWORD i = 0; i < pIpForwardTable->dwNumEntries; i++)
        {
            if (pIpForwardTable->table[i].dwForwardDest == 0)
            {
                // Delete the old default gateway entry.
                if (bToDeleteorAdd == 0)
                {
                    if (!pRow) {
                        // Allocate some memory to store the row in; this is easier than filling
                        // in the row structure ourselves, and we can be sure we change only the
                        // gateway address.
                        pRow = (PMIB_IPFORWARDROW)malloc(sizeof(MIB_IPFORWARDROW));
                        if (!pRow) {
                            printf("Malloc failed. Out of memory.\n");
                            exit(1);
                        }
                        // Copy the row
                        memcpy(pRow, &(pIpForwardTable->table[i]),
                            sizeof(MIB_IPFORWARDROW));
                    }

                    // 先保存默认路由相关信息
                    printf("开始保存默认路由\n");
                    WritePrivateProfileString(L"RECOVER", L"dwForwardDest", SwitchStatusStr(pRow->dwForwardDest), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardMask", SwitchStatusStr(pRow->dwForwardMask), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardPolicy", SwitchStatusStr(pRow->dwForwardPolicy), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardNextHop", SwitchStatusStr(pRow->dwForwardNextHop), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardIfIndex", SwitchStatusStr(pRow->dwForwardIfIndex), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardType", SwitchStatusStr(pRow->dwForwardType), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardProto", SwitchStatusStr(pRow->dwForwardProto), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardAge", SwitchStatusStr(pRow->dwForwardAge), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardNextHopAS", SwitchStatusStr(pRow->dwForwardNextHopAS), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardMetric1", SwitchStatusStr(pRow->dwForwardMetric1), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardMetric2", SwitchStatusStr(pRow->dwForwardMetric2), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardMetric3", SwitchStatusStr(pRow->dwForwardMetric3), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardMetric4", SwitchStatusStr(pRow->dwForwardMetric4), wsFilePath.c_str());
                    WritePrivateProfileString(L"RECOVER", L"dwForwardMetric5", SwitchStatusStr(pRow->dwForwardMetric5), wsFilePath.c_str());

                    // 删除默认路由
                    printf("保存默认路由成功。\n");
                }

                bRet = TRUE;
                goto _END_;
            }
        }
    }
    else if (bToDeleteorAdd == 1) // 恢复默认路由
    {
        printf("正在恢复默认路由......\n");
        pRow = (PMIB_IPFORWARDROW)malloc(sizeof(MIB_IPFORWARDROW));

        pRow->dwForwardDest = GetPrivateProfileIntW(L"RECOVER", L"dwForwardDest", -1, wsFilePath.c_str());
        pRow->dwForwardMask = GetPrivateProfileIntW(L"RECOVER", L"dwForwardMask", -1, wsFilePath.c_str());
        pRow->dwForwardPolicy = GetPrivateProfileIntW(L"RECOVER", L"dwForwardPolicy", -1, wsFilePath.c_str());
        pRow->dwForwardNextHop = GetPrivateProfileIntW(L"RECOVER", L"dwForwardNextHop", -1, wsFilePath.c_str());
        pRow->dwForwardIfIndex = GetPrivateProfileIntW(L"RECOVER", L"dwForwardIfIndex", -1, wsFilePath.c_str());
        pRow->dwForwardType = GetPrivateProfileIntW(L"RECOVER", L"dwForwardType", -1, wsFilePath.c_str());
        pRow->dwForwardProto = GetPrivateProfileIntW(L"RECOVER", L"dwForwardProto", -1, wsFilePath.c_str());
        pRow->dwForwardAge = GetPrivateProfileIntW(L"RECOVER", L"dwForwardAge", -1, wsFilePath.c_str());
        pRow->dwForwardNextHopAS = GetPrivateProfileIntW(L"RECOVER", L"dwForwardNextHopAS", -1, wsFilePath.c_str());
        pRow->dwForwardMetric1 = GetPrivateProfileIntW(L"RECOVER", L"dwForwardMetric1", -1, wsFilePath.c_str());
        pRow->dwForwardMetric2 = GetPrivateProfileIntW(L"RECOVER", L"dwForwardMetric2", -1, wsFilePath.c_str());
        pRow->dwForwardMetric3 = GetPrivateProfileIntW(L"RECOVER", L"dwForwardMetric3", -1, wsFilePath.c_str());
        pRow->dwForwardMetric4 = GetPrivateProfileIntW(L"RECOVER", L"dwForwardMetric4", -1, wsFilePath.c_str());
        pRow->dwForwardMetric5 = GetPrivateProfileIntW(L"RECOVER", L"dwForwardMetric5", -1, wsFilePath.c_str());

        // Create a new route entry for the default gateway.
        dwStatus = CreateIpForwardEntry(pRow);
        printf("恢复默认路由成功。\n");
    }

_END_:
    if (pRow)
    {
        free(pRow);
    }
    // Free the memory. 
    if (pIpForwardTable)
    {
        free(pIpForwardTable);
    }
    return bRet;
}

以上代码仅供学习参考使用,请勿用于其他用途。

四、总结&后续更新

其实没有什么好总结的。主要建议就是不要直接用微软接口,如果你做监考程序的话最好还是用驱动,用自己的实现,不然的话 R3 的挂钩就就全完了。当然,我们需要秉持着诚信考试的原则。


【保留备用】

发布于:2024.02.06;更新于:2024.02.06。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/374811.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第十篇【传奇开心果系列】Python的OpenCV技术点案例示例:图像分割

传奇开心果短博文系列 系列短博文目录Python的OpenCV技术点案例示例系列短博文目录一、前言二、OpenCV图像分割介绍三、OpenCV分割算法示例代码四、归纳总结系列短博文目录 Python的OpenCV技术点案例示例系列 短博文目录 一、前言 OpenCV是一个广泛应用于计算机视觉和图像处…

2023年09月CCF-GESP编程能力等级认证Python编程五级真题解析

Python等级认证GESP(1~6级)全部真题・点这里 一、单选题(共15题,共30分) 第1题 近年来,线上授课变得普遍,很多有助于改善教学效果的设备也逐渐流行,其中包括比较常用的手写板,那么它属于哪类设备?( ) A:输入 B:输出 C:控制 D:记录 答案:A 第2题 以下关于…

计算机服务器中了mkp勒索病毒如何解密,mkp勒索病毒解密流程

随着网络技术的不断发展与应用&#xff0c;越来越多的企业走向数字化办公模式&#xff0c;计算机极大地方便了企业的正常生产运营&#xff0c;但网络威胁的手段也不断增加。近期&#xff0c;云天数据恢复接到很多企业的求助&#xff0c;企业的计算机服务器遭到了mkp勒索病毒攻击…

Qt 常见容器类用法(一)

目录 QMap类 QHash类 QVector类 QMap类 QMap<key,T>提供一个从类型为Key的键到类型为T的值的映射。通常&#xff0c;QMap存储的数据形式是一个键对应一个值&#xff0c;并且按照键Key的次序存储数据。为了能够支持一键多值的情况&#xff0c;QMap提供QMap<key,T&g…

相机图像质量研究(4)常见问题总结:光学结构对成像的影响--焦距

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

MPLS——多协议标签交换

目录 1 多协议标签交换 MPLS 1.1 MPLS 的工作原理 1.1.1 MPLS 工作特点 1.1.2 MPLS 协议的基本原理 1.1.3 MPLS 的基本工作过程 1.2 转发等价类 FEC 1.2.1 FEC 用于负载平衡 1.3 MPLS 首部的位置与格式 1.3.1 MPLS 首部的位置 1.3.2 MPLS 首部的格式 1.4 新一代的…

Jmeter 示例,格式为001-100,按顺序生成三位数的函数

1.先添加一个循环控制器&#xff0c;每次执行生成一个数, 2.添加一个beanshell Sample,编写代码,把按00X这个格式的数字&#xff0c;赋值给一个变量LoopCount // 从JMeter变量中获取当前的计数器值 String loopCountStr vars.get("LoopCount"); int loopCount (lo…

1896_Linux中free命令小结

1896_Linux中free命令小结 全部学习汇总&#xff1a; little_bits_of_linux: 一星半点的Linux经验 (gitee.com) 查看Linux中存储的使用情况&#xff0c;我经常使用htop&#xff0c;毕竟这个命令提供的信息是十分直观的。我现在常用的一个小主机其实是我的树莓派3B&#xff0c;虽…

ETL是什么,有哪些ETL工具?就业前景如何?

ETL是什么 ETL&#xff08;Extract-Transform-Load&#xff09;&#xff0c;用来描述将数据从来源端经过抽取(extract)、转换(transform)、加载(load)至目标端的过程。ETL一词较常用在数据仓库&#xff0c;但其对象并不限于数据仓库。它可以自动化数据处理过程&#xff0c;减少…

C#向数组指定索引位置插入新的元素值:自定义插入方法 vs List<T>.Add(T) 方法

目录 一、使用的方法 1.自定义插入方法 2.使用List.Add(T) 方法 二、实例 1.示例1&#xff1a;List.Add(T) 方法 2.示例&#xff1a;自定义插入方法 一、使用的方法 1.自定义插入方法 首先需要定义一个一维数组&#xff0c;然后修改数组的长度(这里使用Length属性获取…

正点原子--STM32基本定时器学习笔记(1)

这部分是笔者对基本定时器的理论知识进行学习与总结&#xff01;主要记录学习过程中遇到的重难点&#xff0c;其他一些基础点就一笔带过了&#xff01; 1. 定时器概述 1.1 软件定时原理 使用纯软件&#xff08;CPU死等&#xff09;的方式实现定时&#xff08;延时&#xff0…

C++ 动态规划 状态压缩DP 蒙德里安的梦想

求把 NM 的棋盘分割成若干个 12 的长方形&#xff0c;有多少种方案。 例如当 N2&#xff0c;M4 时&#xff0c;共有 5 种方案。当 N2&#xff0c;M3 时&#xff0c;共有 3 种方案。 如下图所示&#xff1a; 2411_1.jpg 输入格式 输入包含多组测试用例。 每组测试用例占一行…

A64指令集架构之PCS过程调用标准

Arm架构对通用寄存器的使用几乎没有限制。简而言之&#xff0c;整数寄存器和浮点寄存器都是通用寄存器。然而&#xff0c;如果你希望你的代码与他人编写的代码互动&#xff0c;或者与编译器生成的代码互动&#xff0c;那么你需要就寄存器的使用达成一致的规则。对于Arm架构&…

Chronos靶机渗透

Chronos靶机 一.信息收集1.靶机IP地址确认2.目录扫描3.常见漏洞扫描5.web网站探测1.网页2.源代码 二.网站渗透1.命令执行2.抓包---burp suite3.反弹shell 三.提权1.node.js原核污染第一个flag 2.sudo提权第二个flag 一.信息收集 1.靶机IP地址确认 ┌──(root㉿kali)-[/] └─…

【Linux系统化学习】文件描述符fd

目录 基础IO预备知识 C语言文件接口 "w"的方式打开&#xff0c;fputs写入 以"a"的方式打开&#xff0c;fputs写入 使用位图传参 系统调用操作文件 open的使用 第一种形式 第二种形式 write() 文件描述符 文件描述符和进程的关系 默认的三个IO流…

JAVASE进阶:高级写法——方法引用(Mybatis-Plus必学前置知识)

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;JAVASE进阶&#xff1a;一文精通Stream流函数式编程 &#x1f4da;订阅专栏&#xff1a;JAVASE进阶 希望文章对你们有所帮助 相信…

SpringCloud--Eureka注册中心服务搭建注册以及服务发现

注意springboot以及springcloud版本&#xff0c;可能有莫名其妙的错误&#xff0c;这里使用的是springboot-2.6.13&#xff0c;springcloud-2021.0.5 一&#xff0c;Eureka-Server搭建&#xff1a; 1.创建项目&#xff1a;引入依赖 <dependency><groupId>org.sp…

MyBatis 分页插件 PageHelper-Dazer007

文章目录 MyBatis 分页插件 PageHelper-Dazer0071、使用方式1.1 原始PageHelper1.2 ruoyi框架封装PageHelper1.3 MyBaits-Plus自带分页&#xff0c;无需PageHeler 2、作用原理3、数据方言实现3.1、MySqlDialect3.2、OracleDialect3.3、SqlServer2012Dialect 3、数据方言实现 My…

【Spring】Spring事务和事务传播机制

文章目录 什么是事务事务的操作Spring 中事务的实现Spring编程式事务Spring 声明式事务 TransactionalTransactional作用Transactional 详解rollbackFor事务隔离级别Spring 事务隔离级别Spring 事务传播机制 什么是事务 事务&#xff08;Transaction&#xff09;是一个程序中一…

嵌入式软件的设计模式与方法

思想有多远&#xff0c;我们就能走多远 4、状态与工作流类设计模式 4.1 状态与事件 行为随条件变化而改变&#xff0c;这里状态切换的模式也称为状态机。有限状态机 (Finite State Machine&#xff0c;FSM) 是由3 个主要元素组成的有向图: 状态、转换和动作。 状态是系统或者…