【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));
}
修改和读取共享内存的内容
思路:首先按结构体解析共享内存指针。
然后,修改是strcpy
和memcpy
混合使用,自定义性强,不需要过多的说明;
读取是直接用结构体正常读取。
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
库的添加方式)
注: 第二篇博客中提到的
bjam
在v1.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
格式的数据:
- 进程信息
{
"name": "YourEXEName.exe"
"id":进程id,
"priority":进程优先级,
"thread":{"handle":线程句柄,"id":线程id},
"EXE":{"size":被Hook的EXE的大小,"drive":被Hook的EXE所在的磁盘}
}
- Hook信息
{
"type": 被Hook的函数的类型编号,
"name": 被Hook的函数名,
"arg":{"arg1":"argValue1",...}, //参数列表
"st":时间戳字符串
}
Web
端对上述两种数据分类处理,分别用来渲染不同的组件。
2. Web向C++发送数据
- 网页端填写的
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
中。
如何拓展或在该基础上新增功能?
非!常!简!单!
-
假设你只要多新Hook一个函数,这个函数属于
heap
类,那么,你要做的就是,在heap
这个字典中,添加一个"FuncName":[]
。 -
假设你需要新增一类函数,只要按照相同的数据结构,多加一个变量,然后在
var page_info = [process, MessageBox, heap, socket, file, Registry, memory]
数组中追加这个变量。
然后在html
的左侧导航栏中添加一行nav
标签,就依葫芦画瓢。(PS: 这一块可以更加简化,是我自己没优化到位,导致复杂了)然后把你添加的标签内容追加到var pageName = ["进程信息", "MessageBox", "堆操作", "网络通信", "文件操作", "注册表"];
里面,就可以了。 -
假如新增一类分析,也是一样的,只是追加的标签内容要放到
var pageName_2 = ["堆操作问题", "网络通信行为", "文件操作异常", "注册表可疑操作", "内存拷贝监测与关联"]
中。
至于分析的逻辑,就在checkUnexpected
函数中写。 -
要新增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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix