Xherlock - CTF
type: channel
link: http://xherlock.top/index.php/category/CTF/
items: 阿里CTF2026-License
items: Windows核心编程-学习笔记13
items: Windows核心编程-学习笔记12
items: Windows核心编程-学习笔记11
items: Windows核心编程-学习笔记10
items: Windows核心编程-学习笔记9
items: Windows核心编程-学习笔记8
items: Windows核心编程-学习笔记7
items: 阿里云CTF 2025 复现
items: Hgame 2025复现

阿里CTF2026-License
type: item
date: 2026-02-03T10:20:00+08:00
description: 阿里CTF2026-License记录下第一次搞协议逆向，很遗憾比赛过程中卡在最后一步了总体协议流程如下数据包=长度（4字节）+nonce（12字节）+数据流（长度-12字节）数据流做AES-GCM解密zstd解压缩base64解码protobuf解析出来四个字段，enc_data、password、salt、sha256_hashAES-160解密（魔改了SBOX、mixcolumn、加密解密互换了，同时是CBC模式加PKC#7），密钥是password、salt做PBKDF2-HMAC-SHA256得到48字节，前28字节作为密钥，后20字节作为iv解密后是一个json格式字符串，要求包含license_code和sign字段，license_code就是license启动时随机生成的字符串，sign是RSA4096签名（比赛分析到这里了，签名没分析出来，题目应该是给了个d，需要用密码学攻击还原e，但实际上e就是65537）上面全部通过读取和打印FLAG环境变量ELF加载直接分析license可以发现他和build_token建立了通信运行过程中打印了license code: xxxx-uuid这样的字符串，但并没有在license文件里找到，猜测是从build_token发送elf回来加载到主elf内存里执行代码直接上调试，ida里按照run.sh里设置--no-redirect -c 127.0.0.1:12345，本地跑./build_token -p 'r&FGW9RpqTc*aqof' -s -l 0.0.0.0:12345，从main函数return 0开始调试刚开始可以看到retn后来到了新的区域慢慢调试可以发现有一些脱壳的感觉，在解密一些数据代码，按照经验不停的跳过循环，最终可以发现来到一块非常大的函数单步进入反编译后可以看到license、flag等字符串，说明这里才是真正的check逻辑数据包结构开头部分是在生成随机的UUID，慢慢调试直到要求输入，发现下图红框位置要求输入数据，分别要求4、12长度（注意回车符也算输入）第三处要求输入长度的正是第一处输入的数值-12（不太可控，因为手动输入只能输可打印字符），因此调试到这里时我手动在第三处输入前把要求长度修改掉，从而得以输入可控长度的测试数据，我们这里成为数据流数据流AES-GCM解密数据包结构分析完后，下图中sub_403D10函数会返回CPU架构是否支持AVX并设置一个bool字节（随后很多if-else会根据这个值选择进入到的代码，这些分支代码逻辑是相同的，可以省略很多分析过程）。我的CPU支持AVX，进入到下图红框中的数据初始化，AI分析可知是密钥扩展，但这里的密钥通过一定运算才能得到所以直接来到最后最终轮密钥结果处，提取出来前32字节即可获得密钥为f6778d8728d8f17ce8c5c81f45c3d5fd869ca851b7575be540776f4f26c1140dif-else出来后发现对16个0字节做了加密void __fastcall sub_40C1B0(__m128i *_RDI, const __m128i *a2, _OWORD *a3) { _XMM0 = _mm_xor_si128(_mm_loadu_si128(a2), *_RDI); __asm { aesenc xmm0, xmmword ptr [rdi+10h] aesenc xmm0, xmmword ptr [rdi+20h] aesenc xmm0, xmmword ptr [rdi+30h] aesenc xmm0, xmmword ptr [rdi+40h] aesenc xmm0, xmmword ptr [rdi+50h] aesenc xmm0, xmmword ptr [rdi+60h] aesenc xmm0, xmmword ptr [rdi+70h] aesenc xmm0, xmmword ptr [rdi+80h] aesenc xmm0, xmmword ptr [rdi+90h] aesenc xmm0, xmmword ptr [rdi+0A0h] aesenc xmm0, xmmword ptr [rdi+0B0h] aesenc xmm0, xmmword ptr [rdi+0C0h] aesenc xmm0, xmmword ptr [rdi+0D0h] aesenclast xmm0, xmmword ptr [rdi+0E0h] } *a3 = _XMM0; }这种模式符合AES-256-GCM模式，里面包含2部分，CTR+GHASH随后的调试里都可以看到特征，比如下图里检查了数据流最后16字节是否等于一组结果可…

Windows核心编程-学习笔记13
type: item
date: 2026-01-20T18:42:16+08:00
description: Windows核心编程-学习笔记13SEH：结构化异常处理（structed exception handling），包含两方面的功能：终止处理异常处理终止处理程序终止处理程序确保不管一个代码块（被保护代码）是如何退出的，另一个代码块（终止处理程序），总会得以调用和执行__try { // Guarded body } __finally { // Termination handler }注意在循环里的try中提前退出包括continue、break等，也会进入finally，之后循环内尚余的代码就不会执行了。所以最好在try、finally中不要包含return、continue、break、goto语句异常处理程序与软件异常异常处理程序如下，只要没有异常，except里面的都不会执行__try { // Guarded body } __except (exception filter) { // Exception handler }注意try后不能同时有finally块和except块，也不能有多个finally块或except块，但可以互相嵌套EXCEPTION_EXECUTE_HANDLER（1）EXCEPTION_EXECUTE_HANDLER告诉系统程序知道这个异常，并预计这个异常会在某种情况下发生，可以执行代码来处理它try-except很容易造成漏洞，比如在try中加入stcpy，加入抛出异常却不知道什么原因，比如可能是参数为NULL或非法地址，或者缓冲区大小不够等等，这时候如果处理了异常，进程继续执行，但却是在有受损状态下执行，可能会导致程序稍后因为不明原因崩溃，更有可能造成容易被利用的安全漏洞。全局展开：try抛出后，检查异常处理者；找不到就继续找外面一层的try代码块EXCEPTION_CONTINUE_EXECUTION（-1）EXCEPTION_CONTINUE_EXECUTION表示从异常发生点继续执行实际上可以调用一个函数来决定返回异常标识符中的哪一个，但如果选择EXCEPTION_CONTINUE_EXECUTION要谨慎，因为不知道从哪条指令重新执行，可能会造成新的异常。例如下面汇编是*pchBuffer=TEXT('J')的汇编，pchBuffer没有初始化而是指向了NULLMOV EAX, DWORD PTR[pchBuffer] MOV WORD PTR[EAX], 'J'这时第二条指令抛出异常，pchBuffer指向的是NULL，就算经过except调用函数的处理修复，pchBuffer虽然不再指向NULL，但寄存器EAX依然是NULL，所以陷入死循环。EXCEPTION_CONTINUE_SEARCH（0）EXCEPTION_CONTINUE_SEARCH表示不处理，继续向上找别的try处理块GetExceptionCode该函数可以返回发生异常的类型下面列举一些异常与内存相关的异常：EXCEPTION_ACCESS_VIOLATION：线程试图读写一个虚拟内存地址，但在这个地址并不具备相应权限EXCEPTION_DATATYPE_MISALIGNMENT：线程试图从没有提供自动对齐机制的硬件中读写没有对齐的数据EXCEPTION_ARRAY_BOUNDS_EXCEEDED：线程在支持边界检查的硬件上访问越界的数组元素EXCEPTION_IN_PAGE_ERROR：因文件系统或设备驱动返回一个读取错误，造成页面错误不能满足EXCEPTION_GUARD_PAGE：线程试图访问具有PAGE_GUARD保护属性的内存页。该内存页是可访问的，但同时引发了EXCEPTION_GUARD_PAGE异常EXCEPTION_STACK_OVERFLOW：线程耗尽了系统分配给它的栈空间EXCEPTION_ILLEGAL_INSTRUCTION：线程执行了一条无效指令。这个异常由具体的CPU架构所定义，在不同CPU上执行一条无效指令可能会导致陷阱错误EXCEPTION_PRIV_INSTRUCTION：线程试图执行在当前机器模式下不允许的指令与异常本身相关的异常：EXCEPTION_INVALID_DISPOSITION：异常过滤程序返回EXCEPTION_EXECUTE_HANDLER、EXCEPTION_CONTINUE_EXECUTION、EXCEPTION_CONTINUE_SEARCH之外的值EXCEPTION_NONCONTINUABLE_EXCEPTION：异常过滤程序返回EXCEPTION_CONTINUE_EXECUTION以响应不可继续的异常与调试相关的异常：EXCEPTION_BREAKPOINT：执行到一个断点EXCEPTION_SINGLE_STEP：由跟踪中断或者其他…

Windows核心编程-学习笔记12
type: item
date: 2026-01-14T17:24:20+08:00
description: Windows核心编程-学习笔记12DLL注入在Windows中，每个进程都有自己私有的地址空间。用指针来引用内存的时候，指针的值表示的是进程自己地址空间的一个内存地址。进程不能创建一个指针来引用属于其他进程的内存。但有些情况需要应用程序跨越进程边界来访问另一个进程的地址空间：想要从另一个进程创建的窗口派生出子类窗口需要辅助调试想要为另一个进程安装挂钩注册表注入DLLWindows注册表中默认提供了AppInit_DLLs与LoadAppInit_DLLs两个注册表项：将要注入的DLL路径字符串写入AppInit_DLLs（空格或逗号分隔文件名），然后LoadAppInit_DLLs值设为1这个方法缺点是自定义的DLL会映射到所有使用了User32.dll的进程中，但CUI程序不会使用；同时映射到的进程很多，可能会导致bugWindows挂钩注入DLL一个例子，进程A为了查看系统中各窗口处理了哪些消息，安装了WH_GETMESSAGE挂钩，通过调用SetWindowsHookEx安装HHOOK hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, hInstDll, 0);WH_GETMESSAGE是安装挂钩的类型GetMsgProc是DLL里的一个函数的地址，在窗口即将处理一条消息时，系统调用这个函数hInstDll标识了一个含有GetMsgProc函数的DLL最后一个0表示要为哪个线程安装挂钩。一个线程可能调用SetWindowsHookEx并传入系统中另一个线程的线程标识符。通过为这个参数传递0，告诉系统为所有系统中的GUI线程安装挂钩接着来看会发生什么：进程B中的一个线程准备向一个窗口发送一条消息系统检查该线程是否已安装了WH_GETMESSAGE挂钩系统检查GetMsgProc所在的DLL是否已被映射到进程B的地址空间如果DLL尚未映射，系统会强制将该DLL映射到进程B的地址空间，并将进程B中该DLL的锁计数器递增由于DLL的hInstDll是在进程B中映射的，因此系统会对它进行检查，看它与该DLL在进程A中的位置是否相同。如果hInstDll相同，那么两个进程的地址空间中，GetMsgProc函数位于相同的位置，系统可以直接在进程A的地址空间中调用GetMsgProc。如果hInstDll不同，那么系统必须确定GetMsgProc函数在进程B中地址空间中的虚拟内存地址。地址公式为GetMsgProc B= hInstDll B + (GetMsgProc A - hInstDll A)系统在进程B中递增该DLL的锁计数器系统在进程B的地址空间中调用GetMsgProc函数GetMsgProc返回时，系统递减该DLL在进程B中的锁计数器注意当系统把挂钩过滤函数所在DLL注入或映射到地址空间中时，会映射整个DLL，而不仅仅是挂钩过滤函数相比注册表注入DLL方法，挂钩允许我们在不需要该DLL时可以从进程的地址空间中撤销对它的映射，只需调用UnHookWindowsHookExBOOL UnhookWindowsHookEx(HHOOK hHook);当一个线程调用UnhookWindowsHookEx时，系统会遍历自己内部的一个已经注入过该DLL的进程列表，并将该DLL锁计数器递减。当锁计数器减到0时，系统会自动从进程的地址空间中撤销对该DLL的映射远程线程注入DLLWindows提供了一些函数让一个进程对另一个进程进行操作。从根本上说，DLL注入技术要求目标进程中的一个线程调用LoadLibrary来加载目标DLL。由于不能轻易控制其他进程中的线程，所以可以在目标进程中创建一个新的线程，由我们自己控制：HANDLE CreateRemoteThread(HANDLE hProcess, PSECURITY_ATTRIBUTES psa, DWORD dwStackSize, PTHREAD_START_ROUTINE pfnStartAddr, PVOID pvParam, DWORD fdwCreate, PDWORD pdwThreadId);除了hProcess，其他参数和CreateThread完全相同，hProcess用来表示新创建的线程归哪个进程所有。参数pfnStartAddr是线程函数的内存地址。对于LoadLibrary来说，实际上是：LoadLibraryA和LoadLibraryW，唯一区别在传给函数的参数类型，前者ANSI后者UNICODE。那么使用下面一行代码实现远程线程注入DLL：HANDLE hThread = CreateRemoteThread(hProcessRemote, NULL, 0, LoadLibraryW, L"C:\\M…

Windows核心编程-学习笔记11
type: item
date: 2026-01-13T23:03:22+08:00
description: Windows核心编程-学习笔记11DLL基础Windows API提供的所有函数都包含在DLL中Kernel32.dll：包含用来管理内存、进程和线程的函数User32.dll：包含用来执行与用户界面相关的任务，如窗口创建、发送消息等函数GDI32.dll：包含用来绘图和显示文本的函数其他：AdvAPI32.dll包含的函数与对象的安全性、注册表的操控和事件日志有关；ComDlg32.dll包含一些常用的对话框（如打开文件和保存文件）；ComCtl32.dll支持所有常用的窗口控件DLL和进程的地址空间创建DLL必须为链接器指定/DLL开关，这样生成的文件映像才能被识别为DLL。在DLL函数能被调用之前，DLL的文件映像必须映射到调用进程的地址空间中隐式加载时链接显式运行时链接一旦系统将DLL的文件映像映射到调用进程的地址空间，进程中的所有线程就可以调用该DLL中的函数。线程调用DLL中的一个函数时，该DLL函数会在线程栈中取得传给它的参数，并使用线程栈来存放它需要的局部变量。此外，由DLL中的函数创建的任何对象都为调用线程或调用进程所拥有，DLL绝对不会拥有任何对象。生成DLL可执行模块：一个从DLL中导入函数和变量的模块DLL模块：导出函数和变量供可执行文件使用的模块如果一个可执行模块需要从一个DLL模块导入函数和变量，必须首先生成DLL模块，然后生成可执行模块。生成DLL：必须创建一个头文件，在其中包含要从DLL导出的函数原型、结构以及符号创建C/C++源代码模块来实现DLL模块中的函数和变量生成DLL模块会造成编译器处理每个源代码模块并生成一个.obj模块，每个源文件模块对应一个.obj模块所有.obj模块都创建完毕后，链接器合并所有.obj模块的内容并生成单一的DLL映像文件。该映像文件包含DLL的所有二进制代码以及全局/静态数据变量如链接器检测到DLL的源文件至少导出了一个函数或变量，还会生成一个.lib文件（只是列出所有被导出的函数和变量符号名称）一旦生成了DLL模块，就可以生成可执行模块：在所有引用了函数、变量、数据结构或符号的源代码模块中，必须包含由DLL开发人员创建的头文件创建C/C++源代码模块来实现想要包含在可执行模块中的函数和变量。当然，代码可以引用在DLL的头文件中定义的函数和变量生成可执行模块会造成编译器处理每个源代码模块并生成一个.obj模块所有.obj模块创建完毕后，链接器合并所有.obj模块的内容并生成一个可执行映像文件。该映像文件包含可执行文件的所有二进制代码以及全局变量/静态变量。可执行模块还包含一个导入段，其中列出了所有它需要的DLL模块的名称。此外，针对列出的每个DLL名称，该段还记录了可执行文件的二进制代码从中引用的函数和变量的符号名称。一旦DLL和可执行模块都生成完毕就可以执行了加载程序为新进程创建一个虚拟地址空间，并将可执行模块映射到新进程的地址空间。加载程序接着解析可执行模块的导入段，针对导入段列出的每个DLL名称，加载程序都会在用户的系统中定位该DLL模块，并将该DLL映射到进程的地址空间。注意DLL模块有可能从其他DLL模块导入函数和变量，因此加载程序需要解析每个模块的导入段，将所有要求的DLL都加载到进程的地址空间，所以进程的初始化可能非常耗时生成DLL模块创建DLL实际是在创建一组可供某个可执行模块（或其他DLL）调用的函数。DLL可向其他模块导出变量、函数或C++类。头文件（MyLib.h）：#ifdef MYLIBAPI #else // 表示所有函数变量都要导入 #define MYLIBAPI extern "C" __declspec(dllimport) #endif // 定义数据结构和符号、导出的变量、导出的函数原型 在DLL每个源代码文件（MyLibFile1.cpp）中必须包含上面的头文件#include<windows.h> #define MYLIBAPI extern "C" __declspec(dllexport) #include "MyLib.h" // 源代码extern "C"用来告诉编译器不要对变量名或函数名进行改编，保证C/C++等任何编程语言编写可执行模块都能访问导出的真正含义：当Microsoft的C/C++编译器看到__declspec(dllexport)修饰的变量、函数原型或C++类时，会在生成的.obj文件中嵌入一些额外的信息，链接DLL时，链接器会检测到这些信息并自动生成一个.lib文件（列出了DLL导出的符号），此外还会在生成的DLL文件中嵌入一个导出符号表（导出段），按字母顺序列出了导出的变量、函数和类的符号名。链接器还会保存相对虚拟地址（RVA），指出每个符号可在DLL模块的什么位置找到。生成可执行模块很…

Windows核心编程-学习笔记10
type: item
date: 2026-01-11T17:26:10+08:00
description: Windows核心编程-学习笔记10内存映射文件内存映射文件允许预定一个地址空间区域并为区域调拨物理存储，但这个物理存储来自磁盘上已有的文件，而不是系统分页文件内存映射文件用于：加载并执行exe和dll，节省分页文件的空间和应用程序的启动时间访问磁盘上的数据文件，避免文件I/O操作和缓冲文件内容在同一台机器的不同进程之间共享数据映射到内存的可执行文件和DLL系统在线程调用CreateProcess时：系统先定位调用CreateProcess时指定的可执行文件。找不到exe文件，进程就不会创建，返回FALSE系统新建一个进程内核对象系统为该进程创建一个私有地址空间系统预定一个足以容纳exe文件的地址空间区域，该区域首选的位置已在exe文件中指定。默认x86 0x400000，x64 0x140000000系统会标注预定区域的后备物理存储来自磁盘上的exe文件而不是系统的分页文件系统将exe文件映射到进程的地址空间之后，会访问exe文件中的一个段，其中列出了一些DLL文件，包含在exe中调用的函数。然后系统针对每个dll都调用LoadLibrary；其中的任何DLL用到了其他DLL，系统同样会调用LoadLibrary来加载DLL。对于DLL：系统预定一个足以容纳DLL文件的地址空间区域。默认x86 0x10000000，x64 0x180000000。所有与windows一起发布的标准系统dll都有不同的基地址，这样即使加载到同一个地址空间也不会发生重叠如果系统无法在DLL文件指定的基地址预定区域，这可能是因为该区域已被另一个DLL或exe占用，也可能区域不够大，这时系统会尝试在其他地址为DLL预定地址空间区域。这样不好有两点，一是DLL去除了重定位信息，系统可能无法加载DLL；二是系统在DLL内部需要重定位，占用分页文件额外的存储，还会增大加载DLL所需的时间系统会进行标注，表明预定区域的后备物理存储来自磁盘上的DLL文件，而非来自系统的分页文件。如果windows不能将DLL加载到他首选的基地址而必须重定位，系统还会另行标注，表明DLL中有一部分物理存储被映射到了分页文件完成exe文件映射后，系统会负责所有换页、缓冲和高速缓存操作。例如，如果exe中的代码导致它跳转到一条尚未载入内存的指令的地址，就会引发页面错误。系统检测到错误自动将代码页从文件映像加载到一个RAM中。然后系统将该RAM页映射到进程地址空间中的适当位置，并让线程继续执行，就好像代码页早已自如内存一样，这一切对于应用程序来说都是透明的。同一个可执行文件或DLL的多个实例不会共享静态数据如果应用程序的一个实例修改了数据页面中的一些全局变量，应用程序所有实例的内存都会被修改。系统通过内存管理系统的写时复制特性来防止这种情况发生，每次当应用程序试图向内存映射文件写入时，系统都会截获此类尝试，接着为应用程序试图写入的内存页分配一个新的内存块，复制页面内容，然后让应用程序向刚才分配的内存块写入。这样应用程序的其他实例不会受任何影响。在同一个可执行文件或DLL的多个实例间共享静态数据默认不行，但可以利用这个共享数据设计一个变量来保存正在运行实例的数量，新实例只需检查这个全局变量的值就可以确定当前运行实例数目。每个exe或DLL文件映像由多个段组成，每个标准段名称都以点号开始。段名目的.bss未初始化的数据.CRT只读的C运行时数据.data已初始化的数据.debug调试信息.didata延迟导入的名字表.edata导出的名字表.idata导入的名字表.rdata只读的运行时变量.reloc重定位表信息.rsrc资源.textexe或DLL的代码.textbss当启用增量链接选项时，由C++编译器生成.tls线程局部存储.xdata异常处理表每个段都有与之关联的属性：READ：可以读取数据WRITE：可以写入数据EXECUTE：可以执行该段内容SHARED：该段内容为多个实例所共享除了上面编译器和链接器创建的标准段，还可以自己定义段#pragma data_seg("sectionname")例如下面创建Shared段，只包含一个LONG变量#pragma data_seg("Shared") LONG g_lInstanceCount = 0; #pragma data_seg()如果没有初始化g_lInstanceCount就会放到Shared段之外的其他段。但可以借助allocate声明标识符将未初始化的数据放到任何希望的段中#pragma data_seg("Shared") LONG g_lInstanceCount = 0; #pragma data_seg() __declspec(allocate("Shared")) int d;要想共享变量，还需要告诉链…

Windows核心编程-学习笔记9
type: item
date: 2026-01-09T19:23:00+08:00
description: Windows核心编程-学习笔记9探索虚拟内存系统信息操作系统的许多值由主机决定，比如页面大小和分配粒度等。可以使用GetSystemInfo函数获取主机相关值VOID GetSystemInfo(LPSYSTEM_INFO psi);必须将一个SYSTEM_INFO结构的地址传给该函数typedef struct _SYSTEM_INFO { union { struct { WORD wProcessorArchitecture; WORD wReserved; }; }; DWORD dwPageSize; LPVOID lpMinimumApplicationAddress; LPVOID lpMaximumApplicationAddress; DWORD_PTR dwActiveProcessorMask; DWORD dwNumberofProcessors; DWORD dwProcessorType; DWORD dwAllocationGranularity; WORD wProcessorLevel; WORD wProcessorRevision; } SYSTEM_INFO, *LPSYSTEM_INFO;系统启动时会确定这些成员的值。对于任何给定的系统，这些值始终一样，所以在任何进程中只需调用一次即可。SYSTEM_INFO结构中与内存有关的成员：dwPageSize：表示CPU的页面大小。x86、x64为4096字节，IA-64为8192字节lpMinimumApplicationAddress：给出每个进程的可用地址空间中最小内存地址lpMaximumApplicationAddress：给出每个进程的私有地址空间中可用的最大内存地址dwAllocationGranularity：显示地址空间预定区域的分配粒度为了能让32位程序在64位版本Windows上运行，Microsoft提供了一个称为Windows 32-bit On Windows 64-bit（WOW64）的仿真层32位程序通过WOW程序运行时，GetSystemInfo返回值和原生64位程序返回值可能不一样。可以调用IsWow64Process函数确定进程是否在WOW64上运行BOOL IsWow64Process(HANDLE hProcess, PBOOL pbWow64Process);hProcess：当前正在运行的应用程序pbWow64Process：如果WOW64，pbWow64Process指向的布尔值为TRUE新的IsWow64Process2可以告诉我们进程机器架构BOOL IsWow64Process2( [in] HANDLE hProcess, [out] USHORT *pProcessMachine, [out, optional] USHORT *pNativeMachine );虚拟内存状态VOID GlobalMemoryStatus(LPMEMORYSTATUS lpBuffer);GlobalMemoryStatus用来获取当前内存状态的动态信息，传入MEMORYSTATUS结构地址typedef struct _MEMORYSTATUS { DWORD dwLength; // 整个结构大小（字节数） DWORD dwMemoryLoad; SIZE_T dwTotalPhys; SIZE_T dwAvailPhys; SIZE_T dwTotalPageFile; SIZE_T dwAvailPageFile; SIZE_T dwTotalVirtual; SIZE_T dwAvailVirtual; } MEMORYSTATUS, *LPMEMORYSTATUS;如果预计程序会在有4GB内存的机器上运行，或者分页文件的大小可能超过4GB，则使用GlobalMemoryStatusExVOID GlobalMemoryStatusEx(LPMEMORYSTATUSEX pmst);传入MEMORYSTATUSEX结构地址typedef struct _MEMORYSTATUSEX { DWORD dwLength; DWORD dwMemoryLoad; DWORDLONG ullTotalPhys; DWORDLONG ullAvailPhys; DWORDLONG ullTotalPageFile; DWORDLONG ullAvailPageFile; DWORDLONG ullTotalVirtual; DWORDLONG ullAvailVirtual; DWORDLONG ullAvailExtendedVirtual; } MEMORYSTATUSEX, *LPMEMORYS…

Windows核心编程-学习笔记8
type: item
date: 2026-01-09T15:15:00+08:00
description: Windows核心编程-学习笔记8Windows内存架构进程的虚拟地址空间每个进程都有自己的虚拟地址空间。对32位进程，地址空间大小为4GB，64位大小为16EB由于每个进程都有私有的地址空间，当进程中的各个线程运行时，它们只能访问属于该进程的内存。线程既看不到属于其他进程的内存，也无法访问它们。虚拟地址空间的分区每个进程的虚拟地址空间被划分为许多分区（partition）。由于地址空间的分区依赖于OS底层实现，所以会随Windows内核的不同而略有变化空指针赋值分区：0~0xFFFF闭区间，如果进程中的线程试图读取或写入位于这一分区的内存地址，会引发访问违例。例如：int* pnSomeInteger = (int*)malloc(sizeof(int)); *pnSomeInteger = 5;malloc可能返回 NULL（例如内存不足），此时解引空指针就会触发访问违例。正确安全做法是先检查malloc返回值是否为NULL用户模式分区：这一分区大小取决于CPU架构。一个进程无法通过指针来读取、写入或以其他任何方式访问驻留在这一分区中其他进程的数据。进程的大部分数据都保留在这一分区。由于每个进程都有自己的数据分区，所以一个应用程序破坏另一个应用程序的可能性非常小，使整个系统更加健壮在Windows中，每个exe、dll模块都在这个区域加载。每个进程都可能将DLL加载到这一分区的不同地址。系统还在这一分区映射该进程可以访问的所有内存映射文件。内核模式分区：这一分区是OS代码所在的地方。与线程调度、内存管理、文件系统支持、网络支持以及设备驱动程序相关的代码都加载到这一分区。驻留在该分区的一切均由所有进程共享。虽然这一分区就在每个进程中用户模式分区的上方，该分区中的所有代码和数据都被完全保护起来。应用程序试图读取或写入位于这一分区的内存地址会引发访问违例。地址空间中的区域系统创建一个进程并赋予它地址空间时，可用地址空间中的大部分都是闲置的或尚未分配的。为了使用这部分地址空间，必须调用VirtualAlloc来分配其中的区域。分配区域的操作被称为预定。应用程序预定地址空间的一个区域时，系统会确保区域的起始地址在分配粒度（allocation granularity）的边界上。分配粒度随不同的CPU平台而不同。区域大小是系统页面大小的整数倍。页面（page）是一个内存单位，系统通过它来管理内存。和分配粒度类似，页面大小会因不同CPU而变化，x86和x64系统页面大小为4KB，IA-64系统页面大小为8KB。所以假如x86或x64系统中，预定一个10KB地址空间，系统会预定12KB区域当程序不再需要访问预定的地址空间区域时，应释放该区域。这个过程称为释放地址空间区域，通常调用VirtualFree函数完成为区域调拨物理存储预定的地址空间区域要想使用，必须分配物理存储，并将该存储映射到预定的区域。这个过程称为调拨物理存储。物理存储始终以页面为单位来调拨。我们调用VirtualAlloc函数将物理存储调拨给所预定的区域为区域调拨物理存储时，并不需要为整个区域都调拨物理存储。程序不再需要访问预定区域中已调拨的物理存储时，应将其释放。这个过程称为撤销物理存储，通过调用VirtualFree完成物理存储和分页文件如今的OS使磁盘空间看起来像内存一样，一种称为分页文件的磁盘文件包含可供任何进程使用的虚拟内存。当线程试图访问存储中的一个字节时，CPU必须知道该字节是在RAM中还是在磁盘上一个线程试图访问所属进程的地址空间中的一个数据块时，有两种情况：线程要访问的数据就在RAM中。CPU会先将数据的虚拟内存地址映射到内存的物理地址，接着就可以访问内存中的数据线程要访问的数据不在RAM中，而是在分页文件的某处。这次不成功的访问称为页面错误（page fault）。发生这个错误时，CPU会通知OS，OS在RAM中找到一个闲置页面。如果找不到，OS必须先释放一个。如果待释放的页面没有修改过，OS可直接释放该页面。但如果系统需要释放一个修改过的页面，必须先将页面从RAM中复制到分页文件，接着OS对它内部的表项进行更新，以指出该数据的虚拟内存地址现已映射到了RAM中对应的物理内存地址。这时，CPU会重试那条引发页面错误的指令。但这一次CPU能将虚拟内存地址映射到物理RAM地址，并成功访问数据块不在分页文件中维护的物理存储：并不是每运行一个程序，系统就必须为该进程的代码和数据预定地址空间区域，为这些区域调拨物理存储，再将硬盘上的程序文件中的代码和数据复制到分页文件中已调拨的物理存储中。相反，用户执行一个应用程序时，系统会打开应用程序的exe文件，确定应用程序的代码和数据大小，然后系统预定一个地址空间，注明与该区域关联的物理存储就是exe文件本身。这样一来不但能快…

Windows核心编程-学习笔记7
type: item
date: 2026-01-05T09:51:00+08:00
description: Windows核心编程-学习笔记7同步和异步设备I/O线程发出一个同步设备I/O请求时，会被临时挂起，直至I/O请求完成。此类挂起会损害性能，我们不希望线程被阻塞，因此需要各个线程就它们正在执行的操作进行通信，这种机制在Microsoft中被称为I/O完成端口。利用该机制，应用程序的线程在读写设备时不必等待设备响应，从而显著提升吞吐量。打开和关闭设备设备以及打开设备的函数：文件：永久存储任意数据；CreateFile（pszName为路径名或UNC路径名）目录：属性和文件压缩设置；CreateFile（pszName为目录名或UNC目录名）逻辑磁盘驱动器：格式化驱动器；CreateFile（pszName为\\.\x:，x是盘符）物理磁盘驱动器：访问分区表；CreateFile（pszName为\\.\PHYSICALDRIVEx，x是物理驱动器编号）串口：通过电话线传输数据；CreateFile（pszName为COMx）并口：将数据传输至打印机；CreateFile（pszName为LPTx）邮槽：一对多数据传输，通常是通过网络传到另一台Windows机器；服务器CreateMailslot（pszName为\\.\mailslot\mailslotname），客户端CreateFile（pszName为\\servername\mailslot\mailslotname）命名管道：一对一数据传输，通常是通过网络传到另一台Windows机器；服务器CreateNamedPipe（pszName为\\.\pipe\pipename），客户端CreateFile（pszName为\\servername\pipe\pipename）匿名管道：单机上的一对一数据传输，不经过网络；CreatePipe用来打开服务器和客户端套接字：报文或数据流传输，通常是通过网络传到任何支持套接字的机器上；Socket，accept或AcceptEx控制台：文本窗口屏幕缓冲区；CreateConsoleScreenBuffer或GetStdHandler上面每个函数都可以返回一个用来标识设备的句柄CreateFile：HANDLE CreateFile(PCTSTR pszName, DWORD dwDesiredAccess, DWORD dwShareMode, PSECURITY_ATTRIBUTES psa, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hFileTemplate)pszName：既可标识设备类型，也可标识设备的特定实例dwDesiredAccess：指定设备的数据传输标志0：不打算从设备读取数据或者写入数据。如果只想改变设备的配置（比如只是修改文件的时间戳），就可以传入0GENERIC_READ：允许对设备进行只读访问GENERIC_WRITE：允许对设备进行只写访问GENERIC_READ|GENERIC_WRITE：允许对设备进行读写操作dwShareMode：指定设备共享特权0：要求独占对设备的访问。如果设备已经打开，CreateFile调用失败；如果成功打开设备，后续的CreateFile调用就会失败FILE_SHARE_READ：要求引用设备的其他任何内核对象不得修改由该设备维护的数据。如果设备以只写或独占方式打开，CreateFile调用失败；如果成功打开设备，后续使用了只写访问标志的CreateFile调用失败FILE_SHARE_WRITE：要求引用设备的其他任何内核对象不得读取由该设备维护的数据。如果设备以只读或独占方式打开，CreateFile调用失败；如果成功打开设备，后续使用了只读访问标志的CreateFile调用失败FILE_SHARE_READ|FILE_SHARE_WRITE：不关心引用设备的其他内核对象是从设备读取还是向设备写入。如果设备已以独占方式打开，CreateFile调用失败FILE_SHARE_DELETE：进行文件操作时，不关心文件是否被逻辑删除或移动psa：指向一个SECURITY_ATTRIBUTES结构，可用来指定安全信息以及CreateFile返回的句柄是否可继承，一般传NULLdwCreationDisposition：打开是文件时使用CREATE_NEW：创建新文件，如存在同名文件则失败CREATE_ALWAYS：无视存在直接创建OPEN_EXISTING：打开现有文件或设备，如不存在则失败OPEN_ALWAYS：打开现有文件或设备，如不存在则新建一个TRUNCATE_EXISTING：打开现有文件并将文件大小截断为0字节，如不存在则失败dwFlagsAndAttributes：设置标…

阿里云CTF 2025 复现
type: item
date: 2025-12-29T19:51:00+08:00
description: 阿里云CTF 2025 复现flag-LS给了一个vsix文件，查询可知是vscode插件，vscode安装下，然后打开flag-LS目录，发现input.flag里是空的，输入内容会被做各种加密打开其他的插件就会崩溃测试发现加密可以选择algo1、algo2、algo3algo1：Base58+一个字符algo2：移位+一个字符algo3：reverse+一个字符接着去flag-LS.exe搜索Base58表，定位编码函数；还可以搜索下algo，定位到一个函数在他的下方找到了rot实现，发现每轮加密都会做base58、rot、reverse，然后从中选取一种加密，所以每次加密都会增加移位数（qword_692320）void __fastcall sub_4CE320(__int64 a1, __int64 a2, __int64 a3, char a4) { // [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND] if ( (unsigned __int64)&retaddr <= *(_QWORD *)(v6 + 16) ) sub_4631E0(); v32 = v4; if ( a4 ) ++qword_692320; v33 = v5; v7 = runtime_makeslice(); v8 = 0LL; v9 = v5; v10 = 0LL; while ( v33 > v8 ) { v26 = v9; v29 = v7; v11 = *(unsigned __int8 *)(v32 + v8); if ( (unsigned int)v11 >= 0x80 ) { v11 = runtime_decoderune(); v12 = v33; } else { v12 = v8 + 1; } if ( (unsigned int)(v11 - 97) <= 0x19 ) v11 = qword_692320 + v11 - 26 * ((v11 + (int)qword_692320 - 97) / 26); if ( (unsigned int)(v11 - 65) <= 0x19 ) v11 = qword_692320 + v11 - 26 * ((v11 + (int)qword_692320 - 65) / 26); v28 = v12; if ( (unsigned int)(v11 - 48) <= 9 ) v11 = qword_692320 + v11 - 10 * (((int)((unsigned __int64)(3435973837LL * (v11 + (int)qword_692320 - 48)) >> 32) >> 3) - ((v11 + (int)qword_692320 - 48) >> 31)); v27 = v10 + 1; v13 = v11; v14 = runtime_intstring(); v15 = v26; v16 = v27; if ( v26 < v27 ) { v30 = v14; runtime_growslice(v21, v22, v23, v24, v25); v17 = v18; v16 = v27; v14 = v30; v13 = v11; } else { v17 = v29; } v19 = 16 * (v16 - 1); *(_QWORD *)(v17 + v19 + 8) = v13; if ( dword_706850 ) { v14 = sub_465120(); *v20 = v14; v20[1] = *(_QWORD *)(v17 + v19); } *(_QWORD *)(v17 + v19) = v14; v7 = v17; v10 = v16; v9 = v15; v8 = v28; } strings_Join(v21, v22, v23, v24, v25); }感觉硬猜也可以，这个字符基本确认是md5的十六进制摘要第一个字符，所以都是0-9a-fmy.flag给了密文，需要倒推回明文flag解密代码如下from base58 import b58decode from hashlib import md5 import re cipher = "[binary]" BASE58_REGEX = re.compile(r'^[1-9A-HJ-NP-Za-km-z]+$') def is_base58(s): return bool(BASE58_REGEX.fullmatch(s)) def rot(s, shift): res = [] …

Hgame 2025复现
type: item
date: 2025-12-25T09:51:00+08:00
description: Hgame 2025复现Compress dot newdeepseek一把梭，自己还分析啥# 哈夫曼树结构 huffman_tree = {"a":{"a":{"a":{"a":{"a":{"s":125},"b":{"a":{"s":119},"b":{"s":123}}},"b":{"a":{"s":104},"b":{"s":105}}},"b":{"a":{"s":101},"b":{"s":103}}},"b":{"a":{"a":{"a":{"s":10},"b":{"s":13}},"b":{"s":32}},"b":{"a":{"s":115},"b":{"s":116}}}},"b":{"a":{"a":{"a":{"a":{"a":{"s":46},"b":{"s":48}},"b":{"a":{"a":{"s":76},"b":{"s":78}},"b":{"a":{"s":83},"b":{"a":{"s":68},"b":{"s":69}}}}},"b":{"a":{"a":{"s":44},"b":{"a":{"s":33},"b":{"s":38}}},"b":{"s":45}}},"b":{"a":{"a":{"s":100},"b":{"a":{"s":98},"b":{"s":99}}},"b":{"a":{"a":{"s":49},"b":{"s":51}},"b":{"s":97}}}},"b":{"a":{"a":{"a":{"s":117},"b":{"s":118}},"b":{"a":{"a":{"s":112},"b":{"s":113}},"b":{"s":114}}},"b":{"a":{"a":{"s":108},"b":{"s":109}},"b":{"a":{"s":110},"b":{"s":111}}}}}} # 编码数据 encoded_data = "00010001110111111010010000011100010111000100111000110000100010111001110010011011010101111011101100110100011101101001110111110111011011001110110011110011110110111011101101011001111011001111000111001101111000011001100001011011101100011100101001110010111001111000011000101001010000000100101000100010011111110110010111010101000111101000110110001110101011010011111111001111111011010101100001101110101101111110100100111100100010110101111111111100110001010101101110010011111000110110101101111010000011110100000110110101011000111111000110101001011100000110111100000010010100010001011100011100111001011101011111000101010110101111000001100111100011100101110101111100010110101110000010100000010110001111011100011101111110101010010011101011100100011110010010110111101110111010111110110001111010101110010001011100100101110001011010100001110101000101111010100110001110101011101100011011011000011010000001011000111011111111100010101011100000" # 解码函数 def decode_huffman(tree, encoded_data): decoded_text = [] current_node = tree for bit in encoded_data: if bit == '0': current_node = current_node['a'] else: current_node = current_node['b'] if 's' in current_node: decoded_text.append(chr(cur…