3kCTF 2020
Linker Revenge - 9 Solves
by FeDEX
Menu based challenge with the option to:
- Add page
- Edit page
- Delete page
- View page
- Relogin
The vulnerability is a Use After Free, in the Edit method. When deleting a page, the pointer to the page is not set to NULL
and the check for editing is done via the page
pointer instead of check_pages
.
-
This vulnerability allows us to overwrite the
fd
pointer in a freed heap chunk and allocate a chunk in the.bss
. -
Now that we have control over the array of pointers I will first overwrite an entry in the
page
array with a GOT address in order to leak libc. -
Once we got a libc leak, we can overwrite an entry in the
page
array with the address ofenviron
in order to leak thestack
address. -
Once we got a stack leak, we can overwrite an entry in the
page
array with the address of the return value of themain
function. -
Now we can start building a ropchain to run mprotect(0x602000, 0x100, 0x7) and read(0, 0x602000, 0x100).
-
The final step consists of a shellcode which will perform:
- openat(0, './flag', 0)
- read('rax', 0x602700, 0x100)
- write(1, 0x602700, 0x100)
Exploit can be found HERE
pyzzle1 - 83 solves
by tcode2k16
Description:
A puzzle be a game, problem, or toy dat tests a personz ingenuity or knowledge. In a puzzle, tha solver is sposed ta fuckin put pieces together up in a logical way, up in order ta arrive all up in tha erect or funk solution of tha puzzle.
challenge
2nd flag : change it from 3K-text to 3k{text}
By taking a look at the file and searching up some of the key terms like SimpleStatementLine
, we quickly realize that the given file is a LibCST concrete syntax tree.
Some documentation reading reveals that by accessing .code
on a syntax tree object, we can recover its source code.
Using this knowledge, I tweaked the file a bit:
from libcst import *
abc = Module(
...
)
print(abc.code)
Running this edited file gives us the source code:
import binascii
plaintext = "REDACTED"
def exor(a, b):
temp = ""
for i in range(n):
if (a[i] == b[i]):
temp += "0"
else:
temp += "1"
return temp
def BinaryToDecimal(binary):
string = int(binary, 2)
return string
# encryption
PT_Ascii = [ord(x) for x in plaintext]
PT_Bin = [format(y, '08b') for y in PT_Ascii]
PT_Bin = "".join(PT_Bin)
n = 26936
K1 = '...'
K2 = '...'
L1 = PT_Bin[0:n]
R1 = PT_Bin[n::]
f1 = exor(R1, K1)
R2 = exor(f1, L1)
L2 = R1
f2 = exor(R2, K2)
R3 = exor(f2, L2)
L3 = R2
R3 = '...'
L3 = '...'
cipher = L3+R3
# decryption (redacted)
plaintext = L6+R6
plaintext = int(plaintext, 2)
plaintext = binascii.unhexlify('%x' % plaintext)
print(plaintext)
We can see that some xor operations have been performed on the original plaintext. We can walk backward and undo all those changes:
R2 = L3
L2 = exor(exor(R3, R2), K2)
R1 = L2
L1 = exor(exor(K1, R1), R2)
plaintext = L1+R1
plaintext = int(plaintext, 2)
plaintext = binascii.unhexlify('%x' % plaintext)
plaintext = binascii.unhexlify(plaintext)
print(plaintext)
This yields the original file which contains our first flag:
33D32945 STP File, STP Format Version 1.0
SECTION Comment
Name "3k{almost_done_shizzle_up_my_nizzle}"
END
SECTION Graph
Nodes 144
Edges 116
E 1 2 1
...
END
SECTION Coordinates
DD 1 5 5
...
END
EOF
flag: 3k{almost_done_shizzle_up_my_nizzle}
Game 1 - 30 solves
by tcode2k16
Description:
find your way to the heart of the maze
challenge:
For Windows
For Linux
flag format is different:
3K-string
For this challenge, we need to look more into the game logic. To accomplish this, I used another tool called ILSpy.
Opening Managed/Assembly-CSharp.dll
, we are able to see most of the game logic:
// CTF.GameManager
using UnityEngine;
private void OnTriggerEnter(Collider other)
{
if (other.tag == "Box1")
{
if (isCollidingBox1)
{
return;
}
isCollidingBox1 = true;
UiManager.current.UpdateTexte(Box1);
Object.Destroy(other.gameObject);
}
if (other.tag == "Box2")
{
if (isCollidingBox2)
{
return;
}
isCollidingBox2 = true;
UiManager.current.UpdateTexte(Box2);
Object.Destroy(other.gameObject);
}
if (other.tag == "Box3")
{
if (isCollidingBox3)
{
return;
}
isCollidingBox3 = true;
UiManager.current.UpdateTexte(Box3);
Object.Destroy(other.gameObject);
}
if (other.tag == "Box4")
{
if (isCollidingBox4)
{
return;
}
isCollidingBox4 = true;
UiManager.current.UpdateTexte(Box4);
Object.Destroy(other.gameObject);
}
if (other.tag == "Box5")
{
if (isCollidingBox5)
{
return;
}
isCollidingBox5 = true;
UiManager.current.UpdateTexte(Box5);
Object.Destroy(other.gameObject);
}
if (other.tag == "Box6" && !isCollidingBox6)
{
isCollidingBox6 = true;
UiManager.current.UpdateTexte(Box6);
Object.Destroy(other.gameObject);
}
}
// CTF.UiManager
public void UpdateTexte(string textToAdd)
{
counter++;
textHolder.text += textToAdd;
if (counter == 6)
{
cText = Encrypt.current.DecryptString(textHolder.text);
textHolder.text = cText;
}
}
// CTF.Encrypt
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
public string DecryptString(string key)
{
byte[] array = Convert.FromBase64String(cipherText);
using (Aes aes = Aes.Create())
{
Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(key, new byte[13]
{
73,
118,
97,
110,
32,
77,
101,
100,
118,
101,
100,
101,
118
});
aes.Key = rfc2898DeriveBytes.GetBytes(32);
aes.IV = rfc2898DeriveBytes.GetBytes(16);
try
{
using (MemoryStream memoryStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write))
{
cryptoStream.Write(array, 0, array.Length);
cryptoStream.Close();
}
cipherText = Encoding.Unicode.GetString(memoryStream.ToArray());
}
return cipherText;
}
catch (Exception)
{
return "wrong Order mate ";
}
}
}
By reading the code, we see that the player is able to append six different words to a string in various orders by hitting different boxes in the maze. The concatenated string is then used as a key to decrypt a cipher message yielding the flag.
To recover the six words and the ciphertext, we can do a simple strings
or xxd
on the level0
asset file:
words:
Tanit
Astarté
Amilcar
Melqart
Dido
Hannibal
ciphertext
jR9MDCzkFQFzZtHjzszeYL1g6kG9+eXaATlf0wCGmnf62QJ9AjmemY0Ao3mFaubhEfVbXfeRrne/VAD59ESYrQ==
At this point, a brute force script should be able to yield the flag, but for some reason, it did not work for me.
In a hopeful attempt, I marked out all the box locations and played the game hitting each of them in the shortest path. Luckily, it worked and gave me the flag...
order:
Hannibal --> Dido --> Melqart --> Amilcar --> Astarté --> Tanit
flag: 3K-CTF-GamingIsNotACrime