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

.net也能写内存挂

最近在研究.net的内存挂。

写了很久的c++,发现c#写出来的东西实在太香。

折腾c#外挂已经有很长时间了。都是用socket和c++配合。

这个模式其实蛮成功的,用rpc调用的方式加上c#的天生await 非常好写逻辑

类似这样

最近想换个口味。注入托管dll到非托管进程

这样做只是为了解决我目前遇见的一个问题。

在一个多线程的程序上逆向,我挂了很多钩子,导致我读写数据和储存我自己的数据

非常容易出现多线程冲突问题,换到.net里以后

lock 和Monitor 在.net里是同线程不互锁。这样能让我不容易出现互锁现象。

有一段时间正在烦恼那些卖驱动的,只有读写功能为什么还能实现很多功能。

在我的认知里,要调用游戏部分函数才能更方便自己做出更有用的功能

当然这里确实有些外挂是只读取角色顶点就能绘制的。

后来细想一下其实不需要调用功能通过写入代码的方式获取执行就可以了。

就是只要有读写就可以了。

游戏外挂无非就是 读写和调用。 调用是可以通过写来实现。

比如hook某个dx的函数。或者修改虚函数表的地址,然后jmp 到自己的函数里

达到获取执行权限。

好比挂钩了GetTickCount  这个API ,然后目标游戏不断的调用这个API

我们在这里插入自己的调用逻辑就可以了。

想明白了这个,于是我就干起了注入托管dll 到游戏进程里的勾当

当然这个托管dll实在是太大了(因为会用Costura.Fody把第三方库都打包在一起)

想法是这样,注入到游戏进程里以后申请内存空间,然后通过asm编译成bytes

然后写入到内存后,再去调用他就可以了

这里要安利一个asm的库 

GitHub - icedland/iced: Blazing fast and correct x86/x64 disassembler, assembler, decoder, encoder for Rust, .NET, Java, Python, Lua

这个库很香

在c#里写asm长这样子,大概就是写好asm以后编译成bytes的过程

然后要介绍在c#内怎么完成thiscall

游戏大部分是thiscall  所以我在内存中申请一段asm

然后通过c#去调用这个段代码就可以了

asm 的作用就是把传入的参数 push到堆栈然后call

之后返回eax 这样就完整的跑通调用了。

用iced把asm生成好,然后通过c#的委托去调用这段asm代码即可

最后实现的效果类是这样

至于读写就更简单了。C#自带Marshal可以直接读写。

而且c#也支持不安全指针 直接 *(int*)(0x123456) = 100;

然后我们无限的包装自己的读写函数,比如byte float int string 之类的读写就可以

至于hook 也可以通过委托回调到自己的c#代码,hook在c#完成编译以后写入到目标地址

至此,完整的c#外挂需要的功能都实现了。

例子分3个工程

TestApp:测试工程模拟调用目标程序比如游戏

InjectionDLL:注入DLL,负责加载.net的dll,如果是远线程注入,就注入这个DLL即可,例子工程是主动loadlibrary

CShareLoadModule : c#的主要工作dll

附上源码一份

netInjection.rar

//以下是补充 2022 10 09

/

因为上面代码是用asm作为Thiscall 调用的,后来发现c#是自带调用约定的可以更优美的实现thiscall 调用

        [UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_0(IntPtr ptr);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_1(IntPtr ptr, IntPtr p1);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_2(IntPtr ptr, IntPtr p1, IntPtr p2);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_3(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_4(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_5(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_6(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_7(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_8(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_9(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_10(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9, IntPtr p10);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_11(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9, IntPtr p10, IntPtr p11);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_12(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9, IntPtr p10, IntPtr p11, IntPtr p12);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_13(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9, IntPtr p10, IntPtr p11, IntPtr p12, IntPtr p13);[UnmanagedFunctionPointer(CallingConvention.ThisCall)]public delegate IntPtr ThisCall_14(IntPtr ptr, IntPtr p1, IntPtr p2, IntPtr p3, IntPtr p4, IntPtr p5, IntPtr p6, IntPtr p7, IntPtr p8, IntPtr p9, IntPtr p10, IntPtr p11, IntPtr p12, IntPtr p13, IntPtr p14);unsafe public static IntPtr ThisCall(IntPtr address, IntPtr dwECX, params object[] args){IntPtr p1 = IntPtr.Zero;IntPtr[] paramPtr = new IntPtr[args.Length*2];int addParamCount = 0;for (int i = 0; i < args.Length; i++){var paramtype = args[i].GetType();if (paramtype == typeof(float)){var v = (float)args[i];paramPtr[addParamCount] = *(IntPtr*)&v;}else if (paramtype == typeof(double)){var v = (double)args[i];paramPtr[addParamCount] = *(IntPtr*)&v;addParamCount++;paramPtr[addParamCount] = *(IntPtr*)((&v) +4);}else if (paramtype == typeof(long)){var v = (long)args[i];paramPtr[addParamCount] = *(IntPtr*)&v;addParamCount++;paramPtr[addParamCount] = *(IntPtr*)((&v) + 4);}else if (paramtype == typeof(IntPtr)){var v = (IntPtr)args[i];paramPtr[addParamCount] = v;}else if (paramtype == typeof(PtrGameUIWindow)){paramPtr[addParamCount] = (args[i] as PtrGameUIWindow).ptr;}else{paramPtr[addParamCount] = (IntPtr)Convert.ToInt32(args[i]);}addParamCount++;}Log.Console($"ThisCall:0x{address.ToString("X8")} ECX:0x{dwECX.ToString("X8")} {paramPtr.ArrayToString(0, addParamCount)}");switch (args.Length){case 0:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_0>(address).Invoke(dwECX);break;case 1:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_1>(address).Invoke(dwECX, paramPtr[0]);break;case 2:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_2>(address).Invoke(dwECX, paramPtr[0], paramPtr[1]);break;case 3:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_3>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2]);break;case 4:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_4>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3]);break;case 5:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_5>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4]);break;case 6:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_6>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5]);break;case 7:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_7>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6]);break;case 8:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_8>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7]);break;case 9:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_9>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8]);break;case 10:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_10>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8], paramPtr[9]);break;case 11:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_11>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8], paramPtr[9], paramPtr[10]);break;case 12:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_12>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8], paramPtr[9], paramPtr[10], paramPtr[11]);break;case 13:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_13>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8], paramPtr[9], paramPtr[10], paramPtr[11], paramPtr[12]);break;case 14:p1 = Marshal.GetDelegateForFunctionPointer<ThisCall_14>(address).Invoke(dwECX, paramPtr[0], paramPtr[1], paramPtr[2], paramPtr[3], paramPtr[4], paramPtr[5], paramPtr[6], paramPtr[7], paramPtr[8], paramPtr[9], paramPtr[10], paramPtr[11], paramPtr[12], paramPtr[13]);break;default:Log.Error(new Exception("ThisCall 参数个数未预测"));p1 = IntPtr.Zero;break;}return p1;}

因为c#会定期GC的问题,导致c#自身函数可能会被GC修改函数位置

所以需要固定住代码位置新增一下函数固定Delegate

        [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Ansi)]public delegate IntPtr Delegate_NewStringID2String(IntPtr dwESP);public uint LockDelegate(Delegate func){GCHandleList.Add(GCHandle.Alloc(func));GCHandleList.Add(GCHandle.Alloc(Marshal.GetFunctionPointerForDelegate(func), GCHandleType.Pinned));GCHandleList.Add(GCHandle.Alloc(func.Method.MethodHandle.GetFunctionPointer()));return (uint)Marshal.GetFunctionPointerForDelegate(func);}//使用方法public static IntPtr MyStringID2String(IntPtr dwESP){var ret = IntPtr.IntPtr.Zero;return ret;}public override void Start(){var asm = new Assembler(32);asm.pushad();asm.mov(eax, esp);asm.add(eax, 0x20);asm.push(eax);asm.mov(eax, LockDelegate(new Delegate_NewStringID2String(MyStringID2String)));asm.call(eax);asm.mov(__[esp + 0x1c], eax);asm.popad();asm.ret();var newJmpCode = PatchSelfHelper.AllocMem(0x30);PatchSelfHelper.WriteAssembler(newJmpCode, asm);}

相关文章:

.net也能写内存挂

最近在研究.net的内存挂。 写了很久的c,发现c#写出来的东西实在太香。 折腾c#外挂已经有很长时间了。都是用socket和c配合。 这个模式其实蛮成功的&#xff0c;用rpc调用的方式加上c#的天生await 非常好写逻辑 类似这样 最近想换个口味。注入托管dll到非托管进程 这样做只…...

python学习笔记2-数字转化为String

题目链接 str() 强制转换&#xff0c; sorted() 转换为有序列表&#xff0c;join() 将列表中的元素连接到字符串中&#xff0c;然后奇偶位组合成数字 class Solution:def splitNum(self, num: int) -> int:stnum "".join(sorted(str(num)))num1, num2 int(stn…...

MAC版Gradle构建Spring5.X源码阅读环境

前言&#xff1a; 三年前鄙人有幸在现已几乎报废的Window的DELL中搭建过Spring源码环境&#xff0c;今天&#xff0c;Mac版的搭建&#xff0c;来了。 本篇文章环境搭建&#xff1a;Spring5.2.1 Gradle5.6.3-all jdk8 IDEA2022.3版本 文章目录 1、Spring源码下载2、Gradle下载…...

Linux 常用通配符

通配符是一种特殊语句&#xff0c;主要有星号&#xff08;*&#xff09;和问号&#xff08;&#xff1f;&#xff09;&#xff0c;用来模糊搜索文件。当查找文件夹时&#xff0c;可以使用它来代替一个或多个真正字符&#xff1b;当不知道真正字符或者懒得输入完整名字时&#x…...

Python皮卡丘

系列文章 序号文章目录直达链接1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏表白代码https://want595.blog.csdn.net/article/details/1297945183跳动的爱心https://want595.blog.csdn.net/article/details/1295031234漂浮爱心https://want…...

【数据结构与算法】三种简单排序算法,包括冒泡排序、选择排序、插入排序算法

冒泡排序算法 冒泡排序他是通过双重循环对每一个值进行比较&#xff0c;将小的值向后移动&#xff0c;以达到最终排序的结果&#xff0c;他的时间复杂度为O(n^2)。 /*** 冒泡排序* param arr*/public static void bubbleSort(int[] arr){int l arr.length;for (int i 0; i <…...

视频太大怎么压缩变小?超过1G的视频这样压缩

视频已经成为了我们日常生活中不可或缺的一部分&#xff0c;然而&#xff0c;很多时候&#xff0c;我们可能会遇到视频文件过大&#xff0c;无法在某些平台上传或保存的问题。那么&#xff0c;如何将过大的视频文件压缩变小呢&#xff1f; 下面就给大家分享三款实用的工具&…...

Edge 无法登录/同步问题【一招搞定】

目录 前言 一、打开 Edge 浏览器显示未同步&#xff0c;点击同步无效 二、Edge 登录报错 0x801901f4 或 0x80190001 解决方法 2.1 报错 0x801901f4 解决方法 2.1.0 Edge 登陆报错图示 2.1.1 添加 Edge 推荐的 DNS 地址 2.1.2 重新登录 Edge 账号成功 2.2 报错 0x801…...

ESP32-S3上手开发

1、搭建开发环境 首先搭建开发环境&#xff0c;这里采用了windows下集成开发环境ide进行开发&#xff0c;具体的安装方法&#xff1a;ESP-IDF安装配置 这里使用的乐鑫的esp32s3&#xff0c;N16R8 2、esp32s3模块 从上面图中可以看到&#xff0c;N16R8这里使用了外扩16M的fl…...

UE4和C++ 开发-编程基础记录(UE4+代码基础知识)

1、UE4基础元素 ①Actor 我们又见面了Actor&#xff0c;Actor是在一个关卡中持续存在的&#xff0c;通常他包含几个Actor组件。支持网络复制和多人游戏。   Actor不包含位置&#xff0c;方向。这些东西在Root Component中存储。对于UE3 中的Pawn也由PlayerCharacter继承了…...

【Unity】【VR】如何让Distance Grab抓取物品时限制物品的Rotation

【背景】 遇到这样的场景,希望抓取Canvas时,Canvas不会沿Z轴旋转。 【问题】 发现Freeze Canvas的Rigid Body没有用。 【分析】 应该是RigidBody的限制仅在物理互动下生效,抓取可能不属于物理互动(比如碰撞),所以不生效。 【思路】 还是得写脚本挂载在Interacta…...

为什么3ds max渲染效果图有噪点?点进来,CG Magic告诉您!

大家在使用3ds max渲染效果图时&#xff0c;可能渲染结果往往会出现的都是不真实&#xff0c;有小伙伴会问如何使3dmax渲染效果图真实呢&#xff1f; 不真实就算了&#xff0c;渲染过程中&#xff0c;会出现3Dmax渲染噪点多这类问题。 什么原因3ds max渲染效果图有噪点呢&a…...

Element UI怎么安装呢?

安装 :::warning 注意 后续演示将会在 Vue CLI 搭建的 Vue 项目上进行操作。如需要请查看 Vue CLI 安装 ::: 通过 YARN 命令安装 $ yarn add element-ui完整引入 代表一次性引入所有组件&#xff0c;比较省心省事&#xff0c;但是项目的打包体积也会跟着变大。 // main.js…...

redis批量删除命令

./redis-cli -h 127.0.0.1 -p 6379 -n 2 KEYS "170*:redisKeyStr" | xargs ./redis-cli -h 127.0.0.1 -p 6379 -n 2 DEL...

kubernetes环境 搭建

1、准备2台机器 2、安装docker环境&#xff08;参考官网&#xff09; 1、 sudo apt-get update sudo apt-get install ca-certificates curl gnupg2、 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dea…...

TCP习题总结

1、在采用TCP连接的数据传输阶段&#xff0c;如果发送端的发送窗口值由1000变为2000&#xff0c;那么发送端在收到一个确认之前可以发送&#xff08;&#xff09;。 A. 2000个TCP报文段 B. 2000B C. 1000B D. 1000 个 TCP 报文 这道题考察的是TCP的基本…...

华为发布LampSite X室内数字化创新解决方案,释放数字世界无限潜能

【阿联酋&#xff0c;迪拜&#xff0c;2023年10月11日】2023全球移动宽带论坛&#xff08;Global MBB Forum 2022&#xff09;期间&#xff0c;华为董事、ICT产品与解决方案总裁杨超斌重磅发布了全新一代5G室内数字化产品解决方案LampSite X系列&#xff0c;助力运营商打开商业…...

麒麟操作系统设置QT程序开机自启动有效方法

在麒麟操作系统上设置QT程序开机自启动的两种简单有效的方法。支持请点赞&#xff01; 一、QT程序打包 1.设置环境变量 设置QT和linuxdeployqt的环境变量&#xff0c;已设置可忽略该步骤。 在/etc/profile文件末尾添加一下内容&#xff1a; export PATH/usr/local/Qt-5.15.…...

Python数组删除元素pop与remove对比

pop()和remove()函数都可以用来删除列表中的函数&#xff0c;pop()是按索引来删除的&#xff0c;remove()是按元素来删除的。 1、pop()默认删除列表中最后一个元素&#xff0c;而且会返回删除的元素。此时的时间复杂度为O&#xff08;1&#xff09; 下面的例子中&#xff0c;…...

【Java 进阶篇】Java Web 编写注册页面案例

当涉及到创建一个Java Web注册页面时&#xff0c;你将需要涵盖很多不同的主题&#xff0c;包括HTML、CSS、Java Servlet和数据库连接。在这篇文章中&#xff0c;我们将详细介绍每个步骤&#xff0c;以帮助你创建一个完整的注册页面。 1. 介绍 注册页面是许多Web应用程序的关键…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

dedecms 织梦自定义表单留言增加ajax验证码功能

增加ajax功能模块&#xff0c;用户不点击提交按钮&#xff0c;只要输入框失去焦点&#xff0c;就会提前提示验证码是否正确。 一&#xff0c;模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

Java多线程实现之Callable接口深度解析

Java多线程实现之Callable接口深度解析 一、Callable接口概述1.1 接口定义1.2 与Runnable接口的对比1.3 Future接口与FutureTask类 二、Callable接口的基本使用方法2.1 传统方式实现Callable接口2.2 使用Lambda表达式简化Callable实现2.3 使用FutureTask类执行Callable任务 三、…...

Cloudflare 从 Nginx 到 Pingora:性能、效率与安全的全面升级

在互联网的快速发展中&#xff0c;高性能、高效率和高安全性的网络服务成为了各大互联网基础设施提供商的核心追求。Cloudflare 作为全球领先的互联网安全和基础设施公司&#xff0c;近期做出了一个重大技术决策&#xff1a;弃用长期使用的 Nginx&#xff0c;转而采用其内部开发…...

华硕a豆14 Air香氛版,美学与科技的馨香融合

在快节奏的现代生活中&#xff0c;我们渴望一个能激发创想、愉悦感官的工作与生活伙伴&#xff0c;它不仅是冰冷的科技工具&#xff0c;更能触动我们内心深处的细腻情感。正是在这样的期许下&#xff0c;华硕a豆14 Air香氛版翩然而至&#xff0c;它以一种前所未有的方式&#x…...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道

文/法律实务观察组 在债务重组领域&#xff0c;专业机构的核心价值不仅在于减轻债务数字&#xff0c;更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明&#xff0c;合法债务优化需同步实现三重平衡&#xff1a; 法律刚性&#xff08;债…...

基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)

引言 在嵌入式系统中&#xff0c;用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例&#xff0c;介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单&#xff0c;执行相应操作&#xff0c;并提供平滑的滚动动画效果。 本文设计了一个…...