Anti-debugging techniques from Hack.lu's mealtime challenge
Hack.lu之mealtime中的反调试技术小结
This note focuses on the anti-debugging layer of mealtime, a reverse-engineering challenge from the Hack.lu CTF. Other teams had already published clear writeups of the underlying TEA-based algorithm, so this note deliberately skips that and concentrates on the anti-debugging techniques the binary used to make casual analysis painful, since those had only been covered briefly elsewhere.
Checking the debugger flag directly
The most common check in the binary relies on a flag inside the Process Environment Block (PEB), the structure the operating system uses to track a process's own state. The thread information block, reachable through a dedicated CPU segment register, points to this PEB, and a specific byte offset within it is set whenever a debugger is attached via the standard debugging API. Reading that single byte is enough to know whether the process is being debugged, which is exactly what the well-known IsDebuggerPresent function does internally, and the binary implements the same check directly rather than calling the API, to make it harder to intercept with a simple API hook.
Delayed exception-handler registration
The binary registers a Vectored Exception Handler, but does so in an unusually indirect way: it creates a new thread in a suspended state at startup, and that suspended thread's job is to register the handler once resumed. The handler registration is deliberately delayed until after some earlier checks pass, and only then does the main code resume the waiting thread. A deliberately triggered breakpoint exception later in execution routes into this handler, which is where part of the challenge's key validation happens, tucked away from a reverse engineer who is not expecting the exception path to matter.
Combining remote injection with code theft
The most elaborate technique splits a piece of the program's own code out of its normal location, injects it into a different, already-running process that holds elevated debug privileges, and has that second process inject the stolen fragment back into the first, waking the original program's suspended main thread once the fragment is restored. Because most debuggers attach to a single process, code that briefly lives and runs inside a second, unrelated process is effectively invisible to a debugger watching only the original target.
Why this is useful beyond one CTF binary
Each of these techniques, direct PEB inspection, deferred handler registration gated behind suspended threads, and splitting execution across two processes, reappears in commercial packers and in real malware, for the same reason: debugging a process or a disassembler reading a file both assume code stays in one place and runs in a predictable order, and each technique breaks exactly one of those assumptions.
Key takeaways
- Reading the PEB's debugger flag directly, rather than calling IsDebuggerPresent, dodges simple API hooks
- Registering an exception handler from a thread kept suspended until checks pass delays detection of the handler
- Splitting code across two processes via injection hides the fragment from a debugger watching only the original target
- None of these techniques are specific to one CTF challenge; packers and malware reuse the same three patterns
- Each technique works by breaking one assumption debuggers rely on: single process, predictable order, or direct memory location
中文摘要
文章总结了Hack.lu CTF题目mealtime中使用的三类反调试技术:直接检测PEB中的调试标志位、通过挂起线程延迟注册VEH异常处理函数、以及结合远程代码注入与代码窃取将部分代码隐藏到另一个进程中执行,这些手法在商业加壳程序和真实恶意代码中也很常见。
This note has been rewritten and checked for this edition; it is not a copy of the original post.