mmuctf 2025 writeup

Over the weekend, I had the opportunity to participate in the mmuctf 2025. It was an individual jeopardy style CTF targeted towards beginners and intermediate people in cybersecurity. This is a write-up to explore some challenges I did, or at least almost did.

Welcome

Through the shadows of the cave, at the door I had a zeal to get a welcome flag, but I couldn't see it though I could hear it calling for me to get it. Happy hacking {: .prompt-info }

This was a sanity check flag. I believe competitions pose such challenges that you can't get zero points. It's the same as flags in Discord servers, a flag after completing the feedback form, or just other free points.

The challenge description talked about the door, my first instinct was to view the source of the front page. I got this html comment with what looked liked a base64 encoded string <!--bW11Y3Rme3dlbGNvbWVfZmxhZ19hZGlvc19NdWNoQGNob30= -->

┌──(mockingspectre㉿blackout)-[~/ctf/mmuctf2025]
└─$ echo -n "bW11Y3Rme3dlbGNvbWVfZmxhZ19hZGlvc19NdWNoQGNob30=" | base64 -d
mmuctf{welcome_flag_adios_Much@cho}   

My First App

Our intern dev swears this app’s secure... {: .prompt-info}

This was a web challenge with an account creation form. Let's fire up Burp. The account creation request didn't seem suspicious for now, so I sent it to the repeater to abuse it some more. The response seemed interesting, the user id was in the query parameter: GET /profile.php?id=5 HTTP/1.1. This was a nice candidate for an Insecure Direct Object Reference(IDOR). I tried repeating the request with values from 0-4. The flag was at 1, the admin user's account.

mmuctf{1d0r_4dm1n_4cc3ss_1s_c0mpr0m1s3d}

Ledilect

Sometimes the path you take is just a jump away. {: .prompt-info}

This was a simple one, I knew what the challenge wanted, but I couldn't get myself to exploit it, definitely a skill issue. The webapp had a landing page that had a button that made this request:

GET /portal/jump.php?url=https://example.com HTTP/1.1
Host: redacted
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US, en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Referer: redacted
Cookie: PHPSESSID=ce7743f6cd447d7a1ee94e230c3d9cdb
Upgrade-Insecure-Requests: 1
Priority: u=0, i

Looking at that, I suspected an open redirect vulnerability, but I don't know anything about this bug class. I let it rest.

Meet my X

In the�days оf cοmfort i met this girl at the Jobless соrner. Аt the�doоrs and windows of iddleness i approached, oops she smiled and thats how i knew she was the one. I guess thatsss whyyy i learned on getting Flags because be warned she later left me at first it feels like a green flag i guess no life was there on blood(heart_break) {: .prompt-info}

This challenge involved homoglyphs, a character or a sequence of characters that looks very similar or identical to another character or sequence of characters, but has a different meaning, encoding, or origin.

A sentence might look normal, but if you copy it into a text editor that shows Unicode details or a specialized homoglyph detector, you'll see different underlying character codes.

I used these online decoders, https://wulfsige.com/crypto/ and https://holloway.nz/steg/ to get the flag:

mmuctf{oops_twitter_steg_1s_fun}

Shadow

Look beyond the appearance, perhaps their numerical essence will guide you to the flag. Can you reveal what lurks in the shadow? {: .prompt-info}

This was another stego challenge; there is an image with what looks like a color palette. The description hints at 'numerical essence', so it might be something to deal with the hex values of the colors. I used this site https://imagecolorpicker.com/ to extract the hex values.

#6d6d75
#637466
#7b346c
#773479
#355f68
#316464
#336e5f
#316e35
#64335f
#6d337d

Decoded becomes

mmuctf{4lw4y5_h1dd3n_1n5d3_m3}

Revealme

We found this mysterious executable on an old USB stick... Can you figure out how to get it to reveal the flag? {: .prompt-info}

The provided file is an ELF executable binary.

┌──(mockingspectre㉿blackout)-[~/ctf/mmuctf2025]
└─$ file revealme
revealme: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=d8546bb2c10c0226384fb2771684af9bc150b55e, for GNU/Linux 3.2.0, stripped
┌──(mockingspectre㉿blackout)-[~/ctf/mmuctf2025]
└─$ checksec --file=revealme
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols        No    0               2               revealme

Opening it with Binary Ninja gives us a quick win:

00401179    int32_t main(int32_t argc, char** argv, char** envp)


00401179  55                 push    rbp {__saved_rbp}
0040117a  4889e5             mov     rbp, rsp {__saved_rbp}
0040117d  4883ec70           sub     rsp, 0x70
00401181  488d05800e0000     lea     rax, [rel data_402008]
00401188  4889c7             mov     rdi, rax  {data_402008, "Enter the secret passphrase: "}
0040118b  b800000000         mov     eax, 0x0
00401190  e8abfeffff         call    printf
00401195  488b15a42e0000     mov     rdx, qword [rel stdin]
0040119c  488d4590           lea     rax, [rbp-0x70 {buf}]
004011a0  be64000000         mov     esi, 0x64
004011a5  4889c7             mov     rdi, rax {buf}
004011a8  e8b3feffff         call    fgets
004011ad  488d4590           lea     rax, [rbp-0x70 {buf}]
004011b1  488d156e0e0000     lea     rdx, [rel data_402026]
004011b8  4889d6             mov     rsi, rdx  {data_402026}
004011bb  4889c7             mov     rdi, rax {buf}
004011be  e88dfeffff         call    strcspn
004011c3  c644059000         mov     byte [rbp+rax-0x70 {buf}], 0x0
004011c8  488d4590           lea     rax, [rbp-0x70 {buf}]
004011cc  488d15550e0000     lea     rdx, [rel data_402028]
004011d3  4889d6             mov     rsi, rdx  {data_402028, "OpenSesame123!"}
004011d6  4889c7             mov     rdi, rax {buf}
004011d9  e892feffff         call    strcmp
004011de  85c0               test    eax, eax
004011e0  7511               jne     0x4011f3


004011e2  488d054f0e0000     lea     rax, [rel data_402038]
004011e9  4889c7             mov     rdi, rax  {data_402038, "Correct! The flag is: mmuctf{r3v3rs1ng_st4rt3r}"}
004011ec  e83ffeffff         call    puts
004011f1  eb0f               jmp     0x401202


004011f3  488d056e0e0000     lea     rax, [rel data_402068]
004011fa  4889c7             mov     rdi, rax  {data_402068, "Nope, try again."}
004011fd  e82efeffff         call    puts


00401202  b800000000         mov     eax, 0x0
00401207  c9                 leave    {__saved_rbp}
00401208  c3                 retn     {__return_addr}

Flag:

mmuctf{r3v3rs1ng_st4rt3r}

Flicker

An ordinary looking Android app... or is it? {: .prompt-info}

The provided app is an Android APK. Let's fire up jadx-gui. The package name is com.example.blink. There is an interesting class r2d2. In the onCreate method, there is an image string variable that seems to contain base64 image data.

┌──(mockingspectre㉿blackout)-[~/ctf/mmuctf2025]
└─$ cat output.txt | base64 -d > decoded_image.png

flag:

CTF{PUCKMAN}

Open_Secrets

Someone trusted the wrong site and network didn’t forget. Can you piece together what was left behind? Some people still think locks are optional on digital doors.* {: .prompt-info}

The provided file was a network capture pcap file. For people who don't know, pcap (a packet capture) consists of an application programming interface (API) for capturing network traffic.The challenge title and filename hint at unencrypted traffic. Open up Wireshark and apply an HTTP filter. There is an HTTP stream that leaks the flag:

mmuctf{plaintext_login_leak}

C-x C-s

Ugh, I keep typing ^x ^s in my shell instead of saving, should’ve stuck with Emacs {: .prompt-info}

This one made me mad. I thought I had the flag, but I couldn't get it. The provided file is a PCAP file. Another network forensics challenge, or so I thought.

opened the file with Wireshark packet and noticed a new kind of communication, to be honest, I didn't know this was possible until I understood what was happening...

I found some articles dealing with this

The source and destination use object IDs for communication, something I gathered when reading about the SNMP protocol, USB(Universal Serial Bus) it's apparent I'm not dealing with any 802.x traffic

Interrupts happen whenever you press a key or click a button, anything that "interrupts" the CPU, after which it has to process your input. Each URB_INTERRUPT in the file corresponds to a key pressed, and the Leftover Capture Data field shows the hex value of the key in 8-byte format.

Creating a filter to list all interrupt communication with non-empty 8 bytes

usb.transfer_type == 0x01
usb.transfer_type == 0x01 && usb.dst == "host" && !(usb.capdata == 00:00:00:00:00:00:00:00)

Crafting a tshark command to get the leftover capture data for the above filters and redirect it into a file

tshark -r log.pcap -Tfields -Eseparator=, -e thekey.pcapng -Y 'usb.transfer_type == 0x01 && usb.dst == "host" && !(usb.capdata == 00:00:00:00:00:00:00:00)' | sed 's/://g' > usb.capdata
┌──(mockingspectre㉿blackout)-[~/ctf/mmuctf2025]
└─$ tshark -r thekey.pcapng -Tfields -Eseparator=, -e usb.capdata -Y 'usb.transfer_type == 0x01 && usb.dst == "host" && !(usb.capdata == 00:00:00:00:00:00:00:00)' | sed 's/://g' > usb.capdata
                                                                                                             
┌──(mockingspectre㉿blackout)-[~/ctf/mmuctf2025]
└─$ cat usb.capdata                                                                                                                                      
0000190000000000
00000c0000000000
00000c1000000000
0000102c00000000
00002c0000000000
0000090000000000
00000f0000000000
0000040000000000
0000040a00000000
................

Python script to decode the hex values to strings:

keyboard = {
   2: "PostFail",    4: "a",    5: "b",    6: "c",    7: "d",    8: "e",    9: "f",    10: "g",    11: "h",    12: "i",    13: "j",    14: "k",    15: "l",    16: "m",    17: "n",    18: "o",    19: "p",    20: "q",    21: "r",    22: "s",    23: "t",    24: "u",    25: "v",    26: "w",    27: "x",    28: "y",    29: "z",    30: "1",    31: "2",    32: "3",    33: "4",    34: "5",    35: "6",    36: "7",    37: "8",    38: "9",    39: "0",    40: "Enter",    41: "esc",    2: "del",    43: "tab",    44: "space",    45: "-",    47: "[",    48: "]",    51: "DownArrow",    54: "1",    55: "*",    56: "/",    57: "CapsLock",    79: "RightArrow",    80: "LeftArrow"
}


with open("usb.capdata") as file:
   i = 1
   for line in file:
       bytesArray = bytearray.fromhex(line.strip())
       for byte in bytesArray:
           if byte != 0:
               keyVal = int(byte)


       if keyVal in keyboard:
           print(keyboard[keyVal])
       else:
           print("no map found for this value: " + str(keyVal))


   i += 1

Running the code outputs what seems to be Vim motions. I wasn't able to decipher this to a correct flag:

v
i
m
space
space
f
l
a
g
g
*
t
t
x
t
Enter
i
3
t
t
h
e
space
f
l
a
g
g
space
i
s
space
c
t
f
esc
v
b
3
u
u
3
a
3
[
[
m
y
3
-
3
f
a
v
o
r
i
t
e
e
3
-
e
d
i
t
o
r
3
-
3
i
s
3
-
v
i
m
3
]
esc
h
h
h
h
h
h
h
h
h
h
h
h
h
h
h
h
h
h
h
a
u
esc
v
i
3
[
3
3
u
3
esc
3
DownArrow
DownArrow
w
q
Enter
a
tab
a
e

These are the challenges I had the time to tackle. if you have any insights or suggestions. Share them in the comments