bad_mood
bad_mood
Platform: grodno_new_year_2026 | Category: pwn | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-01-11 | Status: Solved Techniques: logic_flaw_exploitation, source_code_analysis
Summary
Task: Heap-based note manager binary with an off-by-one bug and an admin check function that compares a hash against itself. Solution: Ignored the red herring heap vulnerability and simply called the check_admin function (menu option 5), which always passes because admin_hash is computed from the same admin_key at startup.
bad_mood - grodno_new_year_2026
Description (Russian)
У Василия сегодня плохое настроение. Он освобождает старые мысли, путает новые и иногда забывает, кто он. Но в глубине памяти его имя всё ещё где-то есть.
Description (English)
Vasily is in a bad mood today. He's letting go of old thoughts, tangling up new ones, and sometimes forgetting who he is. But somewhere in the depths of his memory, his name still lingers.
Binary Analysis
Files provided:
- bad_mood (ELF 64-bit PIE binary)
- libc.so.6 (glibc 2.41 Debian)
- ld-linux-x86-64.so.2
- libgcc_s.so.1
Binary features:
- Heap-based note manager with menu:
- Alloc - allocate note (index 0-7, size 0=small 0x30 / 1=large 0x100)
- Free - free note (sets pointer to NULL)
- Edit - edit note (has off-by-one bug: reads size+1 bytes)
- Read - read note content
- Check admin - compares hash(admin_key) with admin_hash
Key BSS variables:
- notes[8] at 0x4060
- sizes[8] at 0x40a0
- admin_key at 0x40e0 (set to "VASILIY__SECRET" at startup)
- admin_hash at 0x40f0 (set to hash(admin_key) at startup)
Vulnerability found: off-by-one in edit_note function
Analysis
The challenge description strongly hints at heap exploitation:
- "освобождает старые мысли" (frees old thoughts) = free()
- "путает новые" (tangles up new ones) = heap corruption
- "забывает, кто он" (forgets who he is) = memory corruption
However, the actual solution is much simpler!
The check_admin Function
c read-only • copy allowedvoid check_admin() { uint64_t computed_hash = fnv1a_hash(admin_key); if (computed_hash == admin_hash) { char* flag = getenv("FLAG_VAL"); if (flag) { printf("Flag: %s\n", flag); } } else { printf("Access denied!\n"); } }
The main() Initialization
c read-only • copy allowedint main() { strcpy(admin_key, "VASILIY__SECRET"); admin_hash = fnv1a_hash(admin_key); // ... menu loop }
The Logic Flaw
At program startup:
admin_keyis set to "VASILIY__SECRET"admin_hashis set tohash(admin_key)
When we call option 5 (check_admin):
- It computes
hash(admin_key)again - Compares with stored
admin_hash - Since nothing modifies either value before we interact, they always match!
The off-by-one vulnerability in the edit function was either:
- A red herring to waste time
- Intended for a harder version of the challenge
- Left over from development
Solution
Just call menu option 5 immediately:
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * p = remote('ctf.mf.grsu.by', 9073) p.sendlineafter(b'> ', b'5') # Just call check_admin response = p.recvline() print(f"Flag: {response.decode().strip()}") p.close()
Lessons Learned
- Always check the simple path first - Before diving into heap exploitation, verify if the intended goal can be achieved trivially
- Read the code flow carefully - The vulnerability was real but irrelevant to getting the flag
- Challenge descriptions can be misleading - The heap-related hints were red herrings
- Logic bugs > Memory corruption - Sometimes the easiest path is a logic flaw, not a memory safety issue
Alternative Approach (Not Needed)
If the challenge required modifying admin_key or admin_hash, the off-by-one could be used for:
- Tcache poisoning to get arbitrary write
- Overwriting BSS variables via heap overflow
- Leaking heap addresses for further exploitation
But none of this was necessary for this challenge.