代码注入、完整指令与还原
代码注入通过改变已有执行路径,将控制流暂时转向新代码。本章以 CE 自带 Tutorial 第 7 关为场景,解释如何确定覆盖范围、重放指令和恢复原状。
从写入点确定上下文
在 Tutorial 第 7 关先定位训练数值,使用“查找是什么写入此地址”触发一次更新,再到反汇编器检查命中指令及上下文。记录实际模块名、相对位置、原始字节、每条指令长度、可能的分支入口和寄存器状态。32 位练习程序的名称可能是 Tutorial-i386.exe;如果启动 64 位版本,应以实际进程和模块列表中的名称为准。练习时先确认该指令是否同时处理其他对象;只看一次命中不能排除共享代码。
注入点需满足:覆盖区从指令开头开始,长度不小于实际跳转编码,终点也落在完整指令边界。例如跳转实际需要五字节,但连续指令长度是三和四字节,就必须分析七字节,而不能截断第二条。x64 跳转有时需要更长编码;不能只按五字节选范围。原有分支跳入覆盖区中间时,方案还需要重新设计。
执行新代码与返回
CE 模板可生成分配区、跳转和返回标签,但模板是起点,需要人工校对。新代码应保留被覆盖指令原有语义,并根据情况保存和恢复寄存器、标志及栈状态。被移动的相对跳转、调用或 RIP 相对寻址不能简单按原字节复制,因为指令新位置改变了解释目标;应重新组装并核对最终目标。
下面是适用于特定 32 位教学场景的完整骨架,不能直接用于未知程序。Example.exe 和字节均为讲解用假设,并非本次从 Tutorial 采集的值。仅当你在 Tutorial 实际观察到连续的 89 42 18 8B 45 DC 对应下列两条指令,且模块内该模式唯一、实际跳转为五字节时,才可以把 Example.exe 替换为实际模块名试用。否则应在第 7 关的实际写入指令上打开 Auto Assemble,使用模板生成脚本,再按本章检查指令、覆盖区及恢复字节,不可只替换模块名。
[ENABLE]
aobscanmodule(hook,Example.exe,89 42 18 8B 45 DC)
assert(hook,89 42 18 8B 45 DC)
alloc(newmem,1000,hook)
label(returnhere)
registersymbol(hook)
newmem:
mov [edx+18],eax
mov eax,[ebp-24]
jmp returnhere
hook:
jmp newmem
nop
returnhere:
[DISABLE]
hook:
db 89 42 18 8B 45 DC
unregistersymbol(hook)
dealloc(newmem)
这个骨架仅重放原指令,本身不改变计算结果。启用前先在扫描界面独立检查唯一性;启用后用反汇编确认跳转确为五字节加一个 NOP、返回地址恰好位于六字节之后,并比较目标行为。若编码长度不符或任一前提不满足,不要启用。禁用前还需确认没有线程停留在分配区;这段教学骨架没有实现并发停用同步,不能作为通用生产方案。
在 Tutorial 第 7 关先尝试无额外行为的“跳出、重放、跳回”,确保程序原本行为不变。随后才按关卡要求添加最小改动,并观察目标行为。立即退出、卡住或出现非预期变化时应停止练习,排查寄存器、堆栈、共享代码和跳转目标,而不是反复重试。
保证关闭与回滚
启用前检查字节是否与记录一致,保存完整原始字节。禁用时恢复精确的原字节,确认原指令重新出现,再注销符号并释放资源。停止时目标线程若仍在执行分配区,直接释放可能引起竞争;真实程序需要适当同步或安全停用设计。目标程序版本改变后,重新获取字节并测试,不能将旧记录照搬。
验收时完整记录三次状态:启用前原始字节、启用后跳转和新代码、关闭后恢复的字节;同时证明对象行为符合预期。教程中的地址和机器码应当来自当前练习程序,而不是网上的固定地址。