← Back to Writeups
CUSTOMMediumReversing

MadhuFarai

XESXOR9/2/20264 min read
#reversing#custom#medium#accumulator_reconstruction#c_truncated_remainder_simulati#dot_product_targeting#static_analysis

MadhuFarai

Platform: CUSTOM | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: N/A | **Author: XESXOR Techniques: accumulator_reconstruction, c_truncated_remainder_simulation, dot_product_targeting, static_analysis

Summary

Walkthrough of the CUSTOM Reversing MadhuFarai. Task: 32-bit PE32 console crackme (MadhuFarai.exe) reads N and N points, accumulates an even-index-gated dot product mod 32000 and checks Result == -17564. Solution: reverse main() with radarea2, reco

Description

Flag is your numbers separated by comma. Example: 4,1,1,2,2,3,3,4,4 for N = 4 and points [#3] => (1,1) [#2] => (2,2) [#1] => (3,3) [#0] => (4,4). There are many correct flags.

English summary: We are given a 32-bit PE32 console executable MadhuFarai.exe (Intel 80386, MS Windows, linked against msvcrt). The program reads an integer N, then N points (x, y), computes a Result from them, and prints You are welcome! on success or Go out of here! on failure. The flag is the comma-joined list of the numbers we fed the program (first N, then the point coordinates in input order #(N-1) ... #0). Because the success condition is a single equation, many valid flags exist. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis

The real main lives at 0x401000 (called from entry0). Reversed with radare2.

Control flow

  1. scanf("%d", &N) followed by gate checks:

    • N > 2 else Not enough!
    • N <= 100 else Too many!
    • N % 2 == 0 else Not even!
    • So N must be even, in range 4..100.
  2. A loop reads points for index i = N-1 down to 0 (var_14h = i). For each point:

    • prints [#%d] Input valid x and y:
    • scanf("%d %d", &x, &y)
  3. Input validation (re-prompts until satisfied): a point is accepted only if |x| >= 3 AND |y| >= 3. The disassembly checks (x>=3 || x<=-3) AND (y>=3 || y<=-3); otherwise it loops back and re-asks. There is NO upper bound on coordinates — the key semantic trap is that the bound is |coord| >= 3, not <= 3. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  4. The accumulator Result (var_8h) starts at 0. Only for EVEN i it adds:

    Result += (x_i * prev_x + y_i * prev_y) % 32000
    

    where prev_x / prev_y are the previous iteration's coordinates. %32000 is a C truncated remainder (the binary does idiv by 0x7d00 = 32000 and takes edx). prev_x / prev_y are updated to (x_i, y_i) on every iteration.

    Because N is even, i starts odd (N-1), so the first iteration only sets prev before any even-index term is computed → the uninitialized prev never affects Result. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  5. Success condition: Result == 0xffffbb64 (signed -17564) → You are welcome!.

So:

Result = sum over even i of ( dot(point_i, point_{i+1}) % 32000 )   ==  -17564

with every coordinate satisfying |x| >= 3 and |y| >= 3.

Solution

Pick the smallest legal N = 4. Input order is i = 3, 2, 1, 0, i.e. points p3, p2, p1, p0. The even-i terms are:

  • i = 2: dot(p2, p3)
  • i = 0: dot(p0, p1)

Choose two pairs:

  • Pair (p3, p2) — make it zero (orthogonal): p3 = (3, -3), p2 = (3, 3) → dot = 3*3 + 3*(-3) = 0 → term i=2 = 0. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  • Pair (p1, p0) — carry the full target via an unbounded large coordinate: p1 = (4, 3), p0 = (-4388, -4) → dot = (-4388)*4 + (-4)*3 = -17552 - 12 = -17564 → since |-17564| < 32000 the remainder is unchanged → term i=0 = -17564.

Total: Result = 0 + (-17564) = -17564 = target. All four points satisfy |x| >= 3 and |y| >= 3.

The flag is N followed by the point pairs in input order (#3, #2, #1, #0):

4, (3,-3), (3,3), (4,3), (-4388,-4)

→ REDACTED.

Verification script

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

python
#!/usr/bin/env python3
import math

def crem(a, m):
    # C truncated-division remainder (sign of dividend), matching idiv/edx
    return int(math.fmod(a, m))

def simulate(N, points):
    # points are in input order: index i goes N-1 .. 0
    # points[k] corresponds to i = N-1-k
    result = 0
    prev_x = prev_y = 0
    for k in range(N):
        i = N - 1 - k
        x, y = points[k]
        if i % 2 == 0:
            result += crem(x * prev_x + y * prev_y, 32000)
        prev_x, prev_y = x, y
    return result
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Flag points in input order #3,#2,#1,#0
pts = [(3, -3), (3, 3), (4, 3), (-4388, -4)]
print(simulate(4, pts))   # -> -17564  == target 0xffffbb64

Running it prints -17564, matching the success constant 0xffffbb64.

Note: the PE could not be executed on the ARM macOS host (wine-under-qemu crashes with an anon_mmap_fixed assertion). Static analysis with radare2 plus the Python simulation were authoritative, and the server accepted the flag via POST flag. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Key Takeaways / Lessons

  • Mastering accumulator_reconstruction, c_truncated_remainder_simulation, dot_product_targeting, static_analysis was key to this challenge.
  • Always check for common misconfigurations and verify assumptions.

Crafted by XESXOR Research • 2026-06-15 • CUSTOM • MadhuFarai • Difficulty: Medium