This challenge was released during the Vivatech gathering of 2019. It was called Richelieu, as Cardinal Richelieu, a famous french stateman of the seventieth century who was some kind of prime minister back then and who worked hard to establish an absolute monarchy.
This challenge starts with an URL leading to a website showing a countdown to the end of the challenge and nothing else (no further explanation or goal).
Before the real challenging tasks, there are a bunch of small steps.
The first step is really easy: by looking at the HMTL source, we identify the URL of a PDF file.
Inside the PDF, there seems to be only a small text and a lot of blank pages. In fact, the pages are not empty but contains white text.
pdftotext to extract the content of the pdf and the following one-liner
is able to filter correctly everything and decode the base64 encoded content.
The result is the following picture:
step2.jpg we find that there is an archive that has been
concatenated at the end of the picture.
We extract the whole archive by using
dd if=step2.jpg of=step3.zip skip=445628 bs=1
When trying to deflate the file of this ZIP file, we are asked for a password. But what are the file included in this ZIP anyway?
Well... Thank you. The password was hidden in the comments of the files.
When listing the content of a ZIP file using
unzip -l, those comments are displayed.
If you want to jump directly to this step, you can use this one-liner:
We are left with a bunch of files.
But what really happened? Let's look at the
.bash_history file containing the
(partial) command history of a bash session.
Without looking inside the other files, we can tell what basically happened:
- Symmetric encryption of an image (the key is derived from a password gathered through stdin
and most probably written in the file
- Generation of an RSA key pair;
- Extraction of one of the RSA prime in
- Some 'search and replace' of some bytes of the RSA prime extracted;
- Finally, encryption of
motDePasseGPG.txtusing the RSA key pair.
Thus, our goal is to revert the modifications made to
prime.txt in order
to reconstruct the RSA private key. This will not be as traight forward as
some might think: for example, all '7f' bytes of the original prime
were turned into 'fb' but this is not equivalent to say that each 'fb' bytes
in the resulting prime comes from a '7f' byte in the original prime. To recover
the original prime, we have to explore the tree of all possible changes and,
at each leaf, check whether the prime found divides the public modulus or not.
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
After recovering the RSA private key, we can decrypt the file
motDePasseGPG.txt.enc and , then, decrypt
Now that we have
lsb_RGB.png, we will try to find what's hidden in this image.
The name of this image is quite clear on the fact that we will need to use some steganography technique to
extract information from it.
After some search with
zsteg, we extract an ASCII file which is the hex-dump of an ELF binary.
We then use
xxd to recover the binary.
ghidra to reverse the binary that we got from the previous step.
We find the main function, which is straight-forward and focus on the
verify_flag function, after checking that the input is 32 characters long, apply a
bitwise xor between the input and a deterministic sequence of bytes built from some
data in the binary. Then, the program checks that the result of the xor is always
zero. The following python script is used to recover which input would lead
to the correct flag:
1 2 3 4 5 6 7 8 9
The output of this script can be used as the password for the
suite.zip archive found earlier.
The real deal
We can now connect using SSH to a remote server. In this server there is the classic configuration for challenge aiming at using local exploit to escalate privilege. This configuration is the following:
- We are logged in as a user with execution right on a setuid binary which allows us to execute him as another user;
- We don't have the necessary permission to read a given file (here, it is called
drapeau.txtwhich means flag);
- The other user has read permission on the file.
Thus, we need to exploit the binary (here called
prog.bin) to be able to read the flag.
The program claims that it is a wrapper for some functions (like
Let's try too quickly see how
date is called:
strings -n 2 prog.bin | grep --color -a -E "(cal|date|sl|system)"
It looks like it is just a call to
system without using the absolute path of the binary.
system or some underlying function will use the environment variable
PATH to find
the right binary to execute. We have control about
PATH. We can then let our program execute
whatever program we want by just creating a file named
date and pointing the
to its location. We must not forget to put our custom location in front of the
to ensure that the priority will be given to our custom
Here is a one-line that prints the flag and gives us a shell as the more privileged user:
echo "cat drapeau.txt; bash -p" > /tmp/cal && chmod +x /tmp/cal && (echo "4"; cat -) | PATH=/tmp:/bin:/usr/bin ./prog.bin
Now we can connect to the second server using the given host, user and password.
There is the exact same configuration for this challenge: a setuid binary, and a file to read by exploiting this binary.
This binary propose to check the security of a couple login/password. The ASLR is enabled, the binary is dynamically linked and is stripped.
It crashes when the input given is too big, so we can think of a buffer overflow when reading the input given by the user. We can also see that the username is truncated at 10 characters.
By doing some static and dynamic reverse-engineering work we can find that:
- There is indeed a buffer overflow in the password buffer;
- The location of the stack is randomized (because of the ASLR);
- The 11th charaters of the username buffer is set to 0 if the username is too long;
- If the couple username/password is considered good enough, the username buffer address is returned. Else, zero is returned.
We recall that in this binary (as in a most of x86 binary) the return value is stored
by the callee in
Here is my solution:
1 2 3 4 5 6 7
I'm not really familiar with exploit challenge, so my solution is not that nice. But the main parts are:
NOPSLEDat the beginning of the username buffer;
SHELLCODE2is used to store a single instruction not interferring with the rest of the shellcode. This instruction's first byte will be overwritten by a nullbyte (here:
add bl, al)
SHELLCODEcontaining the actual shellcode (source for the shellcode: Shellstorm 905) that will pop a shell;
PATTERNthat was used to find easily where the overflow occured, and contain the "password" that fullfills all requierements for a "strong" password according to the binary;
JMPRAXis the non-randomized address of a
The whole payload is assembled the python CLI and stored in a
payload file. Then all we have to do is to feed the payload
to the binary (while keeping
What will happen is that when the vulnerable function will return, it will try to read the instruction at
$JMPRAX because of the overflow.
So the next instruction will be to move the program counter to the address stored in
which is the start of the username buffer that we control. The binary will now execute whatever is in
the username buffer. In this case, it's our shellcode. Even if the username is overwritten with a nullbyte, it doesn't break our shellcode
Now that we have a shell as privileged user, we can display the flag which gives us another host, user and password. This will be the final step.
Allocating is hard
Work in Progress...
Félicitations à vous, vous avez réussi l'intégralité du challenge Richelieu ! Vous avez la perspicacité et le profil pour relever les défis technologiques au sein de nos équipes. Dès maintenant, vous pouvez envoyer le tag [Y2hhbGxlbmdlLVJpY2hlbGlldQ==] accompagné, si vous le souhaitez, d'un CV et d'une lettre de motivation à l'adresse suivante : firstname.lastname@example.org.
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 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93