ctf-wiki 花指令(Junk Code)去除实战:原理、编写手法与 N1CTF2020 oflo 完整分析流程
文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载花指令junk code是静态逆向中常见的一类反汇编器陷阱程序在真实代码中混入不改变运行时行为的指令片段使 IDA 等工具解析出的控制流与真实执行流不符甚至导致函数完全无法反编译。本文基于 ctf-wiki 的 花指令文档先讲清花指令的原理与常见编写手法再完整走一遍 N1CTF2020 真题oflo的分析过程——从逐处识别并 patch 掉 4 处花指令、解读其中的自修改代码与 ptrace 反调试到最终恢复出 flag 校验函数并给出完整求解脚本读完后你将掌握一套可复用的定位 → 分析真实执行流 → patch 修复 → 重建立函数的花指令去除方法论。一、什么是花指令花指令是一种专门用来迷惑反编译器的指令片段这些指令片段不会影响程序的原有功能但会使得反汇编器的结果出现偏差从而使破解者的静态分析失败。比较经典的花指令技巧是利用jmp、call、ret指令改变执行流使得反汇编器解析出与运行时不相符的错误代码。理解花指令的关键在于反汇编器只能基于静态字节流猜执行流而真实执行流由 CPU 在运行时决定。只要构造一段CPU 会跳过、但反汇编器会误读的字节序列就能让静态视图产生幻觉。ctf-wiki 中与之同属代码混淆家族的技术还有自修改代码SMC运行时改写自身代码段使静态反汇编结果与运行行为不符参见 自修改代码控制流平坦化重组控制流图基本块关系、插入主分发器参见 控制流平坦化mov 混淆利用 mov 指令的图灵完备性用等价 mov 片段替换常规指令参见 movfuscator。花指令通常与反调试手段组合使用静态分析被堵死后往往只能转向动态分析这也是后续真题中oflo的设计思路之一。二、花指令是如何编写的了解对手怎么埋陷阱是识别陷阱的前提。ctf-wiki 的 Windows 平台花指令文档给出了两类典型写法这里作为编写侧的补充证据。写法一条件跳转 不可识别字节VC 内联汇编// 正常的函数代码 int add(int a, int b){ int c 0; c a b; return c; } // 添加花指令的函数代码 int add_with_junk(int a, int b){ int c 0; __asm{ jz label; jnz label; _emit 0xe8; // call 指令后面加4bytes的地址偏移因此导致反汇编器不能正常识别 label: } c a b; return c; }这里jz label与jnz label构成必然有一个会跳转的组合无论标志位如何CPU 都会跳到label中间的_emit 0xe8及后续字节永远不会执行但反汇编器不知道两个必居其一会尝试把call near ptr 3485623h这样的垃圾字节解析成合法指令产生错误的交叉引用和函数边界。IDA 反编译该函数时会出现无法识别的伪代码修复方式就是把花指令区域 patch 成nop。写法二利用call/ret直接操纵栈GCC 内联汇编#include stdio.h // 使用 gcc/g 进行编译 int main(){ __asm__(.byte 0x55;); // push rbp 保存栈 __asm__(.byte 0xe8,0,0,0,0;); // call $5; __asm__(.byte 0x5d;); // pop rbp - 获取rip的值 __asm__(.byte 0x48,0x83,0xc5,0x08;); // add rbp, 8 __asm__(.byte 0x55;); // push rbp - 相当于将call的返回值修改到下面去 __asm__(ret;); __asm__(.byte 0xe8;); // 这是混淆指令不执行 __asm__(.byte 0x5d;); // pop rbp 还原栈 printf(whoami \n); return 0; }这段代码的核心是call $5把返回地址压栈随后通过popadd再压回一个被改写的值最后用ret跳向被修改的目标位置——运行时执行流被偷走了但字节面上仍是一条看起来合法的call。由于 IDA 对栈的判定比较严格push、ret一类的花指令对反汇编器的干扰尤为强烈。注意 N1CTF2020oflo中出现的花指令结构与此高度同源读者可对照理解。三、例题N1CTF2020 - oflo3.1 第一处花指令原地 jmp把oflo拖入 IDAmain()函数无法被反编译。查看汇编代码发现在0x400BB1处存在一个原地jmp使得反汇编出错.text:0000000000400B54 ; int __fastcall main(int, char **, char **) .text:0000000000400B54 main: ; DATA XREF: start1D↑o .text:0000000000400B54 ; .text:0000000000400C21↓o .text:0000000000400B54 ; __unwind { .text:0000000000400B54 push rbp .text:0000000000400B55 mov rbp, rsp .text:0000000000400B58 sub rsp, 240h .text:0000000000400B5F mov rax, fs:28h .text:0000000000400B68 mov [rbp-8], rax .text:0000000000400B6C xor eax, eax .text:0000000000400B6E lea rdx, [rbp-210h] .text:0000000000400B75 mov eax, 0 .text:0000000000400B7A mov ecx, 40h ; .text:0000000000400B7F mov rdi, rdx .text:0000000000400B82 rep stosq .text:0000000000400B85 mov qword ptr [rbp-230h], 0 .text:0000000000400B90 mov qword ptr [rbp-228h], 0 .text:0000000000400B9B mov qword ptr [rbp-220h], 0 .text:0000000000400BA6 mov qword ptr [rbp-218h], 0 .text:0000000000400BB1 .text:0000000000400BB1 loc_400BB1: ; CODE XREF: .text:loc_400BB1↑j .text:0000000000400BB1 jmp short near ptr loc_400BB11 .text:0000000000400BB3 ; --------------------------------------------------------------------------- .text:0000000000400BB3 ror byte ptr [rax-70h], 90h .text:0000000000400BB7 call loc_400BBF .text:0000000000400BB7 ; --------------------------------------------------------------------------- .text:0000000000400BBC db 0E8h, 0EBh, 12h .text:0000000000400BBF ; ---------------------------------------------------------------------------jmp short near ptr loc_400BB11是一条指向自身后一字节0x400BB2的跳转。CPU 执行后跳到0x400BB2反汇编器则会把跳转目标0x400BB2起的字节按新指令边界解析后续全部错位。将0x400BB1处第一个字节改为0x90nop继续反汇编接下来会暴露出一个奇怪的调用.text:0000000000400BB1 nop .text:0000000000400BB2 inc eax .text:0000000000400BB4 xchg rax, rax .text:0000000000400BB6 nop .text:0000000000400BB7 call loc_400BBF .text:0000000000400BB7 ; --------------------------------------------------------------------------- .text:0000000000400BBC db 0E8h, 0EBh, 12h .text:0000000000400BBF ; --------------------------------------------------------------------------- .text:0000000000400BBF .text:0000000000400BBF loc_400BBF: ; CODE XREF: .text:0000000000400BB7↑j .text:0000000000400BBF pop rax .text:0000000000400BC0 add rax, 1 .text:0000000000400BC4 push rax .text:0000000000400BC5 mov rax, rsp .text:0000000000400BC8 xchg rax, [rax] .text:0000000000400BCB pop rsp .text:0000000000400BCC mov [rsp], rax .text:0000000000400BD0 retn3.2 第二处花指令call/ret 栈操纵这段结构值得逐行拆解它正是第二节写法二的变体call指令会将下一条指令的地址0x400BBC压入栈上而0x400BBF处的代码片段先pop rax取出返回地址add rax, 1将其加一变为0x400BBD再push rax压回栈上随后mov rax, rsp; xchg rax, [rax]; pop rsp是一组保持栈顶值不变的过桥操作最后mov [rsp], rax把加一后的地址重新写回栈顶并retn。因此真实的执行流从0x400BBD开始而不是反汇编器认为的0x400BBC。call指令与0x400BBF代码片段整体都是垃圾可以全部 patch 为nopimport idc for i in range(0x400BB7, 0x400BBC 1): idc.patch_byte(i, 0x90) for i in range(0x400BBF, 0x400BD0 1): idc.patch_byte(i, 0x90)patch 后的逻辑变得非常清晰——就是跳到0x400BD1这里会调用sub_4008B9()后直接exit().text:0000000000400BB6 nop .text:0000000000400BB7 nop .text:0000000000400BB8 nop .text:0000000000400BB9 nop .text:0000000000400BBA nop .text:0000000000400BBB nop .text:0000000000400BBC nop .text:0000000000400BBD jmp short loc_400BD1 .text:0000000000400BBF ; --------------------------------------------------------------------------- .text:0000000000400BBF .text:0000000000400BBF loc_400BBF: ; CODE XREF: .text:0000000000400BB7↑j .text:0000000000400BBF nop .text:0000000000400BC0 nop .text:0000000000400BC1 nop .text:0000000000400BC2 nop .text:0000000000400BC3 nop .text:0000000000400BC4 nop .text:0000000000400BC5 nop .text:0000000000400BC6 nop .text:0000000000400BC7 nop .text:0000000000400BC8 nop .text:0000000000400BC9 nop .text:0000000000400BCA nop .text:0000000000400BCB nop .text:0000000000400BCC nop .text:0000000000400BCD nop .text:0000000000400BCE nop .text:0000000000400BCF nop .text:0000000000400BD0 nop .text:0000000000400BD1 ; --------------------------------------------------------------------------- .text:0000000000400BD1 .text:0000000000400BD1 loc_400BD1: ; CODE XREF: .text:0000000000400BBD↑j .text:0000000000400BD1 lea rax, [rbp-210h] .text:0000000000400BD8 mov rdi, rax .text:0000000000400BDB call sub_4008B9 .text:0000000000400BE0 cmp eax, 0FFFFFFFFh .text:0000000000400BE3 jnz short loc_400BEF .text:0000000000400BE5 mov edi, 0 .text:0000000000400BEA call exit3.3 第三处花指令重复出现的 call 结构此时main()仍然无法按F5反编译。继续向下扫描发现在0x400CB5处存在一个与前面结构完全相同的 call/ret 混淆.text:0000000000400CB5 call loc_400CBD .text:0000000000400CB5 ; --------------------------------------------------------------------------- .text:0000000000400CBA dw 0EBE8h .text:0000000000400CBC db 12h .text:0000000000400CBD ; --------------------------------------------------------------------------- .text:0000000000400CBD .text:0000000000400CBD loc_400CBD: ; CODE XREF: .text:0000000000400CB5↑j .text:0000000000400CBD pop rax .text:0000000000400CBE add rax, 1 .text:0000000000400CC2 push rax .text:0000000000400CC3 mov rax, rsp .text:0000000000400CC6 xchg rax, [rax] .text:0000000000400CC9 pop rsp .text:0000000000400CCA mov [rsp], rax .text:0000000000400CCE retn同样返回地址被弹出、加一后压回真实执行流从0x400CCF开始。直接 patch 为nopimport idc for i in range(0x400CB5, 0x400CBA 1): idc.patch_byte(i, 0x90) for i in range(0x400CBD, 0x400CCE 1): idc.patch_byte(i, 0x90)获得一个到0x400CCF的跳转.text:0000000000400CB9 nop .text:0000000000400CBA nop .text:0000000000400CBB jmp short loc_400CCF .text:0000000000400CBD ; --------------------------------------------------------------------------- .text:0000000000400CBD .text:0000000000400CBD loc_400CBD: ; CODE XREF: .text:0000000000400CB5↑j .text:0000000000400CBD nop .text:0000000000400CBE nop .text:0000000000400CBF nop .text:0000000000400CC0 nop3.4 第四处花指令函数尾部的原地 jmp继续向下看在0x400D04又发现一个非常经典的原地jmp花指令还是直接将第一个字节 patch 为nop即可.text:0000000000400D04 loc_400D04: ; CODE XREF: .text:0000000000400CEE↑j .text:0000000000400D04 ; .text:loc_400D04↑j .text:0000000000400D04 jmp short near ptr loc_400D041 .text:0000000000400D04 ; --------------------------------------------------------------------------- .text:0000000000400D06 db 0C0h .text:0000000000400D07 db 48h ; H .text:0000000000400D08 db 90h .text:0000000000400D09 db 90h .text:0000000000400D0A db 0BFh现在这里看起来就是一个非常正常的函数末尾了.text:0000000000400D04 loc_400D04: ; CODE XREF: .text:0000000000400CEE↑j .text:0000000000400D04 nop .text:0000000000400D05 inc eax .text:0000000000400D07 xchg rax, rax .text:0000000000400D09 nop .text:0000000000400D0A mov edi, 0 .text:0000000000400D0F call exit .text:0000000000400D14 ; --------------------------------------------------------------------------- .text:0000000000400D14 nop .text:0000000000400D15 mov rax, [rbp-8] .text:0000000000400D19 xor rax, fs:28h .text:0000000000400D22 jz short locret_400D29 .text:0000000000400D24 call ___stack_chk_fail .text:0000000000400D29 ; --------------------------------------------------------------------------- .text:0000000000400D29 .text:0000000000400D29 locret_400D29: ; CODE XREF: .text:0000000000400D22↑j .text:0000000000400D29 leave .text:0000000000400D2A retn .text:0000000000400D2A ; } // starts at 400B54末尾的mov rax, [rbp-8] / xor rax, fs:28h / call ___stack_chk_fail是典型的 GCC 栈金丝雀检查说明函数边界恢复正确。3.5 main() 整体逻辑自修改代码登场回到main的开头对函数起始处p一下重新建立函数之后就可以正常按F5反编译了。main()的逻辑如下首先调用sub_4008B9()接下来从输入读取 19 字节调用mprotect()修改main 0xFFFFC000处权限为r | w | x。由于权限控制粒度为内存页这里实际上会修改一整张内存页的权限修改sub_400A69()开头的 10 个字节调用sub_400A69()检查 flagvoid __fastcall __noreturn main(int a1, char **a2, char **a3) { int v3; // ecx int v4; // er8 int v5; // er9 int i; // [rsp4h] [rbp-23Ch] __int64 v7[4]; // [rsp10h] [rbp-230h] BYREF char v8[520]; // [rsp30h] [rbp-210h] BYREF unsigned __int64 v9; // [rsp238h] [rbp-8h] v9 __readfsqword(0x28u); memset(v8, 0, 0x200uLL); v7[0] 0LL; v7[1] 0LL; v7[2] 0LL; v7[3] 0LL; if ( (unsigned int)sub_4008B9(v8, a2, v8) -1 ) exit(0LL); read(0LL, v7, 19LL); qword_602048 (__int64)sub_400A69; mprotect((unsigned int)main 0xFFFFC000, 16LL, 7LL); for ( i 0; i 9; i ) { v3 i % 5; *(_BYTE *)(qword_602048 i) ^ *((_BYTE *)v7 i % 5); } if ( (unsigned int)sub_400A69((unsigned int)v8, (unsigned int)v7 5, (unsigned int)v8, v3, v4, v5) ) write(1LL, Cong!\n, 6LL); exit(0LL); }注意最后几行sub_400A69()的前 10 个字节在运行时会用 flag 的前 5 个字节循环取模 5做异或改写后再被调用。也就是说静态文件中该函数的代码与运行时实际执行的代码并不一致——这与 ctf-wiki 中 自修改代码 文档讨论的mprotect()改权限 运行时改写自身代码模式一致是花指令之后叠加的第二重静态分析障碍。3.6 sub_4008B9ptrace 反调试与输出捕获sub_4008B9()调用fork()分出父子进程其中子进程会请求父进程调试执行/bin/cat /proc/version__int64 __fastcall sub_4008B9(__int64 a1) { unsigned int v2; // [rsp14h] [rbp-5Ch] BYREF int v3; // [rsp18h] [rbp-58h] unsigned int v4; // [rsp1Ch] [rbp-54h] __int64 v5; // [rsp20h] [rbp-50h] __int64 v6; // [rsp28h] [rbp-48h] __int64 v7; // [rsp30h] [rbp-40h] __int64 v8; // [rsp38h] [rbp-38h] __int64 v9; // [rsp40h] [rbp-30h] BYREF __int64 v10[4]; // [rsp50h] [rbp-20h] BYREF v10[3] __readfsqword(0x28u); v4 fork(); if ( (v4 0x80000000) ! 0 ) v2 -1; if ( !v4 ) { v10[0] (__int64)unk_400DB8; v10[1] (__int64)/proc/version; v10[2] 0LL; v9 0LL; ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL); execve(/bin/cat, v10, v9); exit(127LL); }父进程以单个系统调用作为步长进行单步调试PTRACE_SYSCALL会在每次系统调用入口/出口停止被调试进程。这里的PTRACE_PEEKUSER会根据提供的偏移值取出子进程对应寄存器的值偏移值与寄存器间的关系参见内核源码arch/x86/include/asm/user_64.h中的user_regs_struct结构体。偏移120取的是orig_rax即系统调用号也就是说直到子进程的系统调用号为1即writecat正在向 fd1 输出/proc/version的内容时父进程才进入核心逻辑——获取rsi偏移104输出缓冲区地址与rdx偏移96输出长度并调用sub_4007D1()把内容搬回自己v3 0; v5 a1; while ( 1 ) { wait4(v4, v2, 0LL, 0LL); if ( (v2 0x7F) 0 ) break; v6 ptrace(PTRACE_PEEKUSER, v4, 120LL, 0LL); if ( v6 1 ) { if ( v3 ) { v3 0; } else { v3 1; v7 ptrace(PTRACE_PEEKUSER, v4, 104LL, 0LL); v8 ptrace(PTRACE_PEEKUSER, v4, 96LL, 0LL); sub_4007D1(v4, v7, v5, v8); v5 v8; } } ptrace(PTRACE_SYSCALL, v4, 0LL, 0LL); } return v2; }sub_4007D1()的实现比较直白就是将子进程调用write()的输出结果通过PTRACE_PEEKDATA按 8 字节QWORD粒度拷贝回父进程缓冲区__int64 __fastcall sub_4007D1(unsigned int a1, __int64 a2, __int64 a3, __int64 a4) { __int64 result; // rax int v6; // [rsp20h] [rbp-20h] int v7; // [rsp24h] [rbp-1Ch] v6 0; v7 a4 / 8; while ( v6 v7 ) { *(_QWORD *)(4 * v6 a3) ptrace(PTRACE_PEEKDATA, a1, a2 4 * v6, 0LL); v6; } result a4 % 8; if ( (unsigned int)(a4 % 8) ) { result ptrace(PTRACE_PEEKDATA, a1, a2 4 * v6, 0LL); *(_QWORD *)(4 * v6 a3) result; } return result; }从这段代码可以看出该函数的作用它并没有真正防住调试而是把/proc/version的内容经 ptrace 通道搬运到父进程的缓冲区中供后续 flag 校验使用——这正是下一节求解的关键信息来源。3.7 修复 sub_400A69XOR 代码 Patch 与伪花指令回到main()。由于sub_400A69()会在运行时被修改见 3.5 节的 XOR 改写循环我们需要把修改结果直接应用到 IDA 的字节上以获得正确的反汇编结果。改写规则是取 flag 的前 5 字节循环异或前 10 个字节而 flag 的前 5 字节恒定为n1ctf因此可以静态还原import idc s n1ctf for i in range(0x400A69, 0x400A69 10): c idc.get_db_byte(i) c ^ ord(s[(i - 0x400A69) % 5]) idc.patch_byte(i, c)在sub_400A69()当中还存在一个伪花指令先是一条jz、紧跟一条jnz两者目标相同CPU 必然跳到0x400AC9但0x400AC4处的jmp near ptr 801AC9h仍会被反汇编器当成一条合法指令解析产生错误交叉引用。直接将0x400AC4的jmppatch 为nop即可.text:0000000000400A69 sub_400A69 proc near ; CODE XREF: main193↓p .text:0000000000400A69 ; DATA XREF: mainB4↓o .text:0000000000400A69 .text:0000000000400A69 var_40 qword ptr -40h .text:0000000000400A69 .text:0000000000400A69 ; __unwind { .text:0000000000400A69 push rbp .text:0000000000400A6A mov rbp, rsp .text:0000000000400A6D sub rsp, 40h .text:0000000000400A71 mov [rbp-38h], rdi .text:0000000000400A75 mov [rbp-40h], rsi .text:0000000000400A79 mov rax, fs:28h .text:0000000000400A82 mov [rbp-8], rax .text:0000000000400A86 xor eax, eax .text:0000000000400A88 mov byte ptr [rbp-20h], 35h ; 5 .text:0000000000400A8C mov byte ptr [rbp-1Fh], 2Dh ; - .text:0000000000400A90 mov byte ptr [rbp-1Eh], 11h .text:0000000000400A94 mov byte ptr [rbp-1Dh], 1Ah .text:0000000000400A98 mov byte ptr [rbp-1Ch], 49h ; I .text:0000000000400A9C mov byte ptr [rbp-1Bh], 7Dh ; } .text:0000000000400AA0 mov byte ptr [rbp-1Ah], 11h .text:0000000000400AA4 mov byte ptr [rbp-19h], 14h .text:0000000000400AA8 mov byte ptr [rbp-18h], 2Bh ; .text:0000000000400AAC mov byte ptr [rbp-17h], 3Bh ; ; .text:0000000000400AB0 mov byte ptr [rbp-16h], 3Eh ; .text:0000000000400AB4 mov byte ptr [rbp-15h], 3Dh ; .text:0000000000400AB8 mov byte ptr [rbp-14h], 3Ch ; .text:0000000000400ABC mov byte ptr [rbp-13h], 5Fh ; _ .text:0000000000400AC0 jz short loc_400AC9 .text:0000000000400AC2 jnz short loc_400AC9 .text:0000000000400AC4 jmp near ptr 801AC9h在0x400B0E处还有一处和前面一样的 call/ret 花指令继续 patch 掉.text:0000000000400B0E call loc_400B16 .text:0000000000400B0E ; --------------------------------------------------------------------------- .text:0000000000400B13 db 0E8h .text:0000000000400B14 db 0EBh .text:0000000000400B15 db 12h .text:0000000000400B16 ; --------------------------------------------------------------------------- .text:0000000000400B16 .text:0000000000400B16 loc_400B16: ; CODE XREF: sub_400A69A5↑j .text:0000000000400B16 pop rax .text:0000000000400B17 add rax, 1 .text:0000000000400B1B push rax .text:0000000000400B1C mov rax, rsp .text:0000000000400B1F xchg rax, [rax] .text:0000000000400B22 pop rsp .text:0000000000400B23 mov [rsp40hvar_40], rax .text:0000000000400B27 retn3.8 最终反编译结果完成上述 patch 后就能正常反编译sub_400A69()。核心逻辑其实非常简单需要注意的是在main()中传入的 flag 从n1ctf之后开始__int64 __fastcall sub_400A69(__int64 a1, __int64 a2) { __int64 v2; // rbp int i; // [rsp14h] [rbp-2Ch] char v5[8]; // [rsp18h] [rbp-28h] _BYTE v6[6]; // [rsp20h] [rbp-20h] BYREF unsigned __int64 v7; // [rsp30h] [rbp-10h] __int64 v8; // [rsp38h] [rbp-8h] v8 v2; v7 __readfsqword(0x28u); v5[0] 53; v5[1] 45; v5[2] 17; v5[3] 26; v5[4] 73; v5[5] 125; v5[6] 17; v5[7] 20; qmemcpy(v6, ;_, sizeof(v6)); for ( i 0; i 13; i ) { if ( v5[i] ! ((*(char *)(i a1) 2) ^ *(char *)(i a2)) ) return 0LL; } return 1LL; }对照main()的调用sub_400A69(v8, v7 5, ...)a1是缓冲区v8即 3.6 节中由 ptrace 捕获的/proc/version内容a2是用户输入去掉前 5 字节n1ctf后的部分。校验条件为对每个iv5[i] (a1[i] 2) ^ a2[i]。由于/proc/version的前 14 字节恒定为Linux version 而v5是静态常量a2即可被直接推出。四、求解由于/proc/version的前 14 字节恒定为Linux version 我们很容易便能得到 flag 内容s 5-\x11\x1AI}\x11\x14;_ b Linux version ans for i in range(14): ans chr(ord(s[i]) ^ (ord(b[i]) 2)) print(ans) # {Fam3_is_NULL}拼上恒定的前缀完整 flag 为n1ctf{Fam3_is_NULL}。五、花指令去除方法论小结回顾oflo的完整处理链路可以提炼出一套通用的花指令去除流程定位异常函数无法 F5、交叉引用指向异常地址、函数边界缺失都是花指令存在的信号。识别结构原地jmp把首个字节 patch 为nop后重新解码即可暴露真实指令流、必居其一的jz/jnz组合、call 出栈/改写/压栈/retn的栈操纵结构。推演真实执行流对call/ret类花指令逐条模拟栈的变化确定retn实际跳向的地址确定哪些字节运行时根本不会被执行。批量 patch 为nop用 IDA 脚本如文中的idc.patch_byte循环把call 指令 被调用的垃圾片段整体清零比逐字节手工 patch 更快也更不容易出错。重建函数并复核回到函数起始处重新定义函数p键确认栈金丝雀、leave/retn等边界特征恢复正常。叠加层处理若还叠了自修改代码先用已知常量如本题的n1ctf前缀静态还原运行时字节再重复步骤 15。动态兜底静态手段失效时可借助调试器的单步跟踪如 ODbg/OD 的 RUN 跟踪记录真实执行路径把有效指令手工提取出来——Windows 平台花指令文档中的 2017 看雪秋季赛例题即演示了这一动态思路。六、相关文档花指令本文档简繁对照见中文站Windows 平台花指令与反调试例题自修改代码 SMC控制流平坦化movfuscatormov 指令混淆赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐ctf-wiki 密码学专题Padding Oracle Attack 原理剖析与完整 CTF 实战利用ctf wiki 密码学专题Padding Oracle Attack 原理剖析与完整 CTF 实战利用 本文以 ctf wiki 密码学模块中的 Paddi文档网络安全教程Agent Zero 的 A2A 客户端连接模块 fasta2a_client.py 全解析打通 Agent 间通信的桥梁Agent Zero 的 A2A 客户端连接模块 fasta2a_client.py 全解析打通 Agent 间通信的桥梁 helpers/fasta2a_c文档网络安全教程CTF-Wiki Windows 栈溢出实战在栈中执行 Shellcode 的完整流程CTF Wiki Windows 栈溢出实战在栈中执行 Shellcode 的完整流程 本文基于 CTF Wiki 的 Windows 栈上执行 Shellc文档网络安全教程上一篇3分钟打造专属桌面猫咪BongoCat跨平台互动桌宠终极指南下一篇Obsidian-Dataloom数据可视化教程如何用表格和列表呈现复杂信息创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

MySQL表空间传输实战:超大单表分钟级迁移原理与踩坑指南

MySQL表空间传输实战:超大单表分钟级迁移原理与踩坑指南

表空间传输这个功能,说实话在很多DBA的日常工具箱里属于"知道但不常用"的那一类。直到有一天我接到一个需求:线上有一张将近200GB的流水表,要从一个MySQL实例迁到另一个新实例,业务方给的窗口只有30分钟。mysqldump导数…

2026/9/25 3:18:43 阅读更多 →
MindSpeed-LLM 数据预处理完整指南:预训练与SFT数据一键转换实操

MindSpeed-LLM 数据预处理完整指南:预训练与SFT数据一键转换实操

MindSpeed-LLM 数据预处理完整指南:预训练与SFT数据一键转换实操 【免费下载链接】MindSpeed-LLM 昇腾LLM分布式训练框架 项目地址: https://gitcode.com/Ascend/MindSpeed-LLM MindSpeed-LLM(昇腾LLM分布式训练框架)通过一条 preproc…

2026/9/25 3:18:43 阅读更多 →
EasyXMen支持哪3款车规芯片?NXP S32K148、英飞凌TC397、瑞萨RH850平台对比与上手指南

EasyXMen支持哪3款车规芯片?NXP S32K148、英飞凌TC397、瑞萨RH850平台对比与上手指南

EasyXMen支持哪3款车规芯片?NXP S32K148、英飞凌TC397、瑞萨RH850平台对比与上手指南 【免费下载链接】开源小满EasyXMen代码仓库 持续18年精心打造的安全车控操作系统BSW代码。 项目地址: https://gitcode.com/easyxmen/XMen 开源小满 EasyXMen 是一款持续打…

2026/9/25 3:18:43 阅读更多 →

最新新闻

Not Shading:调试阶段去修饰化,让底层结构暴露

Not Shading:调试阶段去修饰化,让底层结构暴露

1. 从“Not Shading”这个标题说起:它到底在指什么第一次看到“Not Shading”这个标题,很多人会愣一下。Shading在图形学里是“着色”,在数据分析里是“阴影填充”,在UI设计里是“明暗处理”,在写作里是“轻描淡写”。…

2026/9/25 4:03:08 阅读更多 →
PaddleSeg MedicalSeg 实战指南:从参数配置、一键训练评估到模型部署与自定义数据集扩展

PaddleSeg MedicalSeg 实战指南:从参数配置、一键训练评估到模型部署与自定义数据集扩展

人工智能计算机视觉预训练 【免费下载链接】PaddleSeg Easy-to-use image segmentation library with awesome pre-trained model zoo, supporting wide-range of practical tasks in Semantic Segmentation, Interactive Segmentation, Panoptic Segmentation, Image Matting,…

2026/9/25 4:03:08 阅读更多 →
工业智能体工程化落地:从概念演示到产线闭环的架构与避坑指南

工业智能体工程化落地:从概念演示到产线闭环的架构与避坑指南

1. 工业智能体到底是个什么物种先把概念钉死。工业智能体不是把ChatGPT套个工厂外壳那么简单,也不是传统工业软件加个对话框就叫智能体。我见过太多项目把"能问答"当成"能干活",结果上线三个月就被产线师傅弃用。工业智能体的本质&a…

2026/9/25 4:03:08 阅读更多 →
ESP32-C3+RP2040异构协同架构:SWD免USB烧录与SPI日志透传实践

ESP32-C3+RP2040异构协同架构:SWD免USB烧录与SPI日志透传实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/25 4:03:08 阅读更多 →
通达信智能生命线:自适应趋势指标源码与实战解析

通达信智能生命线:自适应趋势指标源码与实战解析

通达信趋势指标智能生命线玩通达信的朋友应该都有这种感觉:均线人人都会看,但真正靠均线稳定抓到趋势的没几个。金叉追进去,死在半山腰;死叉刚止损,趋势又回来了。市面上大部分均线类指标逻辑都太死板,参数…

2026/9/25 4:03:08 阅读更多 →
构网型储能系统一次设备选型与高海拔设计要点

构网型储能系统一次设备选型与高海拔设计要点

1. 构网型储能系统一次设备选型概述构网型储能系统作为新一代电力储能解决方案,与传统跟网型储能相比具有显著的技术特点。构网型系统能够独立支撑电网电压和频率,具备1.2倍长期过载和3倍短时冲击能力,这对一次回路设备的选型提出了更高要求。…

2026/9/25 4:02:08 阅读更多 →

日新闻

AI元人文:从工具使用到思维重构的深度探索

AI元人文:从工具使用到思维重构的深度探索

最近半年我一直在琢磨一件事:AI元人文到底是什么?说白了,就是“用元视角重新审视人与AI的关系”,也在“探索AI如何反向逼着我们发现自己的思考边界”。标题里的“元探索”,在我看就是一层套一层的追问——当你用AI解决…

2026/9/25 0:00:41 阅读更多 →
Python+CNN车牌识别实战:从数据预处理到模型训练与部署

Python+CNN车牌识别实战:从数据预处理到模型训练与部署

简介:基于Python与卷积神经网络的车牌识别项目,面向计算机视觉初学者及智能交通开发者,目标是帮助用户掌握从数据预处理、模型构建到实际部署的完整流程。压缩包共25个文件,包含jpg/png图像样本、py训练脚本、md说明文档、dat数据…

2026/9/25 0:00:41 阅读更多 →
Vim基础操作全攻略:保存退出、模式切换与高频命令实战

Vim基础操作全攻略:保存退出、模式切换与高频命令实战

1. 项目概述1.1 核心需求解析今天聊聊Vim。写这个题目的原因是:几乎每个后端开发者、运维人员、数据工程师某天都会遇到一个场景——深夜加班,服务器登录界面只有黑底白字,编辑器只有vi/vim,你必须在五分钟内完成一次配置修改并保…

2026/9/25 0:00:41 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/24 14:34:13 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/24 9:10:42 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/24 14:33:56 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/24 12:50:34 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/24 14:33:48 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/24 12:49:17 阅读更多 →