← Back to Writeups
CUSTOMMediumReversing

Deceptive Checker

XESXOR9/10/20263 min read
#reversing#custom#medium#decoy_function_identification#init_array_analysis#mprotect_rwx_identification#self_modifying_code_detection#static_binary_analysis#linux

Deceptive Checker

Platform: miptctf | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-03-14 | Status: Solved Techniques: decoy_function_identification, init_array_analysis, mprotect_rwx_identification, self_modifying_code_detection, static_binary_analysis

Summary

Task: Stripped statically linked ELF binary with flag checker containing deception. Solution: Identified self-modifying code in .init_array that patches CALL instruction at runtime, bypassing fake checker to find real validation function.

Deceptive Checker — miptctf

Description

Stripped, statically linked ELF 64-bit x86-64 binary (698KB). Flag checker that takes the flag as argv[1]. The task name "deceptive_checker" hints that the checking logic contains deception.

Files:

  • task - ELF 64-bit LSB executable, x86-64, statically linked, stripped (698KB)

Analysis

Initial Reconnaissance

bash
file task
# ELF 64-bit LSB executable, x86-64, statically linked, stripped

strings task | grep -i flag
# $nope
# Avoid_NoH3
# miptctf{
# usage: %s <flag>
# $MIPTf

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Interesting: two different flag formats — miptctf{ and MIPT (in the string $MIPTf).

Two Flag Checkers

The binary contains two separate flag validation functions:

  1. Fake checker at 0x403120:

    • Expects format miptctf{...} with 36 characters
    • Reversing yields: miptctf{dumau_ti_ne_tot_flag_nashel}
    • Translation: "I think you found the wrong flag" — this is a decoy
  2. Real checker at 0x403260:

    • Expects format MIPT{...} with 23 characters
    • This is the actual validation function ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The Deception Mechanism

The binary uses self-modifying code to hide the real checker:

1. .init_array Section

Functions registered in the .init_array section execute before main(). Here they perform code patching.

2. mprotect (RWX)

Init code calls mprotect to make the .text section writable:

c
mprotect(text_start, text_size, PROT_READ | PROT_WRITE | PROT_EXEC);

3. JMP Patching

The CALL instruction that initially points to the fake checker (0x403120) is patched at runtime to jump to the real checker (0x403260). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Static vs Dynamic Analysis

AnalysisSees
Static (Ghidra without emulation)CALL 0x403120 → fake checker
Dynamic (execution)CALL 0x403260 → real checker

This is a classic anti-reversing technique: static analysis shows the decoy, while the real code only executes dynamically.

Flag Meaning

The flag MIPT{REDACTED} describes the three techniques used:

  • rwx — mprotect makes code pages read-write-execute
  • initarray — .init_array section performs patching before main
  • jmp — call/jump instruction is patched to redirect execution ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Approach 1: Analyzing .init_array

bash
# Find .init_array section
readelf -S task | grep init_array

# Disassemble init functions
objdump -d task | grep -A 50 "init_array"

Look for:

  1. mprotect calls with PROT_READ | PROT_WRITE | PROT_EXEC flags (7)
  2. Code that modifies bytes in the .text section
  3. Patching of relative addresses in CALL/JMP instructions

Approach 2: Dynamic Analysis

bash
# Run under strace to detect mprotect
strace ./task MIPT{test} 2>&1 | grep mprotect
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Or under gdb with breakpoint on mprotect
gdb ./task
(gdb) catch syscall mprotect
(gdb) run MIPT{test}

Approach 3: Comparing Both Checkers

After discovering two validation functions:

python
#!/usr/bin/env python3
"""
Deceptive Checker - miptctf
Analyze both flag checkers
"""

# Fake checker (0x403120): miptctf{...} format, 36 chars
# Decoy flag: miptctf{dumau_ti_ne_tot_flag_nashel}
# Translation: "I think you found the wrong flag"

# Real checker (0x403260): MIPT{...} format, 23 chars
# Real flag: MIPT{REDACTED}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# The flag itself describes the anti-reversing techniques:
# - rwx: mprotect makes code RWX
# - initarray: .init_array runs before main
# - jmp: patches jump/call instruction

print("MIPT{REDACTED}")

Key Signs of Self-Modifying Code

  1. mprotect with RWX — code makes itself writable
  2. Code in .init_array — executes before main
  3. Two similar code blocks — one decoy, one real
  4. Different flag formats — miptctf{ vs MIPT{ ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍