Skip to content

Commit 157bbcf

Browse files
committed
feat: add challenges
1 parent 028a568 commit 157bbcf

8 files changed

Lines changed: 201 additions & 8 deletions

File tree

assets/challenges/ffiishy.zip

1.2 MB
Binary file not shown.

contents/challenges.html

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<b><~~~~ Challenges ===================></b>
2+
3+
4+
Here are challenges i wrote for fun, and mostly act as an "educational tool" to ease me in explaining stuff to my audience.
5+
6+
To input any flag you found for each challenge, you can type in the flag in the input box of that corresponding challenge and hit the "Check Flag" button.
7+
8+
In case you need any help for solving any of these challenges, i will be very happy to help you so feel free to contact me in discord @<a class="single" href="https://discord.com/channels/@me/414072790745088010" target="_blank" rel="noopener noreferrer">f4r4w4y</a> (but please keep in mind that i also have life to do so dont expect very quick response lol).
9+
10+
Challenge list:
11+
12+
[ <b>1</b> ] <a class="single" href="assets/challenges/ffiishy.zip" target="_blank" rel="noopener noreferrer">FFIshy.apk</a> : Foreign Function Interface is FFIishy (or ffiilthy?)
13+
<code>reverse engineering</code><code>math</code><code>ffi</code><code>jni</code><code>android</code><code>c</code><code>recursion</code>
14+
15+
Note: wrap the flag with "FLAG{}" (e.g. if the flag you found is test,
16+
then input as FLAG{test})
17+
<input type="text" name="flag-1" id="flag-1"><button type="button" id="button-1">Check Flag</button>
18+
19+
<script type="module" src="scripts/toast.js" defer></script>
20+
<script type="module" src="scripts/decrypt_flag.js" defer></script>
21+
<script>
22+
window.sodium = {
23+
onload: function (sodium) {
24+
const encryptedData = [
25+
"uJTSL19fpzZgMcDnFftI2FVaa4b5Zk+7SYmsPIrXGMsNKjpIlu6xsLbHz/w/hMwOZQ==" // ffiishy challenge
26+
]
27+
28+
for (let [i, enc] of encryptedData.entries()) {
29+
i += 1; // index starts from 1
30+
31+
document.body.querySelector(`#button-${i}`).addEventListener('click', async () => {
32+
const secret = document.body.querySelector(`#flag-${i}`).value;
33+
if (await window.checkFlag(enc, secret)) {
34+
window.showToast('Correct!', 'info');
35+
} else {
36+
window.showToast('Wrong!', 'warning');
37+
}
38+
});
39+
}
40+
},
41+
};
42+
</script>
43+
<script
44+
src="https://cdn.jsdelivr.net/npm/libsodium@0.7.15/dist/modules/libsodium.min.js"
45+
async defer></script>
46+
<script
47+
src="https://cdn.jsdelivr.net/npm/libsodium-wrappers@0.7.15/dist/modules/libsodium-wrappers.min.js"
48+
async defer></script>

index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@
5858
│ └──────────────────┤ ├──────────────────┘ │
5959
│ ╘═ ═╛ │
6060
│ │
61-
<no-aboutlink>┏━━┓</no-aboutlink> <a class="cv-link" href="content.html?c=cv">🮹🮺 open my CV</a>
61+
<no-aboutlink>┏━━┓</no-aboutlink> <a class="cv-link" href="content.html?c=cv">🮹🮺 let me see your CV</a>
6262
<no-aboutlink>┃ ▄▄▄▖</no-aboutlink> <a class="about-link" href="content.html?c=about"><b>Who </b></a>
63-
<no-aboutlink>┃ ██▌</no-aboutlink> <a class="about-link" href="content.html?c=about">are </a>
63+
<no-aboutlink>┃ ██▌</no-aboutlink> <a class="about-link" href="content.html?c=about">are </a> <a class="challenge-link" href="content.html?c=challenges">🮤🮥 let me solve your challenges</a>
6464
<no-aboutlink>┃ ▀▀▀▀</no-aboutlink> <a class="about-link" href="content.html?c=about">you?</a>
6565
<no-aboutlink>┗━━┛</no-aboutlink> =-*@@@@@@ │
6666
│ :+=+%*@@@@@@: │

scripts/content.js

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
window.renderContent = async (content) => {
22
const short = await (async () => {
33
const whitelist = [
4-
"about", "youtube", "contact", "blog", "cv",
4+
"about", "youtube", "contact", "blog", "cv", "challenges",
55
"flag{congrats_you_found_a_meaningless_flag_from_a_meaningless_repository_in_github}"
66
]
77
if (whitelist.includes(content)) {
88
return await (await fetch(`../contents/${content}.html`)).text();
99
} else {
10-
if ([".", "/", "{", "}", "]", "[", ")", "("].filter(str => content.includes(str)).length > 0) {
10+
if ([".", "/", "{", "}", "]", "[", ")", "("].filter(str => content?.includes(str)).length > 0) {
1111
return "<span style=\"width: 100%;\"><center>Hacker, please dont attack me :(</center></span>"
1212
} else {
1313
const { origin } = new URL(document.location.href);
@@ -53,6 +53,37 @@ window.renderContent = async (content) => {
5353
characterData: true
5454
});
5555

56+
const parser = new DOMParser();
57+
const doc = parser.parseFromString(short, 'text/html');
58+
const scripts = doc.querySelectorAll("script");
59+
60+
const newShort = doc;
61+
newShort.querySelectorAll("script").forEach((script) => script.remove());
62+
5663
// Render the content
57-
contentPreTag.innerHTML = "\n" + short;
64+
contentPreTag.innerHTML = `\n${newShort.body.innerHTML.trim()}`;
65+
66+
// Execute script tags inside the content
67+
scripts.forEach((script) => {
68+
const newScript = document.createElement("script");
69+
70+
// Copy all attributes
71+
for (const attr of script.attributes) {
72+
newScript.setAttribute(attr.name, attr.value);
73+
}
74+
75+
if (script.src) {
76+
// For external scripts
77+
newScript.src = script.src;
78+
} else {
79+
// For inline scripts
80+
newScript.text = script.textContent;
81+
}
82+
83+
// Append to trigger execution
84+
document.body.appendChild(newScript);
85+
86+
// Clean up
87+
newScript.remove();
88+
});
5889
}

scripts/decrypt_flag.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
window.checkFlag = async (encryptedBase64, keyStr) => {
2+
await sodium.ready;
3+
4+
// Derive the key using Web Crypto's SHA‑256
5+
const keyBytes = sodium.from_string(keyStr);
6+
const hashBuffer = await crypto.subtle.digest("SHA-256", keyBytes);
7+
const hashedKey = new Uint8Array(hashBuffer);
8+
9+
// Decode the base64 input into a Uint8Array
10+
const decoded = sodium.from_base64(encryptedBase64, sodium.base64_variants.ORIGINAL);
11+
12+
// Extract nonce (first 12 bytes), tag (next 16 bytes), and ciphertext (rest)
13+
const nonce = decoded.slice(0, 12);
14+
const tag = decoded.slice(12, 28);
15+
const ciphertext = decoded.slice(28);
16+
17+
// Libsodium expects the ciphertext to have the tag appended at the end.
18+
// Reassemble: [ciphertext || tag]
19+
const combined = new Uint8Array(ciphertext.length + tag.length);
20+
combined.set(ciphertext);
21+
combined.set(tag, ciphertext.length);
22+
23+
try {
24+
// Decrypt the message.
25+
// Note: We pass `null` for additional data (AD) since none was used in encryption.
26+
const decrypted = sodium.crypto_aead_chacha20poly1305_ietf_decrypt(
27+
null, // no additional data
28+
combined, // ciphertext with tag appended
29+
null, // no additional data
30+
nonce, // nonce extracted from the beginning
31+
hashedKey // key derived from SHA-256(keyStr)
32+
);
33+
34+
// Convert decrypted bytes back to a string and return it.
35+
if (sodium.to_string(decrypted) === "This flag is correct!") {
36+
return true;
37+
}
38+
} catch (error) {
39+
return false;
40+
}
41+
}

scripts/encrypt_flag.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from Crypto.Cipher import ChaCha20_Poly1305
2+
from Crypto.Random import get_random_bytes
3+
from base64 import b64encode
4+
5+
import hashlib
6+
import sys
7+
8+
def encrypt_file(key):
9+
key = hashlib.sha256(key.encode('utf-8')).digest()
10+
nonce = get_random_bytes(12)
11+
plaintext = b"This flag is correct!"
12+
13+
cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce)
14+
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
15+
16+
print(b64encode(nonce + tag + ciphertext).decode())
17+
18+
if __name__ == "__main__":
19+
if len(sys.argv) != 2:
20+
print("Usage: python encrypt.py <key>")
21+
sys.exit(1)
22+
23+
encrypt_file(sys.argv[1])

scripts/toast.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
window.showToast = (message, type = 'info') => {
2+
const toast = document.createElement('div');
3+
toast.className = `toast ${type}`;
4+
toast.textContent = message;
5+
6+
document.body.appendChild(toast);
7+
8+
setTimeout(() => {
9+
toast.remove();
10+
}, 3000);
11+
}

styles/main.css

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,13 @@ code {
5555
code::before {
5656
color: #5e5e5e;
5757
padding-right: 2px;
58-
content: "⟦"
58+
content: "⟦";
5959
}
6060

6161
code::after {
6262
color: #5e5e5e;
6363
padding-left: 2px;
64-
content: "⟧"
64+
content: "⟧";
6565
}
6666

6767
a:link,
@@ -77,12 +77,14 @@ a.single:visited {
7777
}
7878

7979
body:has(:is(a.cv-link:hover, a.cv-link:active)) .cv-link,
80+
body:has(:is(a.challenge-link:hover, a.challenge-link:active)) .challenge-link,
8081
body:has(:is(a.backhome-link:hover, a.backhome-link:active)) .backhome-link,
8182
body:has(:is(a.contact-link:hover, a.contact-link:active)) .contact-link,
8283
body:has(:is(a.blog-link:hover, a.blog-link:active)) .blog-link,
8384
body:has(:is(a.youtube-link:hover, a.youtube-link:active)) .youtube-link,
8485
body:has(:is(a.about-link:hover, a.about-link:active)) .about-link,
85-
a.single:hover, a.single:active {
86+
a.single:hover,
87+
a.single:active {
8688
color: black;
8789
background-color: white;
8890
}
@@ -93,3 +95,40 @@ body:has(:is(a.youtube-link:hover, a.youtube-link:active)) no-youtubelink,
9395
body:has(:is(a.about-link:hover, a.about-link:active)) no-aboutlink {
9496
color: yellow;
9597
}
98+
99+
.toast {
100+
position: fixed;
101+
bottom: 30px;
102+
left: 50%;
103+
transform: translateX(-50%);
104+
padding: 12px 24px;
105+
border-radius: 4px;
106+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
107+
opacity: 1;
108+
z-index: 999;
109+
margin-top: 5px;
110+
111+
animation: fadeOut 3s forwards;
112+
113+
font-weight: bold;
114+
font-family: Julia, monospace;
115+
}
116+
117+
.toast.info {
118+
background-color: greenyellow;
119+
color: black;
120+
}
121+
122+
.toast.warning {
123+
background-color: red;
124+
color: white;
125+
}
126+
127+
@keyframes fadeOut {
128+
from {
129+
opacity: 1;
130+
}
131+
to {
132+
opacity: 0;
133+
}
134+
}

0 commit comments

Comments
 (0)