【HUST】网安|软件安全课设|记录

仓库链接
clone之后点开html文件即可使用。
效果如下图:
在这里插入图片描述


我的配置:VS2019,x86,Unicode字符集。

进程通信设计

为了方便将DLL中的输出信息显示到图形界面中,可以采用进程间通信。

DLL共享数据缓冲区的方式虽然简单,但是局限性太多,因此采用共享内存的方式。Windows的共享内存与Linux中有一些不同,在这里干脆统一介绍一下我了解到的一些IPC方式。

共享内存(Windows)

初始化共享内存

第一步,设计共享内存中的数据结构。(以本次实验为例)

struct shm_data {
    int state[10] = { 0 };                      // 如果state是1,则为有信息
    char seg[10][1000] = { 0 };                 // 共享内存的字符串缓冲区
};

第二步,连接或创建一块共享内存。
思路:OpenFileMapping打开共享内存,失败则用CreateFileMapping创建共享内存,得到内存映射对象句柄。然后用MapViewOfFile获取共享内存映射指针。这个指针可以强制转换成任何其他的指针,并直接使用。比如转换成char*或结构体指针struct shm_data*

HANDLE hMap;                        // 内存映射对象句柄
WCHAR nameShm[10] = L"dbgBuf";      // 共享内存名称
LPVOID pShm;                        // 共享内存指针
char outputStr[1000];               // 输出信息
WCHAR outputWStr[1000];

void InitShm()
{
    // 打开或创建共享内存: Name:"dbgBuf", size:3000h
    hMap = OpenFileMapping(FILE_MAP_ALL_ACCESS, 0, nameShm);
    if (hMap == NULL) {
        hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
            PAGE_READWRITE | SEC_COMMIT, 0, 0x3000, nameShm);
        if (hMap == NULL) {
            OutputDebugString(L"共享内存创建失败!");
            exit(-1);
        }
    }
    pShm = MapViewOfFile(hMap, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0);
    if (pShm == NULL)
    {
        OutputDebugString(L"获取共享内存映像失败!");
        exit(-1);
    }
    // 初始化共享内存内容为0
    struct shm_data* shmData = (struct shm_data*)pShm;
    memset(shmData->state, 0, sizeof(shmData->state));
    memset(shmData->seg, 0, sizeof(shmData->seg));
}

修改和读取共享内存的内容

思路:首先按结构体解析共享内存指针。
然后,修改是strcpymemcpy混合使用,自定义性强,不需要过多的说明;
读取是直接用结构体正常读取。

extern "C" __declspec(dllexport)void ChangeSeg(char* str)
{
    // 按结构体解析共享内存
    struct shm_data* shmData = (struct shm_data*)pShm;
    // 写入数据和状态
    while (shmData->state[count] == 1) Sleep(500);   // 如果缓冲区的数据尚未输出完毕,则暂停写入
    memcpy(shmData->seg[count], str, sizeof(shmData->seg[count]));
    shmData->state[count] = 1;
    // 循环递增序号
    count = (count + 1) % 10;
}

共享内存(linux)

参考文章:
Linux 进程间通信(IPC)—大总结
共享内存无锁队列的实现
这两篇都写得非常好。

(尝试使用,但使用失败,暂时不推荐)共享数据缓冲区

(本方法只能在Dll中设置)(以下是老师的解释)
在Dll中设置一个进程间共享缓冲区,所有输出放入该缓冲区,注射程序通过读取该缓冲区获得调试输出: 在这里插入图片描述
注意做好数据操作的互斥操作:
在这里插入图片描述
再设计一个获取这个共享内存信息的函数:
在这里插入图片描述
在注射器程序中调用获得被截获的API的调试输出。
先定义函数指针:
在这里插入图片描述
然后读取消息:
在这里插入图片描述


Web通信设计

C++后端,Web前端。初步设想是websoket利用本地电脑的端口来使网页和桌面程序通信,桌面程序设置为服务端,网页设置为客户端。

Web端会收到由C++程序发送来的用bash64加密的json格式字符串数据,解密并解析成json后即可将相应的内容填入页面中。

websocket++库安装

我用的是vs2019的x86编译,安装的是32位的Boost库。

主要参考:WebSocket使用(C++环境)(一) — websocket++库的安装与使用
补充参考:VS2019配置BOOST-v1.70.0库(重点关注VS中命令行的使用以及boost库的添加方式)

注: 第二篇博客中提到的bjamv1.72.0之后已经被修改为b2.exe,直接运行b2.exe即可,此处应参考第一篇博客。

websocket实现通信

参考: HTML5 WebSocket_菜鸟教程
官方的样例程序echo_server.cpp

#include <websocketpp/config/asio_no_tls.hpp>

#include <websocketpp/server.hpp>

#include <iostream>

typedef websocketpp::server<websocketpp::config::asio> server;

using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;

// pull out the type of messages sent by our config
typedef server::message_ptr message_ptr;

// Define a callback to handle incoming messages
void on_message(server* s, websocketpp::connection_hdl hdl, message_ptr msg) {
    std::cout << "on_message called with hdl: " << hdl.lock().get()
              << " and message: " << msg->get_payload()
              << std::endl;

    // check for a special command to instruct the server to stop listening so
    // it can be cleanly exited.
    if (msg->get_payload() == "stop-listening") {
        s->stop_listening();
        return;
    }

    try {
        s->send(hdl, msg->get_payload(), msg->get_opcode());
    } catch (websocketpp::exception const & e) {
        std::cout << "Echo failed because: "
                  << "(" << e.what() << ")" << std::endl;
    }
}

int main() {
    // Create a server endpoint
    server echo_server;

    try {
        // Set logging settings
        echo_server.set_access_channels(websocketpp::log::alevel::all);
        echo_server.clear_access_channels(websocketpp::log::alevel::frame_payload);

        // Initialize Asio
        echo_server.init_asio();

        // Register our message handler
        echo_server.set_message_handler(bind(&on_message,&echo_server,::_1,::_2));

        // Listen on port 9002
        echo_server.listen(9002);

        // Start the server accept loop
        echo_server.start_accept();

        // Start the ASIO io_service run loop
        echo_server.run();
    } catch (websocketpp::exception const & e) {
        std::cout << e.what() << std::endl;
    } catch (...) {
        std::cout << "other exception" << std::endl;
    }
}

我仅仅修改了其中的on_message函数,也就是消息处理函数,便实现了我的服务端。

其中唯一需要特别提到的是,作为服务端,如何主动向客户端发送数据:

/* 传递s、hdl参数,即可向客户端发送数据
 * 发送失败时,应该及时catch错误信息,并输出对应的错误提示
*/
void send_message(server* s, websocketpp::connection_hdl hdl, char* str)
{
    try {
        s->send(hdl, str, websocketpp::frame::opcode::text);
    }
    catch (websocketpp::exception const& e) {
        if (!strcmp(e.what(), "Bad Connection")) {
            is_hooking = 0;
            is_sending = 0;
            MessageBoxW(NULL,L"意外断连!请用Web控制台断开连接。",L"提示",NULL);
            char order[30];
            if (processId != 0) {
                sprintf_s(order, sizeof(order), "taskkill /PID %lu /F", processId);
                system(order);
            }
        }
        else {
            std::cout << "Echo failed because: "
                << "(" << e.what() << ")" << std::endl;
        }
    }
}

(可拓展)通信过程中交换的数据及处理方式

交换数据之前均使用Base64对数据加密,既提高了数据传输的安全性,又保证了中文字符能够正常被解析。

1. C++向Web发送的数据

只有两种json格式的数据:

  1. 进程信息
{
"name": "YourEXEName.exe"
"id":进程id,
"priority":进程优先级,
"thread":{"handle":线程句柄,"id":线程id},
"EXE":{"size":被Hook的EXE的大小,"drive":被Hook的EXE所在的磁盘}
}
  1. Hook信息
{
"type": 被Hook的函数的类型编号,
"name": 被Hook的函数名,
"arg":{"arg1":"argValue1",...},   //参数列表
"st":时间戳字符串
}

Web端对上述两种数据分类处理,分别用来渲染不同的组件。

2. Web向C++发送数据

  1. 网页端填写的EXE绝对路径HookServer绝对路径json字符串:
{
"exePath": 被Hook的EXE的绝对路径,
"hookPath": HookServer的绝对路径
}

C++接收到这类数据,就会读取其中的路径,并填入程序中用来运行。
2. 其他的控制信息:

start hook // 用来开启C++中的Hook功能
stop hook // 用来关闭C++中的Hook功能
pause // 让C++中的数据停止向Web端发送

前端页面设计

出于轻量级考虑,采用LayUI的库,在这里我对这个库的作者贤心致以最大的谢意!我很喜欢这个库,虽然2021年10月13号它的官网就要关掉了。
并顺便部署到了Gitee Page上。
这是前端链接。

LayUI的使用

参考:(这里本来应该是官网的Demo文档,但是官网要关了,所以可能只能从源代码慢慢研究了)
LayUI码云源代码
附一个非官方的教学:LayUI学习笔记-傻瓜教程,我主要参考的是官网的代码,但是没办法。

数据结构设计

对于一类函数,以堆函数举例,它的数据结构是一个字典。

 var heap = {
    "HeapCreate": [],
    "HeapDestory": [],
    "HeapFree": [],
    "about_analysis": {
      "about": {
        "name": "涉及的堆地址"
      },
      "analysis": {
        "heapSet": [],
        "[HeapCreate]重复创建堆": [],
        "[Unknown]释放或销毁了未知的堆": [],
        "[HeapDestory]销毁了未创建的堆": [],
        "[HeapFree]释放了未创建的堆": []
      },
      "num":0
    },
    "num":0
  };

字典中包含三个必要组成部分:

{
"FuncName":[],
"about_analysis":{},
"num":0
}

其中FuncName就是这类函数的函数名称,用数组记录每一条被Hook到的函数记录。
about_analysis的结构如下:

{
"about":{
"name":"",
},
"analysis":{
"Warning1":[],
"Warning2":[],...
}
"num":0
}

它用来存储一类函数的分析结果。

num是记录的数量,有新的Hook信息/分析信息时,数量增加;查看信息后,数量清零。

设计与交互

主体如下,实质是三种页面的渲染,在撰写过程中我收获了不少新方法,如果现在我重新写,会写得好更多。
在这里插入图片描述
分开介绍一下这些页面的结构。

进程信息

实质就是一个卡片,内含一个树状组件,组件中包含表格,表格中包含一些信息。
在这里插入图片描述

这些信息由C++发送的json字符串解析得到:

{"name": "WindowsProject1.exe", "id": 44328,"priority": "NORMAL", "thread":{"handle": "00000184", "id": 9624}, "EXE":{"size":109568,"drive":"E"}}

json的格式是:

{
"name": "YourEXEName.exe"
"id":进程id,
"priority":进程优先级,
"thread":{"handle":线程句柄,"id":线程id},
"EXE":{"size":被Hook的EXE的大小,"drive":被Hook的EXE所在的磁盘}
}

Hook 函数信息

其实质是一个标题、3个按钮以及一个卡片,卡片中包含一个树状组件。
在这里插入图片描述
这些信息由C++发送的json字符串解析得到:

{"type":5,"name":"ReadFile","arg":{"hFile":"000001BC","lpBuffer":"0133C4C8","nNumberOfBytesToRead":"0000003C","lpNumberOfBytesRead":"0133C4C4","lpOverlapped":"00000000","lpFileType":"dat","FilePath":"C:\\Windows\\Fonts\\StaticCache.dat"},"st":"2021-9-24 20:27 52s758ms"}

json的格式是:

{
"type": 被Hook的函数的类型编号,
"name": 被Hook的函数名,
"arg":{"arg1":"argValue1",...},   //参数列表
"st":时间戳字符串
}

这些结果会存入上面设计的数据结构的FuncName中。

异常分析

实质是一个标题、三个按钮以及一个卡片,卡片中有两个树状组件,一个是分析的关键参数,另一个是分析的行为。
在这里插入图片描述
该部分的数据,由用户(程序员)自己根据获取到的Hook函数参数信息,设定自己的分析逻辑而按格式生成。

这些结果会存入上面设计的数据结构的about_analysis中。

如何拓展或在该基础上新增功能?

非!常!简!单!

  1. 假设你只要多新Hook一个函数,这个函数属于heap类,那么,你要做的就是,在heap这个字典中,添加一个"FuncName":[]

  2. 假设你需要新增一类函数,只要按照相同的数据结构,多加一个变量,然后在var page_info = [process, MessageBox, heap, socket, file, Registry, memory]数组中追加这个变量。
    然后在html的左侧导航栏中添加一行nav标签,就依葫芦画瓢。(PS: 这一块可以更加简化,是我自己没优化到位,导致复杂了)然后把你添加的标签内容追加到var pageName = ["进程信息", "MessageBox", "堆操作", "网络通信", "文件操作", "注册表"];里面,就可以了。

  3. 假如新增一类分析,也是一样的,只是追加的标签内容要放到var pageName_2 = ["堆操作问题", "网络通信行为", "文件操作异常", "注册表可疑操作", "内存拷贝监测与关联"]中。
    至于分析的逻辑,就在checkUnexpected函数中写。

  4. 要新增Hook信息,只要参照其他的Hook函数的样子,修改DLL的代码然后重新生成DLL就行了。其中DLL中传出来的参数决定了最终展现的内容。自定义即可。

所以在这个模板上细化内容,甚至进一步完善分析逻辑,那是非常简单的。(前提是有现成的分析逻辑……比方说怎样判断文件自我复制,这一块我实现得就很糟糕)


技术难点及特点

1. C++函数设置任意类型和个数的参数

参考:泛化之美–C++11可变模版参数的妙用
我使用了逗号展开参数包的方式。
思路:先用可变模版参数定义可变参数类型的原函数,再用逗号展开函数使参数个数任意。
下面是一个完整的小demo。执行expand程序,可以让被格式化的arg[i]字符串存入dst[i]中。

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include "stdarg.h"
#include <iostream>
#include <cstdio>
#include <WinSock2.h>
#include<typeinfo>

template<typename Tx, typename T2, typename Ty>
// 按格式写入变量dst[i]
void sprintfWithType(Tx dst, T2 i, Ty arg)
{
    if(typeid(arg)==typeid(int))
        sprintf(dst[i], "%08X", (UINT32)arg);
    else {
        sprintf(dst[i], "Unknown arg.");
    }
}

template <typename Args1, typename ...Args2>
void expand(Args1 args1, Args2... args2)
{
    int i=0;
    int arr[] = {(sprintfWithType(args1, i++, args2), 0)...};
}

int main()
{
    char tes[2][100];
    //Sexpand(1,2,3,4);
    int hand=0,hand2=1;
    expand(tes, hand, hand2);
    printf("%s\n%s",tes[0],tes[1]);
    return 0;
}

…待补充

posted @ 2021-09-09 12:47  shandianchengzi  阅读(6)  评论(0编辑  收藏  举报  来源