Challenge Richelieu 2019 (WIP)
Posted on 14 Jun 2019, 12:20 in WriteUps
Challenge description
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).
Preliminaries
Before the real challenging tasks, there are a bunch of small steps.
Hidden path
The first step is really easy: by looking at the HMTL source, we identify the URL of a PDF file.
Bleached PDF
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.
We use 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.
One liner
`curl https://challengecybersec.fr/Richelieu.pdf | pdftotext Richelieu.pdf - | sed -z -e 's/\x0a\x0a\x0c/\x0a/g' | tail -n +9 | base64 -d > step2.jpg`The result is the following picture:
Embedded ZIP
By using binwalk
on 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
:
dd if=step2.jpg of=step3.zip skip=445628 bs=1
Commented ZIP
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.
Level up
If you want to jump directly to this step, you can use this one-liner:
One liner
`curl https://challengecybersec.fr/Richelieu.pdf --output tmp.pdf; pdftotext tmp.pdf - | sed -z -e 's/\x0a\x0a\x0c/\x0a/g' | tail -n +9 | base64 -d | dd if=/dev/stdin of=tmp.zip skip=445628 bs=1; unzip -P $(unzip -l tmp.zip | grep -o 'DGSE{.*}') tmp.zip; rm tmp.zip; rm tmp.pdf`Faulty prime
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
motDePasseGPG.txt
); - Generation of an RSA key pair;
- Extraction of one of the RSA prime in
prime.txt
; - Some 'search and replace' of some bytes of the RSA prime extracted;
- Finally, encryption of
motDePasseGPG.txt
using 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.
Python source 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 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 lsb_RGB.png.enc
.
One liner
`openssl rsautl -decrypt -inkey priv.key -in motDePasseGPG.txt.enc | gpg --batch --passphrase-fd 0 -o lsb_RGB.png -d lsb_RGB.png.enc`Stegano-guessing
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.
One liner
`zsteg -E b1,rgb,lsb,yx lsb_RGB.png | head -n +18594 | xxd -r > prog.bin`Reverse 101
We use 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.
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:
Python source code
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
Pathfinding
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.txt
which 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 cal
, date
or sl
).
Let's try too quickly see how cal
or 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.
Then 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 cal
or date
and pointing the PATH
to its location. We must not forget to put our custom location in front of the PATH
variable
to ensure that the priority will be given to our custom cal
or date
.
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.
Password overflowing
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 rax
.
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:
NOPSLED
at the beginning of the username buffer;SHELLCODE2
is 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:ffc3
->00c3
->add bl, al
)SHELLCODE
containing the actual shellcode (source for the shellcode: Shellstorm 905) that will pop a shell;PATTERN
that 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;JMPRAX
is the non-randomized address of ajmp rax
instruction;
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 stdin
open).
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 rax
,
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
thanks to $SHELLCODE2
.
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 : _redacted_@intradef.gouv.fr.
Python source 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 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 |
|