当前位置: 首页 > news >正文

游戏安全入门-扫雷分析远程线程注入

前言

无论学习什么,首先,我们应该有个目标,那么入门windows游戏安全,脑海中浮现出来的一个游戏 – 扫雷,一款家喻户晓的游戏,虽然已经被大家分析的不能再透了,但是我觉得自己去分析一下还是极好的,把它作为一个小目标再好不过了。

我们编写一个妙妙小工具,工具要求实现以下功能:时间暂停、修改表情、透视、一键扫雷等等。

本文所用工具:

Cheat Engine、x32dbg(ollydbg)、Visual Studio 2019

扫雷游戏分析

游戏数据在内存中是地址,那么第一个任务,找内存地址

打开CE修改器

修改时间->时间暂停

计数器的时间是一个精确的值,所以我们通过精确数值扫描出来,游戏开始之前计数器上的数是0,所以我们扫描0。

image.png

时间在变化,选择介于什么数值之间再次扫描

image.png

可得 0x100579c — winmine.exe+579C

image.png

我们发现这个数据都是直接通过基址 + 固定偏移能直接得到的。

然后我们对数据去找 是什么改写了这个地址,得到一个指令和指针:

image.png

时间:0x100579c

修改表情 - 没啥用

修改表情这个功能怎么搞我觉得还是很容易想到的,这个按钮的作用是重新开始游戏,开始游戏,游戏胜利,游戏失败。

(表情的状态被分成了两个变量(4byte)来控制)

所以它是一种状态,所以我们通过0和1进行扫描,游戏进行状态输入1进行扫描,还原游戏之后输入0进行扫描。

首先是游戏进行状态,输入1进行扫描

image.png

再点击表情,将游戏还原,输入0开始扫描

image.png

如此反复进行扫描,得到表情的内存地址

0x1005164 – winmine.exe+5164

image.png

但是嘞,修改成2或者3,表情没有心得反应,所以控制游戏胜利和游戏失败的是其他的地址,我们知道,一般来说,一个功能的代码在内存中基本上都是连续的,(就像你修改一个游戏的血量,浏览血量内存块,你可以发现怒气,蓝量等内存地址)

所以,我们浏览内存

image.png

image.png

0x1005164-4 = 0x1005160

修改为3,发现出现了戴墨镜的表情(游戏胜利)

但是这个胜利知识一个状态,并不能说明扫雷完成.

image.png

表情:0x1005160与0x1005164

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

透视 - 显示雷区

思考游戏结束的时候会自动显示所有的雷,因此我们动态调试,看看在哪个函数调用之后会显示所有的雷

image.png

image.png

经过几次的动态调试之后发现:0x2F80函数是我们要找的结果。

image.png

一键扫雷

通过透视,我们玩一把游戏,使得游戏胜利(点完最后一个)

image.png

image.png

然后后两个函数,是破纪录跟英雄榜的函数

image.png

image.png

ret来到了这儿,游戏通关了,来到了这儿,可以知道,这个0x347c就是判断输赢的函数

并且通过调试发现由一个参数 0 1 来控制,所以跟透视差不多,带个参数线程回调就完了

image.png

编写妙妙小工具

怎么实现这个工具呢,当然是选择DLL注入

那么dll 怎么注入进去呢,这里选择远程线程注入

这里先简单介绍下什么是远程线程注入

前置知识-动态调用dll

主要就是这几个个 API:

LoadLibraryA

加载指定 DLL 并返回模块句柄,参数为字符串,就是 dll 的路径。

GetProcAddress

获取指定 dll 的导出函数的地址。

第一个参数是模块句柄,第二个参数是模块函数,返回值为函数的地址。

通过这两个函数,我们可以拿到所有函数的地址,然后就能进行调用。

CreateThread - 远程线程注入

里面几乎只有一个参数,那就是线程回调函数,然后当然还有返回地址,返回线程 id 啥的,这里我们都可以不用管,几乎是与 Linux 的创建线程函数一致。

还有一个远程版本的叫 CreateRemoteThread,它可以给别的进程创建一个线程并可以在本进程创建那个进程调用的回调函数。我们可以在回调函数中加载指定的 dll,在 dllmain 的入口当中,有一个 switch 的四个选项。

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"BOOL APIENTRY DllMain( HMODULE hModule,//指向自身的句柄DWORD  ul_reason_for_call,//调用原因LPVOID lpReserved//隐式加载or显式加载)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH://附加到进程上时执行case DLL_THREAD_ATTACH://附加到线程上时执行case DLL_THREAD_DETACH://从线程上剥离时执行case DLL_PROCESS_DETACH://从进程上剥离时执行break;}return TRUE;
}

我们可以在 DLL_PROCESS_ATTACH 的选项中加入代码,让它在加载的时候调用执行。

那么我们的步骤是:

  1. 打开指定进程获得句柄
  2. 开辟远程进程的空间,分配可读可写段。
  3. 调用 WriteProcessMemory 将 dll 路径写入该内存区域。
  4. 创建远程线程,回调函数使用 LoadLibrary 加载指定 dll。
  5. 等待返回(loadLibrary返回)
  6. 释放空间
  7. 释放句柄
  8. 返回结果
demo:
void Inject(DWORD ProcessId, const char* szPath)
{//1.打开目标进程获取句柄HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);printf("进程句柄:%p\n", hProcess);//2.在目标进程体内申请空间LPVOID lpAddress = VirtualAllocEx(hProcess, NULL, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);//3.写入DLL路径SIZE_T dwWriteLength = 0;WriteProcessMemory(hProcess, lpAddress, szPath, strlen(szPath), &dwWriteLength);//4.创建远程线程,回调函数使用 LoadLibrary 加载指定 dllHANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpAddress, NULL, NULL);//5.等待返回(loadLibrary返回)WaitForSingleObject(hThread, -1);//6.释放空间VirtualFreeEx(hProcess, lpAddress, 0, MEM_RELEASE);//7.释放句柄CloseHandle(hProcess);CloseHandle(hThread);//返回结果AfxMessageBox(L"完成");
}

编写DLL注入器

#include<windows.h>
#include<iostream>
#include<time.h>
#include<stdlib.h>
#include<TlHelp32.h>
DWORD FindProcess() {HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);PROCESSENTRY32 pe32;pe32 = { sizeof(pe32) };BOOL ret = Process32First(hSnap, &pe32);while (ret){if (!wcsncmp(pe32.szExeFile, L"mine.exe", 11)) {printf("Find winmine.exe Process %d\n", pe32.th32ProcessID);return pe32.th32ProcessID;}ret = Process32Next(hSnap, &pe32);}return 0;
}
void Inject(DWORD ProcessId, const char* szPath)
{//1.打开目标进程获取句柄HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);printf("进程句柄:%p\n", hProcess);//2.在目标进程体内申请空间LPVOID lpAddress = VirtualAllocEx(hProcess, NULL, 0x100, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);//3.写入DLL路径SIZE_T dwWriteLength = 0;WriteProcessMemory(hProcess, lpAddress, szPath, strlen(szPath), &dwWriteLength);//4.创建远程线程,回调函数使用 LoadLibrary 加载指定 dllHANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpAddress, NULL, NULL);//5.等待返回(loadLibrary返回)WaitForSingleObject(hThread, -1);//6.释放空间VirtualFreeEx(hProcess, lpAddress, 0, MEM_RELEASE);//7.释放句柄CloseHandle(hProcess);CloseHandle(hThread);
}int main() {DWORD ProcessId = FindProcess();while (!ProcessId) {printf("未找到扫雷程序,等待两秒中再试\n");Sleep(2000);ProcessId = FindProcess();}printf("开始注入进程...\n");Inject(ProcessId, "E:\\CODE\\wimine\\Mine\\release\\Mine.dll");printf("注入完毕\n");}

编写DLL

这里我们采用MFC DLL 基于对话框 (dialog)的方式编写(简单),使用静态编译的方式

image.png

image.png

然后我们需要在资源窗体,新建一个 Dialog ,简单包装一个界面

image.png

这样我们在加载窗体的时候需要创建一个窗体类对象用它的 DoModal 方法去显示,用线程回调的方式加载并且初始化InitInstance

DWORD WINAPI DlgThreadCallBack(LPVOID lp) {MineDlg* Dlg;Dlg = new MineDlg();Dlg->DoModal();delete Dlg;FreeLibraryAndExitThread(theApp.m_hInstance, 1);return 0;
}
// CMineApp 初始化
BOOL CMineApp::InitInstance()
{CWinApp::InitInstance();::CreateThread(NULL, NULL, DlgThreadCallBack, NULL, NULL, NULL);return TRUE;
}

时间暂停

上面我们找到了它控制时间增加的指令,我们把它们全部 NOP 掉,就可以实现时间暂停

写两个按钮,创建下面的事件实现时间暂停开关。

image.png

DWORD GetBaseAddr() {HMODULE hMode = GetModuleHandle(nullptr);//LPWSTR s = (LPWSTR)malloc(0x100);//wsprintf(s, L"基址:%p", hMode);//AfxMessageBox(s);return (DWORD)hMode;
}void MineDlg::OnBnClickedButton1() // 时间暂停
{// TODO: 在此添加控件通知处理程序代码auto BaseAddr=GetBaseAddr();DWORD TimeOffset = 0x579C;DWORD TimeInsOffset = 0x2FF5;DWORD InsLen = 6;DWORD old;VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, PAGE_EXECUTE_READWRITE, &old);BYTE INS[] = { 0x90,0x90,0x90,0x90,0x90,0x90 };memcpy((void *)(BaseAddr + TimeInsOffset), INS, InsLen);VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, old, &old);
}void MineDlg::OnBnClickedButton2() // 恢复字节即可取消时间暂停
{// TODO: 在此添加控件通知处理程序代码auto BaseAddr = GetBaseAddr();DWORD TimeOffset = 0x579C;DWORD TimeInsOffset = 0x2FF5;DWORD InsLen = 6;DWORD old;VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, PAGE_EXECUTE_READWRITE, &old);BYTE INS[] = { 0xFF,0x05,0x9C,0x57,0x00,0x01 };memcpy((void*)(BaseAddr + TimeInsOffset), INS, 6);VirtualProtect((void*)(BaseAddr + TimeInsOffset), InsLen, old, &old);
}

测试

image.png

透视

经过上面动态调试我们得出结论:0x2F80函数是踩雷函数。

我们如果调用这个函数,是不是就能够实现透视了呢?

我们依旧采取线程回调的方式

void MineDlg::OnBnClickedButton3()
{// TODO: 在此添加控件通知处理程序代码DWORD ESPOffset = 0x2f80;DWORD FuncAddr = GetBaseAddr() + ESPOffset;// 创建不带参数的线程CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)FuncAddr, NULL, 0, NULL);
}

测试

image.png

一键扫雷

跟透视差不多,只不过创建带参数的线程回调

void MineDlg::OnBnClickedButton4()
{// TODO: 在此添加控件通知处理程序代码DWORD ESPOffset = 0x347C;DWORD FuncAddr = GetBaseAddr() + ESPOffset;//创建带参数的线程struct { int a; } s = { 0 };CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)FuncAddr, &s, NULL, NULL);}

测试

image.png

总结

通过这个小项目,对WIN游戏安全有初步的认识,并且加强对软件的逆向思维,增强动态调试的能力,找到软件关键的基地址,通过CE修改器,初步pojie软件,了解软件的状态,修改时间(时间暂停等等),理解几个重要的API,FindWindow获取句柄,WriteProcessMemory写入内存信息,LoadLibraryA加载指定 DLL 并返回模块句柄,GetProcAddress,获取指定 dll 的导出函数的地址,CreateThread 线程回调函数等等。多写,多做,多调,多实验,加油,互勉。

相关文章:

游戏安全入门-扫雷分析远程线程注入

前言 无论学习什么&#xff0c;首先&#xff0c;我们应该有个目标&#xff0c;那么入门windows游戏安全&#xff0c;脑海中浮现出来的一个游戏 – 扫雷&#xff0c;一款家喻户晓的游戏&#xff0c;虽然已经被大家分析的不能再透了&#xff0c;但是我觉得自己去分析一下还是极好…...

bert-base-chinese模型的完整训练、推理和一些思考

前言 使用google-bert/bert-base-chinese模型进行中文文本分类任务&#xff0c;使用THUCNews中文数据集进行训练&#xff0c;训练完成后&#xff0c;可以导出模型&#xff0c;进行预测。 项目详细介绍和数据下载 数据集下载地址 Github完整代码 现记录训练过程中的一些感悟…...

JS基础5(JS的作用域和JS预解析)

JS的作用域 1. 全局作用域 全局作用域是在代码的任何地方都能访问到的最外层作用域。在浏览器环境下&#xff0c;全局作用域就是window对象&#xff0c;因此所有在全局作用域中声明的变量和函数都会成为window对象的属性和方法。 var globalVar "I am global"; …...

Doris 夺命 30 连问!(中)

导言 抱歉&#xff0c;作为从 S2 开始的骨灰级玩家看到 EDGUZI 官宣首发上线&#xff0c;兴奋之余忘了写文档 - -||&#xff0c;还望各位看官老爷见谅&#xff0c;这次错了&#xff0c;下次还敢 ^_^ 这是继上次的 30 问上篇的中篇&#xff0c;也是 10 个问题&#xff0c;有些…...

书生.浦江大模型实战训练营——(四)书生·浦语大模型全链路开源开放体系

最近在学习书生.浦江大模型实战训练营&#xff0c;所有课程都免费&#xff0c;以关卡的形式学习&#xff0c;也比较有意思&#xff0c;提供免费的算力实战&#xff0c;真的很不错&#xff08;无广&#xff09;&#xff01;欢迎大家一起学习&#xff0c;打开LLM探索大门&#xf…...

SpringBoot 整合 RabbitMQ 实现延迟消息

一、业务场景说明 用于解决用户下单以后&#xff0c;订单超时如何取消订单的问题。 用户进行下单操作&#xff08;会有锁定商品库存、使用优惠券、积分一系列的操作&#xff09;&#xff1b;生成订单&#xff0c;获取订单的id&#xff1b;获取到设置的订单超时时间&#xff0…...

Cilium:基于开源 eBPF 的网络、安全性和可观察性

基于 eBPF 的网络、安全性和可观察性 Cilium 是一种开源的云原生解决方案&#xff0c;它利用 Linux 内核中的 eBPF 技术来提供、保护和监控工作负载之间的网络连接。 什么是 eBPF&#xff1f; eBPF 是一项源自 Linux 内核的技术&#xff0c;允许沙盒程序在特权上下文&#x…...

Axios 详解与使用指南

Axios 详解与使用指南 1. Axios 简介 Axios 是一个基于 Promise 的 HTTP 客户端&#xff0c;能够在浏览器和 Node.js 环境中运行。它提供了一种简便的方式来执行 HTTP 请求&#xff0c;并支持多种请求方法&#xff0c;如 GET、POST、PUT、DELETE 等。Axios 的配置灵活&#x…...

深度学习 —— 个人学习笔记20(转置卷积、全卷积网络)

声明 本文章为个人学习使用&#xff0c;版面观感若有不适请谅解&#xff0c;文中知识仅代表个人观点&#xff0c;若出现错误&#xff0c;欢迎各位批评指正。 三十九、转置卷积 import torch from torch import nndef trans_conv(X, K):h, w K.shapeY torch.zeros((X.shape[…...

解决Mac系统Python3.12版本pip安装报错error: externally-managed-environment的问题

遇到的问题 在Mac安装了Python3.12.x版本&#xff08;3.12.3、3.12.4&#xff09;后&#xff0c;当尝试pip3 install xxx的时候&#xff0c;总是报错&#xff1a;error: externally-managed-environment error: externally-managed-environment This environment is external…...

lvm知识终结

、什么是 LVM LVM 是 Linux 下对磁盘分区进行管理的一种工具&#xff0c;适合管理大存储设备&#xff0c;并允许用户动态调整文件系统的大小 lvm 常用的命令 功能 PV 管理命令 VG 管理命令 LV 管理命令 scan 扫描 pvscan vgscan lvscan create 创建 pvcreate v…...

ESP32S3 IDF 对 16路输入输出芯片MCP23017做了个简单的测试

这次还是使用了idf老版本4.4.7&#xff0c;上次用了5.3&#xff0c;感觉不好用&#xff0c;官方的MCP23017芯片是英文版&#xff0c;真的很难读明白&#xff0c;可能是我英语水平不够吧。先看看每个寄存器的功能&#xff1a; IODIRA 和 IODIRB: 输入/输出方向寄存器 IPOLA 和 I…...

【技术前沿】Flux.1部署教程入门--Stable Diffusion团队最前沿、免费的开源AI图像生成器

项目简介 FLUX.1 是一种新的开源图像生成模型。它由 Stable Diffusion 背后的团队 Black Forest Labs 开发。 官网中有以下功能开源供大家参考&#xff1a; FLUX.1 擅长在图像中准确再现文字&#xff0c;因此非常适合需要清晰文字或短语的设计。无论是标牌、书籍封面还是品牌…...

Redis 的 STREAM 和 RocketMQ 是两种不同的消息队列和流处理解决方案,它们在设计理念、功能和用途上有显著区别。以下是它们的主要区别:

20240813 Redis 的 STREAM 和 RocketMQ 是两种不同的消息队列和流处理解决方案&#xff0c;它们在设计理念、功能和用途上有显著区别。以下是它们的主要区别&#xff1a;1. 使用 Redis 的 Sorted Set 数据结构连接到 Redis示例用法添加事件获取滑动窗口内的事件移除过期事件连接…...

Visual Studio Code安装与C/C++语言运行(上)

Visual Studio Code&#xff08;VS Code&#xff09;作为微软开发的一款轻量级但功能强大的源代码编辑器&#xff0c;广泛应用于各种编程语言的开发&#xff0c;包括C/C。以下将详细介绍VS Code的安装过程以及与C/C语言运行环境的配置。 一、Visual Studio Code的安装 1. 准备…...

探索数据可视化,数据看板在各行业中的应用

数据可视化是一种通过图形化手段将数据呈现出来的技术&#xff0c;它将复杂的数据和信息转化为易于理解的图表、地图、仪表盘等视觉元素&#xff0c;使得数据的模式、趋势和关系更加直观地展现出来。通过数据可视化&#xff0c;用户可以快速识别重要信息、发现潜在问题&#xf…...

haralyzer 半自动,一次性少量数据采集快捷方法

使用场景&#xff1a;半自动&#xff0c;一次性少量数据采集需求在工作中还是不少遇到的&#xff0c;无论使用模拟的方式&#xff0c;或者破解都不太划算。其实这种需求&#xff0c;使用半自动爬虫是最简单的。不需要考虑网站反爬虫的问题&#xff0c;因为你使用的就是真实的浏…...

mall-admin-web-master前端项目下载依赖失败解决

碰壁后的总结 pythone 环境 2.XX版本&#xff0c;切记不要3.0以上的。node 16.x不能太高 错误案例 npm ERR! code 1 npm ERR! path D:\workspace\springBootMall\mall-admin-web-master\node_modules\node-sass npm ERR! command failed npm ERR! command C:\windows\system…...

【07】JVM是怎么实现invokedynamic的

在Java中&#xff0c;方法调用会被编译为invokeStatic&#xff0c;invokeSpecial&#xff0c;invokVirtual以及invokeInterface四种指令。这些指令与包含目标方法类名、方法名以及方法描述符的符号引用捆绑&#xff0c;在实际运行之前&#xff0c;JVM根据这个符号引用链接到具体…...

使用API有效率地管理Dynadot域名,查看参与的拍卖列表

前言 Dynadot是通过ICANN认证的域名注册商&#xff0c;自2002年成立以来&#xff0c;服务于全球108个国家和地区的客户&#xff0c;为数以万计的客户提供简洁&#xff0c;优惠&#xff0c;安全的域名注册以及管理服务。 Dynadot平台操作教程索引&#xff08;包括域名邮箱&…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

Python:操作 Excel 折叠

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

CMake基础:构建流程详解

目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...

《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》

在注意力分散、内容高度同质化的时代&#xff0c;情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现&#xff0c;消费者对内容的“有感”程度&#xff0c;正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中&#xff0…...

LRU 缓存机制详解与实现(Java版) + 力扣解决

&#x1f4cc; LRU 缓存机制详解与实现&#xff08;Java版&#xff09; 一、&#x1f4d6; 问题背景 在日常开发中&#xff0c;我们经常会使用 缓存&#xff08;Cache&#xff09; 来提升性能。但由于内存有限&#xff0c;缓存不可能无限增长&#xff0c;于是需要策略决定&am…...

STM32---外部32.768K晶振(LSE)无法起振问题

晶振是否起振主要就检查两个1、晶振与MCU是否兼容&#xff1b;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容&#xff08;CL&#xff09;与匹配电容&#xff08;CL1、CL2&#xff09;的关系 2. 如何选择 CL1 和 CL…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...

在 Spring Boot 项目里,MYSQL中json类型字段使用

前言&#xff1a; 因为程序特殊需求导致&#xff0c;需要mysql数据库存储json类型数据&#xff0c;因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...

32单片机——基本定时器

STM32F103有众多的定时器&#xff0c;其中包括2个基本定时器&#xff08;TIM6和TIM7&#xff09;、4个通用定时器&#xff08;TIM2~TIM5&#xff09;、2个高级控制定时器&#xff08;TIM1和TIM8&#xff09;&#xff0c;这些定时器彼此完全独立&#xff0c;不共享任何资源 1、定…...