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

《Windows PE权威指南》学习之第21章 EXE加密

EXE加密是软件保护范畴的一种技术通过对指定的PE文件进行加密可以增加逆向分析代码的难度在一定程度上保护软件代码的安全。EXE加密技术经常用于对软件的加壳处理通过PE分析软件对加密后的PE文件进行分析只能看到与补丁代码有关的信息原始的PE文件相关信息被隐藏同时如果在EXE补丁代码中使用一些技巧也能有效地增加PE反调试的难度达到保护软件的目的。21.1 基本思路加密一个PE文件的基本思路如下步骤1由补丁工具完成对目标PE文件数据目录表的修改并将原始数据目录表的内容转移到补丁代码中。步骤2由补丁工具完成对目标PE文件节区数据的加密。步骤3由补丁工具设置目标PE文件入口地址指向补丁代码。步骤4由补丁代码完成对目标PE文件数据目录表的还原。步骤5由补丁代码完成对目标PE文件节区数据的解密。步骤6由补丁代码完成对目标PE文件导入表中动态链接库的动态加载。步骤7由补丁代码完成对目标PE文件IAT的修正。加密以后的目标文件结构如图21-1所示。图21-1 加密以后的目标PE文件结构如图所示由于加密以后的数据目录表被清零通过结构分析工具如PEInfo查看目标PE文件时数据目录表中注册的数据类型都不会显示原始数据目录表会被补丁工具转移到补丁代码中。除数据目录表、SizeOfImage、最后一节的SizeOfRawData、函数入口地址AddressOfEntryPoint外目标PE文件的头部数据基本保持不变。目标PE文件的节区数据全部被加密保存但节区数据的长度不变补丁代码被选择附加到目标PE文件的最后一节中。21.2 加密算法加密算法是EXE加密的核心。本节首先介绍了常用的两类加密算法然后介绍了自行设计的可逆加密算法并给出了加密代码。21.2.1 加密算法的分类按照加密后的信息是否可以被还原常用的加密算法分为两大类❑ 不可逆加密算法❑ 可逆加密算法下面分别来介绍。1.不可逆加密算法不可逆加密算法的特征是加密过程中不需要使用密钥输入明文后由系统直接经过加密算法处理成密文。这种加密后的数据是无法被解密的只有重新输入明文并再次经过同样不可逆的加密算法处理得到相同的加密密文并被系统重新识别后才能真正解密。为了避免有人通过可逆算法得到加密前的信息在用户权限认证的系统中通常会使用一张不可逆加密的表表中存放着经过加密以后的用户密码。这个密码是任何人也破解不了的除非被他有幸猜中虽然不能还原最初的密码但可以通过对用户密码的再一次加密实现用户密码的验证。举个最简单的例子假设用户最初输入的密码是“123456”使用的不可逆加密算法是将每一位当成一个数字然后与3相除取余数作为每一位加密后的结果最终用户密码被加密成“120120”。很显然你无法正确地还原回去因为许多原始密码经过这种加密算法得到的值都是“120120”如原始密码为“789123”、“456123”等。但是这种加密算法却可以用在对用户的验证上即用户只要输入“123456”其加密以后的值一定是表中存储的值。很明显在这类加密过程中加密是自己解密还得是自己而所谓解密实际上就是重新加一次密所应用的“密码”也就是输入的明文。不可逆加密算法不存在密钥保管和分发问题非常适合在分布式网络系统上使用但因加密计算复杂工作量相当繁重通常只在数据量有限的情形下使用例如广泛应用在计算机系统中的口令加密利用的就是不可逆加密算法。近年来随着计算机系统性能的不断提高不可逆加密的应用领域正在逐渐增大。在计算机网络中应用较多不可逆加密算法的有许多例如RSA公司发明的MD5算法以及由美国国家标准局建议的不可逆加密标准SHSSecure Hash Standard安全散列信息标准等。2.可逆加密算法可逆加密算法又分为两大类“对称式”和“非对称式”。对称式加密 加密和解密使用同一个密钥通常称之为“Session Key ”。这种加密技术目前被广泛采用如美国政府所采用的DES加密标准就是一种典型的“对称式”加密法它的Session Key长度为56Bits。非对称式加密 加密和解密所使用的不是同一个密钥而是两个密钥一个称为“公钥”另一个称为“私钥”它们两个必须配对使用否则不能打开加密文件。这里的“公钥”是指可以对外公布的“私钥”则只能由持有人本人知道。它的优越性就在这里因为如果是在网络上传输加密文件对称式的加密方法就很难把密钥告诉对方不管用什么方法都有可能被别人窃听到。而非对称式的加密方法有两个密钥且其中的“公钥”是可以公开的不怕别人知道收件人解密时只要用自己的私钥即可以这样就很好地避免了密钥的传输安全性问题。最典型的可逆加密算法是异或运算。大家都知道对一个值连续异或两次其结果还是原值于是第一次异或被看成是加密第二次异或被看成是解密。对EXE进行加密必须使用一些可逆的加密算法即不仅能加密数据还要能还原数据。下面我们就自行开发一种简单的可逆加密算法。21.2.2 自定义可逆加密算法实例本节自行定义了一种可逆的加密算法基本思路是首先构造一个256字节的加密用基表该基表中的每一项的值都不重复这就意味着该基表中拥有了00h0ffh所有的字节值。有了这个基表以后再对PE中的每一个字节进行加密。加密方法非常简单将要加密的字节当做是基表的索引查找索引处的字节值该值即为加密后的值。加密算法示意见图21-2。图21-2 字节加密算法流程如图所示加密基表共有256项其中存储着256个不同的值并且这些值不是按顺序排列的。例如待加密的字节为50h将这个值当成是基表的索引查找该表50h处的字节是98h找到的这个值即为加密以后的值。解密的过程刚好相反根据要解密的字节98h遍历基表如果找到与之相等的值则记录此处的索引值该索引值50h即为解密后的字节。特别注意 基表的最后一个字节固定为00h。这个值的设置与基表的构造方式有关详见下一节。21.2.3 构造加密基表基表的组成包括255个随机数均不重复和最后一个00h字节。其中随机数的构造方法如下;生成加密基表 mov dwCount,0 mov edi,offset EncryptionTable .while TRUE invoke _getAByte mov byte ptr [edi],al inc edi inc dwCount .break .if dwCount0ffh .endw开始时加密基表所有表项值均被初始化为00h函数_getAByte得到一个与当前表项中已存在的所有值均不重复的字节并将其添加到表项。以下是函数_getAByte的定义_getAByte proc local ret pushad loc1: ; 取随机数 invoke _getRandom,1,255 mov ret,eax ; 判断随机数是否在基表中 invoke _isExists,eax .if eax ;如果在则重新获取随机数 jmp loc1 .endif popad mov eax,ret ;如果不在基表则返回 ret _getAByte endp函数_getAByte首先取一个1255之间的数注意这里舍弃了0因为0被设置为基表索引255处这样做主要是为了方便函数_isExists的运行。取指定范围随机数的方法如下_getRandom proc _dwMin:dword, _dwMax:dword local dwRet:dword pushad ; 取得随机数种子当然可用别的方法代替 invoke GetTickCount mov ecx, 19 ; X ecx 19 mul ecx ; eax eax X add eax, 37 ; eax eax YY 37 mov ecx, _dwMax ; ecx 上限 sub ecx, _dwMin ; ecx 上限 - 下限 inc ecx ; Z ecx 1得到了范围 xor edx, edx ; edx 0 div ecx ; eax eax mod Z余数在edx里面 add edx,_dwMin mov dwRet, edx popad mov eax, dwRet ; eax Rand_Number ret _getRandom endp获取随机数的基本算法为随机数下限随机数*1937mod上限-下限 1随机数1255之间获取以后首先通过函数_isExists判断该值是否在基表中已经存在。以下是函数_isExists的详细代码_isExists proc _byte local ret pushad mov esi,offset EncryptionTable mov ecx,0 .while TRUE mov al,byte ptr [esi] .if al0 mov ret,FALSE .break .endif mov ebx,_byte .if albl mov ret,TRUE .break .endif inc esi inc ecx .if ecx0ffh mov ret,FALSE .break .endif .endw popad mov eax,ret ret _isExists endp函数_isExists将循环检测基表结束条件有两个一是当函数在基表中找到了相同的值则返回TRUE另一个条件是函数已经完全遍历完基表未发现相同的值返回FALSE如果在基表中碰到00h则表示已经到了基表末尾返回FALSE。这里解释了上一节最后提出的问题即为什么要在基表的最后一个位置存放固定的字节00h。通过以上方法由计算机自动完成填充一个基表以下字节码是代码chapter21\HelloWorld.exe运行期获取的一个基表内容。可以看到基表中任何一项的值都是介于00h0FFh的每个值都不相等且基表的最后一个字节是00h。00401000 63 94 B2 E3 02 33 64 82 B3 E4 03 敳?3d偝? 00401010 34 52 83 B4 D2 04 35 53 84 A2 D3 05 23 54 85 A3 4R兇?5S劉?#T叄 00401020 D4 F2 24 55 73 A4 D5 F3 25 43 74 A5 C3 F4 26 44 则$Usふ?Ctッ?D 00401030 75 93 C4 F5 14 45 76 C5 15 46 95 C6 16 65 96 E5 u撃?Ev?F暺┬e栧 00401040 17 66 B5 E6 36 67 B6 06 37 86 B7 07 56 87 D6 08 ┤f垫6g?7喎●V囍■○○ 00401050 57 A6 D7 27 58 A7 F6 28 77 A8 F7 47 78 C7 F8 48 WψX (w Gx区H 00401060 97 C8 18 49 98 E7 19 68 99 E8 38 69 B8 E9 39 88 椚↑I樼├h欒8i搁9 00401070 B9 09 3A 89 D8 0A 59 8A D9 29 5A A9 DA 2A 79 AA ?:壺.Y娰)Z┶y 00401080 F9 2B 7A C9 FA 4A 7B CA 1A 4B 9A CB 1B 6A 9B EA ?z生J{?K毸←j涥 00401090 1C 6B BA EB 3B 6C BB 0B 3C 8B BC 0C 5B 8C DB 0D k弘;l?嫾.[屰. 004010A0 5C AB DC 2C 5D AC FB 2D 7C AD FC 4C 7D CC FD 4D \ ,] -| L} 听 M 004010B0 9C CD 1D 4E 9D EC 1E 6D 9E ED 3D 6E BD EE 3E 8D 溚N濎-m烅n筋 00401090 6D 9E ED 3D 6E BD EE 3E 8D BE 0E 3F 8E DD 0F 5E -| L} 0溚N濎 004010A0 8F DE 2E 5F AE DF 2F 7E AF FE 30 7F CE FF 4F 80 忁._ / 0 ?O€ 004010B0 CF 1F 50 9F D0 20 6F A0 EF 21 70 BF F0 40 71 C0 ?P熜 o狅!p筐q 004010C0 BE 0E 3F 8E DD 0F 5E 8F DE 2E 5F AE DF 2F 7E AF ??庉¤^忁._ / 004010D0 FE 30 7F CE FF 4F 80 CF 1F 50 9F D0 20 6F A0 EF ? ?O€?P熜 o狅 004010E0 21 70 BF F0 40 71 C0 10 41 90 C1 11 60 91 E0 12 !p筐q?A惲▲戉↑↓ 004010F0 61 B0 E1 31 62 B1 01 32 81 51 D1 A1 22 F1 72 42 a搬1b?2丵选駌B 00401100 C2 92 13 E2 00 聮!!?.构造加密基表的完整代码可以从随书文件chapter21\HelloWorld.asm中获得。21.2.4 利用基表测试加密数据接下来我们就用上节生成的基表进行数据加密的测试。首先在数据段中定义两部分数据一部分为加密前的数据szSrc另一部分为加密后的数据szDst。为了测试只定义了8个字节加密前的数据随便取一些值加密后的数据全部初始化为00h。加密数据的代码见代码清单21-1。代码清单21-1 加密数据的函数_encrptItchapter21\HelloWorld.asm138 ;------------------------------- 139 ; 加密算法可逆算法字节数不变 140 ; 入口参数 141 ; _src:要加密的字节码起始地址 142 ; _dst:生成加密后的字节码起始地址 143 ; _size:要加密的字节码的数量 144 ;------------------------------- 145 _encrptIt proc _src,_dst,_size 146 local ret 147 148 pushad 149 ;开始按照基表对字节进行加密 150 mov esi,_src 151 mov edi,_dst 152 .while TRUE 153 mov al,byte ptr [esi] 154 xor ebx,ebx ;将获取的值变成索引存储到ebx中 155 mov bl,al 156 mov al,byte ptr EncryptionTable[ebx] ;按索引值从基表里取出加密后的值 157 mov byte ptr [edi],al 158 159 inc esi 160 inc edi 161 dec _size 162 .break .if _size0 163 .endw 164 popad 165 ret 166 _encrptIt endp开始加密前esi指向要加密的数据edi指向存储加密结果的缓冲区行152163是一个循环循环次数为要加密数据的字节个数。行153155将获取的值当成一个索引存储在ebx寄存器中行156157按照该索引值从基表中取出加密后的值存储到结果缓冲区。以下是运行函数后的数据加密结果00403000 13 15 A0 00 17 !┴?┤ 00403010 01 00 FF 84 D3 AC 63 23 94 63 00 ┌. 動琧#攃....从列出的字节码可以看出加密前后数据的对应关系请对比以下所列自行查看基表看这些值是否真正符合我们的加密算法。加密前字节← →加密后字节 ---------------------------------------- 13← →84 15← → D3 A0← → AC 00← → 63 17← → 23 01← → 94 00← → 63 FF← → 0021.3 开发补丁工具补丁工具主要完成的操作包括❑ 处理目标PE文件的数据目录表向补丁代码传递原始数据目录表。❑ 生成加密基表向补丁代码传递加密基表及其他要传递给补丁代码的参数。❑ 加密节区数据。❑ 将补丁代码附加到目标PE文件的最后一节。本节相关文件在随书文件的目录chapter21\b中下面分别介绍。21.3.1 转移数据目录由于导入表的数据全部存储在节区而补丁工具会对这些数据进行加密处理破坏原有的导入表结构造成PE加载器加载目标PE文件失败因此必须事先将目标程序的数据目录表的导入表项设置为0即告诉加载器凡是目标PE中涉及的所有动态链接库不需要操作系统加载器来处理。不仅导入表的数据这样处理数据目录表中的其他数据也需要这样处理。修改数据目录表的方法是将所有项的RVA均设置为0。下面来看一个例子通过第17章介绍的方法将一个最简单的HelloWorld补丁打到记事本程序中打补丁的过程输出如下信息补丁程序D:\masm32\source\chapter21\patch.exe 目标PE程序C:\notepad.exe 补丁代码段大小00000196 PE文件大小00010400 对齐以后的大小00010400 目标文件最后一节在文件中的起始偏移00008400 目标文件最后一节对齐后的大小00008200 新文件大小00010600 补丁代码中的E9指令后的操作数修正为ffff4208最终生成的补丁程序为chapter21\patch_notepad.exe由于对原始notepad.exe程序的节数据没有进行加密处理所以使用OD加载该程序后内存空间分配情况见表21-1。表21-1 未加密前的进程内存空间分配表如表所示第17行为进程的PE文件头部分1820行为进程的其他节所在的起始地址和结束地址。下面将文件头部数据目录表项全部清零重新使用OD加载程序测试内存布局以下是清零以后的记事本的数据目录表字节码00000150 00 00 00 00 00 00 00 00 ........ 00000160 04 76 00 00 C8 00 00 00 00 B0 00 00 20 7F 00 00 .v........ ... 00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000180 00 00 00 00 00 00 00 00 50 13 00 00 1C 00 00 00 ........P....... 00000190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000001A0 00 00 00 00 00 00 00 00 A8 18 00 00 40 00 00 00 .............. 000001B0 50 02 00 00 D0 00 00 00 00 10 00 00 48 03 00 00 P..........H... 000001C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000001D0 00 00 00 00 00 00 00 00 ........从字节码可以看出记事本程序数据目录中的以下数据类别项被定义导入表、资源表、调试信息、加载配置、绑定引入和IAT。现在把以上加黑部分全部清零然后用OD加载此时的内存空间分配见表21-2。表21-2 加密后的进程地址空间分配表从表21-1和表21-2的对比来看如果把数据目录中所有的内容全部清零目标程序patch_notepad.exe自身不含导入的其他动态链接库加载进内存后的空间分配是一致的如两个表的黑体部分所示。这就意味着对数据目录表清零的操作不影响进程自身模块最终加载进内存的空间分配。被加密的目标PE最终还是要运行的所以在运行前必须将数据目录表的内容恢复原样这就要求必须首先保存目标PE文件的数据目录表的原始内容保存的首选位置是嵌入到目标PE文件的补丁程序。代码清单21-2该代码使用了随书文件chapter17\bind.asm为补丁工具中模拟清除数据目录内容及备份内容到补丁程序的代码代码清单21-2 补丁工具中转移目标PE文件原始数据目录表内容的函数_openFile chapter21\b\bind.asm1264 1265 ;---------------------------到此为止数据复制完毕 1266 1267 ;清空目标文件中的数据目录表的内容 1268 ;并把该内容填充到补丁代码处 1269 ;该部分内容在补丁代码起始5个字节处共16个目录项 1270 ;该部分操作全部在lpDstMemory中完成 1271 1272 mov esi,lpDstMemory 1273 assume esi:ptr IMAGE_DOS_HEADER 1274 add esi,[esi].e_lfanew 1275 assume esi:ptr IMAGE_NT_HEADERS 1276 1277 ;复制数据目录表内容到补丁字节码处 1278 mov eax,[esi].OptionalHeader.NumberOfRvaAndSizes 1279 sal eax,3 1280 mov ecx,eax 1281 mov dwDDSize,ecx 1282 add esi,78h 1283 mov dwDDStart,esi 1284 1285 mov edi,lpDstMemory 1286 add edi,dwNewFileAlignSize 1287 add edi,5 ;跳转指令长度定位到补丁程序中保存数据目录表的位置 1288 rep movsb 1289 1290 ;清空目标文件数据目录表内容 1291 mov edi,dwDDStart 1292 mov ecx,dwDDSize 1293 mov al,0 1294 rep stosb 1295如上所示行12771288负责将目标PE文件的原始目录表的数据共78h个字节复制到补丁代码指定的位置。行12901294则将目标PE文件的数据目录表的内容全部置0。实现该功能的相关测试文件请参照随书文件目录chapter21\b。其中bind.asm是补丁工具 patch.asm为补丁程序patch_notepad.exe是生成的打了补丁的记事本程序。使用FlexHex查看补丁后的记事本程序会发现记事本数据目录中的所有内容均被移动到了补丁代码中。21.3.2 传递程序参数要传给补丁程序的参数包括解密用的基表以及补丁前最后一节在文件中的大小。基表用于补丁程序解密使用补丁前最后一节在文件中的大小需要事先传递给补丁程序因为一旦补丁被打上该值会发生变化。向补丁程序传递参数的相关代码见代码清单21-3。1296 ;初始化加密基表 1297 invoke _encrptAlg 1298 1299 ;将基表复制到补丁代码中 1300 mov esi,offset EncryptionTable 1301 mov edi,lpDstMemory 1302 add edi,dwNewFileAlignSize 1303 add edi,5 ;5个字节的跳转指令 1304 add edi,168 ;168个数据目录表长度 1305 mov ecx,256 1306 rep movsb 1307 … 1327 1328 ;将最后一节在文件中对齐后的大小存储到补丁代码位置 1329 1330 mov edi,lpDstMemory 1331 assume edi:ptr IMAGE_DOS_HEADER代码清单21-3 补丁工具向补丁程序传递参数chapter21\b\bind.asm1332 add edi,[edi].e_lfanew 1333 assume edi:ptr IMAGE_NT_HEADERS 1334 ;获取节的个数 1335 movzx eax,[edi].FileHeader.NumberOfSections 1336 mov dwSections,eax 1337 add edi,sizeof IMAGE_NT_HEADERS 1338 1339 dec eax 1340 mov ecx,sizeof IMAGE_SECTION_HEADER ;定位到最后一个节 1341 mul ecx 1342 add edi,eax 1343 assume edi:ptr IMAGE_SECTION_HEADER 1344 mov ecx,[edi].SizeOfRawData 1345 1346 mov edi,lpDstMemory 1347 add edi,dwNewFileAlignSize 1348 add edi,5 ;5个字节的跳转指令 1349 add edi,168 ;168个数据目录表长度 1350 add edi,257 ;基表 1351 mov dword ptr [edi],ecx如上所示行1297调用函数_encrptAlg创建加密用的基表。行12991306将获得的基表内容复制到补丁程序的指定位置共256个字节。行13281344得到目标PE文件最后一节的SizeOfRawData行13461351将该参数传递给补丁程序。补丁程序chapter21\b\patch.asm中存放加密基表和目标PE文件最后一节的SizeOfRawData两个参数定义如下jmp start ;补丁程序起始代码该指令占了5个字节0000h ;保存目标程序的相关信息 dstDataDirectory dd 32 dup(0) ; 原始目标程序的数据目录表0005h EncryptionTable db 256 dup(0),0 ; 加密基表 0085h dwLastSectionSize dd ? ; 最后一节的尺寸以字节计如上所示补丁程序中保存的与目标PE有关的信息主要包括三个原始数据目录表、解密用的基表和目标PE文件最后一节的尺寸。以下节选了运行期该位置显示的数据内容00010400 E9 FC 05 00 00【00 00 00 00 00 00 00 00 04 76 00 ............v. 00010410 00 C8 00 00 00 00 B0 00 00 20 7F 00 00 00 00 00 ....... ...... 00010420 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00010430 00 00 00 00 00 50 13 00 00 1C 00 00 00 00 00 00 .....P.......... 00010440 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00010450 00 00 00 00 00 A8 18 00 00 40 00 00 00 50 02 00 ...........P.. 00010460 00 D0 00 00 00 00 10 00 00 48 03 00 00 00 00 00 ........H...... 00010470 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00010480 00 00 00 00 00 】【73 91 C2 F3 12 43 61 92 C3 E1 13 .....s.Ca. 00010490 44 62 93 B1 E2 14 32 63 94 B2 E3 02 33 64 82 B3 Db.2c.3d 000104A0 E4 03 34 52 83 B4 D2 04 35 53 84 A2 D3 05 23 54 .4R.5S.#T 000104B0 85 A3 D4 F2 24 55 A4 D5 25 74 A5 F4 26 75 C4 F5 $U%tu 000104C0 45 76 C5 15 46 95 C6 16 65 96 E5 17 66 B5 E6 36 Ev.F.e.f6 000104D0 67 B6 06 37 86 B7 07 56 87 D6 08 57 A6 D7 27 58 g.7.V.WX 000104E0 A7 F6 28 77 A8 F7 47 78 C7 F8 48 97 C8 18 49 98 (wGxH.I 000104F0 E7 19 68 99 E8 38 69 B8 E9 39 88 B9 09 3A 89 D8 .h8i9.: 00010500 0A 59 8A D9 29 5A A9 DA 2A 79 AA F9 2B 7A C9 FA .Y)Zyz 00010510 4A 7B CA 1A 4B 9A CB 1B 6A 9B EA 1C 6B BA EB 3B J{.K.j.k; 00010520 6C BB 0B 3C 8B BC 0C 5B 8C DB 0D 5C AB DC 2C 5D l..[.\,] 00010530 AC FB 2D 7C AD FC 4C 7D CC FD 4D 9C CD 1D 4E 9D -|L}M.N 00010540 EC 1E 6D 9E ED 3D 6E BD EE 3E 8D BE 0E 3F 8E DD .mn.? 00010550 0F 5E 8F DE 2E 5F AE DF 2F 7E AF FE 30 7F CE FF .^._/0. 00010560 4F 80 CF 1F 50 9F D0 20 6F A0 EF 21 70 BF F0 40 O€.P o!p 00010570 71 C0 10 41 90 C1 11 60 E0 B0 31 01 81 51 D1 A1 q.A.1.Q 00010580 22 F1 72 42 00】00【00 00 01 00】00 00 00 00 00 00 rB............如上所示第一个【】包含了目标程序的数据目录表第二个【】是解密用基表内容第三个【】是目标程序最后一节的实际字节码长度。21.3.3 加密节区内容补丁工具负责对目标PE文件节区数据的加密工作。首先计算出要加密数据的范围从第一个节开始到文件末尾的数据全部要进行加密。由于加密采用不变长度算法能保证PE加载器在加载数据到内存以后所有的字节码位置保持相对不变在进行解密以后不需要重新计算代码中与定位有关的数据所以比较方便。对节区数据进行加密的代码见代码清单21-4。1308 ;加密节区数据 1309 ;------------------------------ 1310 ;首先计算加密范围 1311 ;取第一个节的文件起始偏移 1312 1313 mov edi,lpDstMemory 1314 assume edi:ptr IMAGE_DOS_HEADER 1315 add edi,[edi].e_lfanew 1316 add edi,sizeof IMAGE_NT_HEADERS 1317 1318 assume edi:ptr IMAGE_SECTION_HEADER 1319 mov eax,[edi].PointerToRawData 1320 1321 mov ecx,dwFileSize1 1322 sub ecx,eax 1323 add eax,lpDstMemory ;起始偏移 1324 mov esi,eax 1325 1326 invoke _encrptIt,esi,esi,ecx代码清单21-4 加密节区数据chapter21\b\bind.asm函数_encrptIt在21.2.4节已经讲解过了传入参数esi为第一个节在文件中的起始地址ecx为从该位置到文件尾的长度。程序运行到此目标文件中的指定范围的数据均被加密。经过同一个加密算法生成的目标程序使用了相同的补丁程序所以使用一些PE分析工具得到的结果除了文件头部描述不相同外其他地方的描述基本是相同的。使用PEInfo小工具分析加密以后的记事本程序结果显示如下文件名D:\masm32\source\chapter21\b\patch_notepad.exe ----------------------------------------- 运行平台 0x014c 节的数量 3 文件属性 0x010f 建议装入基地址 0x01000000 文件执行入口(RVA地址) 0x13000 节的名称 未对齐前真实长度 内存中的偏移(对齐后的) 文件中对齐后的长度 文件中的偏移 节的属性 ----------------------------------------------------------------------------- .text 00007748 00001000 00007800 00000400 60000020 .data 00001ba8 00009000 00000800 00007c00 c0000040 .rsrc 00009000 0000b000 00008800 00008400 c0000060 未发现该文件有导入函数 未发现该文件有导出函数 未发现该文件有重定位信息 未发现该文件有资源表从分析结果来看这里显示的内容根本不是原始的记事本程序用FlexHex查看字节码也只是看到一堆乱码而已。补丁工具还有最后一个功能即负责将补丁代码嵌入到目标PE文件的最后一节。由于这部分功能在第17章中有详细描述在此略过具体代码请参照随书文件chapter21\b\bind.asm。21.4 处理补丁程序前几章讲过的补丁程序实现的功能都与目标PE无关而这次补丁程序的代码针对的对象是目标程序本身所以需要额外对目标程序的数据进行如下处理❑ 对目标PE数据目录进行还原。❑ 对目标PE加密的节区的数据进行解密。❑ 对目标PE导入表中的动态链接库实施动态加载。❑ 对目标PE的IAT中的值进行修正。以下将对这四项处理进行逐一介绍。21.4.1 还原数据目录表对EXE加密必须保证加密以后的PE文件可以正常运行但补丁工具却将其数据目录表全部清零所以在运行目标程序前补丁程序要做的第一件事情就是依据事先保存在补丁程序中的目标PE文件的原始数据目录表信息恢复目标PE文件的数据目录表。代码清单21-5演示了还原目标PE文件数据目录表的整个过程447 ;获取目标进程的基地址 448 mov eax,offset dwImageBase 449 add eax,ebx 450 451 push eax 452 lea edx,_getImageBase代码清单21-5 还原数据目录表chapter21\b\patch.asm453 add edx,ebx 454 call edx 455 mov dwImageBase[ebx],eax 456 457 458 ;还原目标进程的数据目录表 459 mov esi,dwImageBase[ebx] 460 add esi,[esi3ch] 461 add esi,78h 462 push esi 463 464 assume fs:nothing 465 mov eax,fs:[20h] 466 mov hProcessID[ebx],eax 467 468 469 push hProcessID[ebx] 470 push FALSE 471 push PROCESS_ALL_ACCESS 472 call _openProcess 473 mov hProcess[ebx],eax ;找到的进程句柄在hProcess中 474 475 476 477 ;设置文件头部分为可读可写可执行 478 lea edx,hOldPageValue 479 add edx,ebx 480 push edx 481 push PAGE_EXECUTE_READWRITE 482 ;获取SizeOfImage大小 483 push esi 484 mov esi,dwImageBase[ebx] 485 add esi,[esi3ch] 486 assume esi:ptr IMAGE_NT_HEADERS 487 mov edx,[esi].OptionalHeader.SizeOfImage 488 pop esi 489 push edx ;设置页面大小 490 push dwImageBase[ebx] 491 push hProcess[ebx] 492 call _virtualProtectEx 493 494 pop esi 495 push NULL 496 push 168 497 mov edx,offset dstDataDirectory 498 add edx,ebx 499 push edx 500 push esi 501 push hProcess[ebx] 502 call _writeProcessMemory大致思路是通过OpenProcess函数传递PROCESS_ALL_ACCESS参数打开目标进程。然后使用WriteProcessMemory将补丁代码中保存的数据目录表项全部写回到目标进程头部数据目录表位置。代码行447455获取目标PE的基地址存储到变量dwImageBase中。行464466从fs:[20h]位置处取出进程的ID号存储到变量hProcessID中。行469473调用OpenProcess函数打开目标进程。行477492调用函数VirtualProtectEx将目标进程文件头部内存页设置为可读可写。行494502调用函数WriteProcessMemory将esi指向的数据写入变量dstDataDirectory指向的位置即还原目标进程的数据目录表。数据目录表被还原以后程序还无法正常运行因为此时数据目录表中的数据项指向的位置都是经过加密以后的数据对正常的目标进程而言这些数据根本无法识别。接下来要做的就是将节区对应的数据进行解密。21.4.2 解密节区内容加密的EXE程序运行前需要先将补丁工具加密的节区数据解密。由于加密使用了不变长度的算法解密后的节区数据大小和解密前一样所以解密时不需要额外构造解密用的缓冲区相对比较简单。首先来看解密函数。1.解密函数_UnEncrptIt解密函数的运行依赖于加密时创建的基表。解密以字节为单位进行每个字节的解密与其他字节没有关系解密后的数据大小与解密前大小一致。解密函数见代码清单21-6。代码清单21-6 解密函数_UnEncrptItchapter21\b\patch.asm65 ;------------------------------- 66 ; 解密算法可逆算法字节数不变 67 ; 入口参数 68 ; _src:要解密的字节码起始地址 69 ; _size:要加密的字节码的数量 70 ;------------------------------- 71 _UnEncrptIt proc _src,_size,_writeProcessMemory 72 local ret 73 local dwTemp 74 75 pushad 76 ;开始按照基表对字节进行加密 77 mov esi,_src 78 .while TRUE 79 mov al,byte ptr [esi] 80 mov edi,offset EncryptionTable 81 add edi,ebx 82 mov dwTemp,0 83 .while TRUE ;查找基表索引在dwTemp中 84 mov cl,byte ptr [edi] 85 .break .if alcl ;如果找到则退出 86 inc dwTemp 87 inc edi 88 .endw 89 90 ;用解密后的字节更新字节码 91 mov ecx,dwTemp 92 mov byte ptr dbEncrptValue[ebx],cl 93 94 ;使用远程写入 95 push NULL 96 push 1 97 mov edx,offset dbEncrptValue 98 add edx,ebx 99 push edx 100 push esi ;?? 101 push hProcess[ebx] 102 call _writeProcessMemory 103 104 inc esi 105 dec _size 106 .break .if _size0 107 .endw 108 popad 109 ret 110 _UnEncrptIt endp解密算法其实很简单查找加密基表如果在基表中找到指定字节码则记录该位置在基表中的索引这个索引值即为解密后的字节码然后用这个字节码替换原来位置的字节值。2.解密过程程序将所有节的数据均进行一遍解密还原目标程序为原始内容然后才启动其他诸如动态加载DLL、修正IAT等操作。代码清单21-7是解密数据的代码。504 ;解密数据 505 mov edi,dwImageBase[ebx] 506 assume edi:ptr IMAGE_DOS_HEADER 507 add edi,[edi].e_lfanew 508 assume edi:ptr IMAGE_NT_HEADERS 509 ;获取节的个数 510 movzx eax,[edi].FileHeader.NumberOfSections 511 mov dwSections[ebx],eax 512 add edi,sizeof IMAGE_NT_HEADERS 513 514 dec eax 515 mov ecx,sizeof IMAGE_SECTION_HEADER ;定位到最后一个节 516 mul ecx 517 add edi,eax 518 assume edi:ptr IMAGE_SECTION_HEADER 519 520 mov first,1 521 522 .while TRUE 523 mov esi,[edi].VirtualAddress 524 add esi,dwImageBase[ebx] ;要解密的起始地址 525 526 .if first ;如果是最后一节补丁工具更改了此处的大小 527 ;必须使用由补丁工具传入的原始值 528 mov ecx,dwLastSectionSize[ebx] 529 mov first,0 530 .else ;如果是其他节则使用SizeOfRawData代码清单21-7 解密节区数据过程chapter21\b\patch.asm531 mov ecx,[edi].SizeOfRawData 532 .endif 533 534 push _writeProcessMemory 535 push ecx 536 push esi 537 mov edx,offset _UnEncrptIt 538 add edx,ebx 539 call edx 540 541 dec dwSections[ebx] 542 sub edi,sizeof IMAGE_SECTION_HEADER 543 .break .if dwSections[ebx]0 544 .endw如上所示代码行505518通过文件头部描述获取节的数量并存储在变量dwSections中然后将指针定位到最后一节。由于此时CPU的控制权尚处于补丁程序手里所以最后一节的SizeOfRawData是被补丁工具修改以后的大小。如果用这个大小来解密数据势必会越界导致后面的补丁代码被修改所以最后一个节要解密的数据大小由补丁工具传进来的变量dwLastSectionSize决定其他的节要解密的数据大小则直接通过每个节的SizeOfRawData来决定。行522544是解密目标进程所有节的一个循环。其中变量first如果为1表示当前处理的是最后一节否则从最后一节向前处理直到循环次数达到节的总数为止。节区的数据解密完成是否就可以跳转到目标进程的入口地址处运行了呢答案是否定的。因为加密的目标PE文件在最初被操作系统加载器加载进内存时是误认为该文件没有导入表的导入表项被设置为0。所以记事本代码中用到的所有动态链接库在记事本进程地址空间中还不存在。如果补丁代码只还原完数据目录表将加密的节区解密就直接转移到原始入口地址处运行还不会成功补丁代码需要继续完成对记事本导入表中登记的各模块的动态载入以及IAT的修正后才会成功。21.4.3 加载目标DLL加密的目标PE文件被加载进内存后其原始的导入信息丢失。加载器不会将其导入表中的动态链接库加载进进程的地址空间这个操作必须由补丁程序代为完成。首先利用小工具程序PEInfo查看notepad.exe导入表中都引入了哪些动态链接库这些链接库包括comdlg32.dll、SHELL32.dll、WINSPOOL.DRV、COMCTL32.dll、msvcrt.dll、ADVAPI32.dll、KERNEL32.dll、GDI32.dll和USER32.dll。但是从OD加载的记事本进程地址空间中看到的链接库SECUR32.DLL、USP10.DLL、SHLWAPI.DLL、RPCRT4.DLL、LPK. DLL、IMM32.DLL并未出现在NOTEPAD.EXE的导入表定义中。这说明这些动态链接库的导入应该是包含在其他动态链接库中的例如❑ WINSPOOL.DRV→RPCRT4.DLL❑ COMDLG32.DLL→SHLWAPI.DLL❑ LPK.DLL→USP10.DLL接下来要为补丁代码增加动态加载DLL功能。通过遍历目标程序的导入表调用LoadLibraryA函数将导入表中列出的所有模块加载到内存中并记录各模块的基地址。详细代码见代码清单21-8。278 ;获取目标进程的基地址 279 mov eax,offset dwImageBase 280 add eax,ebx 281 282 push eax 283 lea edx,_getImageBase 284 add edx,ebx 285 call edx 286 mov dwImageBase[ebx],eax 287 288 ;遍历目标进程导入表 289 mov edi,offset dstDataDirectory 290 add edi,ebx 291 add edi,8 ;定位到导入表项 292 293 mov eax,dword ptr [edi] ;获取VirtualAddress 294 ;未做判断假设处理的PE文件均有导入表 295 add eax,dwImageBase[ebx] ;所在内存偏移 296 297 mov edi,eax ;计算引入表所在文件偏移位置 298 assume edi:ptr IMAGE_IMPORT_DESCRIPTOR 299 300 mov eax,dword ptr [edi].Name1 ;取第一个动态链接库名字字符串所在的RVA值 301 add eax,dwImageBase[ebx] ;在内存定位只需加上基地址即可 302 ;invoke _messageBox,NULL,eax,NULL,MB_OK 303 304 ;动态加载该DLL 305 invoke _loadLibrary,eax 306 mov dwModuleBase[ebx],eax代码清单21-8 动态加载目标进程导入表中登记的DLLchapter21\patch2.asm只加载一个DLLcomdlg32.dll的示例可以调试chapter21\bindC.exe文件。在OD环境下对比动态加载前后的内存分配可以看到comdlg32.dll被正确地加入到地址0x76320000起始处。以下是加载所有的动态链接库代码chapter21\a\patch.asm...... mov edi,eax ;计算引入表所在文件偏移位置 assume edi:ptr IMAGE_IMPORT_DESCRIPTOR .while [edi].Name1 ;循环结束条件只需简单判断Name1是否为0即可 push edi mov eax,dword ptr [edi].Name1 ;取第一个动态链接库名字字符串所在的RVA值 add eax,dwImageBase[ebx] ;在内存定位只需加上基地址即可 ;动态加载该DLL invoke _loadLibrary,eax mov dwModuleBase[ebx],eax ;修正从该链接库引入的函数IAT项 ;----------------------------- pop edi add edi,sizeof IMAGE_IMPORT_DESCRIPTOR .endw离最终可运行的目标越来越近了最后一步是修正目标文件的IAT。21.4.4 修正目标IAT引入的动态链接库被动态加载到内存以后接下来要做的就是修正目标进程中的IAT内容。从导入表中得到每个函数的名称字符串然后从加载的模块基地址获取该函数在内存的VA值并填到对应的IAT位置。下面开始IAT修复工作具体代码见代码清单21-9。代码清单21-9 修正目标进程IAT的_updateIAT函数chapter21\a\patch.asm240 ;------------------ 241 ; 修正IAT表 242 ; 传入全局变量参数 243 ; dwModuleBase 模块的地址 244 ; dwImageBase 进程基地址 245 ;------------------ 246 _updateIAT proc _lpIID,_writeProcessMemory 247 local dwCount 248 249 pushad 250 mov dwCount,0 251 252 mov edi,_lpIID 253 assume edi:ptr IMAGE_IMPORT_DESCRIPTOR 254 255 ;获取函数名字字符串 256 mov esi,[edi].OriginalFirstThunk 257 add esi,dwImageBase[ebx] 258 .while TRUE 259 mov eax,[esi] 260 .break .if !eax 261 add eax,dwImageBase[ebx] 262 add eax,2 ;跳过hint/name中的hint 263 264 ;此时eax指向了函数字符串 265 lea edx,_getApi ;获取函数地址 266 add edx,ebx 267 push eax 268 push dwModuleBase[ebx] 269 call edx 270 ;add eax,dwImageBase[ebx] ;获取函数VA值 271 272 ;将函数地址覆盖IAT对应位置 273 push esi 274 push eax 275 mov esi,[edi].FirstThunk 276 add esi,dwImageBase[ebx] ;esi指向IAT表开始 277 278 mov eax,dwCount ;求索引对应偏移 279 sal eax,2 280 add esi,eax 281 pop eax 282 283 284 mov dwIATValue[ebx],eax 285 ;使用远程写入 286 push NULL 287 push 4 ; 写入长度 288 mov edx,offset dwIATValue 289 add edx,ebx 290 push edx ; 写入的值所在缓冲区 291 push esi ; 写入起始地址 292 push hProcess[ebx] 293 call _writeProcessMemory 294 295 ;mov dword ptr [esi],eax ;将函数VA值写入IAT 296 pop esi 297 298 inc dwCount 299 add esi,4 300 .endw 301 302 popad 303 ret 304 _updateIAT endp调用函数_updateIAT前目标程序的数据目录表数据已经得到恢复。dwModuleBase中存放了当前动态加载的模块的基地址dwImageBase中存放了目标进程的基地址。hProcess为打开的目标进程为内存写作准备的句柄。函数传入两个参数参数1是_lpIID该参数指向当前导入描述IMAGE_IMPORT_DESCRIPTOR结构参数2为WriteProcessMemory的函数VA。函数首先通过IMAGE_IMPORT_DESCRIPTOR.OriginalFirstThunk找到函数名字字符串注意要跳过Hint/Name前的2个字节值然后通过调用_getApi函数获取该函数指令字节码所在内存的地址并将该地址保存到IAT的相关项位置。通过以上几步对EXE加密、解密、运行的任务就完成了。运行补丁后的chapter21\b\path_notepad.exe程序先出现补丁代码中的对话框提示然后出现记事本程序。通过PEInfo小工具查看该程序显示的信息中大部分与记事本程序无关。----------------------------------测试--------------------------------打开补丁程序patch_a.exe打开PE文件notepad.exe生成bind21a.exe在C盘目录下用peInfo工具查看运行打开是正常的说明运行时解密bind21b.exe测试打开PE文件notepad.exe记事本程序生成bind21b.exe生成在C盘目录下用peInfo工具查看运行21.5 小结本章以记事本为例向大家介绍了一种加密EXE文件的思路。方法是对目标PE文件的节区进行加密运行时由补丁代码接管控制权实施解密并重新组织原程序的运行。本章用到了第17章中介绍的补丁工具。本实例的加密方法很简单大家可以根据实际需要对加密算法进行改进以满足一些特殊需要。注意本章介绍的代码只适合存在导入表且导入函数均为名称导入的目标PE程序如果大家想让其适应更多的程序请根据所学知识自行修改。

相关文章:

《Windows PE权威指南》学习之第21章 EXE加密

EXE加密是软件保护范畴的一种技术,通过对指定的PE文件进行加密,可以增加逆向分析代码的难度,在一定程度上保护软件代码的安全。 EXE加密技术经常用于对软件的加壳处理,通过PE分析软件对加密后的PE文件进行分析,只能看…...

从零到一:基于STM32F407VET6与CubeMX的CAN通信实战配置与调试

1. CAN通信基础与STM32F407VET6硬件准备 CAN总线在工业控制领域就像老司机们熟悉的"对讲机"——不需要主机调度,任何节点都能随时发言,遇到冲突时会自动仲裁。STM32F407VET6内置了两个CAN控制器,我们这次用的是CAN1,它挂…...

Tessent Scan实战:用UPF/CPF文件搞定低功耗设计测试的完整流程(含DRC避坑)

Tessent Scan实战:用UPF/CPF文件搞定低功耗设计测试的完整流程(含DRC避坑) 在芯片设计领域,低功耗已经成为衡量产品竞争力的关键指标之一。随着工艺节点不断演进,设计复杂度呈指数级增长,如何在保证功能正确…...

LangAlpha:基于程序化工具调用与持久化工作空间的金融AI研究平台深度解析

1. 项目概述:当金融研究遇上“代码式”智能体如果你在金融行业待过,或者自己做过投资研究,肯定对那种“信息过载”的疲惫感深有体会。每天开盘前,你需要快速浏览几十份研报、追踪全球宏观数据、分析公司财报、监控市场情绪&#x…...

Rust高性能番茄小说下载器:从网络爬虫到电子书生成的完整解决方案

Rust高性能番茄小说下载器:从网络爬虫到电子书生成的完整解决方案 【免费下载链接】Tomato-Novel-Downloader 番茄小说下载器不精简版 项目地址: https://gitcode.com/gh_mirrors/to/Tomato-Novel-Downloader 在数字阅读时代,网络小说平台如番茄小…...

质子交换膜燃料电池PEMFC Matlab/simulink滑模控制模型,过氧比控制,温度控制...

质子交换膜燃料电池PEMFC Matlab/simulink滑模控制模型,过氧比控制,温度控制,阴,阳极气压控制。 直接上手质子交换膜燃料电池(PEMFC)的滑模控制建模,就像给一台精密仪器装上自动驾驶系统。这玩意…...

远程桌面复制粘贴用不了?可能是组策略在‘捣鬼’,教你一键检查和修复(附GPUpdate命令)

企业级远程桌面剪贴板故障排查:从策略配置到进程管理的深度指南 当你作为企业IT管理员,在跨部门协作或远程支持时,突然发现无法通过远程桌面共享剪贴板内容,这种中断不仅影响效率,还可能延误关键业务流程。不同于个人用…...

免费开源AMD Ryzen调试工具:三步掌握硬件调优终极秘籍

免费开源AMD Ryzen调试工具:三步掌握硬件调优终极秘籍 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: https://gi…...

量子计算中的Trotter误差测量与资源估算优化

1. 量子计算在资源估算中的范式突破量子计算正从理论走向工程实践,而资源估算始终是量子算法落地过程中的关键瓶颈。传统方法依赖于经典计算机进行误差分析,但面对100量子比特以上的系统时,这种方式的局限性日益凸显。我在量子算法优化领域深…...

别再踩坑了!微信小程序登录code无效或被使用的完整避坑指南(附代码示例)

微信小程序登录机制深度解析:从原理到实战避坑指南 微信小程序的登录流程看似简单,却隐藏着许多让开发者头疼的"坑"。本文将带你深入理解微信登录机制的核心原理,并通过实际案例展示如何避免常见的invalid code和code been used错误…...

告别pip依赖地狱:从ERROR到成功安装的实战解决指南

1. 当pip开始"闹脾气":依赖地狱的日常写照 刚接手一个新项目,满心欢喜地准备搭建开发环境,结果pip install命令刚敲下去,屏幕上就蹦出一串刺眼的红色ERROR。这种场景对于Python开发者来说简直像每天喝咖啡一样常见。我管…...

从游戏角色瞄准到机械臂抓取:详解‘圆外一点求切线切点’的几何编程实战

从游戏角色瞄准到机械臂抓取:详解‘圆外一点求切线切点’的几何编程实战 在游戏开发中,NPC如何绕过圆形障碍物精准射击?在机器人控制领域,机械臂如何优雅地避开圆形工作区域并沿切线路径抓取目标?这些看似不同领域的问…...

MATLAB网格线进阶:从基础显示到自定义布局与样式

1. MATLAB网格线基础操作:从显示到关闭 刚接触MATLAB绘图时,我经常遇到这样的困惑:明明数据很清晰,但图表总是显得杂乱无章。后来发现,合理使用网格线能显著提升图表可读性。让我们从最基础的网格线操作开始。 显示网格…...

基于语义层的LLM Agent与图数据库集成实践:以电影推荐为例

1. 项目概述:用语义层为LLM与图数据库架起一座桥最近在折腾大语言模型(LLM)与图数据库的集成,发现一个挺有意思的项目:llm-movieagent。这个项目本质上是一个“电影推荐智能体”,但它背后的设计思路&#x…...

Spring Boot 3项目里,用Hutool 5.8.23搞定四种验证码(含GIF动图)的完整配置流程

Spring Boot 3中Hutool验证码的深度配置与实战指南 验证码作为现代Web应用的基础安全组件,其实现方式直接影响着系统的防护能力和用户体验。在Spring Boot 3项目中,Hutool 5.8.23提供的验证码模块以其丰富的类型选择和灵活的配置选项,成为开发…...

Unity进阶:巧用FBX Exporter打通3DMax到Unity的无损数据管道

1. 为什么需要FBX Exporter这个"数据管道"? 做3D游戏开发的朋友们肯定都遇到过这样的烦恼:在3DMax里精心制作的模型,导入Unity后材质丢失了、动画变形了、场景结构全乱了。每次修改都要重新导出导入,效率低得让人抓狂。…...

lvgl_v8之动态添加控件代码示例

static uint32_t btn_cnt = 1;static void float_button_event_cb(lv_event_t* e) {lv_event_code_t code...

Python空间分析利器:GeoPandas的四大部署策略与避坑指南

1. 裸机Python环境部署:硬核玩家的选择 裸机安装GeoPandas就像自己组装一台高性能电脑——过程充满挑战但成就感十足。我曾在三个不同版本的Windows系统上反复测试,发现Python 3.8确实是最稳定的选择。最新版本虽然诱人,但GDAL等依赖包的兼容…...

lvgl_v8之list控件标题样式设置

void lv_label_demo(void) {lv_obj_t* list;list = lv_list_create(lv_scr_act()...

手把手解决 Stable Diffusion 反推功能安装的那些坑:从 BLIP 模型下载超时到 CLIP 文件缺失

手把手解决 Stable Diffusion 反推功能安装的那些坑:从 BLIP 模型下载超时到 CLIP 文件缺失 当你第一次点击 Stable Diffusion WebUI 的"反推"按钮时,是不是也遇到过这样的场景:满怀期待地等待系统分析图片内容,结果却弹…...

告别数据线!用Windows自带的WiFi Direct功能,无线传文件到手机(保姆级图文教程)

告别数据线!用Windows自带的WiFi Direct功能无线传文件到手机 每次需要把电脑里的照片、文档传到手机时,翻箱倒柜找数据线的经历想必大家都不陌生。更糟的是,当你终于找到线,却发现接口不匹配——Type-C、Lightning、Micro USB&am…...

别再死记硬背DFA了!用Java手把手带你实现一个可配置的字符串识别器(附完整源码)

从零构建可配置的DFA引擎:Java实现与编译原理实战 在计算机科学领域,确定性有限自动机(DFA)是理论计算机科学和编译原理课程中的核心概念。许多学习者虽然能够理解DFA的理论定义,却难以将其转化为可运行的代码。本文将…...

渗透测试方法

渗透测试方法:揭开网络安全的“攻防战” 在数字化时代,网络安全已成为企业和组织不可忽视的核心议题。渗透测试(Penetration Testing)作为一种主动防御手段,通过模拟黑客攻击的方式,发现系统漏洞并评估安全…...

团队协作利器:Miniconda-Python3.10镜像统一开发环境配置方案

团队协作利器:Miniconda-Python3.10镜像统一开发环境配置方案 1. 为什么需要统一开发环境 在团队协作开发中,最令人头疼的问题之一就是"在我机器上能跑"的经典困境。不同开发者使用不同版本的Python解释器、不同版本的依赖库,导致…...

一个Python实现的K线图表程序:从数据计算到可视化渲染的完整实践

1. 为什么我们需要自己实现K线图表程序? 第一次接触量化交易的朋友可能会有疑问:市面上已经有那么多成熟的股票软件,为什么还要自己写K线图表程序?我刚开始做量化时也这么想,直到真正开始策略开发才发现现成工具的限制…...

Equalizer APO终极指南:Windows系统级音频均衡器完整教程

Equalizer APO终极指南:Windows系统级音频均衡器完整教程 【免费下载链接】equalizerapo Equalizer APO mirror 项目地址: https://gitcode.com/gh_mirrors/eq/equalizerapo 你知道吗?Windows系统自带的音频处理其实很基础,无法满足音…...

Windows批处理脚本实战:处理含感叹号、百分号的文本替换,保姆级避坑指南

Windows批处理脚本实战:处理含感叹号、百分号的文本替换,保姆级避坑指南 在Windows自动化运维和数据清洗中,批处理脚本(.bat)是工程师们的老朋友。但当遇到包含感叹号(!)、百分号(%)等特殊字符的文本处理时&#xff0c…...

BetterNCM安装器:三步打造个性化网易云音乐体验

BetterNCM安装器:三步打造个性化网易云音乐体验 【免费下载链接】BetterNCM-Installer 一键安装 Better 系软件 项目地址: https://gitcode.com/gh_mirrors/be/BetterNCM-Installer BetterNCM安装器是一款专为网易云音乐PC客户端设计的插件管理工具&#xff…...

OpenHarmony开发板到手后,这5个HDC命令帮你快速上手调试(DAYU200/RK3568实战)

OpenHarmony开发板实战:5个HDC命令快速上手调试 刚拿到OpenHarmony开发板时,很多开发者都会感到既兴奋又迷茫。DAYU200/RK3568作为当前热门的开发平台,其强大的性能与OpenHarmony系统的开放性为创新提供了无限可能。但面对全新的开发环境&…...

手把手教你用ChatAll和360AI浏览器,一次搞定所有主流AI模型(含免费方案)

多模型AI协同作战指南:ChatAll与360AI浏览器的高效整合方案 当你在不同AI模型间频繁切换,只为找到最适合当前任务的工具时,是否想过有一种更优雅的解决方案?本文将带你探索如何通过开源工具ChatAll和360AI浏览器的巧妙组合&#x…...